aboutsummaryrefslogtreecommitdiffstats
path: root/pkg
diff options
context:
space:
mode:
authorDmitry Vyukov <dvyukov@google.com>2017-08-28 15:59:22 +0200
committerDmitry Vyukov <dvyukov@google.com>2017-09-02 13:06:53 +0200
commita7206b24cac96c08aecf2f3b4cc3c2e555eec708 (patch)
tree80c678141148ce2eafaab5617f168bd840b8c8a6 /pkg
parentaa51461a34f998908d10f551615ad242bdff8fe9 (diff)
pkg/compiler: check and generate types
Move most of the logic from sysgen to pkg/compiler. Update #217
Diffstat (limited to 'pkg')
-rw-r--r--pkg/ast/parser.go4
-rw-r--r--pkg/ast/scanner.go16
-rw-r--r--pkg/compiler/compiler.go581
-rw-r--r--pkg/compiler/compiler_test.go28
-rw-r--r--pkg/compiler/consts.go187
-rw-r--r--pkg/compiler/fuzz.go25
-rw-r--r--pkg/compiler/gen.go150
-rw-r--r--pkg/compiler/testdata/errors.txt137
-rw-r--r--pkg/compiler/types.go575
-rw-r--r--pkg/csource/csource.go11
-rw-r--r--pkg/host/host.go3
-rw-r--r--pkg/serializer/serializer.go165
-rw-r--r--pkg/serializer/serializer_test.go44
13 files changed, 1608 insertions, 318 deletions
diff --git a/pkg/ast/parser.go b/pkg/ast/parser.go
index fd7b9ad4f..214be7fb9 100644
--- a/pkg/ast/parser.go
+++ b/pkg/ast/parser.go
@@ -96,10 +96,10 @@ func (p *parser) parseTopRecover() Node {
case nil:
case skipLine:
// Try to recover by consuming everything until next NEWLINE.
- for p.tok != tokNewLine {
+ for p.tok != tokNewLine && p.tok != tokEOF {
p.next()
}
- p.consume(tokNewLine)
+ p.tryConsume(tokNewLine)
default:
panic(err)
}
diff --git a/pkg/ast/scanner.go b/pkg/ast/scanner.go
index f1573350b..6dab16183 100644
--- a/pkg/ast/scanner.go
+++ b/pkg/ast/scanner.go
@@ -134,19 +134,19 @@ func (s *scanner) Scan() (tok token, lit string, pos Pos) {
s.next()
case s.ch == '`':
tok = tokCExpr
- for s.next(); s.ch != '`'; s.next() {
- if s.ch == 0 || s.ch == '\n' {
- s.Error(pos, "C expression is not terminated")
- break
- }
+ for s.next(); s.ch != '`' && s.ch != '\n'; s.next() {
+ }
+ if s.ch == '\n' {
+ s.Error(pos, "C expression is not terminated")
+ } else {
+ lit = string(s.data[pos.Off+1 : s.off])
+ s.next()
}
- lit = string(s.data[pos.Off+1 : s.off])
- s.next()
case s.prev2 == tokDefine && s.prev1 == tokIdent:
// Note: the old form for C expressions, not really lexable.
// TODO(dvyukov): get rid of this eventually.
tok = tokCExpr
- for s.next(); s.ch != '\n'; s.next() {
+ for ; s.ch != '\n'; s.next() {
}
lit = string(s.data[pos.Off:s.off])
case s.ch == '#':
diff --git a/pkg/compiler/compiler.go b/pkg/compiler/compiler.go
index 737cc878e..57b9dd951 100644
--- a/pkg/compiler/compiler.go
+++ b/pkg/compiler/compiler.go
@@ -1,22 +1,43 @@
// Copyright 2017 syzkaller project authors. All rights reserved.
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
+// Package compiler generates sys descriptions of syscalls, types and resources
+// from textual descriptions.
package compiler
import (
"fmt"
"sort"
+ "strconv"
"strings"
"github.com/google/syzkaller/pkg/ast"
"github.com/google/syzkaller/sys"
)
+// Overview of compilation process:
+// 1. ast.Parse on text file does tokenization and builds AST.
+// This step catches basic syntax errors. AST contains full debug info.
+// 2. ExtractConsts as AST returns set of constant identifiers.
+// This step also does verification of include/incdir/define AST nodes.
+// 3. User translates constants to values.
+// 4. Compile on AST and const values does the rest of the work and returns Prog
+// containing generated sys objects.
+// 4.1. assignSyscallNumbers: uses consts to assign syscall numbers.
+// This step also detects unsupported syscalls and discards no longer
+// needed AST nodes (inlcude, define, comments, etc).
+// 4.2. patchConsts: patches Int nodes refering to consts with corresponding values.
+// Also detects unsupported syscalls, structs, resources due to missing consts.
+// 4.3. check: does extensive semantical checks of AST.
+// 4.4. gen: generates sys objects from AST.
+
// Prog is description compilation result.
type Prog struct {
// Processed AST (temporal measure, remove later).
- Desc *ast.Description
- Resources []*sys.ResourceDesc
+ Desc *ast.Description
+ Resources []*sys.ResourceDesc
+ Syscalls []*sys.Call
+ StructFields []*sys.StructFields
// Set of unsupported syscalls/flags.
Unsupported map[string]bool
}
@@ -29,17 +50,17 @@ func Compile(desc *ast.Description, consts map[string]uint64, eh ast.ErrorHandle
comp := &compiler{
desc: ast.Clone(desc),
eh: eh,
+ ptrSize: 8, // TODO(dvyukov): must be provided by target
unsupported: make(map[string]bool),
+ resources: make(map[string]*ast.Resource),
+ structs: make(map[string]*ast.Struct),
+ intFlags: make(map[string]*ast.IntFlags),
+ strFlags: make(map[string]*ast.StrFlags),
+ structUses: make(map[sys.StructKey]*ast.Struct),
}
-
comp.assignSyscallNumbers(consts)
comp.patchConsts(consts)
- if comp.errors != 0 {
- return nil
- }
-
comp.check()
-
if comp.errors != 0 {
return nil
}
@@ -47,8 +68,11 @@ func Compile(desc *ast.Description, consts map[string]uint64, eh ast.ErrorHandle
eh(w.pos, w.msg)
}
return &Prog{
- Desc: comp.desc,
- Unsupported: comp.unsupported,
+ Desc: comp.desc,
+ Resources: comp.genResources(),
+ Syscalls: comp.genSyscalls(),
+ StructFields: comp.genStructFields(),
+ Unsupported: comp.unsupported,
}
}
@@ -57,11 +81,14 @@ type compiler struct {
eh ast.ErrorHandler
errors int
warnings []warn
+ ptrSize uint64
unsupported map[string]bool
-
- udt map[string]ast.Node // structs, unions and resources
- flags map[string]ast.Node // int and string flags
+ resources map[string]*ast.Resource
+ structs map[string]*ast.Struct
+ intFlags map[string]*ast.IntFlags
+ strFlags map[string]*ast.StrFlags
+ structUses map[sys.StructKey]*ast.Struct
}
type warn struct {
@@ -78,119 +105,80 @@ func (comp *compiler) warning(pos ast.Pos, msg string, args ...interface{}) {
comp.warnings = append(comp.warnings, warn{pos, fmt.Sprintf(msg, args...)})
}
-type typeDesc struct {
- Names []string
- CanBeArg bool
- NeedBase bool
- AllowColon bool
- ResourceBase bool
- Args []*typeDesc
-}
-
-var (
- typeDir = &typeDesc{
- Names: []string{"in", "out", "inout"},
- }
-
- topTypes = []*typeDesc{
- &typeDesc{
- Names: []string{"int8", "int16", "int32", "int64",
- "int16be", "int32be", "int64be", "intptr"},
- CanBeArg: true,
- AllowColon: true,
- ResourceBase: true,
- },
- &typeDesc{
- Names: []string{"fileoff"},
- CanBeArg: true,
- NeedBase: true,
- },
- &typeDesc{
- Names: []string{"buffer"},
- CanBeArg: true,
- Args: []*typeDesc{typeDir},
- },
- &typeDesc{
- Names: []string{"string"},
- //Args: []*typeDesc{typeDir},
- },
- }
-
- builtinTypes = make(map[string]bool)
-)
-
-func init() {
- for _, desc := range topTypes {
- for _, name := range desc.Names {
- if builtinTypes[name] {
- panic(fmt.Sprintf("duplicate builtin type %q", name))
- }
- builtinTypes[name] = true
- }
- }
-}
-
-var typeCheck bool
-
func (comp *compiler) check() {
// TODO: check len in syscall arguments referring to parent.
// TODO: incorrect name is referenced in len type
// TODO: infinite recursion via struct pointers (e.g. a linked list)
// TODO: no constructor for a resource
- // TODO: typo of intour instead of inout
comp.checkNames()
comp.checkFields()
- if typeCheck {
- for _, decl := range comp.desc.Nodes {
- switch n := decl.(type) {
- case *ast.Resource:
- comp.checkType(n.Base, false, true)
- case *ast.Struct:
- for _, f := range n.Fields {
- comp.checkType(f.Type, false, false)
- }
- case *ast.Call:
- for _, a := range n.Args {
- comp.checkType(a.Type, true, false)
- }
- if n.Ret != nil {
- comp.checkType(n.Ret, true, false)
- }
+ for _, decl := range comp.desc.Nodes {
+ switch n := decl.(type) {
+ case *ast.Resource:
+ comp.checkType(n.Base, false, true)
+ comp.checkResource(n)
+ case *ast.Struct:
+ for _, f := range n.Fields {
+ comp.checkType(f.Type, false, false)
+ }
+ comp.checkStruct(n)
+ case *ast.Call:
+ for _, a := range n.Args {
+ comp.checkType(a.Type, true, false)
+ }
+ if n.Ret != nil {
+ comp.checkType(n.Ret, true, false)
}
}
}
}
func (comp *compiler) checkNames() {
- comp.udt = make(map[string]ast.Node)
- comp.flags = make(map[string]ast.Node)
calls := make(map[string]*ast.Call)
for _, decl := range comp.desc.Nodes {
switch decl.(type) {
case *ast.Resource, *ast.Struct:
pos, typ, name := decl.Info()
- if builtinTypes[name] {
+ if builtinTypes[name] != nil {
comp.error(pos, "%v name %v conflicts with builtin type", typ, name)
continue
}
- if prev := comp.udt[name]; prev != nil {
- pos1, typ1, _ := prev.Info()
+ if prev := comp.resources[name]; prev != nil {
+ comp.error(pos, "type %v redeclared, previously declared as resource at %v",
+ name, prev.Pos)
+ continue
+ }
+ if prev := comp.structs[name]; prev != nil {
+ _, typ, _ := prev.Info()
comp.error(pos, "type %v redeclared, previously declared as %v at %v",
- name, typ1, pos1)
+ name, typ, prev.Pos)
continue
}
- comp.udt[name] = decl
- case *ast.IntFlags, *ast.StrFlags:
- pos, typ, name := decl.Info()
- if prev := comp.flags[name]; prev != nil {
- pos1, typ1, _ := prev.Info()
- comp.error(pos, "%v %v redeclared, previously declared as %v at %v",
- typ, name, typ1, pos1)
+ if res, ok := decl.(*ast.Resource); ok {
+ comp.resources[name] = res
+ } else if str, ok := decl.(*ast.Struct); ok {
+ comp.structs[name] = str
+ }
+ case *ast.IntFlags:
+ n := decl.(*ast.IntFlags)
+ name := n.Name.Name
+ if prev := comp.intFlags[name]; prev != nil {
+ comp.error(n.Pos, "flags %v redeclared, previously declared at %v",
+ name, prev.Pos)
+ continue
+ }
+ comp.intFlags[name] = n
+ case *ast.StrFlags:
+ n := decl.(*ast.StrFlags)
+ name := n.Name.Name
+ if prev := comp.strFlags[name]; prev != nil {
+ comp.error(n.Pos, "string flags %v redeclared, previously declared at %v",
+ name, prev.Pos)
continue
}
- comp.flags[name] = decl
+ comp.strFlags[name] = n
case *ast.Call:
c := decl.(*ast.Call)
name := c.Name.Name
@@ -250,26 +238,92 @@ func (comp *compiler) checkFields() {
}
}
-func (comp *compiler) checkType(t *ast.Type, isArg, isResourceBase bool) {
- if t.String != "" {
- comp.error(t.Pos, "unexpected string %q, expecting type", t.String)
- return
+func (comp *compiler) checkResource(n *ast.Resource) {
+ var seen []string
+ for n != nil {
+ if arrayContains(seen, n.Name.Name) {
+ chain := ""
+ for _, r := range seen {
+ chain += r + "->"
+ }
+ chain += n.Name.Name
+ comp.error(n.Pos, "recursive resource %v", chain)
+ return
+ }
+ seen = append(seen, n.Name.Name)
+ n = comp.resources[n.Base.Ident]
}
- if t.Ident == "" {
- comp.error(t.Pos, "unexpected integer %v, expecting type", t.Value)
- return
+}
+
+func (comp *compiler) checkStruct(n *ast.Struct) {
+ if n.IsUnion {
+ comp.parseUnionAttrs(n)
+ } else {
+ comp.parseStructAttrs(n)
}
- var desc *typeDesc
- for _, desc1 := range topTypes {
- for _, name := range desc1.Names {
- if name == t.Ident {
- desc = desc1
- break
+}
+
+func (comp *compiler) parseUnionAttrs(n *ast.Struct) (varlen bool) {
+ for _, attr := range n.Attrs {
+ switch attr.Name {
+ case "varlen":
+ varlen = true
+ default:
+ comp.error(attr.Pos, "unknown union %v attribute %v",
+ n.Name.Name, attr.Name)
+ }
+ }
+ return
+}
+
+func (comp *compiler) parseStructAttrs(n *ast.Struct) (packed bool, align uint64) {
+ for _, attr := range n.Attrs {
+ switch {
+ case attr.Name == "packed":
+ packed = true
+ case attr.Name == "align_ptr":
+ align = comp.ptrSize
+ case strings.HasPrefix(attr.Name, "align_"):
+ a, err := strconv.ParseUint(attr.Name[6:], 10, 64)
+ if err != nil {
+ comp.error(attr.Pos, "bad struct %v alignment %v",
+ n.Name.Name, attr.Name[6:])
+ continue
+ }
+ if a&(a-1) != 0 || a == 0 || a > 1<<30 {
+ comp.error(attr.Pos, "bad struct %v alignment %v (must be a sane power of 2)",
+ n.Name.Name, a)
}
+ align = a
+ default:
+ comp.error(attr.Pos, "unknown struct %v attribute %v",
+ n.Name.Name, attr.Name)
}
}
+ return
+}
+
+func (comp *compiler) getTypeDesc(t *ast.Type) *typeDesc {
+ if desc := builtinTypes[t.Ident]; desc != nil {
+ return desc
+ }
+ if comp.resources[t.Ident] != nil {
+ return typeResource
+ }
+ if comp.structs[t.Ident] != nil {
+ return typeStruct
+ }
+ return nil
+}
+
+func (comp *compiler) checkType(t *ast.Type, isArg, isResourceBase bool) {
+ if unexpected, _, ok := checkTypeKind(t, kindIdent); !ok {
+ comp.error(t.Pos, "unexpected %v, expect type", unexpected)
+ return
+ }
+ desc := comp.getTypeDesc(t)
if desc == nil {
- comp.error(t.Pos, "unknown type %q", t.Ident)
+ comp.error(t.Pos, "unknown type %v", t.Ident)
return
}
if !desc.AllowColon && t.HasColon {
@@ -284,174 +338,176 @@ func (comp *compiler) checkType(t *ast.Type, isArg, isResourceBase bool) {
comp.error(t.Pos, "%v can't be resource base (int types can)", t.Ident)
return
}
-}
-
-// assignSyscallNumbers assigns syscall numbers, discards unsupported syscalls
-// and removes no longer irrelevant nodes from the tree (comments, new lines, etc).
-func (comp *compiler) assignSyscallNumbers(consts map[string]uint64) {
- // Pseudo syscalls starting from syz_ are assigned numbers starting from syzbase.
- // Note: the numbers must be stable (not depend on file reading order, etc),
- // so we have to do it in 2 passes.
- const syzbase = 1000000
- syzcalls := make(map[string]bool)
- for _, decl := range comp.desc.Nodes {
- c, ok := decl.(*ast.Call)
- if !ok {
- continue
- }
- if strings.HasPrefix(c.CallName, "syz_") {
- syzcalls[c.CallName] = true
+ args, opt := removeOpt(t)
+ if opt && (desc.CantBeOpt || isResourceBase) {
+ what := "resource base"
+ if desc.CantBeOpt {
+ what = t.Ident
}
+ pos := t.Args[len(t.Args)-1].Pos
+ comp.error(pos, "%v can't be marked as opt", what)
+ return
}
- syznr := make(map[string]uint64)
- for i, name := range toArray(syzcalls) {
- syznr[name] = syzbase + uint64(i)
+ addArgs := 0
+ needBase := !isArg && desc.NeedBase
+ if needBase {
+ addArgs++ // last arg must be base type, e.g. const[0, int32]
}
-
- var top []ast.Node
- for _, decl := range comp.desc.Nodes {
- switch decl.(type) {
- case *ast.Call:
- c := decl.(*ast.Call)
- if strings.HasPrefix(c.CallName, "syz_") {
- c.NR = syznr[c.CallName]
- top = append(top, decl)
- continue
- }
- // Lookup in consts.
- str := "__NR_" + c.CallName
- nr, ok := consts[str]
- if ok {
- c.NR = nr
- top = append(top, decl)
- continue
- }
- name := "syscall " + c.CallName
- if !comp.unsupported[name] {
- comp.unsupported[name] = true
- comp.warning(c.Pos, "unsupported syscall: %v due to missing const %v",
- c.CallName, str)
- }
- case *ast.IntFlags, *ast.Resource, *ast.Struct, *ast.StrFlags:
- top = append(top, decl)
- case *ast.NewLine, *ast.Comment, *ast.Include, *ast.Incdir, *ast.Define:
- // These are not needed anymore.
- default:
- panic(fmt.Sprintf("unknown node type: %#v", decl))
+ if len(args) > len(desc.Args)+addArgs || len(args) < len(desc.Args)-desc.OptArgs+addArgs {
+ comp.error(t.Pos, "wrong number of arguments for type %v, expect %v",
+ t.Ident, expectedTypeArgs(desc, needBase))
+ return
+ }
+ if needBase {
+ base := args[len(args)-1]
+ args = args[:len(args)-1]
+ comp.checkTypeArg(t, base, typeArgBase)
+ }
+ err0 := comp.errors
+ for i, arg := range args {
+ if desc.Args[i].Type == typeArgType {
+ comp.checkType(arg, false, false)
+ } else {
+ comp.checkTypeArg(t, arg, desc.Args[i])
}
}
- comp.desc.Nodes = top
+ if err0 != comp.errors {
+ return
+ }
+ if desc.Check != nil {
+ _, args, base := comp.getArgsBase(t, "", sys.DirIn, isArg)
+ desc.Check(comp, t, args, base)
+ }
}
-// patchConsts replaces all symbolic consts with their numeric values taken from consts map.
-// Updates desc and returns set of unsupported syscalls and flags.
-// After this pass consts are not needed for compilation.
-func (comp *compiler) patchConsts(consts map[string]uint64) {
- var top []ast.Node
- for _, decl := range comp.desc.Nodes {
- switch decl.(type) {
- case *ast.IntFlags:
- // Unsupported flag values are dropped.
- n := decl.(*ast.IntFlags)
- var values []*ast.Int
- for _, v := range n.Values {
- if comp.patchIntConst(v.Pos, &v.Value, &v.Ident, consts, nil) {
- values = append(values, v)
- }
- }
- n.Values = values
- top = append(top, n)
- case *ast.StrFlags:
- top = append(top, decl)
- case *ast.Resource, *ast.Struct, *ast.Call:
- // Walk whole tree and replace consts in Int's and Type's.
- missing := ""
- ast.WalkNode(decl, func(n0 ast.Node) {
- switch n := n0.(type) {
- case *ast.Int:
- comp.patchIntConst(n.Pos, &n.Value, &n.Ident, consts, &missing)
- case *ast.Type:
- if c := typeConstIdentifier(n); c != nil {
- comp.patchIntConst(c.Pos, &c.Value, &c.Ident,
- consts, &missing)
- if c.HasColon {
- comp.patchIntConst(c.Pos2, &c.Value2, &c.Ident2,
- consts, &missing)
- }
- }
- }
- })
- if missing == "" {
- top = append(top, decl)
- continue
- }
- // Produce a warning about unsupported syscall/resource/struct.
- // TODO(dvyukov): we should transitively remove everything that
- // depends on unsupported things.
- pos, typ, name := decl.Info()
- if id := typ + " " + name; !comp.unsupported[id] {
- comp.unsupported[id] = true
- comp.warning(pos, "unsupported %v: %v due to missing const %v",
- typ, name, missing)
- }
- // We have to keep partially broken resources and structs,
- // because otherwise their usages will error.
- if _, ok := decl.(*ast.Call); !ok {
- top = append(top, decl)
- }
+func (comp *compiler) checkTypeArg(t, arg *ast.Type, argDesc namedArg) {
+ desc := argDesc.Type
+ if len(desc.Names) != 0 {
+ if unexpected, _, ok := checkTypeKind(arg, kindIdent); !ok {
+ comp.error(arg.Pos, "unexpected %v for %v argument of %v type, expect %+v",
+ unexpected, argDesc.Name, t.Ident, desc.Names)
+ return
+ }
+ if !arrayContains(desc.Names, arg.Ident) {
+ comp.error(arg.Pos, "unexpected value %v for %v argument of %v type, expect %+v",
+ arg.Ident, argDesc.Name, t.Ident, desc.Names)
+ return
+ }
+ } else {
+ if unexpected, expect, ok := checkTypeKind(arg, desc.Kind); !ok {
+ comp.error(arg.Pos, "unexpected %v for %v argument of %v type, expect %v",
+ unexpected, argDesc.Name, t.Ident, expect)
+ return
}
}
- comp.desc.Nodes = top
+ if !desc.AllowColon && arg.HasColon {
+ comp.error(arg.Pos2, "unexpected ':'")
+ return
+ }
+ if desc.Check != nil {
+ desc.Check(comp, arg)
+ }
+}
+
+func (comp *compiler) getArgsBase(t *ast.Type, field string, dir sys.Dir, isArg bool) (
+ *typeDesc, []*ast.Type, sys.IntTypeCommon) {
+ desc := comp.getTypeDesc(t)
+ args, opt := removeOpt(t)
+ com := genCommon(t.Ident, field, dir, opt)
+ base := genIntCommon(com, comp.ptrSize, 0, false)
+ if !isArg && desc.NeedBase {
+ baseType := args[len(args)-1]
+ args = args[:len(args)-1]
+ base = typeInt.Gen(comp, baseType, nil, base).(*sys.IntType).IntTypeCommon
+ }
+ return desc, args, base
}
-func (comp *compiler) patchIntConst(pos ast.Pos, val *uint64, id *string,
- consts map[string]uint64, missing *string) bool {
- if *id == "" {
- return true
+func expectedTypeArgs(desc *typeDesc, needBase bool) string {
+ expect := ""
+ for i, arg := range desc.Args {
+ if expect != "" {
+ expect += ", "
+ }
+ opt := i >= len(desc.Args)-desc.OptArgs
+ if opt {
+ expect += "["
+ }
+ expect += arg.Name
+ if opt {
+ expect += "]"
+ }
}
- v, ok := consts[*id]
- if !ok {
- name := "const " + *id
- if !comp.unsupported[name] {
- comp.unsupported[name] = true
- comp.warning(pos, "unsupported const: %v", *id)
+ if needBase {
+ if expect != "" {
+ expect += ", "
}
- if missing != nil && *missing == "" {
- *missing = *id
+ expect += typeArgBase.Name
+ }
+ if !desc.CantBeOpt {
+ if expect != "" {
+ expect += ", "
}
+ expect += "[opt]"
}
- *val = v
- *id = ""
- return ok
+ if expect == "" {
+ expect = "no arguments"
+ }
+ return expect
}
-// typeConstIdentifier returns type arg that is an integer constant (subject for const patching), if any.
-func typeConstIdentifier(n *ast.Type) *ast.Type {
- if n.Ident == "const" && len(n.Args) > 0 {
- return n.Args[0]
- }
- if n.Ident == "array" && len(n.Args) > 1 && n.Args[1].Ident != "opt" {
- return n.Args[1]
- }
- if n.Ident == "vma" && len(n.Args) > 0 && n.Args[0].Ident != "opt" {
- return n.Args[0]
+func checkTypeKind(t *ast.Type, kind int) (unexpected string, expect string, ok bool) {
+ switch {
+ case kind == kindAny:
+ ok = true
+ case t.String != "":
+ ok = kind == kindString
+ if !ok {
+ unexpected = fmt.Sprintf("string %q", t.String)
+ }
+ case t.Ident != "":
+ ok = kind == kindIdent
+ if !ok {
+ unexpected = fmt.Sprintf("identifier %v", t.Ident)
+ }
+ default:
+ ok = kind == kindInt
+ if !ok {
+ unexpected = fmt.Sprintf("int %v", t.Value)
+ }
}
- if n.Ident == "vma" && len(n.Args) > 0 && n.Args[0].Ident != "opt" {
- return n.Args[0]
+ if !ok {
+ switch kind {
+ case kindString:
+ expect = "string"
+ case kindIdent:
+ expect = "identifier"
+ case kindInt:
+ expect = "int"
+ }
}
- if n.Ident == "string" && len(n.Args) > 1 && n.Args[1].Ident != "opt" {
- return n.Args[1]
+ return
+}
+
+func removeOpt(t *ast.Type) ([]*ast.Type, bool) {
+ args := t.Args
+ if len(args) != 0 && args[len(args)-1].Ident == "opt" {
+ return args[:len(args)-1], true
}
- if n.Ident == "csum" && len(n.Args) > 2 && n.Args[1].Ident == "pseudo" {
- return n.Args[2]
+ return args, false
+}
+
+func (comp *compiler) parseIntType(name string) (size uint64, bigEndian bool) {
+ be := strings.HasSuffix(name, "be")
+ if be {
+ name = name[:len(name)-len("be")]
}
- switch n.Ident {
- case "int8", "int16", "int16be", "int32", "int32be", "int64", "int64be", "intptr":
- if len(n.Args) > 0 && n.Args[0].Ident != "opt" {
- return n.Args[0]
- }
+ size = comp.ptrSize
+ if name != "intptr" {
+ size, _ = strconv.ParseUint(name[3:], 10, 64)
+ size /= 8
}
- return nil
+ return size, be
}
func toArray(m map[string]bool) []string {
@@ -465,3 +521,12 @@ func toArray(m map[string]bool) []string {
sort.Strings(res)
return res
}
+
+func arrayContains(a []string, v string) bool {
+ for _, s := range a {
+ if s == v {
+ return true
+ }
+ }
+ return false
+}
diff --git a/pkg/compiler/compiler_test.go b/pkg/compiler/compiler_test.go
index 1ef77a19c..261cffe9c 100644
--- a/pkg/compiler/compiler_test.go
+++ b/pkg/compiler/compiler_test.go
@@ -12,7 +12,6 @@ import (
)
func TestCompileAll(t *testing.T) {
- t.Skip()
eh := func(pos ast.Pos, msg string) {
t.Logf("%v: %v", pos, msg)
}
@@ -31,13 +30,12 @@ func TestCompileAll(t *testing.T) {
}
}
-func init() {
- typeCheck = true
-}
-
func TestErrors(t *testing.T) {
consts := map[string]uint64{
"__NR_foo": 1,
+ "C0": 0,
+ "C1": 1,
+ "C2": 2,
}
name := "errors.txt"
em := ast.NewErrorMatcher(t, filepath.Join("testdata", name))
@@ -46,6 +44,26 @@ func TestErrors(t *testing.T) {
em.DumpErrors(t)
t.Fatalf("parsing failed")
}
+ ExtractConsts(desc, em.ErrorHandler)
Compile(desc, consts, em.ErrorHandler)
em.Check(t)
}
+
+func TestFuzz(t *testing.T) {
+ inputs := []string{
+ "d~^gB̉`i\u007f?\xb0.",
+ "da[",
+ "define\x98define(define\x98define\x98define\x98define\x98define)define\tdefin",
+ "resource g[g]",
+ }
+ consts := map[string]uint64{"A": 1, "B": 2, "C": 3, "__NR_C": 4}
+ eh := func(pos ast.Pos, msg string) {
+ t.Logf("%v: %v", pos, msg)
+ }
+ for _, data := range inputs {
+ desc := ast.Parse([]byte(data), "", eh)
+ if desc != nil {
+ Compile(desc, consts, eh)
+ }
+ }
+}
diff --git a/pkg/compiler/consts.go b/pkg/compiler/consts.go
index dd56d99c7..970a79050 100644
--- a/pkg/compiler/consts.go
+++ b/pkg/compiler/consts.go
@@ -93,12 +93,186 @@ func ExtractConsts(desc *ast.Description, eh0 ast.ErrorHandler) *ConstInfo {
return info
}
+// assignSyscallNumbers assigns syscall numbers, discards unsupported syscalls
+// and removes no longer irrelevant nodes from the tree (comments, new lines, etc).
+func (comp *compiler) assignSyscallNumbers(consts map[string]uint64) {
+ // Pseudo syscalls starting from syz_ are assigned numbers starting from syzbase.
+ // Note: the numbers must be stable (not depend on file reading order, etc),
+ // so we have to do it in 2 passes.
+ const syzbase = 1000000
+ syzcalls := make(map[string]bool)
+ for _, decl := range comp.desc.Nodes {
+ c, ok := decl.(*ast.Call)
+ if !ok {
+ continue
+ }
+ if strings.HasPrefix(c.CallName, "syz_") {
+ syzcalls[c.CallName] = true
+ }
+ }
+ syznr := make(map[string]uint64)
+ for i, name := range toArray(syzcalls) {
+ syznr[name] = syzbase + uint64(i)
+ }
+
+ var top []ast.Node
+ for _, decl := range comp.desc.Nodes {
+ switch decl.(type) {
+ case *ast.Call:
+ c := decl.(*ast.Call)
+ if strings.HasPrefix(c.CallName, "syz_") {
+ c.NR = syznr[c.CallName]
+ top = append(top, decl)
+ continue
+ }
+ // Lookup in consts.
+ str := "__NR_" + c.CallName
+ nr, ok := consts[str]
+ if ok {
+ c.NR = nr
+ top = append(top, decl)
+ continue
+ }
+ name := "syscall " + c.CallName
+ if !comp.unsupported[name] {
+ comp.unsupported[name] = true
+ comp.warning(c.Pos, "unsupported syscall: %v due to missing const %v",
+ c.CallName, str)
+ }
+ case *ast.IntFlags, *ast.Resource, *ast.Struct, *ast.StrFlags:
+ top = append(top, decl)
+ case *ast.NewLine, *ast.Comment, *ast.Include, *ast.Incdir, *ast.Define:
+ // These are not needed anymore.
+ default:
+ panic(fmt.Sprintf("unknown node type: %#v", decl))
+ }
+ }
+ comp.desc.Nodes = top
+}
+
+// patchConsts replaces all symbolic consts with their numeric values taken from consts map.
+// Updates desc and returns set of unsupported syscalls and flags.
+// After this pass consts are not needed for compilation.
+func (comp *compiler) patchConsts(consts map[string]uint64) {
+ var top []ast.Node
+ for _, decl := range comp.desc.Nodes {
+ switch decl.(type) {
+ case *ast.IntFlags:
+ // Unsupported flag values are dropped.
+ n := decl.(*ast.IntFlags)
+ var values []*ast.Int
+ for _, v := range n.Values {
+ if comp.patchIntConst(v.Pos, &v.Value, &v.Ident, consts, nil) {
+ values = append(values, v)
+ }
+ }
+ n.Values = values
+ top = append(top, n)
+ case *ast.StrFlags:
+ top = append(top, decl)
+ case *ast.Resource, *ast.Struct, *ast.Call:
+ // Walk whole tree and replace consts in Int's and Type's.
+ missing := ""
+ ast.WalkNode(decl, func(n0 ast.Node) {
+ switch n := n0.(type) {
+ case *ast.Int:
+ comp.patchIntConst(n.Pos, &n.Value, &n.Ident, consts, &missing)
+ case *ast.Type:
+ if c := typeConstIdentifier(n); c != nil {
+ comp.patchIntConst(c.Pos, &c.Value, &c.Ident,
+ consts, &missing)
+ if c.HasColon {
+ comp.patchIntConst(c.Pos2, &c.Value2, &c.Ident2,
+ consts, &missing)
+ }
+ }
+ }
+ })
+ if missing == "" {
+ top = append(top, decl)
+ continue
+ }
+ // Produce a warning about unsupported syscall/resource/struct.
+ // TODO(dvyukov): we should transitively remove everything that
+ // depends on unsupported things.
+ // Potentially we still can get, say, a bad int range error
+ // due to the 0 const value.
+ pos, typ, name := decl.Info()
+ if id := typ + " " + name; !comp.unsupported[id] {
+ comp.unsupported[id] = true
+ comp.warning(pos, "unsupported %v: %v due to missing const %v",
+ typ, name, missing)
+ }
+ // We have to keep partially broken resources and structs,
+ // because otherwise their usages will error.
+ if _, ok := decl.(*ast.Call); !ok {
+ top = append(top, decl)
+ }
+ }
+ }
+ comp.desc.Nodes = top
+}
+
+func (comp *compiler) patchIntConst(pos ast.Pos, val *uint64, id *string,
+ consts map[string]uint64, missing *string) bool {
+ if *id == "" {
+ return true
+ }
+ v, ok := consts[*id]
+ if !ok {
+ name := "const " + *id
+ if !comp.unsupported[name] {
+ comp.unsupported[name] = true
+ comp.warning(pos, "unsupported const: %v", *id)
+ }
+ if missing != nil && *missing == "" {
+ *missing = *id
+ }
+ }
+ *val = v
+ *id = ""
+ return ok
+}
+
+// typeConstIdentifier returns type arg that is an integer constant (subject for const patching), if any.
+func typeConstIdentifier(n *ast.Type) *ast.Type {
+ // TODO: see if we can extract this info from typeDesc/typeArg.
+ if n.Ident == "const" && len(n.Args) > 0 {
+ return n.Args[0]
+ }
+ if n.Ident == "array" && len(n.Args) > 1 && n.Args[1].Ident != "opt" {
+ return n.Args[1]
+ }
+ if n.Ident == "vma" && len(n.Args) > 0 && n.Args[0].Ident != "opt" {
+ return n.Args[0]
+ }
+ if n.Ident == "string" && len(n.Args) > 1 && n.Args[1].Ident != "opt" {
+ return n.Args[1]
+ }
+ if n.Ident == "csum" && len(n.Args) > 2 && n.Args[1].Ident == "pseudo" {
+ return n.Args[2]
+ }
+ switch n.Ident {
+ case "int8", "int16", "int16be", "int32", "int32be", "int64", "int64be", "intptr":
+ if len(n.Args) > 0 && n.Args[0].Ident != "opt" {
+ return n.Args[0]
+ }
+ }
+ return nil
+}
+
func SerializeConsts(consts map[string]uint64) []byte {
+ type nameValuePair struct {
+ name string
+ val uint64
+ }
var nv []nameValuePair
for k, v := range consts {
nv = append(nv, nameValuePair{k, v})
}
- sort.Sort(nameValueArray(nv))
+ sort.Slice(nv, func(i, j int) bool {
+ return nv[i].name < nv[j].name
+ })
buf := new(bytes.Buffer)
fmt.Fprintf(buf, "# AUTOGENERATED FILE\n")
@@ -188,14 +362,3 @@ func DeserializeConstsGlob(glob string, eh ast.ErrorHandler) map[string]uint64 {
}
return consts
}
-
-type nameValuePair struct {
- name string
- val uint64
-}
-
-type nameValueArray []nameValuePair
-
-func (a nameValueArray) Len() int { return len(a) }
-func (a nameValueArray) Less(i, j int) bool { return a[i].name < a[j].name }
-func (a nameValueArray) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
diff --git a/pkg/compiler/fuzz.go b/pkg/compiler/fuzz.go
new file mode 100644
index 000000000..9372f6d1c
--- /dev/null
+++ b/pkg/compiler/fuzz.go
@@ -0,0 +1,25 @@
+// Copyright 2017 syzkaller project authors. All rights reserved.
+// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
+
+// +build gofuzz
+
+package compiler
+
+import (
+ "github.com/google/syzkaller/pkg/ast"
+)
+
+func Fuzz(data []byte) int {
+ eh := func(pos ast.Pos, msg string) {}
+ desc := ast.Parse(data, "", eh)
+ if desc == nil {
+ return 0
+ }
+ prog := Compile(desc, fuzzConsts, eh)
+ if prog == nil {
+ return 0
+ }
+ return 1
+}
+
+var fuzzConsts = map[string]uint64{"A": 1, "B": 2, "C": 3, "__NR_C": 4}
diff --git a/pkg/compiler/gen.go b/pkg/compiler/gen.go
new file mode 100644
index 000000000..8eb558189
--- /dev/null
+++ b/pkg/compiler/gen.go
@@ -0,0 +1,150 @@
+// Copyright 2017 syzkaller project authors. All rights reserved.
+// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
+
+package compiler
+
+import (
+ "sort"
+
+ "github.com/google/syzkaller/pkg/ast"
+ "github.com/google/syzkaller/sys"
+)
+
+func (comp *compiler) genResources() []*sys.ResourceDesc {
+ var resources []*sys.ResourceDesc
+ for _, decl := range comp.desc.Nodes {
+ if n, ok := decl.(*ast.Resource); ok {
+ resources = append(resources, comp.genResource(n))
+ }
+ }
+ sort.Slice(resources, func(i, j int) bool {
+ return resources[i].Name < resources[j].Name
+ })
+ return resources
+}
+
+func (comp *compiler) genResource(n *ast.Resource) *sys.ResourceDesc {
+ res := &sys.ResourceDesc{
+ Name: n.Name.Name,
+ }
+ var base *ast.Type
+ for n != nil {
+ res.Values = append(genIntArray(n.Values), res.Values...)
+ res.Kind = append([]string{n.Name.Name}, res.Kind...)
+ base = n.Base
+ n = comp.resources[n.Base.Ident]
+ }
+ if len(res.Values) == 0 {
+ res.Values = []uint64{0}
+ }
+ res.Type = comp.genType(base, "", sys.DirIn, false)
+ return res
+}
+
+func (comp *compiler) genSyscalls() []*sys.Call {
+ var calls []*sys.Call
+ for _, decl := range comp.desc.Nodes {
+ if n, ok := decl.(*ast.Call); ok {
+ calls = append(calls, comp.genSyscall(n))
+ }
+ }
+ sort.Slice(calls, func(i, j int) bool {
+ return calls[i].Name < calls[j].Name
+ })
+ return calls
+}
+
+func (comp *compiler) genSyscall(n *ast.Call) *sys.Call {
+ var ret sys.Type
+ if n.Ret != nil {
+ ret = comp.genType(n.Ret, "ret", sys.DirOut, true)
+ }
+ return &sys.Call{
+ Name: n.Name.Name,
+ CallName: n.CallName,
+ NR: n.NR,
+ Args: comp.genFieldArray(n.Args, sys.DirIn, true),
+ Ret: ret,
+ }
+}
+
+func (comp *compiler) genStructFields() []*sys.StructFields {
+ var structs []*sys.StructFields
+ generated := make(map[sys.StructKey]bool)
+ for n := -1; n != len(generated); {
+ n = len(generated)
+ for key, n := range comp.structUses {
+ if generated[key] {
+ continue
+ }
+ generated[key] = true
+ structs = append(structs, comp.genStructField(key, n))
+ }
+ }
+ sort.Slice(structs, func(i, j int) bool {
+ si, sj := structs[i], structs[j]
+ if si.Key.Name != sj.Key.Name {
+ return si.Key.Name < sj.Key.Name
+ }
+ return si.Key.Dir < sj.Key.Dir
+ })
+ return structs
+}
+
+func (comp *compiler) genStructField(key sys.StructKey, n *ast.Struct) *sys.StructFields {
+ return &sys.StructFields{
+ Key: key,
+ Fields: comp.genFieldArray(n.Fields, key.Dir, false),
+ }
+}
+
+func (comp *compiler) genField(f *ast.Field, dir sys.Dir, isArg bool) sys.Type {
+ return comp.genType(f.Type, f.Name.Name, dir, isArg)
+}
+
+func (comp *compiler) genFieldArray(fields []*ast.Field, dir sys.Dir, isArg bool) []sys.Type {
+ var res []sys.Type
+ for _, f := range fields {
+ res = append(res, comp.genField(f, dir, isArg))
+ }
+ return res
+}
+
+func (comp *compiler) genType(t *ast.Type, field string, dir sys.Dir, isArg bool) sys.Type {
+ desc, args, base := comp.getArgsBase(t, field, dir, isArg)
+ return desc.Gen(comp, t, args, base)
+}
+
+func genCommon(name, field string, dir sys.Dir, opt bool) sys.TypeCommon {
+ return sys.TypeCommon{
+ TypeName: name,
+ FldName: field,
+ ArgDir: dir,
+ IsOptional: opt,
+ }
+}
+
+func genIntCommon(com sys.TypeCommon, size, bitLen uint64, bigEndian bool) sys.IntTypeCommon {
+ return sys.IntTypeCommon{
+ TypeCommon: com,
+ BigEndian: bigEndian,
+ TypeSize: size,
+ BitfieldLen: bitLen,
+ }
+}
+
+func genIntArray(a []*ast.Int) []uint64 {
+ r := make([]uint64, len(a))
+ for i, v := range a {
+ r[i] = v.Value
+ }
+ return r
+}
+
+func genStrArray(a []*ast.String) []string {
+ r := make([]string, len(a))
+ for i, v := range a {
+ r[i] = v.Value
+ }
+ return r
+}
diff --git a/pkg/compiler/testdata/errors.txt b/pkg/compiler/testdata/errors.txt
index 7af998316..d5a596457 100644
--- a/pkg/compiler/testdata/errors.txt
+++ b/pkg/compiler/testdata/errors.txt
@@ -2,62 +2,149 @@
# Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
foo$0(x fileoff, y int8, z buffer[in])
-foo$1(x "bar") ### unexpected string "bar", expecting type
-foo$2(x 123, y "bar") ### unexpected integer 123, expecting type ### unexpected string "bar", expecting type
-foo$3(x string) ### string can't be syscall argument/return
+foo$1(x "bar") ### unexpected string "bar", expect type
+foo$2(x 123, y "bar") ### unexpected int 123, expect type ### unexpected string "bar", expect type
+foo$3(x string) ### string can't be syscall argument/return
resource r0[int32]: 0, 0x1
-resource r1[string["foo"]] ### string can't be resource base (int types can)
-resource r1[int32] ### type r1 redeclared, previously declared as resource at errors.txt:10:1
-resource int32[int32] ### resource name int32 conflicts with builtin type
-resource fileoff[intptr] ### resource name fileoff conflicts with builtin type
+resource r1[string["foo"]] ### string can't be resource base (int types can)
+resource r1[int32] ### type r1 redeclared, previously declared as resource at errors.txt:10:1
+resource int32[int32] ### resource name int32 conflicts with builtin type
+resource fileoff[intptr] ### resource name fileoff conflicts with builtin type
s1 {
f1 int32
}
-s1 { ### type s1 redeclared, previously declared as struct at errors.txt:15:1
+s1 { ### type s1 redeclared, previously declared as struct at errors.txt:15:1
f1 int32
- f1 intptr ### duplicate field f1 in struct s1
- parent int8 ### reserved field name parent in struct s1
+ f1 intptr ### duplicate field f1 in struct s1
+ parent int8 ### reserved field name parent in struct s1
}
-s2 { ### struct s2 has no fields, need at least 1 field
+s2 { ### struct s2 has no fields, need at least 1 field
}
-int32 { ### struct name int32 conflicts with builtin type
+int32 { ### struct name int32 conflicts with builtin type
f1 int32
}
-r0 { ### type r0 redeclared, previously declared as resource at errors.txt:9:1
+r0 { ### type r0 redeclared, previously declared as resource at errors.txt:9:1
f1 int32
}
u0 [
f1 int32
- f2 fileoff
+ f2 fileoff[int32]
]
-u1 [ ### union u1 has only 1 field, need at least 2 fields
+u1 [ ### union u1 has only 1 field, need at least 2 fields
f1 int32
]
u2 [
f1 int8
- f1 int16 ### duplicate field f1 in union u2
- parent int32 ### reserved field name parent in union u2
+ f1 int16 ### duplicate field f1 in union u2
+ parent int32 ### reserved field name parent in union u2
]
-foo$4(a int8, a int16) ### duplicate argument a in syscall foo$4
-foo$4() ### syscall foo$4 redeclared, previously declared at errors.txt:51:1
+foo$4(a int8, a int16) ### duplicate argument a in syscall foo$4
+foo$4() ### syscall foo$4 redeclared, previously declared at errors.txt:51:1
foo()
-foo() ### syscall foo redeclared, previously declared at errors.txt:53:1
+foo() ### syscall foo redeclared, previously declared at errors.txt:53:1
foo$5(a0 int8, a1 int8, a2 int8, a3 int8, a4 int8, a5 int8, a6 int8, a7 int8, a8 int8, a9 int8) ### syscall foo$5 has 10 arguments, allowed maximum is 9
-foo$6(parent int8) ### reserved argument name parent in syscall foo$6
+foo$6(parent int8) ### reserved argument name parent in syscall foo$6
-#s1 {
-# f1 int32:8
-# f2 int32:12
-#}
+f1 = 1
+f2 = 1, 2
+f2 = 1, 2 ### flags f2 redeclared, previously declared at errors.txt:59:1
+sf1 = "a"
+sf2 = "a", "b"
+sf2 = "c" ### string flags sf2 redeclared, previously declared at errors.txt:62:1
+resource r2[r0]: 2
+resource r3[int32:1]
+resource r4[int32[opt]] ### resource base can't be marked as opt
+resource r5[non_existent] ### unknown type non_existent
+resource r6[r6] ### recursive resource r6->r6
+resource r7[r8] ### recursive resource r7->r8->r7
+resource r8[r7] ### recursive resource r8->r7->r8
+resource r9["foo"] ### unexpected string "foo", expect type
+foo$7(a r0, a1 r2[opt])
+foo$8(a fileoff[a, b, c]) ### wrong number of arguments for type fileoff, expect no arguments
+foo$9(a buffer[inout])
+foo$10(a buffer[intout]) ### unexpected value intout for direction argument of buffer type, expect [in out inout]
+foo$11(a buffer["in"]) ### unexpected string "in" for direction argument of buffer type, expect [in out inout]
+foo$12(a buffer[10]) ### unexpected int 10 for direction argument of buffer type, expect [in out inout]
+foo$13(a int32[2:3])
+foo$14(a int32[2:2])
+foo$15(a int32[3:2]) ### bad int range [3:2]
+foo$16(a int32[3])
+foo$17(a ptr[in, int32])
+foo$18(a ptr[in, int32[2:3]])
+foo$19(a ptr[in, int32[opt]])
+foo$20(a ptr) ### wrong number of arguments for type ptr, expect direction, type, [opt]
+foo$21(a ptr["foo"]) ### wrong number of arguments for type ptr, expect direction, type, [opt]
+foo$22(a ptr[in]) ### wrong number of arguments for type ptr, expect direction, type, [opt]
+foo$23(a ptr[in, s3[in]]) ### wrong number of arguments for type s3, expect no arguments
+foo$24(a ptr[in, int32[3:2]]) ### bad int range [3:2]
+foo$25(a proc[0, "foo"]) ### unexpected string "foo" for per-proc values argument of proc type, expect int
+foo$26(a flags[no]) ### unknown flags no
+foo$27(a flags["foo"]) ### unexpected string "foo" for flags argument of flags type, expect identifier
+foo$28(a ptr[in, string["foo"]], b ptr[in, string["foo", 4]])
+foo$29(a ptr[in, string["foo", 3]]) ### string value "foo\x00" exceeds buffer length 3
+foo$30(a ptr[in, string[no]]) ### unknown string flags no
+foo$31(a int8, b ptr[in, csum[a, inet]]) ### wrong number of arguments for type csum, expect csum target, kind, [proto], base type
+foo$32(a int8, b ptr[in, csum[a, inet, 1, int32]]) ### only pseudo csum can have proto
+foo$33(a int8, b ptr[in, csum[a, pseudo, 1, int32]])
+foo$34(a int32["foo"]) ### unexpected string "foo" for range argument of int32 type, expect int
+foo$35(a ptr[in, s3[opt]]) ### s3 can't be marked as opt
+foo$36(a const[1:2]) ### unexpected ':'
+foo$37(a ptr[in, proc[1000, 1, int8]]) ### values starting from 1000 overflow base type
+foo$38(a ptr[in, proc[20, 10, int8]]) ### values starting from 20 with step 10 overflow base type for 32 procs
+foo$39(a fileoff:1) ### unexpected ':'
+foo$40(a len["a"]) ### unexpected string "a" for len target argument of len type, expect identifier
+foo$41(a vma[C1:C2])
+foo$42(a proc[20, 0]) ### proc per-process values must not be 0
+foo$43(a ptr[in, string[1]]) ### unexpected int 1, string arg must be a string literal or string flags
+
+bar()
+
+s3 {
+ f1 int8:0
+ f2 int8:1
+ f3 int8:7
+ f4 int8:8
+ f5 int8:9 ### bitfield of size 9 is too large for base type of size 8
+ f6 int32:32
+ f7 int32:33 ### bitfield of size 33 is too large for base type of size 32
+} [packed, align_4]
+
+s4 {
+ f1 int8
+} [align_7] ### bad struct s4 alignment 7 (must be a sane power of 2)
+
+s5 {
+ f1 int8
+} [varlen] ### unknown struct s5 attribute varlen
+
+s6 {
+ f1 int8
+} [align_foo] ### bad struct s6 alignment foo
+
+u3 [
+ f1 int8
+ f2 int32
+] [varlen]
+
+u4 [
+ f1 int8
+ f2 int32
+] [packed] ### unknown union u4 attribute packed
+
+define d0 SOMETHING
+define d1 `some C expression`
+define d2 some C expression
+define d2 SOMETHING ### duplicate define d2
+define d3 1
diff --git a/pkg/compiler/types.go b/pkg/compiler/types.go
new file mode 100644
index 000000000..710bc1b60
--- /dev/null
+++ b/pkg/compiler/types.go
@@ -0,0 +1,575 @@
+// Copyright 2017 syzkaller project authors. All rights reserved.
+// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
+
+package compiler
+
+import (
+ "fmt"
+ "strconv"
+
+ "github.com/google/syzkaller/pkg/ast"
+ "github.com/google/syzkaller/sys"
+)
+
+// typeDesc is arg/field type descriptor.
+type typeDesc struct {
+ Names []string
+ CanBeArg bool // can be argument of syscall?
+ CantBeOpt bool // can't be marked as opt?
+ NeedBase bool // needs base type when used as field?
+ AllowColon bool // allow colon (int8:2)?
+ ResourceBase bool // can be resource base type?
+ OptArgs int // number of optional arguments in Args array
+ Args []namedArg // type arguments
+ // Check does custom verification of the type (optional).
+ Check func(comp *compiler, t *ast.Type, args []*ast.Type, base sys.IntTypeCommon)
+ // Gen generates corresponding sys.Type.
+ Gen func(comp *compiler, t *ast.Type, args []*ast.Type, base sys.IntTypeCommon) sys.Type
+}
+
+// typeArg describes a type argument.
+type typeArg struct {
+ Names []string
+ Kind int // int/ident/string
+ AllowColon bool // allow colon (2:3)?
+ // Check does custom verification of the arg (optional).
+ Check func(comp *compiler, t *ast.Type)
+}
+
+type namedArg struct {
+ Name string
+ Type *typeArg
+}
+
+const (
+ kindAny = iota
+ kindInt
+ kindIdent
+ kindString
+)
+
+var typeInt = &typeDesc{
+ Names: []string{"int8", "int16", "int32", "int64", "int16be", "int32be", "int64be", "intptr"},
+ CanBeArg: true,
+ AllowColon: true,
+ ResourceBase: true,
+ OptArgs: 1,
+ Args: []namedArg{{"range", typeArgRange}},
+ Check: func(comp *compiler, t *ast.Type, args []*ast.Type, base sys.IntTypeCommon) {
+ typeArgBase.Type.Check(comp, t)
+ },
+ Gen: func(comp *compiler, t *ast.Type, args []*ast.Type, base sys.IntTypeCommon) sys.Type {
+ size, be := comp.parseIntType(t.Ident)
+ kind, rangeBegin, rangeEnd := sys.IntPlain, uint64(0), uint64(0)
+ if len(args) > 0 {
+ kind, rangeBegin, rangeEnd = sys.IntRange, args[0].Value, args[0].Value2
+ }
+ return &sys.IntType{
+ IntTypeCommon: genIntCommon(base.TypeCommon, size, t.Value2, be),
+ Kind: kind,
+ RangeBegin: rangeBegin,
+ RangeEnd: rangeEnd,
+ }
+ },
+}
+
+var typePtr = &typeDesc{
+ Names: []string{"ptr"},
+ CanBeArg: true,
+ Args: []namedArg{{"direction", typeArgDir}, {"type", typeArgType}},
+ Gen: func(comp *compiler, t *ast.Type, args []*ast.Type, base sys.IntTypeCommon) sys.Type {
+ base.ArgDir = sys.DirIn // pointers are always in
+ return &sys.PtrType{
+ TypeCommon: base.TypeCommon,
+ Type: comp.genType(args[1], "", genDir(args[0]), false),
+ }
+ },
+}
+
+var typeArray = &typeDesc{
+ Names: []string{"array"},
+ CantBeOpt: true,
+ OptArgs: 1,
+ Args: []namedArg{{"type", typeArgType}, {"size", typeArgRange}},
+ Gen: func(comp *compiler, t *ast.Type, args []*ast.Type, base sys.IntTypeCommon) sys.Type {
+ elemType := comp.genType(args[0], "", base.ArgDir, false)
+ kind, begin, end := sys.ArrayRandLen, uint64(0), uint64(0)
+ if len(args) > 1 {
+ kind, begin, end = sys.ArrayRangeLen, args[1].Value, args[1].Value2
+ }
+ if it, ok := elemType.(*sys.IntType); ok && it.TypeSize == 1 {
+ // Special case: buffer is better mutated.
+ bufKind := sys.BufferBlobRand
+ if kind == sys.ArrayRangeLen {
+ bufKind = sys.BufferBlobRange
+ }
+ return &sys.BufferType{
+ TypeCommon: base.TypeCommon,
+ Kind: bufKind,
+ RangeBegin: begin,
+ RangeEnd: end,
+ }
+ }
+ return &sys.ArrayType{
+ TypeCommon: base.TypeCommon,
+ Type: elemType,
+ Kind: kind,
+ RangeBegin: begin,
+ RangeEnd: end,
+ }
+ },
+}
+
+var typeLen = &typeDesc{
+ Names: []string{"len", "bytesize", "bytesize2", "bytesize4", "bytesize8"},
+ CanBeArg: true,
+ CantBeOpt: true,
+ NeedBase: true,
+ Args: []namedArg{{"len target", typeArgLenTarget}},
+ Check: func(comp *compiler, t *ast.Type, args []*ast.Type, base sys.IntTypeCommon) {
+ // TODO(dvyukov): check args[0].Ident as len target
+ },
+ Gen: func(comp *compiler, t *ast.Type, args []*ast.Type, base sys.IntTypeCommon) sys.Type {
+ var byteSize uint64
+ switch t.Ident {
+ case "bytesize":
+ byteSize = 1
+ case "bytesize2", "bytesize4", "bytesize8":
+ byteSize, _ = strconv.ParseUint(t.Ident[8:], 10, 8)
+ }
+ return &sys.LenType{
+ IntTypeCommon: base,
+ Buf: args[0].Ident,
+ ByteSize: byteSize,
+ }
+ },
+}
+
+var typeConst = &typeDesc{
+ Names: []string{"const"},
+ CanBeArg: true,
+ CantBeOpt: true,
+ NeedBase: true,
+ Args: []namedArg{{"value", typeArgInt}},
+ Gen: func(comp *compiler, t *ast.Type, args []*ast.Type, base sys.IntTypeCommon) sys.Type {
+ return &sys.ConstType{
+ IntTypeCommon: base,
+ Val: args[0].Value,
+ }
+ },
+}
+
+var typeArgLenTarget = &typeArg{
+ Kind: kindIdent,
+}
+
+var typeFlags = &typeDesc{
+ Names: []string{"flags"},
+ CanBeArg: true,
+ CantBeOpt: true,
+ NeedBase: true,
+ Args: []namedArg{{"flags", typeArgFlags}},
+ Gen: func(comp *compiler, t *ast.Type, args []*ast.Type, base sys.IntTypeCommon) sys.Type {
+ name := args[0].Ident
+ base.TypeName = name
+ f := comp.intFlags[name]
+ if len(f.Values) == 0 {
+ // We can get this if all values are unsupported consts.
+ return &sys.IntType{
+ IntTypeCommon: base,
+ }
+ }
+ return &sys.FlagsType{
+ IntTypeCommon: base,
+ Vals: genIntArray(f.Values),
+ }
+ },
+}
+
+var typeArgFlags = &typeArg{
+ Kind: kindIdent,
+ Check: func(comp *compiler, t *ast.Type) {
+ if comp.intFlags[t.Ident] == nil {
+ comp.error(t.Pos, "unknown flags %v", t.Ident)
+ return
+ }
+ },
+}
+
+var typeFilename = &typeDesc{
+ Names: []string{"filename"},
+ CantBeOpt: true,
+ Gen: func(comp *compiler, t *ast.Type, args []*ast.Type, base sys.IntTypeCommon) sys.Type {
+ return &sys.BufferType{
+ TypeCommon: base.TypeCommon,
+ Kind: sys.BufferFilename,
+ }
+ },
+}
+
+var typeFileoff = &typeDesc{
+ Names: []string{"fileoff"},
+ CanBeArg: true,
+ CantBeOpt: true,
+ NeedBase: true,
+ Gen: func(comp *compiler, t *ast.Type, args []*ast.Type, base sys.IntTypeCommon) sys.Type {
+ return &sys.IntType{
+ IntTypeCommon: base,
+ Kind: sys.IntFileoff,
+ }
+ },
+}
+
+var typeVMA = &typeDesc{
+ Names: []string{"vma"},
+ CanBeArg: true,
+ OptArgs: 1,
+ Args: []namedArg{{"size range", typeArgRange}},
+ Gen: func(comp *compiler, t *ast.Type, args []*ast.Type, base sys.IntTypeCommon) sys.Type {
+ begin, end := uint64(0), uint64(0)
+ if len(args) > 0 {
+ begin, end = args[0].Value, args[0].Value2
+ }
+ return &sys.VmaType{
+ TypeCommon: base.TypeCommon,
+ RangeBegin: begin,
+ RangeEnd: end,
+ }
+ },
+}
+
+// TODO(dvyukov): replace with type with int flags.
+// Or, perhaps, we need something like typedefs:
+// typedef int32[0:32] signalno
+var typeSignalno = &typeDesc{
+ Names: []string{"signalno"},
+ CanBeArg: true,
+ CantBeOpt: true,
+ Gen: func(comp *compiler, t *ast.Type, args []*ast.Type, base sys.IntTypeCommon) sys.Type {
+ base.TypeSize = 4
+ return &sys.IntType{
+ IntTypeCommon: base,
+ Kind: sys.IntSignalno,
+ }
+ },
+}
+
+var typeCsum = &typeDesc{
+ Names: []string{"csum"},
+ NeedBase: true,
+ CantBeOpt: true,
+ OptArgs: 1,
+ Args: []namedArg{{"csum target", typeArgLenTarget}, {"kind", typeArgCsumType}, {"proto", typeArgInt}},
+ Check: func(comp *compiler, t *ast.Type, args []*ast.Type, base sys.IntTypeCommon) {
+ if len(args) > 2 && genCsumKind(args[1]) != sys.CsumPseudo {
+ comp.error(args[2].Pos, "only pseudo csum can have proto")
+ }
+ // TODO(dvyukov): check args[0].Ident as len target
+ },
+ Gen: func(comp *compiler, t *ast.Type, args []*ast.Type, base sys.IntTypeCommon) sys.Type {
+ var proto uint64
+ if len(args) > 2 {
+ proto = args[2].Value
+ }
+ return &sys.CsumType{
+ IntTypeCommon: base,
+ Buf: args[0].Ident,
+ Kind: genCsumKind(args[1]),
+ Protocol: proto,
+ }
+ },
+}
+
+var typeArgCsumType = &typeArg{
+ Kind: kindIdent,
+ Names: []string{"inet", "pseudo"},
+}
+
+func genCsumKind(t *ast.Type) sys.CsumKind {
+ switch t.Ident {
+ case "inet":
+ return sys.CsumInet
+ case "pseudo":
+ return sys.CsumPseudo
+ default:
+ panic(fmt.Sprintf("unknown csum kind %q", t.Ident))
+ }
+}
+
+var typeProc = &typeDesc{
+ Names: []string{"proc"},
+ CanBeArg: true,
+ CantBeOpt: true,
+ NeedBase: true,
+ Args: []namedArg{{"range start", typeArgInt}, {"per-proc values", typeArgInt}},
+ Check: func(comp *compiler, t *ast.Type, args []*ast.Type, base sys.IntTypeCommon) {
+ start := args[0].Value
+ perProc := args[1].Value
+ if perProc == 0 {
+ comp.error(args[1].Pos, "proc per-process values must not be 0")
+ return
+ }
+ size := base.TypeSize * 8
+ if size != 64 {
+ const maxPids = 32 // executor knows about this constant (MAX_PIDS)
+ if start >= 1<<size {
+ comp.error(args[0].Pos, "values starting from %v overflow base type", start)
+ } else if start+maxPids*perProc >= 1<<size {
+ comp.error(args[0].Pos, "values starting from %v with step %v overflow base type for %v procs",
+ start, perProc, maxPids)
+ }
+ }
+ },
+ Gen: func(comp *compiler, t *ast.Type, args []*ast.Type, base sys.IntTypeCommon) sys.Type {
+ return &sys.ProcType{
+ IntTypeCommon: base,
+ ValuesStart: args[0].Value,
+ ValuesPerProc: args[1].Value,
+ }
+ },
+}
+
+var typeText = &typeDesc{
+ Names: []string{"text"},
+ CantBeOpt: true,
+ Args: []namedArg{{"kind", typeArgTextType}},
+ Gen: func(comp *compiler, t *ast.Type, args []*ast.Type, base sys.IntTypeCommon) sys.Type {
+ return &sys.BufferType{
+ TypeCommon: base.TypeCommon,
+ Kind: sys.BufferText,
+ Text: genTextType(args[0]),
+ }
+ },
+}
+
+var typeArgTextType = &typeArg{
+ Kind: kindIdent,
+ Names: []string{"x86_real", "x86_16", "x86_32", "x86_64", "arm64"},
+}
+
+func genTextType(t *ast.Type) sys.TextKind {
+ switch t.Ident {
+ case "x86_real":
+ return sys.Text_x86_real
+ case "x86_16":
+ return sys.Text_x86_16
+ case "x86_32":
+ return sys.Text_x86_32
+ case "x86_64":
+ return sys.Text_x86_64
+ case "arm64":
+ return sys.Text_arm64
+ default:
+ panic(fmt.Sprintf("unknown text type %q", t.Ident))
+ }
+}
+
+var typeBuffer = &typeDesc{
+ Names: []string{"buffer"},
+ CanBeArg: true,
+ Args: []namedArg{{"direction", typeArgDir}},
+ Gen: func(comp *compiler, t *ast.Type, args []*ast.Type, base sys.IntTypeCommon) sys.Type {
+ return &sys.PtrType{
+ TypeCommon: base.TypeCommon,
+ Type: &sys.BufferType{
+ TypeCommon: genCommon("", "", genDir(args[0]), false),
+ Kind: sys.BufferBlobRand,
+ },
+ }
+ },
+}
+
+var typeString = &typeDesc{
+ Names: []string{"string"},
+ OptArgs: 2,
+ Args: []namedArg{{"literal or flags", typeArgStringFlags}, {"size", typeArgInt}},
+ Check: func(comp *compiler, t *ast.Type, args []*ast.Type, base sys.IntTypeCommon) {
+ if len(args) > 1 {
+ size := args[1].Value
+ vals := []string{args[0].String}
+ if args[0].Ident != "" {
+ vals = genStrArray(comp.strFlags[args[0].Ident].Values)
+ }
+ for _, s := range vals {
+ s += "\x00"
+ if uint64(len(s)) > size {
+ comp.error(args[0].Pos, "string value %q exceeds buffer length %v",
+ s, size)
+ }
+ }
+ }
+ },
+ Gen: func(comp *compiler, t *ast.Type, args []*ast.Type, base sys.IntTypeCommon) sys.Type {
+ subkind := ""
+ var vals []string
+ if len(args) > 0 {
+ if args[0].String != "" {
+ vals = append(vals, args[0].String)
+ } else {
+ subkind = args[0].Ident
+ vals = genStrArray(comp.strFlags[subkind].Values)
+ }
+ }
+ var size uint64
+ if len(args) > 1 {
+ size = args[1].Value
+ }
+ for i, s := range vals {
+ s += "\x00"
+ for uint64(len(s)) < size {
+ s += "\x00"
+ }
+ vals[i] = s
+ }
+ for _, s := range vals {
+ if size != 0 && size != uint64(len(s)) {
+ size = 0
+ break
+ }
+ size = uint64(len(s))
+ }
+ return &sys.BufferType{
+ TypeCommon: base.TypeCommon,
+ Kind: sys.BufferString,
+ SubKind: subkind,
+ Values: vals,
+ Length: size,
+ }
+ },
+}
+
+var typeArgStringFlags = &typeArg{
+ Check: func(comp *compiler, t *ast.Type) {
+ if t.String == "" && t.Ident == "" {
+ comp.error(t.Pos, "unexpected int %v, string arg must be a string literal or string flags", t.Value)
+ return
+ }
+ if t.Ident != "" && comp.strFlags[t.Ident] == nil {
+ comp.error(t.Pos, "unknown string flags %v", t.Ident)
+ return
+ }
+ },
+}
+
+// typeArgType is used as placeholder for any type (e.g. ptr target type).
+var typeArgType = &typeArg{
+ Check: func(comp *compiler, t *ast.Type) {
+ panic("must not be called")
+ },
+}
+
+var typeResource = &typeDesc{
+ // No Names, but compiler knows how to match it.
+ CanBeArg: true,
+ ResourceBase: true,
+ Gen: func(comp *compiler, t *ast.Type, args []*ast.Type, base sys.IntTypeCommon) sys.Type {
+ return &sys.ResourceType{
+ TypeCommon: base.TypeCommon,
+ }
+ },
+}
+
+var typeStruct = &typeDesc{
+ // No Names, but compiler knows how to match it.
+ CantBeOpt: true,
+ Gen: func(comp *compiler, t *ast.Type, args []*ast.Type, base sys.IntTypeCommon) sys.Type {
+ s := comp.structs[base.TypeName]
+ comp.structUses[sys.StructKey{base.TypeName, base.ArgDir}] = s
+ if s.IsUnion {
+ return &sys.UnionType{
+ TypeCommon: base.TypeCommon,
+ IsVarlen: comp.parseUnionAttrs(s),
+ }
+ } else {
+ packed, align := comp.parseStructAttrs(s)
+ return &sys.StructType{
+ TypeCommon: base.TypeCommon,
+ IsPacked: packed,
+ AlignAttr: align,
+ }
+ }
+ },
+}
+
+var typeArgDir = &typeArg{
+ Kind: kindIdent,
+ Names: []string{"in", "out", "inout"},
+}
+
+func genDir(t *ast.Type) sys.Dir {
+ switch t.Ident {
+ case "in":
+ return sys.DirIn
+ case "out":
+ return sys.DirOut
+ case "inout":
+ return sys.DirInOut
+ default:
+ panic(fmt.Sprintf("unknown direction %q", t.Ident))
+ }
+}
+
+var typeArgInt = &typeArg{
+ Kind: kindInt,
+}
+
+var typeArgRange = &typeArg{
+ Kind: kindInt,
+ AllowColon: true,
+ Check: func(comp *compiler, t *ast.Type) {
+ if !t.HasColon {
+ t.Value2 = t.Value
+ }
+ if t.Value > t.Value2 {
+ comp.error(t.Pos, "bad int range [%v:%v]", t.Value, t.Value2)
+ }
+ },
+}
+
+// Base type of const/len/etc. Same as typeInt, but can't have range.
+var typeArgBase = namedArg{
+ Name: "base type",
+ Type: &typeArg{
+ Names: []string{"int8", "int16", "int32", "int64", "int16be", "int32be", "int64be", "intptr"},
+ AllowColon: true,
+ Check: func(comp *compiler, t *ast.Type) {
+ size, _ := comp.parseIntType(t.Ident)
+ if t.Value2 > size*8 {
+ comp.error(t.Pos2, "bitfield of size %v is too large for base type of size %v",
+ t.Value2, size*8)
+ }
+ },
+ },
+}
+
+var builtinTypes = make(map[string]*typeDesc)
+
+func init() {
+ builtins := []*typeDesc{
+ typeInt,
+ typePtr,
+ typeArray,
+ typeLen,
+ typeConst,
+ typeFlags,
+ typeFilename,
+ typeFileoff,
+ typeVMA,
+ typeSignalno,
+ typeCsum,
+ typeProc,
+ typeText,
+ typeBuffer,
+ typeString,
+ typeResource,
+ typeStruct,
+ }
+ for _, desc := range builtins {
+ for _, name := range desc.Names {
+ if builtinTypes[name] != nil {
+ panic(fmt.Sprintf("duplicate builtin type %q", name))
+ }
+ builtinTypes[name] = desc
+ }
+ }
+}
diff --git a/pkg/csource/csource.go b/pkg/csource/csource.go
index 6ac5d2eba..5c4b2f848 100644
--- a/pkg/csource/csource.go
+++ b/pkg/csource/csource.go
@@ -79,12 +79,12 @@ func Write(p *prog.Prog, opts Options) ([]byte, error) {
fmt.Fprint(w, "// autogenerated by syzkaller (http://github.com/google/syzkaller)\n\n")
- handled := make(map[string]int)
+ handled := make(map[string]uint64)
for _, c := range p.Calls {
handled[c.Meta.CallName] = c.Meta.NR
}
for name, nr := range handled {
- // Only generate defines for new (added after commit 8a1ab3155c2ac on 2012-10-04) native syscalls.
+ // Only generate defines for new syscalls (added after commit 8a1ab3155c2ac on 2012-10-04).
// TODO: the syscall number 313 implies that we're dealing with linux/amd64.
if nr >= 313 && !strings.HasPrefix(name, "syz_") {
fmt.Fprintf(w, "#ifndef __NR_%v\n", name)
@@ -375,8 +375,9 @@ loop:
if !opts.EnableTun && (meta.CallName == "syz_emit_ethernet" || meta.CallName == "syz_extract_tcp_res") {
emitCall = false
}
+ native := !strings.HasPrefix(meta.CallName, "syz_")
if emitCall {
- if meta.Native {
+ if native {
fmt.Fprintf(w, "\tr[%v] = syscall(__NR_%v", n, meta.CallName)
} else {
fmt.Fprintf(w, "\tr[%v] = %v(", n, meta.CallName)
@@ -387,7 +388,7 @@ loop:
typ := read()
size := read()
_ = size
- if emitCall && (meta.Native || i > 0) {
+ if emitCall && (native || i > 0) {
fmt.Fprintf(w, ", ")
}
switch typ {
@@ -419,7 +420,7 @@ loop:
return calls, n
}
-func preprocessCommonHeader(opts Options, handled map[string]int, useBitmasks, useChecksums bool) (string, error) {
+func preprocessCommonHeader(opts Options, handled map[string]uint64, useBitmasks, useChecksums bool) (string, error) {
var defines []string
if useBitmasks {
defines = append(defines, "SYZ_USE_BITMASKS")
diff --git a/pkg/host/host.go b/pkg/host/host.go
index 4e634f1aa..720bb60f1 100644
--- a/pkg/host/host.go
+++ b/pkg/host/host.go
@@ -38,9 +38,6 @@ func DetectSupportedSyscalls() (map[*sys.Call]bool, error) {
}
func isSupported(kallsyms []byte, c *sys.Call) bool {
- if c.NR == -1 {
- return false // don't even have a syscall number
- }
if strings.HasPrefix(c.CallName, "syz_") {
return isSupportedSyzkall(c)
}
diff --git a/pkg/serializer/serializer.go b/pkg/serializer/serializer.go
new file mode 100644
index 000000000..2b1bbb905
--- /dev/null
+++ b/pkg/serializer/serializer.go
@@ -0,0 +1,165 @@
+// Copyright 2017 syzkaller project authors. All rights reserved.
+// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
+
+package serializer
+
+import (
+ "reflect"
+
+ "fmt"
+ "io"
+)
+
+// Write writes Go-syntax representation of v into w.
+// This is similar to fmt.Fprintf(w, "%#v", v), but properly handles pointers,
+// does not write package names before types, omits struct fields with default values,
+// omits type names where possible, etc. On the other hand, it currently does not
+// support all types (e.g. channels and maps).
+func Write(w io.Writer, v interface{}) {
+ ww := writer{w}
+ ww.do(reflect.ValueOf(v), false)
+}
+
+type writer struct {
+ w io.Writer
+}
+
+func (w *writer) do(v reflect.Value, sliceElem bool) {
+ switch v.Kind() {
+ case reflect.Ptr:
+ if v.IsNil() {
+ w.string("nil")
+ return
+ }
+ if !sliceElem {
+ w.byte('&')
+ }
+ if v.Elem().Kind() != reflect.Struct {
+ panic(fmt.Sprintf("only pointers to structs are supported, got %v",
+ v.Type().Name()))
+ }
+ w.do(v.Elem(), sliceElem)
+ case reflect.Interface:
+ if v.IsNil() {
+ w.string("nil")
+ } else {
+ w.do(v.Elem(), false)
+ }
+ case reflect.Slice:
+ if v.IsNil() || v.Len() == 0 {
+ w.string("nil")
+ } else {
+ w.typ(v.Type())
+ if sub := v.Type().Elem().Kind(); sub == reflect.Ptr || sub == reflect.Interface {
+ // Elem per-line.
+ w.string("{\n")
+ for i := 0; i < v.Len(); i++ {
+ w.do(v.Index(i), true)
+ w.string(",\n")
+ }
+ w.byte('}')
+ } else {
+ // All on one line.
+ w.byte('{')
+ for i := 0; i < v.Len(); i++ {
+ if i > 0 {
+ w.byte(',')
+ }
+ w.do(v.Index(i), true)
+ }
+ w.byte('}')
+ }
+ }
+ case reflect.Struct:
+ if !sliceElem {
+ w.string(v.Type().Name())
+ }
+ w.byte('{')
+ needComma := false
+ for i := 0; i < v.NumField(); i++ {
+ f := v.Field(i)
+ if isDefaultValue(f) {
+ continue
+ }
+ if needComma {
+ w.byte(',')
+ }
+ w.string(v.Type().Field(i).Name)
+ w.byte(':')
+ w.do(f, false)
+ needComma = true
+ }
+ w.byte('}')
+ case reflect.Bool:
+ if v.Bool() {
+ w.string("true")
+ } else {
+ w.string("false")
+ }
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ fmt.Fprintf(w.w, "%v", v.Int())
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+ fmt.Fprintf(w.w, "%v", v.Uint())
+ case reflect.String:
+ fmt.Fprintf(w.w, "%q", v.String())
+ default:
+ panic(fmt.Sprintf("unsupported type: %#v", v.Type().String()))
+ }
+}
+
+func (w *writer) typ(t reflect.Type) {
+ switch t.Kind() {
+ case reflect.Ptr:
+ w.byte('*')
+ w.typ(t.Elem())
+ case reflect.Slice:
+ w.string("[]")
+ w.typ(t.Elem())
+ default:
+ w.string(t.Name())
+ }
+}
+
+func (w *writer) write(v []byte) {
+ w.w.Write(v)
+}
+
+func (w *writer) string(v string) {
+ io.WriteString(w.w, v)
+}
+
+func (w *writer) byte(v byte) {
+ if bw, ok := w.w.(io.ByteWriter); ok {
+ bw.WriteByte(v)
+ } else {
+ w.w.Write([]byte{v})
+ }
+}
+
+func isDefaultValue(v reflect.Value) bool {
+ switch v.Kind() {
+ case reflect.Ptr:
+ return v.IsNil()
+ case reflect.Interface:
+ return v.IsNil()
+ case reflect.Slice:
+ return v.IsNil() || v.Len() == 0
+ case reflect.Struct:
+ for i := 0; i < v.NumField(); i++ {
+ if !isDefaultValue(v.Field(i)) {
+ return false
+ }
+ }
+ return true
+ case reflect.Bool:
+ return !v.Bool()
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ return v.Int() == 0
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+ return v.Uint() == 0
+ case reflect.String:
+ return v.String() == ""
+ default:
+ return false
+ }
+}
diff --git a/pkg/serializer/serializer_test.go b/pkg/serializer/serializer_test.go
new file mode 100644
index 000000000..17cc9550b
--- /dev/null
+++ b/pkg/serializer/serializer_test.go
@@ -0,0 +1,44 @@
+// Copyright 2017 syzkaller project authors. All rights reserved.
+// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
+
+package serializer
+
+import (
+ "bytes"
+ "testing"
+)
+
+func TestSerializer(t *testing.T) {
+ x := &X{
+ Y: Y{1},
+ P: &Y{2},
+ A: []Y{Y{3}, Y{4}},
+ F: true,
+ S: "a\x09b",
+ T: T1,
+ }
+ buf := new(bytes.Buffer)
+ Write(buf, x)
+ t.Logf("\n%s", buf.String())
+ t.Logf("\n%#v", x)
+}
+
+type X struct {
+ Y Y
+ P *Y
+ A []Y
+ F bool
+ S string
+ T T
+}
+
+type Y struct {
+ V int
+}
+
+type T int
+
+const (
+ T0 T = iota
+ T1
+)