github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/executor/style_test.go (about)

     1  // Copyright 2020 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 executor
     5  
     6  import (
     7  	"bytes"
     8  	"os"
     9  	"path/filepath"
    10  	"regexp"
    11  	"sort"
    12  	"strings"
    13  	"testing"
    14  )
    15  
    16  func TestExecutorMistakes(t *testing.T) {
    17  	checks := []*struct {
    18  		pattern     string
    19  		suppression string
    20  		message     string
    21  		tests       []string
    22  		commonOnly  bool // only applies to common*.h files
    23  		fuzzerOnly  bool // only applies to files used during fuzzing
    24  	}{
    25  		{
    26  			pattern:    `\)\n\t*(debug|debug_dump_data)\(`,
    27  			message:    "debug() calls are stripped from C reproducers, this code will break. Use {} around debug() to fix",
    28  			commonOnly: true,
    29  			tests: []string{
    30  				`
    31  if (foo)
    32  	debug("foo failed");
    33  `, `
    34  	if (x + y)
    35  		debug_dump_data(data, len);
    36  `,
    37  			},
    38  		},
    39  		{
    40  			pattern:     `\) {\n[^\n}]+?\n\t*}\n`,
    41  			suppression: "debug|__except",
    42  			message:     "Don't use single-line compound statements (remove {})",
    43  			tests: []string{
    44  				`
    45  if (foo) {
    46  	bar();
    47  }
    48  `, `
    49  	while (x + y) {
    50  		foo(a, y);
    51  	}
    52  `,
    53  			},
    54  		},
    55  		{
    56  			// These are also not properly stripped by pkg/csource.
    57  			pattern: `/\*[^{/"]`,
    58  			message: "Don't use /* */ block comments. Use // line comments instead",
    59  			tests: []string{
    60  				`/* C++ comment */`,
    61  			},
    62  		},
    63  		{
    64  			pattern: `#define __NR_`,
    65  			message: "Don't define syscall __NR_foo constants.\n" +
    66  				"These should be guarded by #ifndef __NR_foo, but this is dependent on the host " +
    67  				"and may break on other machines (after pkg/csource processing).\n" +
    68  				"Define sys_foo constants instead.",
    69  			commonOnly: true,
    70  			tests: []string{
    71  				`
    72  #ifndef __NR_io_uring_setup
    73  #ifdef __alpha__
    74  #define __NR_io_uring_setup 535
    75  #else // !__alpha__
    76  #define __NR_io_uring_setup 425
    77  #endif
    78  #endif // __NR_io_uring_setup
    79  `,
    80  			},
    81  		},
    82  		{
    83  			pattern:     `//[^\s]`,
    84  			suppression: `https?://|//%`,
    85  			message:     "Add a space after //",
    86  			tests: []string{
    87  				`//foo`,
    88  			},
    89  		},
    90  		{
    91  			// This detects C89-style variable declarations in the beginning of block in a best-effort manner.
    92  			// Struct fields look exactly as C89 variable declarations, to filter them out we look for "{"
    93  			// at the beginning of the line.
    94  			// nolint: lll
    95  			pattern: `
    96  {[^{]*
    97  \s+((unsigned )?([A-Z][A-Z0-9_]+|[a-z][a-z0-9_]+)\s*\*?|(struct )?[a-zA-Z][a-zA-Z0-9_]+\*)\s+([a-zA-Z][a-zA-Z0-9_]*(,\s*)?)+;
    98  `,
    99  			suppression: `return |goto |va_list |pthread_|zx_`,
   100  			message:     "Don't use C89 var declarations. Declare vars where they are needed and combine with initialization",
   101  			tests: []string{
   102  				`
   103  {
   104  	int i;
   105  `,
   106  				`
   107  {
   108  	socklen_t optlen;
   109  `,
   110  				`
   111  {
   112  	int fd, rv;
   113  `,
   114  				`
   115  {
   116  	int fd, rv;
   117  `,
   118  				`
   119  {
   120  	struct nlattr* attr;
   121  `,
   122  				`
   123  {
   124  	int* i;
   125  `,
   126  				`
   127  {
   128  	DIR* dp;
   129  `,
   130  			},
   131  		},
   132  		{
   133  			pattern: `(fail|exitf)\(".*\\n`,
   134  			message: "Don't use \\n in fail/exitf messages",
   135  			tests: []string{
   136  				`fail("some message with new line\n");`,
   137  			},
   138  		},
   139  		{
   140  			pattern: `fail(msg)?\("[^"]*%`,
   141  			message: "DON'T",
   142  			tests: []string{
   143  				`fail("format %s string")`,
   144  				`failmsg("format %s string", "format")`,
   145  			},
   146  		},
   147  		{
   148  			pattern: `ifn?def\s+SYZ_`,
   149  			message: "SYZ_* are always defined, use #if instead of #ifdef",
   150  			tests: []string{
   151  				`#ifndef SYZ_EXECUTOR_USES_FORK_SERVER`,
   152  				`#ifdef SYZ_EXECUTOR_USES_FORK_SERVER`,
   153  			},
   154  		},
   155  		{
   156  			// Dynamic memory allocation reduces test reproducibility across
   157  			// different libc versions and kernels. Malloc will cause unspecified
   158  			// number of additional mmap's at unspecified locations.
   159  			// For small objects prefer stack allocations, for larger -- either global
   160  			// objects (this may have issues with concurrency), or controlled mmaps.
   161  			fuzzerOnly: true,
   162  			pattern:    `(malloc|calloc|operator new)\(|new [a-zA-Z]`,
   163  			message: "Don't use standard allocation functions," +
   164  				" they disturb address space and issued syscalls",
   165  			suppression: `// `,
   166  			tests: []string{
   167  				`malloc(10)`,
   168  				`malloc(sizeof(int))`,
   169  				`calloc(sizeof T, n)`,
   170  				`operator new(10)`,
   171  				`new int`,
   172  			},
   173  		},
   174  		{
   175  			// Exit/_exit do not necessary work (e.g. if fuzzer sets seccomp
   176  			// filter that prohibits exit_group). Use doexit instead.
   177  			pattern:     `\b[_]?exit\(`,
   178  			suppression: `doexit\(|syz_exit`,
   179  			message:     "Don't use [_]exit, use doexit/exitf/fail instead",
   180  			tests: []string{
   181  				`_exit(1)`,
   182  				`exit(FAILURE)`,
   183  			},
   184  		},
   185  	}
   186  	for _, check := range checks {
   187  		re := regexp.MustCompile(check.pattern)
   188  		for _, test := range check.tests {
   189  			if !re.MatchString(test) {
   190  				t.Fatalf("pattern %q does not match test %q", check.pattern, test)
   191  			}
   192  		}
   193  	}
   194  	runnerFiles := regexp.MustCompile(`(executor_runner|conn|shmem|files)\.h`)
   195  	for _, file := range executorFiles(t) {
   196  		data, err := os.ReadFile(file)
   197  		if err != nil {
   198  			t.Fatal(err)
   199  		}
   200  		for _, check := range checks {
   201  			if check.commonOnly && !strings.Contains(file, "common") {
   202  				continue
   203  			}
   204  			if check.fuzzerOnly && runnerFiles.MatchString(file) {
   205  				continue
   206  			}
   207  			re := regexp.MustCompile(check.pattern)
   208  			supp := regexp.MustCompile(check.suppression)
   209  			for _, match := range re.FindAllIndex(data, -1) {
   210  				end := match[1] - 1
   211  				for end != len(data) && data[end] != '\n' {
   212  					end++
   213  				}
   214  				// Match suppressions against all lines of the match.
   215  				start := match[0] - 1
   216  				for start != 0 && data[start-1] != '\n' {
   217  					start--
   218  				}
   219  				if check.suppression != "" && supp.Match(data[start:end]) {
   220  					continue
   221  				}
   222  				// Locate the last line of the match, that's where we assume the error is.
   223  				start = end - 1
   224  				for start != 0 && data[start-1] != '\n' {
   225  					start--
   226  				}
   227  
   228  				line := bytes.Count(data[:start], []byte{'\n'}) + 1
   229  				t.Errorf("\nexecutor/%v:%v: %v\n%s", file, line, check.message, data[start:end])
   230  			}
   231  		}
   232  	}
   233  }
   234  
   235  func executorFiles(t *testing.T) []string {
   236  	cc, err := filepath.Glob("*.cc")
   237  	if err != nil {
   238  		t.Fatal(err)
   239  	}
   240  	h, err := filepath.Glob("*.h")
   241  	if err != nil {
   242  		t.Fatal(err)
   243  	}
   244  	if len(cc) == 0 || len(h) == 0 {
   245  		t.Fatal("found no executor files")
   246  	}
   247  	res := append(cc, h...)
   248  	sort.Strings(res)
   249  	return res
   250  }