diff options
| author | Dmitry Vyukov <dvyukov@google.com> | 2017-08-27 19:55:14 +0200 |
|---|---|---|
| committer | Dmitry Vyukov <dvyukov@google.com> | 2017-08-27 20:19:41 +0200 |
| commit | 4074aed7c0c28afc7d4a3522045196c3f39b5208 (patch) | |
| tree | 8d2c2ce5f6767f8f4355e37e262f85223ee362e3 /pkg/compiler/compiler.go | |
| parent | 58579664687b203ff34fad8aa02bf470ef0bc981 (diff) | |
pkg/compiler: more static error checking
Update #217
Diffstat (limited to 'pkg/compiler/compiler.go')
| -rw-r--r-- | pkg/compiler/compiler.go | 288 |
1 files changed, 233 insertions, 55 deletions
diff --git a/pkg/compiler/compiler.go b/pkg/compiler/compiler.go index e85fdf6e2..3afab96d9 100644 --- a/pkg/compiler/compiler.go +++ b/pkg/compiler/compiler.go @@ -38,6 +38,14 @@ func Compile(desc *ast.Description, consts map[string]uint64, eh ast.ErrorHandle return nil } + comp.check() + + if comp.errors != 0 { + return nil + } + for _, w := range comp.warnings { + eh(w.pos, w.msg) + } return &Prog{ Desc: comp.desc, Unsupported: comp.unsupported, @@ -45,20 +53,237 @@ func Compile(desc *ast.Description, consts map[string]uint64, eh ast.ErrorHandle } type compiler struct { - desc *ast.Description - eh ast.ErrorHandler - errors int + desc *ast.Description + eh ast.ErrorHandler + errors int + warnings []warn unsupported map[string]bool + + udt map[string]ast.Node // structs, unions and resources + flags map[string]ast.Node // int and string flags +} + +type warn struct { + pos ast.Pos + msg string } func (comp *compiler) error(pos ast.Pos, msg string, args ...interface{}) { comp.errors++ - comp.warning(pos, msg, args...) + comp.eh(pos, fmt.Sprintf(msg, args...)) } func (comp *compiler) warning(pos ast.Pos, msg string, args ...interface{}) { - comp.eh(pos, fmt.Sprintf(msg, args...)) + 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) + } + } + } + } +} + +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] { + comp.error(pos, "%v name %v conflicts with builtin type", typ, name) + continue + } + if prev := comp.udt[name]; prev != nil { + pos1, typ1, _ := prev.Info() + comp.error(pos, "type %v redeclared, previously declared as %v at %v", + name, typ1, pos1) + 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) + continue + } + comp.flags[name] = decl + case *ast.Call: + c := decl.(*ast.Call) + name := c.Name.Name + if prev := calls[name]; prev != nil { + comp.error(c.Pos, "syscall %v redeclared, previously declared at %v", + name, prev.Pos) + } + calls[name] = c + } + } +} + +func (comp *compiler) checkFields() { + const maxArgs = 9 // executor does not support more + for _, decl := range comp.desc.Nodes { + switch n := decl.(type) { + case *ast.Struct: + _, typ, name := n.Info() + fields := make(map[string]bool) + for _, f := range n.Fields { + fn := f.Name.Name + if fn == "parent" { + comp.error(f.Pos, "reserved field name %v in %v %v", fn, typ, name) + } + if fields[fn] { + comp.error(f.Pos, "duplicate field %v in %v %v", fn, typ, name) + } + fields[fn] = true + } + if !n.IsUnion && len(n.Fields) < 1 { + comp.error(n.Pos, "struct %v has no fields, need at least 1 field", name) + } + if n.IsUnion && len(n.Fields) < 2 { + comp.error(n.Pos, "union %v has only %v field, need at least 2 fields", + name, len(n.Fields)) + } + case *ast.Call: + name := n.Name.Name + args := make(map[string]bool) + for _, a := range n.Args { + an := a.Name.Name + if an == "parent" { + comp.error(a.Pos, "reserved argument name %v in syscall %v", + an, name) + } + if args[an] { + comp.error(a.Pos, "duplicate argument %v in syscall %v", + an, name) + } + args[an] = true + } + if len(n.Args) > maxArgs { + comp.error(n.Pos, "syscall %v has %v arguments, allowed maximum is %v", + name, len(n.Args), maxArgs) + } + } + } +} + +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 + } + if t.Ident == "" { + comp.error(t.Pos, "unexpected integer %v, expecting type", t.Value) + return + } + var desc *typeDesc + for _, desc1 := range topTypes { + for _, name := range desc1.Names { + if name == t.Ident { + desc = desc1 + break + } + } + } + if desc == nil { + comp.error(t.Pos, "unknown type %q", t.Ident) + return + } + if !desc.AllowColon && t.HasColon { + comp.error(t.Pos2, "unexpected ':'") + return + } + if isArg && !desc.CanBeArg { + comp.error(t.Pos, "%v can't be syscall argument/return", t.Ident) + return + } + if isResourceBase && !desc.ResourceBase { + comp.error(t.Pos, "%v can't be resource base (int types can)", t.Ident) + return + } } // assignSyscallNumbers assigns syscall numbers, discards unsupported syscalls @@ -141,7 +366,7 @@ func (comp *compiler) patchConsts(consts map[string]uint64) { case *ast.Resource, *ast.Struct, *ast.Call: // Walk whole tree and replace consts in Int's and Type's. missing := "" - ast.WalkNode(decl, nil, func(n0, _ ast.Node) { + ast.WalkNode(decl, func(n0 ast.Node) { switch n := n0.(type) { case *ast.Int: comp.patchIntConst(n.Pos, &n.Value, &n.Ident, consts, &missing) @@ -164,18 +389,10 @@ func (comp *compiler) patchConsts(consts map[string]uint64) { // Unsupported syscalls are discarded. // Unsupported resource/struct lead to compilation error. // Fixing that would require removing all uses of the resource/struct. - pos, typ, name := ast.Pos{}, "", "" + pos, typ, name := decl.Info() fn := comp.error - switch n := decl.(type) { - case *ast.Call: - pos, typ, name = n.Pos, "syscall", n.Name.Name + if _, ok := decl.(*ast.Call); ok { fn = comp.warning - case *ast.Resource: - pos, typ, name = n.Pos, "resource", n.Name.Name - case *ast.Struct: - pos, typ, name = n.Pos, "struct", n.Name.Name - default: - panic(fmt.Sprintf("unknown type: %#v", decl)) } if id := typ + " " + name; !comp.unsupported[id] { comp.unsupported[id] = true @@ -187,45 +404,6 @@ func (comp *compiler) patchConsts(consts map[string]uint64) { comp.desc.Nodes = top } -// ExtractConsts returns list of literal constants and other info required const value extraction. -func ExtractConsts(desc *ast.Description) (consts, includes, incdirs []string, defines map[string]string) { - constMap := make(map[string]bool) - defines = make(map[string]string) - - ast.Walk(desc, func(n1, _ ast.Node) { - switch n := n1.(type) { - case *ast.Include: - includes = append(includes, n.File.Value) - case *ast.Incdir: - incdirs = append(incdirs, n.Dir.Value) - case *ast.Define: - v := fmt.Sprint(n.Value.Value) - switch { - case n.Value.CExpr != "": - v = n.Value.CExpr - case n.Value.Ident != "": - v = n.Value.Ident - } - defines[n.Name.Name] = v - constMap[n.Name.Name] = true - case *ast.Call: - if !strings.HasPrefix(n.CallName, "syz_") { - constMap["__NR_"+n.CallName] = true - } - case *ast.Type: - if c := typeConstIdentifier(n); c != nil { - constMap[c.Ident] = true - constMap[c.Ident2] = true - } - case *ast.Int: - constMap[n.Ident] = true - } - }) - - consts = toArray(constMap) - return -} - func (comp *compiler) patchIntConst(pos ast.Pos, val *uint64, id *string, consts map[string]uint64, missing *string) bool { if *id == "" { |
