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