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  }