github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/pkg/compiler/compiler_test.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 "bytes" 8 "flag" 9 "fmt" 10 "os" 11 "path/filepath" 12 "reflect" 13 "sort" 14 "testing" 15 16 "github.com/google/syzkaller/pkg/ast" 17 "github.com/google/syzkaller/pkg/serializer" 18 "github.com/google/syzkaller/prog" 19 "github.com/google/syzkaller/sys/targets" 20 ) 21 22 var flagUpdate = flag.Bool("update", false, "reformat all.txt") 23 24 func TestCompileAll(t *testing.T) { 25 for os, arches := range targets.List { 26 t.Run(os, func(t *testing.T) { 27 t.Parallel() 28 eh := func(pos ast.Pos, msg string) { 29 t.Logf("%v: %v", pos, msg) 30 } 31 path := filepath.Join("..", "..", "sys", os) 32 desc := ast.ParseGlob(filepath.Join(path, "*.txt"), eh) 33 if desc == nil { 34 t.Fatalf("parsing failed") 35 } 36 for arch, target := range arches { 37 t.Run(arch, func(t *testing.T) { 38 t.Parallel() 39 errors := new(bytes.Buffer) 40 eh := func(pos ast.Pos, msg string) { 41 fmt.Fprintf(errors, "%v: %v\n", pos, msg) 42 } 43 defer func() { 44 t.Logf("\n%s", errors.Bytes()) 45 }() 46 consts := DeserializeConstFile(filepath.Join(path, "*.const"), eh).Arch(arch) 47 if consts == nil { 48 t.Fatalf("reading consts failed") 49 } 50 prog := Compile(desc, consts, target, eh) 51 if prog == nil { 52 t.Fatalf("compilation failed") 53 } 54 }) 55 } 56 }) 57 } 58 } 59 60 func TestData(t *testing.T) { 61 t.Parallel() 62 // Compile the canned descriptions in testdata and match expected errors. 63 // Errors are produced in batches in different compilation phases. 64 // If one phase produces errors, subsequent phases are not executed. 65 // E.g. if we failed to parse descriptions, we won't run type checking at all. 66 // Because of this we have one file per phase. 67 for _, name := range []string{"errors.txt", "errors2.txt", "errors3.txt", "warnings.txt", "all.txt"} { 68 for _, arch := range []string{targets.TestArch32, targets.TestArch64} { 69 t.Run(fmt.Sprintf("%v/%v", name, arch), func(t *testing.T) { 70 t.Parallel() 71 target := targets.List[targets.TestOS][arch] 72 fileName := filepath.Join("testdata", name) 73 em := ast.NewErrorMatcher(t, fileName) 74 astDesc := ast.Parse(em.Data, name, em.ErrorHandler) 75 if astDesc == nil { 76 em.DumpErrors() 77 t.Fatalf("parsing failed") 78 } 79 constInfo := ExtractConsts(astDesc, target, em.ErrorHandler) 80 if name == "errors.txt" { 81 em.Check() 82 return 83 } 84 if constInfo == nil { 85 em.DumpErrors() 86 t.Fatalf("const extraction failed") 87 } 88 cf := NewConstFile() 89 if err := cf.AddArch(arch, map[string]uint64{ 90 "SYS_foo": 1, 91 "C0": 0, 92 "C1": 1, 93 "C2": 2, 94 "U8_MAX": 0xff, 95 "U16_MAX": 0xffff, 96 }, nil); err != nil { 97 t.Fatal(err) 98 } 99 FabricateSyscallConsts(target, constInfo, cf) 100 consts := cf.Arch(arch) 101 delete(consts, "SYS_unsupported") 102 desc := Compile(astDesc, consts, target, em.ErrorHandler) 103 if name == "errors2.txt" || name == "errors3.txt" { 104 em.Check() 105 return 106 } 107 if desc == nil { 108 em.DumpErrors() 109 t.Fatalf("compilation failed") 110 } 111 if name == "warnings.txt" { 112 em.Check() 113 return 114 } 115 if formatted := ast.Format(astDesc); !bytes.Equal(em.Data, formatted) { 116 if *flagUpdate { 117 os.WriteFile(fileName, formatted, 0644) 118 } 119 t.Fatalf("description is not formatted") 120 } 121 if len(desc.Unsupported) != 0 { 122 t.Fatalf("something is unsupported:\n%+v", desc.Unsupported) 123 } 124 out := new(bytes.Buffer) 125 fmt.Fprintf(out, "\n\nRESOURCES:\n") 126 serializer.Write(out, desc.Resources) 127 fmt.Fprintf(out, "\n\nSYSCALLS:\n") 128 serializer.Write(out, desc.Syscalls) 129 if false { 130 t.Log(out.String()) // useful for debugging 131 } 132 }) 133 } 134 } 135 } 136 137 func TestAutoConsts(t *testing.T) { 138 t.Parallel() 139 eh := func(pos ast.Pos, msg string) { 140 t.Errorf("%v: %v", pos, msg) 141 } 142 desc := ast.ParseGlob(filepath.Join("testdata", "auto*.txt"), eh) 143 if desc == nil { 144 t.Fatalf("parsing failed") 145 } 146 constFile := DeserializeConstFile(filepath.Join("testdata", "auto*.txt.const"), eh) 147 if constFile == nil { 148 t.Fatalf("const loading failed") 149 } 150 target := targets.List[targets.TestOS][targets.TestArch64] 151 consts := constFile.Arch(targets.TestArch64) 152 prog := Compile(desc, consts, target, eh) 153 if prog == nil { 154 t.Fatalf("compilation failed") 155 } 156 } 157 158 func TestFuzz(t *testing.T) { 159 t.Parallel() 160 for _, data := range []string{ 161 ` 162 type H b[A] 163 type b[L] { 164 m b[u:L] 165 l b[z:L] 166 m b[V:L] 167 m b[0:L] 168 H b[o:L] 169 } 170 `, 171 ` 172 type p b[L] 173 type b[L]{ 174 e b[3:L] 175 e b[2:L] 176 e b[1[L]] 177 k b[H] 178 k b[Q] 179 }`, 180 "d~^gB̉`i\u007f?\xb0.", 181 "da[", 182 "define\x98define(define\x98define\x98define\x98define\x98define)define\tdefin", 183 "resource g[g]", 184 `t[ 185 l t 186 ]`, 187 `t()D[0] 188 type D[e]l`, 189 "E", 190 "#", 191 ` 192 type p b[L] 193 type b[L] { 194 e b[L[L]] 195 }`, 196 ` 197 p() b[len] 198 type b[b] b 199 `, 200 ` 201 p() b[len[opt]] 202 type b[b] b 203 `, 204 } { 205 Fuzz([]byte(data)[:len(data):len(data)]) 206 } 207 } 208 209 func TestAlign(t *testing.T) { 210 t.Parallel() 211 const input = ` 212 foo$0(a ptr[in, s0]) 213 s0 { 214 f0 int8 215 f1 int16 216 } 217 218 foo$1(a ptr[in, s1]) 219 s1 { 220 f0 ptr[in, s2, opt] 221 } 222 s2 { 223 f1 s1 224 f2 array[s1, 2] 225 f3 array[array[s1, 2], 2] 226 } 227 ` 228 eh := func(pos ast.Pos, msg string) { 229 t.Errorf("%v: %v", pos, msg) 230 } 231 desc := ast.Parse([]byte(input), "input", eh) 232 if desc == nil { 233 t.Fatal("failed to parse") 234 } 235 p := Compile(desc, map[string]uint64{"SYS_foo": 1}, targets.List[targets.TestOS][targets.TestArch64], eh) 236 if p == nil { 237 t.Fatal("failed to compile") 238 } 239 } 240 241 func TestCollectUnusedError(t *testing.T) { 242 t.Parallel() 243 const input = ` 244 s0 { 245 f0 fidl_string 246 } 247 ` 248 nopErrorHandler := func(pos ast.Pos, msg string) {} 249 desc := ast.Parse([]byte(input), "input", nopErrorHandler) 250 if desc == nil { 251 t.Fatal("failed to parse") 252 } 253 254 _, err := CollectUnused(desc, targets.List[targets.TestOS][targets.TestArch64], nopErrorHandler) 255 if err == nil { 256 t.Fatal("CollectUnused should have failed but didn't") 257 } 258 } 259 260 func TestCollectUnused(t *testing.T) { 261 t.Parallel() 262 inputs := []struct { 263 text string 264 names []string 265 }{ 266 { 267 text: ` 268 s0 { 269 f0 string 270 } 271 `, 272 names: []string{"s0"}, 273 }, 274 { 275 text: ` 276 foo$0(a ptr[in, s0]) 277 s0 { 278 f0 int8 279 f1 int16 280 } 281 `, 282 names: []string{}, 283 }, 284 { 285 text: ` 286 s0 { 287 f0 int8 288 f1 int16 289 } 290 s1 { 291 f2 int32 292 } 293 foo$0(a ptr[in, s0]) 294 `, 295 names: []string{"s1"}, 296 }, 297 } 298 299 for i, input := range inputs { 300 desc := ast.Parse([]byte(input.text), "input", nil) 301 if desc == nil { 302 t.Fatalf("test %d: failed to parse", i) 303 } 304 305 nodes, err := CollectUnused(desc, targets.List[targets.TestOS][targets.TestArch64], nil) 306 if err != nil { 307 t.Fatalf("test %d: CollectUnused failed: %v", i, err) 308 } 309 310 if len(input.names) != len(nodes) { 311 t.Errorf("test %d: want %d nodes, got %d", i, len(input.names), len(nodes)) 312 } 313 314 names := make([]string, len(nodes)) 315 for i := range nodes { 316 _, _, names[i] = nodes[i].Info() 317 } 318 319 sort.Strings(names) 320 sort.Strings(input.names) 321 322 if !reflect.DeepEqual(names, input.names) { 323 t.Errorf("test %d: Unused nodes differ. Want %v, Got %v", i, input.names, names) 324 } 325 } 326 } 327 328 func TestFlattenFlags(t *testing.T) { 329 t.Parallel() 330 const input = ` 331 flags1 = 1, 2, 3, flags2 332 flags2 = 4, 5, 6 333 334 foo$1(a int32[flags1], b int32[flags2]) 335 336 str1 = "three", "four" 337 str2 = "one", "two", str1 338 339 foo$2(a ptr[in, string[str1]], b ptr[in, string[str2]]) 340 ` 341 expectedInts := map[string][]uint64{ 342 "flags1": {1, 2, 3, 4, 5, 6}, 343 "flags2": {4, 5, 6}, 344 } 345 expectedStrings := map[string][]string{ 346 "str1": {"three\x00", "four\x00"}, 347 "str2": {"one\x00", "two\x00", "three\x00", "four\x00"}, 348 } 349 eh := func(pos ast.Pos, msg string) { 350 t.Errorf("%v: %v", pos, msg) 351 } 352 desc := ast.Parse([]byte(input), "input", eh) 353 if desc == nil { 354 t.Fatal("failed to parse") 355 } 356 p := Compile(desc, map[string]uint64{"SYS_foo": 1}, targets.List[targets.TestOS][targets.TestArch64], eh) 357 if p == nil { 358 t.Fatal("failed to compile") 359 } 360 361 for _, n := range p.Types { 362 switch typ := n.(type) { 363 case *prog.FlagsType: 364 expected := expectedInts[typ.TypeName] 365 if !reflect.DeepEqual(typ.Vals, expected) { 366 t.Fatalf("unexpected values %v for flags %v, expected %v", typ.Vals, typ.TypeName, expected) 367 } 368 case *prog.BufferType: 369 expected := expectedStrings[typ.SubKind] 370 if !reflect.DeepEqual(typ.Values, expected) { 371 t.Fatalf("unexpected values %v for flags %v, expected %v", typ.Values, typ.SubKind, expected) 372 } 373 } 374 } 375 } 376 377 func TestSquashablePtr(t *testing.T) { 378 t.Parallel() 379 // recursive must not be marked as squashable b/c it contains a pointer. 380 const input = ` 381 foo(a ptr[in, recursive]) 382 383 recursive { 384 f0 ptr[in, recursive, opt] 385 f1 int32 386 f2 array[int8] 387 } 388 ` 389 eh := func(pos ast.Pos, msg string) { 390 t.Errorf("%v: %v", pos, msg) 391 } 392 desc := ast.Parse([]byte(input), "input", eh) 393 if desc == nil { 394 t.Fatal("failed to parse") 395 } 396 p := Compile(desc, map[string]uint64{"SYS_foo": 1}, targets.List[targets.TestOS][targets.TestArch64], eh) 397 if p == nil { 398 t.Fatal("failed to compile") 399 } 400 for _, typ := range p.Types { 401 if ptr, ok := typ.(*prog.PtrType); ok && ptr.SquashableElem { 402 t.Fatal("got squashable ptr") 403 } 404 } 405 }