From 402a0dc87e7d51812a18fa76feeb46d66efda175 Mon Sep 17 00:00:00 2001 From: Dmitry Vyukov Date: Sat, 6 Jan 2018 14:46:52 +0100 Subject: sys: support type aliases (aka typedefs) Complex types that are often repeated can be given short type aliases using the following syntax: ``` type identifier underlying_type ``` For example: ``` type signalno int32[0:65] type net_port proc[20000, 4, int16be] ``` Then, type alias can be used instead of the underlying type in any contexts. Underlying type needs to be described as if it's a struct field, that is, with the base type if it's required. However, type alias can be used as syscall arguments as well. Underlying types are currently restricted to integer types, `ptr`, `ptr64`, `const`, `flags` and `proc` types. --- pkg/compiler/check.go | 64 +++++++++++++++++++++++++++++++++++----- pkg/compiler/compiler.go | 5 ++++ pkg/compiler/compiler_test.go | 49 ++++++++++++++++++------------ pkg/compiler/consts.go | 4 +-- pkg/compiler/testdata/errors.txt | 63 +++++++++++++++++++++++++++++++++++++++ pkg/compiler/types.go | 62 +++++++++++++++++++++++--------------- 6 files changed, 195 insertions(+), 52 deletions(-) (limited to 'pkg/compiler') diff --git a/pkg/compiler/check.go b/pkg/compiler/check.go index bfe1c9d75..ff87ead79 100644 --- a/pkg/compiler/check.go +++ b/pkg/compiler/check.go @@ -34,7 +34,7 @@ func (comp *compiler) checkNames() { calls := make(map[string]*ast.Call) for _, decl := range comp.desc.Nodes { switch decl.(type) { - case *ast.Resource, *ast.Struct: + case *ast.Resource, *ast.Struct, *ast.TypeDef: pos, typ, name := decl.Info() if reservedName[name] { comp.error(pos, "%v uses reserved name %v", typ, name) @@ -49,6 +49,11 @@ func (comp *compiler) checkNames() { name, prev.Pos) continue } + if prev := comp.typedefs[name]; prev != nil { + comp.error(pos, "type %v redeclared, previously declared as type alias 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", @@ -57,6 +62,8 @@ func (comp *compiler) checkNames() { } if res, ok := decl.(*ast.Resource); ok { comp.resources[name] = res + } else if n, ok := decl.(*ast.TypeDef); ok { + comp.typedefs[name] = n } else if str, ok := decl.(*ast.Struct); ok { comp.structs[name] = str } @@ -146,21 +153,34 @@ func (comp *compiler) checkFields() { } func (comp *compiler) checkTypes() { + for _, decl := range comp.desc.Nodes { + switch n := decl.(type) { + case *ast.TypeDef: + if comp.typedefs[n.Name.Name] == nil { + continue + } + err0 := comp.errors + comp.checkType(n.Type, false, false, false, false, true, true) + if err0 != comp.errors { + delete(comp.typedefs, n.Name.Name) + } + } + } for _, decl := range comp.desc.Nodes { switch n := decl.(type) { case *ast.Resource: - comp.checkType(n.Base, false, false, false, true) + comp.checkType(n.Base, false, false, false, true, false, false) case *ast.Struct: for _, f := range n.Fields { - comp.checkType(f.Type, false, false, !n.IsUnion, false) + comp.checkType(f.Type, false, false, !n.IsUnion, false, false, false) } comp.checkStruct(n) case *ast.Call: for _, a := range n.Args { - comp.checkType(a.Type, true, false, false, false) + comp.checkType(a.Type, true, false, false, false, false, false) } if n.Ret != nil { - comp.checkType(n.Ret, true, true, false, false) + comp.checkType(n.Ret, true, true, false, false, false, false) } } } @@ -459,7 +479,7 @@ func (comp *compiler) checkStruct(n *ast.Struct) { } } -func (comp *compiler) checkType(t *ast.Type, isArg, isRet, isStruct, isResourceBase bool) { +func (comp *compiler) checkType(t *ast.Type, isArg, isRet, isStruct, isResourceBase, isTypedef, isTypedefCtx bool) { if unexpected, _, ok := checkTypeKind(t, kindIdent); !ok { comp.error(t.Pos, "unexpected %v, expect type", unexpected) return @@ -469,6 +489,32 @@ func (comp *compiler) checkType(t *ast.Type, isArg, isRet, isStruct, isResourceB comp.error(t.Pos, "unknown type %v", t.Ident) return } + if desc == typeTypedef { + if isTypedefCtx { + comp.error(t.Pos, "type aliases can't refer to other type aliases") + return + } + if t.HasColon { + comp.error(t.Pos, "type alias %v with ':'", t.Ident) + return + } + if len(t.Args) != 0 { + comp.error(t.Pos, "type alias %v with arguments", t.Ident) + return + } + *t = *comp.typedefs[t.Ident].Type.Clone(t.Pos).(*ast.Type) + desc = comp.getTypeDesc(t) + if isArg && desc.NeedBase { + baseTypePos := len(t.Args) - 1 + if t.Args[baseTypePos].Ident == "opt" { + baseTypePos-- + } + copy(t.Args[baseTypePos:], t.Args[baseTypePos+1:]) + t.Args = t.Args[:len(t.Args)-1] + } + comp.checkType(t, isArg, isRet, isStruct, isResourceBase, isTypedef, isTypedefCtx) + return + } if t.HasColon { if !desc.AllowColon { comp.error(t.Pos2, "unexpected ':'") @@ -487,6 +533,10 @@ func (comp *compiler) checkType(t *ast.Type, isArg, isRet, isStruct, isResourceB comp.error(t.Pos, "%v can't be syscall argument", t.Ident) return } + if isTypedef && !desc.CanBeTypedef { + comp.error(t.Pos, "%v can't be type alias target", t.Ident) + return + } if isResourceBase && !desc.ResourceBase { comp.error(t.Pos, "%v can't be resource base (int types can)", t.Ident) return @@ -519,7 +569,7 @@ func (comp *compiler) checkType(t *ast.Type, isArg, isRet, isStruct, isResourceB err0 := comp.errors for i, arg := range args { if desc.Args[i].Type == typeArgType { - comp.checkType(arg, false, isRet, false, false) + comp.checkType(arg, false, isRet, false, false, false, isTypedefCtx) } else { comp.checkTypeArg(t, arg, desc.Args[i]) } diff --git a/pkg/compiler/compiler.go b/pkg/compiler/compiler.go index d250eda68..0be7f2817 100644 --- a/pkg/compiler/compiler.go +++ b/pkg/compiler/compiler.go @@ -53,6 +53,7 @@ func Compile(desc *ast.Description, consts map[string]uint64, target *targets.Ta ptrSize: target.PtrSize, unsupported: make(map[string]bool), resources: make(map[string]*ast.Resource), + typedefs: make(map[string]*ast.TypeDef), structs: make(map[string]*ast.Struct), intFlags: make(map[string]*ast.IntFlags), strFlags: make(map[string]*ast.StrFlags), @@ -89,6 +90,7 @@ type compiler struct { unsupported map[string]bool resources map[string]*ast.Resource + typedefs map[string]*ast.TypeDef structs map[string]*ast.Struct intFlags map[string]*ast.IntFlags strFlags map[string]*ast.StrFlags @@ -163,6 +165,9 @@ func (comp *compiler) getTypeDesc(t *ast.Type) *typeDesc { if comp.structs[t.Ident] != nil { return typeStruct } + if comp.typedefs[t.Ident] != nil { + return typeTypedef + } return nil } diff --git a/pkg/compiler/compiler_test.go b/pkg/compiler/compiler_test.go index ac2d88167..f26c272e6 100644 --- a/pkg/compiler/compiler_test.go +++ b/pkg/compiler/compiler_test.go @@ -5,7 +5,6 @@ package compiler import ( "path/filepath" - "runtime" "testing" "github.com/google/syzkaller/pkg/ast" @@ -13,21 +12,33 @@ import ( ) func TestCompileAll(t *testing.T) { - eh := func(pos ast.Pos, msg string) { - t.Logf("%v: %v", pos, msg) - } - desc := ast.ParseGlob(filepath.Join("..", "..", "sys", "linux", "*.txt"), eh) - if desc == nil { - t.Fatalf("parsing failed") - } - glob := filepath.Join("..", "..", "sys", "linux", "*_"+runtime.GOARCH+".const") - consts := DeserializeConstsGlob(glob, eh) - if consts == nil { - t.Fatalf("reading consts failed") - } - prog := Compile(desc, consts, targets.List["linux"]["amd64"], eh) - if prog == nil { - t.Fatalf("compilation failed") + for os, arches := range targets.List { + os, arches := os, arches + t.Run(os, func(t *testing.T) { + t.Parallel() + eh := func(pos ast.Pos, msg string) { + t.Logf("%v: %v", pos, msg) + } + path := filepath.Join("..", "..", "sys", os) + desc := ast.ParseGlob(filepath.Join(path, "*.txt"), eh) + if desc == nil { + t.Fatalf("parsing failed") + } + for arch, target := range arches { + arch, target := arch, target + t.Run(arch, func(t *testing.T) { + t.Parallel() + consts := DeserializeConstsGlob(filepath.Join(path, "*_"+arch+".const"), eh) + if consts == nil { + t.Fatalf("reading consts failed") + } + prog := Compile(desc, consts, target, eh) + if prog == nil { + t.Fatalf("compilation failed") + } + }) + } + }) } } @@ -38,7 +49,7 @@ func TestErrors(t *testing.T) { "C1": 1, "C2": 2, } - target := targets.List["linux"]["amd64"] + target := targets.List["test"]["64"] for _, name := range []string{"errors.txt", "errors2.txt"} { name := name t.Run(name, func(t *testing.T) { @@ -69,7 +80,7 @@ func TestFuzz(t *testing.T) { for _, data := range inputs { desc := ast.Parse([]byte(data), "", eh) if desc != nil { - Compile(desc, consts, targets.List["linux"]["amd64"], eh) + Compile(desc, consts, targets.List["test"]["64"], eh) } } } @@ -96,7 +107,7 @@ s2 { if desc == nil { t.Fatal("failed to parse") } - p := Compile(desc, map[string]uint64{"__NR_foo": 1}, targets.List["linux"]["amd64"], nil) + p := Compile(desc, map[string]uint64{"__NR_foo": 1}, targets.List["test"]["64"], nil) if p == nil { t.Fatal("failed to compile") } diff --git a/pkg/compiler/consts.go b/pkg/compiler/consts.go index 61198c96a..338dea4b1 100644 --- a/pkg/compiler/consts.go +++ b/pkg/compiler/consts.go @@ -145,7 +145,7 @@ func (comp *compiler) assignSyscallNumbers(consts map[string]uint64) { comp.warning(c.Pos, "unsupported syscall: %v due to missing const %v", c.CallName, str) } - case *ast.IntFlags, *ast.Resource, *ast.Struct, *ast.StrFlags: + case *ast.IntFlags, *ast.Resource, *ast.Struct, *ast.StrFlags, *ast.TypeDef: top = append(top, decl) case *ast.NewLine, *ast.Comment, *ast.Include, *ast.Incdir, *ast.Define: // These are not needed anymore. @@ -176,7 +176,7 @@ func (comp *compiler) patchConsts(consts map[string]uint64) { top = append(top, n) case *ast.StrFlags: top = append(top, decl) - case *ast.Resource, *ast.Struct, *ast.Call: + case *ast.Resource, *ast.Struct, *ast.Call, *ast.TypeDef: // Walk whole tree and replace consts in Int's and Type's. missing := "" ast.WalkNode(decl, func(n0 ast.Node) { diff --git a/pkg/compiler/testdata/errors.txt b/pkg/compiler/testdata/errors.txt index ade8c624f..237519aa3 100644 --- a/pkg/compiler/testdata/errors.txt +++ b/pkg/compiler/testdata/errors.txt @@ -178,3 +178,66 @@ define d1 `some C expression` define d2 some C expression define d2 SOMETHING ### duplicate define d2 define d3 1 + +# Type aliases. + +type bool8 int8[0:1] +type bool16 int16[0:1] +type net_port proc[100, 1, int16be] +resource typeres0[bool8] +typestruct { + f1 bool8 + f2 bool16 +} +typeunion [ + f1 bool8 + f2 bool16 +] + +type type0 int8 +type type0 int8 ### type type0 redeclared, previously declared as type alias at errors.txt:197:6 +resource type0[int32] ### type type0 redeclared, previously declared as type alias at errors.txt:197:6 +type0 = 0, 1 +type type1 type1 ### type aliases can't refer to other type aliases +type type2 int8:4 ### unexpected ':', only struct fields can be bitfields +type type3 type2 ### unknown type type2 +type type4 const[0] ### wrong number of arguments for type const, expect value, base type +type type5 typeunion ### typeunion can't be type alias target +type type6 len[foo, int32] ### len can't be type alias target +type type7 len[foo] ### len can't be type alias target +resource typeres1[int32] +type type8 typeres1 ### typeres1 can't be type alias target +type int8 int8 ### type name int8 conflicts with builtin type +type opt int8 ### type uses reserved name opt +type type9 const[0, int8] +type type10 type0 ### type aliases can't refer to other type aliases +type type11 typestruct11 ### typestruct11 can't be type alias target +type type12 proc[123, 2, int16, opt] +type type13 ptr[in, typestruct13] +type type14 flags[type0, int32] +type type15 const[0, type0] ### unexpected value type0 for base type argument of const type, expect [int8 int16 int32 int64 int16be int32be int64be intptr] +type type16 ptr[in, type0] ### type aliases can't refer to other type aliases + +typestruct11 { + f type11 ### unknown type type11 +} + +typestruct12 { + f type11 ### unknown type type11 +} + +typestruct13 { + f1 type9 + f2 type12 +} + +foo$100(a bool8, b bool16) +foo$101(a type5) ### unknown type type5 +foo$102(a type2) ### unknown type type2 +foo$103(a type0:4) ### type alias type0 with ':' +foo$104(a type0[opt]) ### type alias type0 with arguments +foo$105() type0 +foo$106() type6 ### unknown type type6 +foo$107(a type9, b type12) +foo$108(a flags[type0]) +foo$109(a ptr[in, type0]) diff --git a/pkg/compiler/types.go b/pkg/compiler/types.go index 30d3dac64..fbd44c0fa 100644 --- a/pkg/compiler/types.go +++ b/pkg/compiler/types.go @@ -15,6 +15,7 @@ import ( type typeDesc struct { Names []string CanBeArg bool // can be argument of syscall? + CanBeTypedef bool // can be type alias target? CantBeOpt bool // can't be marked as opt? CantBeRet bool // can't be syscall return (directly or indirectly)? NeedBase bool // needs base type when used as field? @@ -54,6 +55,7 @@ const ( var typeInt = &typeDesc{ Names: []string{"int8", "int16", "int32", "int64", "int16be", "int32be", "int64be", "intptr"}, CanBeArg: true, + CanBeTypedef: true, AllowColon: true, ResourceBase: true, OptArgs: 1, @@ -78,9 +80,10 @@ var typeInt = &typeDesc{ } var typePtr = &typeDesc{ - Names: []string{"ptr", "ptr64"}, - CanBeArg: true, - Args: []namedArg{{"direction", typeArgDir}, {"type", typeArgType}}, + Names: []string{"ptr", "ptr64"}, + CanBeArg: true, + CanBeTypedef: true, + Args: []namedArg{{"direction", typeArgDir}, {"type", typeArgType}}, Gen: func(comp *compiler, t *ast.Type, args []*ast.Type, base prog.IntTypeCommon) prog.Type { base.ArgDir = prog.DirIn // pointers are always in base.TypeSize = comp.ptrSize @@ -175,11 +178,12 @@ var typeLen = &typeDesc{ } var typeConst = &typeDesc{ - Names: []string{"const"}, - CanBeArg: true, - CantBeOpt: true, - NeedBase: true, - Args: []namedArg{{"value", typeArgInt}}, + Names: []string{"const"}, + CanBeArg: true, + CanBeTypedef: true, + CantBeOpt: true, + NeedBase: true, + Args: []namedArg{{"value", typeArgInt}}, Gen: func(comp *compiler, t *ast.Type, args []*ast.Type, base prog.IntTypeCommon) prog.Type { return &prog.ConstType{ IntTypeCommon: base, @@ -193,11 +197,12 @@ var typeArgLenTarget = &typeArg{ } var typeFlags = &typeDesc{ - Names: []string{"flags"}, - CanBeArg: true, - CantBeOpt: true, - NeedBase: true, - Args: []namedArg{{"flags", typeArgFlags}}, + Names: []string{"flags"}, + CanBeArg: true, + CanBeTypedef: true, + CantBeOpt: true, + NeedBase: true, + Args: []namedArg{{"flags", typeArgFlags}}, Gen: func(comp *compiler, t *ast.Type, args []*ast.Type, base prog.IntTypeCommon) prog.Type { name := args[0].Ident base.TypeName = name @@ -334,10 +339,11 @@ func genCsumKind(t *ast.Type) prog.CsumKind { } var typeProc = &typeDesc{ - Names: []string{"proc"}, - CanBeArg: true, - NeedBase: true, - Args: []namedArg{{"range start", typeArgInt}, {"per-proc values", typeArgInt}}, + Names: []string{"proc"}, + CanBeArg: true, + CanBeTypedef: true, + NeedBase: true, + Args: []namedArg{{"range start", typeArgInt}, {"per-proc values", typeArgInt}}, Check: func(comp *compiler, t *ast.Type, args []*ast.Type, base prog.IntTypeCommon) { start := args[0].Value perProc := args[1].Value @@ -522,7 +528,7 @@ var typeArgType = &typeArg{ } var typeResource = &typeDesc{ - // No Names, but compiler knows how to match it. + // No Names, but getTypeDesc knows how to match it. CanBeArg: true, ResourceBase: true, // Gen is assigned below to avoid initialization loop. @@ -544,7 +550,7 @@ func init() { } var typeStruct = &typeDesc{ - // No Names, but compiler knows how to match it. + // No Names, but getTypeDesc knows how to match it. CantBeOpt: true, // Varlen/Gen are assigned below due to initialization cycle. } @@ -579,6 +585,16 @@ func init() { } } +var typeTypedef = &typeDesc{ + // No Names, but getTypeDesc knows how to match it. + Check: func(comp *compiler, t *ast.Type, args []*ast.Type, base prog.IntTypeCommon) { + panic("must not be called") + }, + Gen: func(comp *compiler, t *ast.Type, args []*ast.Type, base prog.IntTypeCommon) prog.Type { + panic("must not be called") + }, +} + var typeArgDir = &typeArg{ Kind: kindIdent, Names: []string{"in", "out", "inout"}, @@ -660,16 +676,14 @@ func init() { typeConst, typeFlags, typeFilename, - typeFileoff, + typeFileoff, // make a type alias typeVMA, - typeSignalno, + typeSignalno, // make a type alias typeCsum, typeProc, typeText, - typeBuffer, + typeBuffer, // make a type alias typeString, - typeResource, - typeStruct, } for _, desc := range builtins { for _, name := range desc.Names { -- cgit mrf-deployment