From a7206b24cac96c08aecf2f3b4cc3c2e555eec708 Mon Sep 17 00:00:00 2001 From: Dmitry Vyukov Date: Mon, 28 Aug 2017 15:59:22 +0200 Subject: pkg/compiler: check and generate types Move most of the logic from sysgen to pkg/compiler. Update #217 --- pkg/compiler/compiler.go | 581 ++++++++++++++++++++++----------------- pkg/compiler/compiler_test.go | 28 +- pkg/compiler/consts.go | 187 ++++++++++++- pkg/compiler/fuzz.go | 25 ++ pkg/compiler/gen.go | 150 ++++++++++ pkg/compiler/testdata/errors.txt | 137 +++++++-- pkg/compiler/types.go | 575 ++++++++++++++++++++++++++++++++++++++ 7 files changed, 1383 insertions(+), 300 deletions(-) create mode 100644 pkg/compiler/fuzz.go create mode 100644 pkg/compiler/gen.go create mode 100644 pkg/compiler/types.go (limited to 'pkg/compiler') 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<= 1< 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 + } + } +} -- cgit mrf-deployment