aboutsummaryrefslogtreecommitdiffstats
path: root/pkg/compiler/compiler.go
diff options
context:
space:
mode:
authorDmitry Vyukov <dvyukov@google.com>2017-08-27 19:55:14 +0200
committerDmitry Vyukov <dvyukov@google.com>2017-08-27 20:19:41 +0200
commit4074aed7c0c28afc7d4a3522045196c3f39b5208 (patch)
tree8d2c2ce5f6767f8f4355e37e262f85223ee362e3 /pkg/compiler/compiler.go
parent58579664687b203ff34fad8aa02bf470ef0bc981 (diff)
pkg/compiler: more static error checking
Update #217
Diffstat (limited to 'pkg/compiler/compiler.go')
-rw-r--r--pkg/compiler/compiler.go288
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 == "" {