aboutsummaryrefslogtreecommitdiffstats
path: root/pkg/compiler/compiler.go
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/compiler/compiler.go')
-rw-r--r--pkg/compiler/compiler.go581
1 files changed, 323 insertions, 258 deletions
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
+}