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  }