blob: 402e9a84e8405b1ff83546ab7fb4c799dfb5b799 [file] [log] [blame]
// 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 (
"errors"
"fmt"
"regexp"
"strings"
"github.com/google/syzkaller/pkg/ast"
"github.com/google/syzkaller/prog"
"github.com/google/syzkaller/sys/targets"
)
func (comp *compiler) typecheck() {
comp.checkComments()
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) checkComments() {
confusingComment := regexp.MustCompile(`^\s*(include|incdir|define)`)
for _, decl := range comp.desc.Nodes {
switch n := decl.(type) {
case *ast.Comment:
if confusingComment.MatchString(n.Text) {
comp.error(n.Pos, "confusing comment faking a directive (rephrase if it's intentional)")
}
}
}
}
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 {
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 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() {
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) > prog.MaxArgs {
comp.error(n.Pos, "syscall %v has %v arguments, allowed maximum is %v",
name, len(n.Args), prog.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)
}
hasDirections, hasOutOverlay := false, false
for fieldIdx, f := range n.Fields {
if n.IsUnion {
comp.parseAttrs(nil, f, f.Attrs)
continue
}
attrs := comp.parseAttrs(structFieldAttrs, f, f.Attrs)
dirCount := attrs[attrIn] + attrs[attrOut] + attrs[attrInOut]
if dirCount != 0 {
hasDirections = true
}
if dirCount > 1 {
_, typ, _ := f.Info()
comp.error(f.Pos, "%v has multiple direction attributes", typ)
}
if attrs[attrOutOverlay] > 0 {
if fieldIdx == 0 {
comp.error(f.Pos, "%v attribute must not be specified on the first field", attrOutOverlay.Name)
}
if hasOutOverlay || attrs[attrOutOverlay] > 1 {
comp.error(f.Pos, "multiple %v attributes", attrOutOverlay.Name)
}
hasOutOverlay = true
}
if hasDirections && hasOutOverlay {
comp.error(f.Pos, "mix of direction and %v attributes is not supported", attrOutOverlay.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 == prog.ParentRef || fn == prog.SyscallRef {
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
}
}
const argBase = "BASE"
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 i, arg := range n.Args {
if arg.Name == argBase && i != len(n.Args)-1 {
comp.error(arg.Pos, "type argument BASE must be the last argument")
}
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:
comp.checkCall(n)
}
}
}
func (comp *compiler) checkTypeValues() {
for _, decl := range comp.desc.Nodes {
switch n := 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)
}
}
})
case *ast.IntFlags:
allEqual := len(n.Values) >= 2
for _, val := range n.Values {
if val.Value != n.Values[0].Value {
allEqual = false
}
}
if allEqual {
comp.error(n.Pos, "all %v values are equal %v", n.Name.Name, n.Values[0].Value)
}
}
}
}
func (comp *compiler) checkAttributeValues() {
for _, decl := range comp.desc.Nodes {
switch n := decl.(type) {
case *ast.Struct:
for _, attr := range n.Attrs {
desc := structOrUnionAttrs(n)[attr.Ident]
if desc.CheckConsts != nil {
desc.CheckConsts(comp, n, attr)
}
}
// Check each field's attributes.
st := decl.(*ast.Struct)
hasOutOverlay := false
for _, f := range st.Fields {
isOut := hasOutOverlay
for _, attr := range f.Attrs {
desc := structFieldAttrs[attr.Ident]
if desc.CheckConsts != nil {
desc.CheckConsts(comp, f, attr)
}
switch attr.Ident {
case attrOutOverlay.Name:
hasOutOverlay = true
fallthrough
case attrOut.Name, attrInOut.Name:
isOut = true
}
}
if isOut && comp.getTypeDesc(f.Type).CantBeOut {
comp.error(f.Pos, "%v type must not be used as output", f.Type.Ident)
}
}
}
}
}
func (comp *compiler) checkLenTargets() {
warned := make(map[string]bool)
for _, decl := range comp.desc.Nodes {
switch n := decl.(type) {
case *ast.Call:
for _, arg := range n.Args {
checked := make(map[string]bool)
parents := []parentDesc{{fields: n.Args}}
comp.checkLenType(arg.Type, arg.Type, parents, checked, warned, true)
}
}
}
}
type parentDesc struct {
name string
fields []*ast.Field
}
// templateName return the part before '[' for full template names.
func templateBase(name string) string {
if pos := strings.IndexByte(name, '['); pos != -1 {
return name[:pos]
}
return name
}
func parentTargetName(s *ast.Struct) string {
// For template parents name is "struct_name[ARG1, ARG2]", strip the part after '['.
return templateBase(s.Name.Name)
}
func (comp *compiler) checkLenType(t0, t *ast.Type, parents []parentDesc,
checked, warned 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
fields := s.Fields
if s.IsUnion {
fields = nil
}
parentName := parentTargetName(s)
parents = append(parents, parentDesc{name: parentName, fields: fields})
for _, fld := range s.Fields {
comp.checkLenType(fld.Type, fld.Type, parents, checked, warned, false)
}
warned[parentName] = true
return
}
_, args, _ := comp.getArgsBase(t, isArg)
for i, arg := range args {
argDesc := desc.Args[i]
if argDesc.Type == typeArgLenTarget {
comp.checkLenTarget(arg, t0, t, parents, warned)
} else if argDesc.Type == typeArgType {
comp.checkLenType(t0, arg, parents, checked, warned, argDesc.IsArg)
}
}
}
func (comp *compiler) checkLenTarget(arg, t0, t *ast.Type, parents []parentDesc, warned map[string]bool) {
targets := append([]*ast.Type{arg}, arg.Colon...)
for i, target := range targets {
if target.Ident == prog.ParentRef && len(targets) != 1 {
comp.error(target.Pos, "%v can't be part of path expressions", prog.ParentRef)
return
}
if target.Ident == prog.SyscallRef {
if i != 0 {
comp.error(target.Pos, "syscall can't be in the middle of path expressions")
return
}
if len(targets) == 1 {
comp.error(targets[0].Pos, "no argument name after syscall reference")
return
}
}
}
comp.checkLenTargetRec(t0, t, targets, parents, warned)
}
func (comp *compiler) checkLenTargetRec(t0, t *ast.Type, targets []*ast.Type,
parents []parentDesc, warned map[string]bool) {
if len(targets) == 0 {
return
}
target := targets[0]
targets = targets[1:]
fields := parents[len(parents)-1].fields
for _, fld := range fields {
if target.Ident != fld.Name.Name {
continue
}
if fld.Type == t0 {
comp.error(target.Pos, "%v target %v refers to itself", t.Ident, target.Ident)
return
}
if len(targets) == 0 {
if t.Ident == "len" {
typ, desc := comp.derefPointers(fld.Type)
if desc == typeArray && comp.isVarlen(typ.Args[0]) {
// We can reach the same struct multiple times starting from different
// syscall arguments. Warn only once.
if !warned[parents[len(parents)-1].name] {
comp.warning(target.Pos, "len target %v refer to an array with"+
" variable-size elements (do you mean bytesize?)",
target.Ident)
}
}
}
return
}
typ, desc := comp.derefPointers(fld.Type)
if desc != typeStruct {
comp.error(target.Pos, "%v path %v does not refer to a struct", t.Ident, target.Ident)
return
}
s := comp.structs[typ.Ident]
if s.IsUnion {
comp.error(target.Pos, "%v path %v does not refer to a struct", t.Ident, target.Ident)
return
}
parents = append(parents, parentDesc{name: parentTargetName(s), fields: s.Fields})
comp.checkLenTargetRec(t0, t, targets, parents, warned)
return
}
for pi := len(parents) - 1; pi >= 0; pi-- {
parent := parents[pi]
if parent.name != "" && (parent.name == target.Ident || target.Ident == prog.ParentRef) ||
parent.name == "" && target.Ident == prog.SyscallRef {
if len(targets) == 0 {
if t.Ident == "offsetof" {
comp.error(target.Pos, "%v must refer to fields", t.Ident)
return
}
} else {
parents1 := make([]parentDesc, pi+1)
copy(parents1, parents[:pi+1])
comp.checkLenTargetRec(t0, t, targets, parents1, warned)
}
return
}
}
comp.error(target.Pos, "%v target %v does not exist", t.Ident, target.Ident)
}
func CollectUnused(desc *ast.Description, target *targets.Target, eh ast.ErrorHandler) ([]ast.Node, error) {
comp := createCompiler(desc, target, eh)
comp.typecheck()
if comp.errors > 0 {
return nil, errors.New("typecheck failed")
}
nodes := comp.collectUnused()
if comp.errors > 0 {
return nil, errors.New("collectUnused failed")
}
return nodes, nil
}
func (comp *compiler) collectUnused() []ast.Node {
var unused []ast.Node
comp.used, _, _ = comp.collectUsed(false)
structs, flags, strflags := comp.collectUsed(true)
note := func(n ast.Node) {
if pos, _, _ := n.Info(); pos.Builtin() {
return
}
unused = append(unused, n)
}
for name, n := range comp.intFlags {
if !flags[name] {
note(n)
}
}
for name, n := range comp.strFlags {
if !strflags[name] {
note(n)
}
}
for name, n := range comp.resources {
if !structs[name] {
note(n)
}
}
for name, n := range comp.structs {
if !structs[name] {
note(n)
}
}
for name, n := range comp.typedefs {
if !comp.usedTypedefs[name] {
note(n)
}
}
return unused
}
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, 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() {
for _, n := range comp.collectUnused() {
pos, typ, name := n.Info()
comp.error(pos, fmt.Sprintf("unused %v %v", typ, name))
}
}
type structDir struct {
Struct string
Dir prog.Dir
}
func (comp *compiler) checkConstructors() {
ctors := make(map[string]bool) // resources for which we have ctors
inputs := make(map[string]bool) // resources which are used as inputs
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, true, ctors, inputs, checked)
}
if n.Ret != nil {
comp.checkTypeCtors(n.Ret, prog.DirOut, true, true, ctors, inputs, checked)
}
}
}
for _, decl := range comp.desc.Nodes {
switch n := decl.(type) {
case *ast.Resource:
name := n.Name.Name
if !comp.used[name] {
continue
}
if !ctors[name] {
comp.error(n.Pos, "resource %v can't be created"+
" (never mentioned as a syscall return value or output argument/field)", name)
}
if !inputs[name] {
comp.error(n.Pos, "resource %v is never used as an input"+
" (such resources are not useful)", name)
}
}
}
}
func (comp *compiler) checkTypeCtors(t *ast.Type, dir prog.Dir, isArg, canCreate bool,
ctors, inputs map[string]bool, checked map[structDir]bool) {
desc, args, base := comp.getArgsBase(t, isArg)
if base.IsOptional {
canCreate = false
}
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 canCreate && 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]
}
}
if dir != prog.DirOut {
r := comp.resources[t.Ident]
for r != nil && !inputs[r.Name.Name] {
inputs[r.Name.Name] = true
r = comp.resources[r.Base.Ident]
}
}
return
}
if desc == typeStruct {
s := comp.structs[t.Ident]
if s.IsUnion {
canCreate = false
}
name := s.Name.Name
key := structDir{name, dir}
if checked[key] {
return
}
checked[key] = true
for _, fld := range s.Fields {
fldDir, fldHasDir := comp.genFieldDir(fld)
if !fldHasDir {
fldDir = dir
}
comp.checkTypeCtors(fld.Type, fldDir, false, canCreate, ctors, inputs, checked)
}
return
}
if desc == typePtr {
dir = genDir(t.Args[0])
}
for i, arg := range args {
if desc.Args[i].Type == typeArgType {
comp.checkTypeCtors(arg, dir, desc.Args[i].IsArg, canCreate, ctors, inputs, 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, 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)
}
comp.parseAttrs(structOrUnionAttrs(n), n, n.Attrs)
}
func (comp *compiler) checkCall(n *ast.Call) {
for _, a := range n.Args {
comp.checkType(checkCtx{}, a.Type, checkIsArg)
}
if n.Ret != nil {
comp.checkType(checkCtx{}, n.Ret, checkIsArg|checkIsRet)
}
comp.parseAttrs(callAttrs, n, n.Attrs)
}
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) {
comp.checkTypeImpl(ctx, t, comp.getTypeDesc(t), flags)
}
func (comp *compiler) checkTypeImpl(ctx checkCtx, t *ast.Type, desc *typeDesc, flags checkFlags) {
if unexpected, _, ok := checkTypeKind(t, kindIdent); !ok {
comp.error(t.Pos, "unexpected %v, expect type", unexpected)
return
}
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, flags&checkIsArg != 0)
desc.Check(comp, t, args, base)
}
}
func (comp *compiler) checkTypeBasic(t *ast.Type, desc *typeDesc, flags checkFlags) {
for i, col := range t.Colon {
if i >= desc.MaxColon {
comp.error(col.Pos, "unexpected ':'")
return
}
if flags&checkIsStruct == 0 {
comp.error(col.Pos, "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.CanBeResourceBase == nil || !desc.CanBeResourceBase(comp, t)) {
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
typedef := comp.typedefs[typedefName]
fullTypeName := ast.SerializeNode(t)
if comp.brokenTypedefs[fullTypeName] {
// We've already produced some errors for this exact type instantiation.
// Don't produce duplicates, also helps to prevent exponential
// slowdown due to some cases of recursion. But increment the number
// of errors so that callers can understand that we did not succeed.
comp.errors++
return
}
comp.usedTypedefs[typedefName] = true
err0 := comp.errors
defer func() {
comp.brokenTypedefs[fullTypeName] = err0 != comp.errors
}()
if len(t.Colon) != 0 {
comp.error(t.Pos, "type alias %v with ':'", t.Ident)
return
}
// Handling optional BASE argument.
if len(typedef.Args) > 0 && typedef.Args[len(typedef.Args)-1].Name == argBase {
if flags&checkIsArg != 0 && len(t.Args) == len(typedef.Args)-1 {
t.Args = append(t.Args, &ast.Type{
Pos: t.Pos,
Ident: "intptr",
})
} else if len(t.Args) == len(typedef.Args) {
comp.checkTypeArg(t, t.Args[len(t.Args)-1], typeArgBase)
}
}
recursion := 0
ctx.instantiationStack = append(ctx.instantiationStack, fullTypeName)
for i, prev := range ctx.instantiationStack[:len(ctx.instantiationStack)-1] {
if typedefName == templateBase(prev) {
recursion++
if recursion > 10 {
comp.error(t.Pos, "type instantiation recursion: %v", strings.Join(ctx.instantiationStack, " -> "))
return
}
}
if prev != fullTypeName {
continue
}
comp.error(t.Pos, "type instantiation loop: %v", strings.Join(ctx.instantiationStack[i:], " -> "))
return
}
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 {
if flags&checkIsArg != 0 && typedef.Args[len(typedef.Args)-1].Name == argBase {
nargs--
}
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)
if err0 != comp.errors {
return
}
comp.desc.Nodes = append(comp.desc.Nodes, inst)
comp.structs[fullTypeName] = inst
}
*t = ast.Type{
Ident: fullTypeName,
}
}
t.Pos = pos0
comp.maybeRemoveBase(t, flags)
}
func (comp *compiler) maybeRemoveBase(t *ast.Type, flags checkFlags) {
// Remove base type if it's not needed in this context.
// If desc is nil, will return an error later when we typecheck the result.
desc := comp.getTypeDesc(t)
if desc != nil && flags&checkIsArg != 0 && desc.NeedBase && len(t.Args) != 0 {
baseTypePos := len(t.Args) - 1
if t.Args[baseTypePos].Ident == "opt" && len(t.Args) >= 2 {
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]
}
argUsed := make(map[string]bool)
err0 := comp.errors
ast.PostRecursive(func(n ast.Node) {
templArg, ok := n.(*ast.Type)
if !ok {
return
}
if concreteArg := argMap[templArg.Ident]; concreteArg != nil {
argUsed[templArg.Ident] = true
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 len(templArg.Colon) != 0 {
col := templArg.Colon[0]
if concreteArg := argMap[col.Ident]; concreteArg != nil {
argUsed[col.Ident] = true
col.Ident = concreteArg.Ident
col.Pos = concreteArg.Pos
}
}
})(templ)
for _, param := range params {
if !argUsed[param.Name] {
comp.error(argMap[param.Name].Pos,
"template argument %v is not used", param.Name)
}
}
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
}
}
for i, col := range arg.Colon {
if i >= desc.MaxColon {
comp.error(col.Pos, "unexpected ':'")
return
}
if desc.Kind == kindIdent {
if unexpected, expect, ok := checkTypeKind(col, kindIdent); !ok {
comp.error(arg.Pos, "unexpected %v after colon, expect %v", unexpected, expect)
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, 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, false)
return desc.Varlen != nil && desc.Varlen(comp, t, args)
}
func (comp *compiler) isZeroSize(t *ast.Type) bool {
desc, args, _ := comp.getArgsBase(t, 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 {
attrs := comp.parseAttrs(unionAttrs, n, n.Attrs)
if attrs[attrVarlen] != 0 {
return
}
} else {
attrs := comp.parseAttrs(structAttrs, n, n.Attrs)
if attrs[attrPacked] != 0 {
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)
}