| // 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" |
| "strings" |
| |
| "github.com/google/syzkaller/pkg/ast" |
| "github.com/google/syzkaller/prog" |
| ) |
| |
| func (comp *compiler) typecheck() { |
| comp.checkDirectives() |
| comp.checkNames() |
| comp.checkFields() |
| comp.checkTypedefs() |
| comp.checkTypes() |
| } |
| |
| func (comp *compiler) check() { |
| comp.checkTypeValues() |
| comp.checkAttributeValues() |
| comp.checkUnused() |
| comp.checkRecursion() |
| comp.checkLenTargets() |
| comp.checkConstructors() |
| comp.checkVarlens() |
| comp.checkDupConsts() |
| } |
| |
| func (comp *compiler) checkDirectives() { |
| includes := make(map[string]bool) |
| incdirs := make(map[string]bool) |
| defines := make(map[string]bool) |
| for _, decl := range comp.desc.Nodes { |
| switch n := decl.(type) { |
| case *ast.Include: |
| name := n.File.Value |
| path := n.Pos.File + "/" + name |
| if includes[path] { |
| comp.error(n.Pos, "duplicate include %q", name) |
| } |
| includes[path] = true |
| case *ast.Incdir: |
| name := n.Dir.Value |
| path := n.Pos.File + "/" + name |
| if incdirs[path] { |
| comp.error(n.Pos, "duplicate incdir %q", name) |
| } |
| incdirs[path] = true |
| case *ast.Define: |
| name := n.Name.Name |
| path := n.Pos.File + "/" + name |
| if defines[path] { |
| comp.error(n.Pos, "duplicate define %v", name) |
| } |
| defines[path] = true |
| } |
| } |
| } |
| |
| func (comp *compiler) checkNames() { |
| calls := make(map[string]*ast.Call) |
| for _, decl := range comp.desc.Nodes { |
| switch n := decl.(type) { |
| 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) |
| continue |
| } |
| if builtinTypes[name] != nil || builtinTypedefs[name] != nil { |
| comp.error(pos, "%v name %v conflicts with builtin type", typ, name) |
| continue |
| } |
| 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.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", |
| name, typ, prev.Pos) |
| continue |
| } |
| switch n := decl.(type) { |
| case *ast.Resource: |
| comp.resources[name] = n |
| case *ast.TypeDef: |
| comp.typedefs[name] = n |
| case *ast.Struct: |
| comp.structs[name] = n |
| } |
| case *ast.IntFlags: |
| name := n.Name.Name |
| if name == "_" { |
| continue |
| } |
| if reservedName[name] { |
| comp.error(n.Pos, "flags uses reserved name %v", name) |
| continue |
| } |
| 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: |
| name := n.Name.Name |
| if reservedName[name] { |
| comp.error(n.Pos, "string flags uses reserved name %v", name) |
| continue |
| } |
| if builtinStrFlags[name] != nil { |
| comp.error(n.Pos, "string flags %v conflicts with builtin flags", name) |
| continue |
| } |
| if prev := comp.strFlags[name]; prev != nil { |
| comp.error(n.Pos, "string flags %v redeclared, previously declared at %v", |
| name, prev.Pos) |
| continue |
| } |
| comp.strFlags[name] = n |
| case *ast.Call: |
| name := n.Name.Name |
| if prev := calls[name]; prev != nil { |
| comp.error(n.Pos, "syscall %v redeclared, previously declared at %v", |
| name, prev.Pos) |
| } |
| calls[name] = n |
| } |
| } |
| } |
| |
| 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() |
| comp.checkStructFields(n, typ, name) |
| case *ast.TypeDef: |
| if n.Struct != nil { |
| _, typ, _ := n.Struct.Info() |
| comp.checkStructFields(n.Struct, "template "+typ, n.Name.Name) |
| } |
| case *ast.Call: |
| name := n.Name.Name |
| comp.checkFieldGroup(n.Args, "argument", "syscall "+name) |
| 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) checkStructFields(n *ast.Struct, typ, name string) { |
| comp.checkFieldGroup(n.Fields, "field", typ+" "+name) |
| if len(n.Fields) < 1 { |
| comp.error(n.Pos, "%v %v has no fields, need at least 1 field", typ, name) |
| } |
| } |
| |
| func (comp *compiler) checkFieldGroup(fields []*ast.Field, what, ctx string) { |
| existing := make(map[string]bool) |
| for _, f := range fields { |
| fn := f.Name.Name |
| if fn == "parent" { |
| comp.error(f.Pos, "reserved %v name %v in %v", what, fn, ctx) |
| } |
| if existing[fn] { |
| comp.error(f.Pos, "duplicate %v %v in %v", what, fn, ctx) |
| } |
| existing[fn] = true |
| } |
| } |
| |
| func (comp *compiler) checkTypedefs() { |
| for _, decl := range comp.desc.Nodes { |
| switch n := decl.(type) { |
| case *ast.TypeDef: |
| if len(n.Args) == 0 { |
| // Non-template types are fully typed, so we check them ahead of time. |
| err0 := comp.errors |
| comp.checkType(checkCtx{}, n.Type, checkIsTypedef) |
| if err0 != comp.errors { |
| // To not produce confusing errors on broken type usage. |
| delete(comp.typedefs, n.Name.Name) |
| } |
| } else { |
| // For templates we only do basic checks of arguments. |
| names := make(map[string]bool) |
| for _, arg := range n.Args { |
| if names[arg.Name] { |
| comp.error(arg.Pos, "duplicate type argument %v", arg.Name) |
| } |
| names[arg.Name] = true |
| for _, c := range arg.Name { |
| if c >= 'A' && c <= 'Z' || |
| c >= '0' && c <= '9' || |
| c == '_' { |
| continue |
| } |
| comp.error(arg.Pos, "type argument %v must be ALL_CAPS", |
| arg.Name) |
| break |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| func (comp *compiler) checkTypes() { |
| for _, decl := range comp.desc.Nodes { |
| switch n := decl.(type) { |
| case *ast.Resource: |
| comp.checkType(checkCtx{}, n.Base, checkIsResourceBase) |
| case *ast.Struct: |
| comp.checkStruct(checkCtx{}, n) |
| case *ast.Call: |
| for _, a := range n.Args { |
| comp.checkType(checkCtx{}, a.Type, checkIsArg) |
| } |
| if n.Ret != nil { |
| comp.checkType(checkCtx{}, n.Ret, checkIsArg|checkIsRet) |
| } |
| } |
| } |
| } |
| |
| func (comp *compiler) checkTypeValues() { |
| for _, decl := range comp.desc.Nodes { |
| switch decl.(type) { |
| case *ast.Call, *ast.Struct, *ast.Resource, *ast.TypeDef: |
| comp.foreachType(decl, func(t *ast.Type, desc *typeDesc, |
| args []*ast.Type, base prog.IntTypeCommon) { |
| if desc.CheckConsts != nil { |
| desc.CheckConsts(comp, t, args, base) |
| } |
| for i, arg := range args { |
| if check := desc.Args[i].Type.CheckConsts; check != nil { |
| check(comp, arg) |
| } |
| } |
| }) |
| } |
| } |
| } |
| |
| func (comp *compiler) checkAttributeValues() { |
| for _, decl := range comp.desc.Nodes { |
| switch n := decl.(type) { |
| case *ast.Struct: |
| for _, attr := range n.Attrs { |
| if attr.Ident == "size" { |
| _, typ, name := n.Info() |
| if comp.structIsVarlen(n.Name.Name) { |
| comp.error(attr.Pos, "varlen %v %v has size attribute", |
| typ, name) |
| } |
| sz := attr.Args[0].Value |
| if sz == 0 || sz > 1<<20 { |
| comp.error(attr.Args[0].Pos, "size attribute has bad value %v"+ |
| ", expect [1, 1<<20]", sz) |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| func (comp *compiler) checkLenTargets() { |
| for _, decl := range comp.desc.Nodes { |
| switch n := decl.(type) { |
| case *ast.Call: |
| for _, arg := range n.Args { |
| comp.checkLenType(arg.Type, arg.Name.Name, n.Args, nil, make(map[string]bool), true) |
| } |
| } |
| } |
| } |
| |
| func (comp *compiler) checkLenType(t *ast.Type, name string, fields []*ast.Field, |
| parents []*ast.Struct, checked map[string]bool, isArg bool) { |
| desc := comp.getTypeDesc(t) |
| if desc == typeStruct { |
| s := comp.structs[t.Ident] |
| // Prune recursion, can happen even on correct tree via opt pointers. |
| if checked[s.Name.Name] { |
| return |
| } |
| checked[s.Name.Name] = true |
| parents = append(parents, s) |
| if !s.IsUnion { |
| fields = s.Fields |
| } |
| for _, fld := range s.Fields { |
| comp.checkLenType(fld.Type, fld.Name.Name, fields, parents, checked, false) |
| } |
| return |
| } |
| _, args, _ := comp.getArgsBase(t, "", prog.DirIn, isArg) |
| for i, arg := range args { |
| argDesc := desc.Args[i] |
| if argDesc.Type == typeArgLenTarget { |
| comp.checkLenTarget(t, name, arg.Ident, fields, parents) |
| } else if argDesc.Type == typeArgType { |
| comp.checkLenType(arg, name, fields, parents, checked, argDesc.IsArg) |
| } |
| } |
| } |
| |
| func (comp *compiler) checkLenTarget(t *ast.Type, name, target string, fields []*ast.Field, parents []*ast.Struct) { |
| if target == name { |
| comp.error(t.Pos, "%v target %v refer to itself", t.Ident, target) |
| return |
| } |
| if target == "parent" { |
| if len(parents) == 0 { |
| comp.error(t.Pos, "%v target %v does not exist", t.Ident, target) |
| } |
| return |
| } |
| for _, fld := range fields { |
| if target != fld.Name.Name { |
| continue |
| } |
| if fld.Type == t { |
| comp.error(t.Pos, "%v target %v refer to itself", t.Ident, target) |
| } |
| if t.Ident == "len" { |
| inner := fld.Type |
| desc, args, _ := comp.getArgsBase(inner, "", prog.DirIn, false) |
| for desc == typePtr { |
| if desc != typePtr { |
| break |
| } |
| inner = args[1] |
| desc, args, _ = comp.getArgsBase(inner, "", prog.DirIn, false) |
| } |
| if desc == typeArray && comp.isVarlen(args[0]) { |
| comp.warning(t.Pos, "len target %v refer to an array with"+ |
| " variable-size elements (do you mean bytesize?)", target) |
| } |
| } |
| return |
| } |
| for _, parent := range parents { |
| parentName := parent.Name.Name |
| if pos := strings.IndexByte(parentName, '['); pos != -1 { |
| // For template parents name is "struct_name[ARG1, ARG2]", strip the part after '['. |
| parentName = parentName[:pos] |
| } |
| if target == parentName { |
| return |
| } |
| } |
| comp.error(t.Pos, "%v target %v does not exist", t.Ident, target) |
| } |
| |
| func (comp *compiler) collectUsed(all bool) (structs, flags, strflags map[string]bool) { |
| structs = make(map[string]bool) |
| flags = make(map[string]bool) |
| strflags = make(map[string]bool) |
| for _, decl := range comp.desc.Nodes { |
| switch n := decl.(type) { |
| case *ast.Call: |
| if !all && n.NR == ^uint64(0) { |
| break |
| } |
| for _, arg := range n.Args { |
| comp.collectUsedType(structs, flags, strflags, arg.Type, true) |
| } |
| if n.Ret != nil { |
| comp.collectUsedType(structs, flags, strflags, n.Ret, true) |
| } |
| } |
| } |
| return |
| } |
| |
| func (comp *compiler) collectUsedType(structs, flags, strflags map[string]bool, t *ast.Type, isArg bool) { |
| desc := comp.getTypeDesc(t) |
| if desc == typeResource { |
| r := comp.resources[t.Ident] |
| for r != nil && !structs[r.Name.Name] { |
| structs[r.Name.Name] = true |
| r = comp.resources[r.Base.Ident] |
| } |
| return |
| } |
| if desc == typeStruct { |
| if structs[t.Ident] { |
| return |
| } |
| structs[t.Ident] = true |
| s := comp.structs[t.Ident] |
| for _, fld := range s.Fields { |
| comp.collectUsedType(structs, flags, strflags, fld.Type, false) |
| } |
| return |
| } |
| if desc == typeFlags { |
| flags[t.Args[0].Ident] = true |
| return |
| } |
| if desc == typeString { |
| if len(t.Args) != 0 && t.Args[0].Ident != "" { |
| strflags[t.Args[0].Ident] = true |
| } |
| return |
| } |
| _, args, _ := comp.getArgsBase(t, "", prog.DirIn, isArg) |
| for i, arg := range args { |
| if desc.Args[i].Type == typeArgType { |
| comp.collectUsedType(structs, flags, strflags, arg, desc.Args[i].IsArg) |
| } |
| } |
| } |
| |
| func (comp *compiler) checkUnused() { |
| comp.used, _, _ = comp.collectUsed(false) |
| structs, flags, strflags := comp.collectUsed(true) |
| _, _, _ = structs, flags, strflags |
| |
| for name, n := range comp.intFlags { |
| if !flags[name] { |
| comp.error(n.Pos, "unused flags %v", name) |
| } |
| } |
| for name, n := range comp.strFlags { |
| if !strflags[name] && builtinStrFlags[name] == nil { |
| comp.error(n.Pos, "unused string flags %v", name) |
| } |
| } |
| for name, n := range comp.resources { |
| if !structs[name] { |
| comp.error(n.Pos, "unused resource %v", name) |
| } |
| } |
| for name, n := range comp.structs { |
| if !structs[name] { |
| _, typ, _ := n.Info() |
| comp.error(n.Pos, "unused %v %v", typ, name) |
| } |
| } |
| for name, n := range comp.typedefs { |
| if !comp.usedTypedefs[name] { |
| comp.error(n.Pos, "unused type %v", name) |
| } |
| } |
| } |
| |
| type structDir struct { |
| Struct string |
| Dir prog.Dir |
| } |
| |
| func (comp *compiler) checkConstructors() { |
| ctors := make(map[string]bool) // resources for which we have ctors |
| checked := make(map[structDir]bool) |
| for _, decl := range comp.desc.Nodes { |
| switch n := decl.(type) { |
| case *ast.Call: |
| for _, arg := range n.Args { |
| comp.checkTypeCtors(arg.Type, prog.DirIn, true, ctors, checked) |
| } |
| if n.Ret != nil { |
| comp.checkTypeCtors(n.Ret, prog.DirOut, true, ctors, checked) |
| } |
| } |
| } |
| for _, decl := range comp.desc.Nodes { |
| switch n := decl.(type) { |
| case *ast.Resource: |
| name := n.Name.Name |
| if !ctors[name] && comp.used[name] { |
| comp.error(n.Pos, "resource %v can't be created"+ |
| " (never mentioned as a syscall return value or output argument/field)", |
| name) |
| } |
| } |
| } |
| } |
| |
| func (comp *compiler) checkTypeCtors(t *ast.Type, dir prog.Dir, isArg bool, |
| ctors map[string]bool, checked map[structDir]bool) { |
| desc := comp.getTypeDesc(t) |
| if desc == typeResource { |
| // TODO(dvyukov): consider changing this to "dir == prog.DirOut". |
| // We have few questionable cases where resources can be created |
| // only by inout struct fields. These structs should be split |
| // into two different structs: one is in and second is out. |
| // But that will require attaching dir to individual fields. |
| if dir != prog.DirIn { |
| r := comp.resources[t.Ident] |
| for r != nil && !ctors[r.Name.Name] { |
| ctors[r.Name.Name] = true |
| r = comp.resources[r.Base.Ident] |
| } |
| } |
| return |
| } |
| if desc == typeStruct { |
| s := comp.structs[t.Ident] |
| name := s.Name.Name |
| key := structDir{name, dir} |
| if checked[key] { |
| return |
| } |
| checked[key] = true |
| for _, fld := range s.Fields { |
| comp.checkTypeCtors(fld.Type, dir, false, ctors, checked) |
| } |
| return |
| } |
| if desc == typePtr { |
| dir = genDir(t.Args[0]) |
| } |
| _, args, _ := comp.getArgsBase(t, "", dir, isArg) |
| for i, arg := range args { |
| if desc.Args[i].Type == typeArgType { |
| comp.checkTypeCtors(arg, dir, desc.Args[i].IsArg, ctors, checked) |
| } |
| } |
| } |
| |
| func (comp *compiler) checkRecursion() { |
| checked := make(map[string]bool) |
| for _, decl := range comp.desc.Nodes { |
| switch n := decl.(type) { |
| case *ast.Resource: |
| comp.checkResourceRecursion(n) |
| case *ast.Struct: |
| var path []pathElem |
| comp.checkStructRecursion(checked, n, path) |
| } |
| } |
| } |
| |
| func (comp *compiler) checkResourceRecursion(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] |
| } |
| } |
| |
| type pathElem struct { |
| Pos ast.Pos |
| Struct string |
| Field string |
| } |
| |
| func (comp *compiler) checkStructRecursion(checked map[string]bool, n *ast.Struct, path []pathElem) { |
| name := n.Name.Name |
| if checked[name] { |
| return |
| } |
| for i, elem := range path { |
| if elem.Struct != name { |
| continue |
| } |
| path = path[i:] |
| str := "" |
| for _, elem := range path { |
| str += fmt.Sprintf("%v.%v -> ", elem.Struct, elem.Field) |
| } |
| str += name |
| comp.error(path[0].Pos, "recursive declaration: %v (mark some pointers as opt)", str) |
| checked[name] = true |
| return |
| } |
| for _, f := range n.Fields { |
| path = append(path, pathElem{ |
| Pos: f.Pos, |
| Struct: name, |
| Field: f.Name.Name, |
| }) |
| comp.recurseField(checked, f.Type, path) |
| path = path[:len(path)-1] |
| } |
| checked[name] = true |
| } |
| |
| func (comp *compiler) recurseField(checked map[string]bool, t *ast.Type, path []pathElem) { |
| desc := comp.getTypeDesc(t) |
| if desc == typeStruct { |
| comp.checkStructRecursion(checked, comp.structs[t.Ident], path) |
| return |
| } |
| _, args, base := comp.getArgsBase(t, "", prog.DirIn, false) |
| if desc == typePtr && base.IsOptional { |
| return // optional pointers prune recursion |
| } |
| for i, arg := range args { |
| if desc.Args[i].Type == typeArgType { |
| comp.recurseField(checked, arg, path) |
| } |
| } |
| } |
| |
| func (comp *compiler) checkStruct(ctx checkCtx, n *ast.Struct) { |
| var flags checkFlags |
| if !n.IsUnion { |
| flags |= checkIsStruct |
| } |
| for _, f := range n.Fields { |
| comp.checkType(ctx, f.Type, flags) |
| } |
| for _, attr := range n.Attrs { |
| if unexpected, _, ok := checkTypeKind(attr, kindIdent); !ok { |
| comp.error(attr.Pos, "unexpected %v, expect attribute", unexpected) |
| return |
| } |
| if attr.HasColon { |
| comp.error(attr.Pos2, "unexpected ':'") |
| return |
| } |
| } |
| if n.IsUnion { |
| comp.parseUnionAttrs(n) |
| } else { |
| comp.parseStructAttrs(n) |
| } |
| } |
| |
| type checkFlags int |
| |
| const ( |
| checkIsArg checkFlags = 1 << iota // immediate syscall arg type |
| checkIsRet // immediate syscall ret type |
| checkIsStruct // immediate struct field type |
| checkIsResourceBase // immediate resource base type |
| checkIsTypedef // immediate type alias/template type |
| ) |
| |
| type checkCtx struct { |
| instantiationStack []string |
| } |
| |
| func (comp *compiler) checkType(ctx checkCtx, t *ast.Type, flags checkFlags) { |
| 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 %v", t.Ident) |
| return |
| } |
| if desc == typeTypedef { |
| err0 := comp.errors |
| // Replace t with type alias/template target type inplace, |
| // and check the replaced type recursively. |
| comp.replaceTypedef(&ctx, t, flags) |
| if err0 == comp.errors { |
| comp.checkType(ctx, t, flags) |
| } |
| return |
| } |
| err0 := comp.errors |
| comp.checkTypeBasic(t, desc, flags) |
| if err0 != comp.errors { |
| return |
| } |
| args := comp.checkTypeArgs(t, desc, flags) |
| if err0 != comp.errors { |
| return |
| } |
| for i, arg := range args { |
| if desc.Args[i].Type == typeArgType { |
| var innerFlags checkFlags |
| if desc.Args[i].IsArg { |
| innerFlags |= checkIsArg |
| } |
| comp.checkType(ctx, arg, innerFlags) |
| } else { |
| comp.checkTypeArg(t, arg, desc.Args[i]) |
| } |
| } |
| if err0 != comp.errors { |
| return |
| } |
| if desc.Check != nil { |
| _, args, base := comp.getArgsBase(t, "", prog.DirIn, flags&checkIsArg != 0) |
| desc.Check(comp, t, args, base) |
| } |
| } |
| |
| func (comp *compiler) checkTypeBasic(t *ast.Type, desc *typeDesc, flags checkFlags) { |
| if t.HasColon { |
| if !desc.AllowColon { |
| comp.error(t.Pos2, "unexpected ':'") |
| return |
| } |
| if flags&checkIsStruct == 0 { |
| comp.error(t.Pos2, "unexpected ':', only struct fields can be bitfields") |
| return |
| } |
| } |
| if flags&checkIsTypedef != 0 && !desc.CanBeTypedef { |
| comp.error(t.Pos, "%v can't be type alias target", t.Ident) |
| return |
| } |
| if flags&checkIsResourceBase != 0 && !desc.ResourceBase { |
| comp.error(t.Pos, "%v can't be resource base (int types can)", t.Ident) |
| return |
| } |
| canBeArg, canBeRet := false, false |
| if desc.CanBeArgRet != nil { |
| canBeArg, canBeRet = desc.CanBeArgRet(comp, t) |
| } |
| if flags&checkIsRet != 0 && !canBeRet { |
| comp.error(t.Pos, "%v can't be syscall return", t.Ident) |
| return |
| } |
| if flags&checkIsArg != 0 && !canBeArg { |
| comp.error(t.Pos, "%v can't be syscall argument", t.Ident) |
| return |
| } |
| } |
| |
| func (comp *compiler) checkTypeArgs(t *ast.Type, desc *typeDesc, flags checkFlags) []*ast.Type { |
| args, opt := removeOpt(t) |
| if opt != nil { |
| if len(opt.Args) != 0 { |
| comp.error(opt.Pos, "opt can't have arguments") |
| } |
| if flags&checkIsResourceBase != 0 || desc.CantBeOpt { |
| what := "resource base" |
| if desc.CantBeOpt { |
| what = t.Ident |
| } |
| comp.error(opt.Pos, "%v can't be marked as opt", what) |
| return nil |
| } |
| } |
| addArgs := 0 |
| needBase := flags&checkIsArg == 0 && desc.NeedBase |
| if needBase { |
| addArgs++ // last arg must be base type, e.g. const[0, int32] |
| } |
| 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 nil |
| } |
| if needBase { |
| base := args[len(args)-1] |
| args = args[:len(args)-1] |
| comp.checkTypeArg(t, base, typeArgBase) |
| } |
| return args |
| } |
| |
| func (comp *compiler) replaceTypedef(ctx *checkCtx, t *ast.Type, flags checkFlags) { |
| typedefName := t.Ident |
| comp.usedTypedefs[typedefName] = true |
| if t.HasColon { |
| comp.error(t.Pos, "type alias %v with ':'", t.Ident) |
| return |
| } |
| typedef := comp.typedefs[typedefName] |
| fullTypeName := ast.SerializeNode(t) |
| for i, prev := range ctx.instantiationStack { |
| if prev == fullTypeName { |
| ctx.instantiationStack = append(ctx.instantiationStack, fullTypeName) |
| path := "" |
| for j := i; j < len(ctx.instantiationStack); j++ { |
| if j != i { |
| path += " -> " |
| } |
| path += ctx.instantiationStack[j] |
| } |
| comp.error(t.Pos, "type instantiation loop: %v", path) |
| return |
| } |
| } |
| ctx.instantiationStack = append(ctx.instantiationStack, fullTypeName) |
| nargs := len(typedef.Args) |
| args := t.Args |
| if nargs != len(t.Args) { |
| if nargs == 0 { |
| comp.error(t.Pos, "type %v is not a template", typedefName) |
| } else { |
| comp.error(t.Pos, "template %v needs %v arguments instead of %v", |
| typedefName, nargs, len(t.Args)) |
| } |
| return |
| } |
| pos0 := t.Pos |
| if typedef.Type != nil { |
| *t = *typedef.Type.Clone().(*ast.Type) |
| if !comp.instantiate(t, typedef.Args, args) { |
| return |
| } |
| } else { |
| if comp.structs[fullTypeName] == nil { |
| inst := typedef.Struct.Clone().(*ast.Struct) |
| inst.Name.Name = fullTypeName |
| if !comp.instantiate(inst, typedef.Args, args) { |
| return |
| } |
| comp.checkStruct(*ctx, inst) |
| comp.desc.Nodes = append(comp.desc.Nodes, inst) |
| comp.structs[fullTypeName] = inst |
| } |
| *t = ast.Type{ |
| Ident: fullTypeName, |
| } |
| } |
| t.Pos = pos0 |
| |
| // Remove base type if it's not needed in this context. |
| desc := comp.getTypeDesc(t) |
| if flags&checkIsArg != 0 && 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] |
| } |
| } |
| |
| func (comp *compiler) instantiate(templ ast.Node, params []*ast.Ident, args []*ast.Type) bool { |
| if len(params) == 0 { |
| return true |
| } |
| argMap := make(map[string]*ast.Type) |
| for i, param := range params { |
| argMap[param.Name] = args[i] |
| } |
| err0 := comp.errors |
| templ.Walk(ast.Recursive(func(n ast.Node) { |
| templArg, ok := n.(*ast.Type) |
| if !ok { |
| return |
| } |
| if concreteArg := argMap[templArg.Ident]; concreteArg != nil { |
| origArgs := templArg.Args |
| if len(origArgs) != 0 && len(concreteArg.Args) != 0 { |
| comp.error(templArg.Pos, "both template parameter %v and its usage"+ |
| " have sub-arguments", templArg.Ident) |
| return |
| } |
| *templArg = *concreteArg.Clone().(*ast.Type) |
| if len(origArgs) != 0 { |
| templArg.Args = origArgs |
| } |
| } |
| // TODO(dvyukov): somewhat hacky, but required for int8[0:CONST_ARG] |
| // Need more checks here. E.g. that CONST_ARG does not have subargs. |
| // And if CONST_ARG is a value, then use concreteArg.Value. |
| // Also need to error if CONST_ARG is a string. |
| if concreteArg := argMap[templArg.Ident2]; concreteArg != nil { |
| templArg.Ident2 = concreteArg.Ident |
| templArg.Pos2 = concreteArg.Pos |
| } |
| })) |
| return err0 == comp.errors |
| } |
| |
| 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 |
| } |
| } |
| if !desc.AllowColon && arg.HasColon { |
| comp.error(arg.Pos2, "unexpected ':'") |
| return |
| } |
| if len(arg.Args) > desc.MaxArgs { |
| comp.error(arg.Pos, "%v argument has subargs", argDesc.Name) |
| return |
| } |
| if desc.Check != nil { |
| desc.Check(comp, arg) |
| } |
| } |
| |
| 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 += "]" |
| } |
| } |
| if needBase { |
| if expect != "" { |
| expect += ", " |
| } |
| expect += typeArgBase.Name |
| } |
| if !desc.CantBeOpt { |
| if expect != "" { |
| expect += ", " |
| } |
| expect += "[opt]" |
| } |
| if expect == "" { |
| expect = "no arguments" |
| } |
| return expect |
| } |
| |
| func checkTypeKind(t *ast.Type, kind int) (unexpected string, expect string, ok bool) { |
| switch { |
| case kind == kindAny: |
| ok = true |
| case t.HasString: |
| ok = kind == kindString |
| if !ok { |
| unexpected = fmt.Sprintf("string %q", t.String) |
| } |
| case t.Ident != "": |
| ok = kind == kindIdent || kind == kindInt |
| if !ok { |
| unexpected = fmt.Sprintf("identifier %v", t.Ident) |
| } |
| default: |
| ok = kind == kindInt |
| if !ok { |
| unexpected = fmt.Sprintf("int %v", t.Value) |
| } |
| } |
| if !ok { |
| switch kind { |
| case kindString: |
| expect = "string" |
| case kindIdent: |
| expect = "identifier" |
| case kindInt: |
| expect = "int" |
| } |
| } |
| return |
| } |
| |
| func (comp *compiler) checkVarlens() { |
| for _, decl := range comp.desc.Nodes { |
| switch n := decl.(type) { |
| case *ast.Struct: |
| comp.checkVarlen(n) |
| } |
| } |
| } |
| |
| func (comp *compiler) isVarlen(t *ast.Type) bool { |
| desc, args, _ := comp.getArgsBase(t, "", prog.DirIn, false) |
| return desc.Varlen != nil && desc.Varlen(comp, t, args) |
| } |
| |
| func (comp *compiler) isZeroSize(t *ast.Type) bool { |
| desc, args, _ := comp.getArgsBase(t, "", prog.DirIn, false) |
| return desc.ZeroSize != nil && desc.ZeroSize(comp, t, args) |
| } |
| |
| func (comp *compiler) checkVarlen(n *ast.Struct) { |
| // Non-varlen unions can't have varlen fields. |
| // Non-packed structs can't have varlen fields in the middle. |
| if n.IsUnion { |
| if varlen, _ := comp.parseUnionAttrs(n); varlen { |
| return |
| } |
| } else { |
| if packed, _, _ := comp.parseStructAttrs(n); packed { |
| return |
| } |
| } |
| for i, f := range n.Fields { |
| if !n.IsUnion && i == len(n.Fields)-1 { |
| break |
| } |
| if comp.isVarlen(f.Type) { |
| if n.IsUnion { |
| comp.error(f.Pos, "variable size field %v in non-varlen union %v", |
| f.Name.Name, n.Name.Name) |
| } else { |
| comp.error(f.Pos, "variable size field %v in the middle of non-packed struct %v", |
| f.Name.Name, n.Name.Name) |
| } |
| } |
| } |
| } |
| |
| func (comp *compiler) checkDupConsts() { |
| // The idea is to detect copy-paste errors in const arguments, e.g.: |
| // call$FOO(fd fd, arg const[FOO]) |
| // call$BAR(fd fd, arg const[FOO]) |
| // The second one is meant to be const[BAR], |
| // Unfortunately, this does not fully work as it detects lots of false positives. |
| // But was useful to find real bugs as well. So for now it's disabled, but can be run manually. |
| if true { |
| return |
| } |
| dups := make(map[string]map[string]dupConstArg) |
| for _, decl := range comp.desc.Nodes { |
| switch n := decl.(type) { |
| case *ast.Call: |
| comp.checkDupConstsCall(n, dups) |
| } |
| } |
| } |
| |
| type dupConstArg struct { |
| pos ast.Pos |
| name string |
| } |
| |
| func (comp *compiler) checkDupConstsCall(n *ast.Call, dups map[string]map[string]dupConstArg) { |
| if n.NR == ^uint64(0) { |
| return |
| } |
| for dups[n.CallName] == nil { |
| dups[n.CallName] = make(map[string]dupConstArg) |
| } |
| hasConsts := false |
| constArgID := "" |
| for i, arg := range n.Args { |
| desc := comp.getTypeDesc(arg.Type) |
| if desc == typeConst { |
| v := arg.Type.Args[0].Value |
| if v != 0 && v != 18446744073709551516 { // AT_FDCWD |
| constArgID += fmt.Sprintf("(%v-%v)", i, fmt.Sprintf("%v", v)) |
| hasConsts = true |
| } |
| } else if desc == typeResource { |
| constArgID += fmt.Sprintf("(%v-%v)", i, arg.Type.Ident) |
| } |
| } |
| if !hasConsts { |
| return |
| } |
| dup, ok := dups[n.CallName][constArgID] |
| if !ok { |
| dups[n.CallName][constArgID] = dupConstArg{ |
| pos: n.Pos, |
| name: n.Name.Name, |
| } |
| return |
| } |
| comp.error(n.Pos, "call %v: duplicate const %v, previously used in call %v at %v", |
| n.Name.Name, constArgID, dup.name, dup.pos) |
| } |