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 }