github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/pkg/compiler/consts.go (about)

     1  // Copyright 2017 syzkaller project authors. All rights reserved.
     2  // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
     3  
     4  package compiler
     5  
     6  import (
     7  	"fmt"
     8  	"sort"
     9  	"strings"
    10  
    11  	"github.com/google/syzkaller/pkg/ast"
    12  	"github.com/google/syzkaller/prog"
    13  	"github.com/google/syzkaller/sys/targets"
    14  )
    15  
    16  type ConstInfo struct {
    17  	Consts   []*Const
    18  	Includes []string
    19  	Incdirs  []string
    20  	Defines  map[string]string
    21  }
    22  
    23  type Const struct {
    24  	Name string
    25  	Pos  ast.Pos
    26  	Used bool // otherwise only defined
    27  }
    28  
    29  func ExtractConsts(desc *ast.Description, target *targets.Target, eh ast.ErrorHandler) map[string]*ConstInfo {
    30  	res := Compile(desc, nil, target, eh)
    31  	if res == nil {
    32  		return nil
    33  	}
    34  	return res.fileConsts
    35  }
    36  
    37  // FabricateSyscallConsts adds syscall number constants to consts map.
    38  // Used for test OS to not bother specifying consts for all syscalls.
    39  func FabricateSyscallConsts(target *targets.Target, constInfo map[string]*ConstInfo, cf *ConstFile) {
    40  	if !target.SyscallNumbers {
    41  		return
    42  	}
    43  	for _, info := range constInfo {
    44  		for _, c := range info.Consts {
    45  			if strings.HasPrefix(c.Name, target.SyscallPrefix) {
    46  				cf.addConst(target.Arch, c.Name, 0, true, false)
    47  			}
    48  		}
    49  	}
    50  }
    51  
    52  type constContext struct {
    53  	infos           map[string]*constInfo
    54  	instantionStack []map[string]ast.Pos
    55  }
    56  
    57  // extractConsts returns list of literal constants and other info required for const value extraction.
    58  func (comp *compiler) extractConsts() map[string]*ConstInfo {
    59  	ctx := &constContext{
    60  		infos: make(map[string]*constInfo),
    61  	}
    62  	extractIntConsts := ast.Recursive(func(n0 ast.Node) bool {
    63  		if n, ok := n0.(*ast.Int); ok {
    64  			comp.addConst(ctx, n.Pos, n.Ident)
    65  		}
    66  		return true
    67  	})
    68  	comp.desc.Walk(extractIntConsts)
    69  	for _, decl := range comp.desc.Nodes {
    70  		pos, _, _ := decl.Info()
    71  		info := ctx.getConstInfo(pos)
    72  		switch n := decl.(type) {
    73  		case *ast.Include:
    74  			info.includeArray = append(info.includeArray, n.File.Value)
    75  		case *ast.Incdir:
    76  			info.incdirArray = append(info.incdirArray, n.Dir.Value)
    77  		case *ast.Define:
    78  			v := fmt.Sprint(n.Value.Value)
    79  			switch {
    80  			case n.Value.CExpr != "":
    81  				v = n.Value.CExpr
    82  			case n.Value.Ident != "":
    83  				v = n.Value.Ident
    84  			}
    85  			name := n.Name.Name
    86  			if _, builtin := comp.builtinConsts[name]; builtin {
    87  				comp.error(pos, "redefining builtin const %v", name)
    88  			}
    89  			info.defines[name] = v
    90  			ctx.addConst(pos, name, false)
    91  		case *ast.Call:
    92  			if comp.target.HasCallNumber(n.CallName) {
    93  				comp.addConst(ctx, pos, comp.target.SyscallPrefix+n.CallName)
    94  			}
    95  			for _, attr := range n.Attrs {
    96  				if callAttrs[attr.Ident].Type == intAttr {
    97  					comp.addConst(ctx, attr.Pos, attr.Args[0].Ident)
    98  				}
    99  			}
   100  		case *ast.Struct:
   101  			// The instantionStack allows to add consts that are used in template structs
   102  			// to all files that use the template. Without this we would add these consts
   103  			// to only one random file, which would leads to flaky changes in const files.
   104  			ctx.instantionStack = append(ctx.instantionStack, comp.structFiles[n])
   105  			for _, attr := range n.Attrs {
   106  				attrDesc := structOrUnionAttrs(n)[attr.Ident]
   107  				if attrDesc.Type == intAttr {
   108  					comp.addConst(ctx, attr.Pos, attr.Args[0].Ident)
   109  				}
   110  			}
   111  			foreachFieldAttrConst(n, func(t *ast.Type) {
   112  				comp.addConst(ctx, t.Pos, t.Ident)
   113  			})
   114  			extractIntConsts(n)
   115  			comp.extractTypeConsts(ctx, decl)
   116  			ctx.instantionStack = ctx.instantionStack[:len(ctx.instantionStack)-1]
   117  		}
   118  		switch decl.(type) {
   119  		case *ast.Call, *ast.Resource, *ast.TypeDef:
   120  			comp.extractTypeConsts(ctx, decl)
   121  		}
   122  	}
   123  	return convertConstInfo(ctx, comp.fileMetas)
   124  }
   125  
   126  func foreachFieldAttrConst(n *ast.Struct, cb func(*ast.Type)) {
   127  	for _, field := range n.Fields {
   128  		for _, attr := range field.Attrs {
   129  			attrDesc := structOrUnionFieldAttrs(n)[attr.Ident]
   130  			if attrDesc == nil {
   131  				return
   132  			}
   133  			if attrDesc.Type != exprAttr {
   134  				// For now, only these field attrs may have consts.
   135  				return
   136  			}
   137  			ast.Recursive(func(n ast.Node) bool {
   138  				t, ok := n.(*ast.Type)
   139  				if !ok || t.Expression != nil {
   140  					return true
   141  				}
   142  				if t.Ident != valueIdent {
   143  					cb(t)
   144  				}
   145  				return false
   146  			})(attr.Args[0])
   147  		}
   148  	}
   149  }
   150  
   151  func (comp *compiler) extractTypeConsts(ctx *constContext, n ast.Node) {
   152  	comp.foreachType(n, func(t *ast.Type, desc *typeDesc, args []*ast.Type, _ prog.IntTypeCommon) {
   153  		for i, arg := range args {
   154  			if desc.Args[i].Type.Kind&kindInt != 0 {
   155  				if arg.Ident != "" {
   156  					comp.addConst(ctx, arg.Pos, arg.Ident)
   157  				}
   158  				for _, col := range arg.Colon {
   159  					if col.Ident != "" {
   160  						comp.addConst(ctx, col.Pos, col.Ident)
   161  					}
   162  				}
   163  			}
   164  		}
   165  	})
   166  }
   167  
   168  func (comp *compiler) addConst(ctx *constContext, pos ast.Pos, name string) {
   169  	if name == "" {
   170  		return
   171  	}
   172  	if _, builtin := comp.builtinConsts[name]; builtin {
   173  		return
   174  	}
   175  	// In case of intN[identA], identA may refer to a constant or to a set of
   176  	// flags. To avoid marking all flags as constants, we must check here
   177  	// whether identA refers to a flag. We have a check in the compiler to
   178  	// ensure an identifier can never refer to both a constant and flags.
   179  	if _, isFlag := comp.intFlags[name]; isFlag {
   180  		return
   181  	}
   182  	ctx.addConst(pos, name, true)
   183  	for _, instantions := range ctx.instantionStack {
   184  		for _, pos1 := range instantions {
   185  			ctx.addConst(pos1, name, true)
   186  		}
   187  	}
   188  }
   189  
   190  type constInfo struct {
   191  	consts       map[string]*Const
   192  	defines      map[string]string
   193  	includeArray []string
   194  	incdirArray  []string
   195  }
   196  
   197  func (ctx *constContext) addConst(pos ast.Pos, name string, used bool) {
   198  	info := ctx.getConstInfo(pos)
   199  	if c := info.consts[name]; c != nil && c.Used {
   200  		used = true
   201  	}
   202  	info.consts[name] = &Const{
   203  		Pos:  pos,
   204  		Name: name,
   205  		Used: used,
   206  	}
   207  }
   208  
   209  func (ctx *constContext) getConstInfo(pos ast.Pos) *constInfo {
   210  	info := ctx.infos[pos.File]
   211  	if info == nil {
   212  		info = &constInfo{
   213  			consts:  make(map[string]*Const),
   214  			defines: make(map[string]string),
   215  		}
   216  		ctx.infos[pos.File] = info
   217  	}
   218  	return info
   219  }
   220  
   221  func convertConstInfo(ctx *constContext, metas map[string]Meta) map[string]*ConstInfo {
   222  	res := make(map[string]*ConstInfo)
   223  	for file, info := range ctx.infos {
   224  		if file == ast.BuiltinFile {
   225  			continue
   226  		}
   227  		var allConsts []*Const
   228  		for _, val := range info.consts {
   229  			allConsts = append(allConsts, val)
   230  		}
   231  		sort.Slice(allConsts, func(i, j int) bool {
   232  			return allConsts[i].Name < allConsts[j].Name
   233  		})
   234  		res[file] = &ConstInfo{
   235  			Consts:   allConsts,
   236  			Includes: info.includeArray,
   237  			Incdirs:  info.incdirArray,
   238  			Defines:  info.defines,
   239  		}
   240  	}
   241  	return res
   242  }
   243  
   244  // assignSyscallNumbers assigns syscall numbers, discards unsupported syscalls.
   245  func (comp *compiler) assignSyscallNumbers(consts map[string]uint64) {
   246  	for _, decl := range comp.desc.Nodes {
   247  		c, ok := decl.(*ast.Call)
   248  		if !ok || strings.HasPrefix(c.CallName, "syz_") {
   249  			continue
   250  		}
   251  		str := comp.target.SyscallPrefix + c.CallName
   252  		nr, ok := consts[str]
   253  		if ok {
   254  			c.NR = nr
   255  			continue
   256  		}
   257  		c.NR = ^uint64(0) // mark as unused to not generate it
   258  		name := "syscall " + c.CallName
   259  		if !comp.unsupported[name] {
   260  			comp.unsupported[name] = true
   261  			comp.warning(c.Pos, "unsupported syscall: %v due to missing const %v",
   262  				c.CallName, str)
   263  		}
   264  	}
   265  }
   266  
   267  // patchConsts replaces all symbolic consts with their numeric values taken from consts map.
   268  // Updates desc and returns set of unsupported syscalls and flags.
   269  func (comp *compiler) patchConsts(consts0 map[string]uint64) {
   270  	consts := make(map[string]uint64)
   271  	for name, val := range consts0 {
   272  		consts[name] = val
   273  	}
   274  	for name, val := range comp.builtinConsts {
   275  		if _, ok := consts[name]; ok {
   276  			panic(fmt.Sprintf("builtin const %v already defined", name))
   277  		}
   278  		consts[name] = val
   279  	}
   280  	for _, decl := range comp.desc.Nodes {
   281  		switch n := decl.(type) {
   282  		case *ast.IntFlags:
   283  			// Unsupported flag values are dropped.
   284  			var values []*ast.Int
   285  			for _, v := range n.Values {
   286  				if comp.patchIntConst(v, consts, nil) {
   287  					values = append(values, v)
   288  				}
   289  			}
   290  			n.Values = values
   291  		case *ast.Resource, *ast.Struct, *ast.Call, *ast.TypeDef:
   292  			// Walk whole tree and replace consts in Type's and Int's.
   293  			missing := ""
   294  			comp.foreachType(decl, func(_ *ast.Type, desc *typeDesc,
   295  				args []*ast.Type, _ prog.IntTypeCommon) {
   296  				for i, arg := range args {
   297  					if desc.Args[i].Type.Kind&kindInt != 0 {
   298  						comp.patchTypeConst(arg, consts, &missing)
   299  					}
   300  				}
   301  			})
   302  			switch n := decl.(type) {
   303  			case *ast.Resource:
   304  				for _, v := range n.Values {
   305  					comp.patchIntConst(v, consts, &missing)
   306  				}
   307  			case *ast.Call:
   308  				for _, attr := range n.Attrs {
   309  					if callAttrs[attr.Ident].Type == intAttr {
   310  						comp.patchTypeConst(attr.Args[0], consts, &missing)
   311  					}
   312  				}
   313  			case *ast.Struct:
   314  				for _, attr := range n.Attrs {
   315  					attrDesc := structOrUnionAttrs(n)[attr.Ident]
   316  					if attrDesc.Type == intAttr {
   317  						comp.patchTypeConst(attr.Args[0], consts, &missing)
   318  					}
   319  				}
   320  				foreachFieldAttrConst(n, func(t *ast.Type) {
   321  					comp.patchTypeConst(t, consts, &missing)
   322  				})
   323  			}
   324  			if missing == "" {
   325  				continue
   326  			}
   327  			// Produce a warning about unsupported syscall/resource/struct.
   328  			// TODO(dvyukov): we should transitively remove everything that
   329  			// depends on unsupported things. Potentially we still can get,
   330  			// say, a bad int range error due to the wrong const value.
   331  			// However, if we have a union where one of the options is
   332  			// arch-specific and does not have a const value, it's probably
   333  			// better to remove just that option. But then if we get to 0
   334  			// options in the union, we still need to remove it entirely.
   335  			pos, typ, name := decl.Info()
   336  			if id := typ + " " + name; !comp.unsupported[id] {
   337  				comp.unsupported[id] = true
   338  				comp.warning(pos, "unsupported %v: %v due to missing const %v",
   339  					typ, name, missing)
   340  			}
   341  			if c, ok := decl.(*ast.Call); ok {
   342  				c.NR = ^uint64(0) // mark as unused to not generate it
   343  			}
   344  		}
   345  	}
   346  }
   347  
   348  func (comp *compiler) patchIntConst(n *ast.Int, consts map[string]uint64, missing *string) bool {
   349  	return comp.patchConst(&n.Value, &n.Ident, consts, missing, false)
   350  }
   351  
   352  func (comp *compiler) patchTypeConst(n *ast.Type, consts map[string]uint64, missing *string) {
   353  	comp.patchConst(&n.Value, &n.Ident, consts, missing, true)
   354  	for _, col := range n.Colon {
   355  		comp.patchConst(&col.Value, &col.Ident, consts, missing, true)
   356  	}
   357  }
   358  
   359  func (comp *compiler) patchConst(val *uint64, id *string, consts map[string]uint64, missing *string, reset bool) bool {
   360  	if *id == "" {
   361  		return true
   362  	}
   363  	if v, ok := consts[*id]; ok {
   364  		if reset {
   365  			*id = ""
   366  		}
   367  		*val = v
   368  		return true
   369  	}
   370  	// This check is necessary because in intN[identA], identA may be a
   371  	// constant or a set of flags.
   372  	if _, isFlag := comp.intFlags[*id]; isFlag {
   373  		return true
   374  	}
   375  	if missing != nil && *missing == "" {
   376  		*missing = *id
   377  	}
   378  	// 1 is slightly safer than 0 and allows to work-around e.g. an array size
   379  	// that comes from a const missing on an arch. Also see the TODO in patchConsts.
   380  	*val = 1
   381  	return false
   382  }