github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/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
    23  	}{
    24  		{
    25  			pattern:    `\)\n\t*(debug|debug_dump_data)\(`,
    26  			message:    "debug() calls are stripped from C reproducers, this code will break. Use {} around debug() to fix",
    27  			commonOnly: true,
    28  			tests: []string{
    29  				`
    30  if (foo)
    31  	debug("foo failed");
    32  `, `
    33  	if (x + y)
    34  		debug_dump_data(data, len);
    35  `,
    36  			},
    37  		},
    38  		{
    39  			pattern:     `\) {\n[^\n}]+?\n\t*}\n`,
    40  			suppression: "debug|__except",
    41  			message:     "Don't use single-line compound statements (remove {})",
    42  			tests: []string{
    43  				`
    44  if (foo) {
    45  	bar();
    46  }
    47  `, `
    48  	while (x + y) {
    49  		foo(a, y);
    50  	}
    51  `,
    52  			},
    53  		},
    54  		{
    55  			// These are also not properly stripped by pkg/csource.
    56  			pattern: `/\*[^{]`,
    57  			message: "Don't use /* */ block comments. Use // line comments instead",
    58  			tests: []string{
    59  				`/* C++ comment */`,
    60  			},
    61  		},
    62  		{
    63  			pattern: `#define __NR_`,
    64  			message: "Don't define syscall __NR_foo constants.\n" +
    65  				"These should be guarded by #ifndef __NR_foo, but this is dependent on the host " +
    66  				"and may break on other machines (after pkg/csource processing).\n" +
    67  				"Define sys_foo constants instead.",
    68  			commonOnly: true,
    69  			tests: []string{
    70  				`
    71  #ifndef __NR_io_uring_setup
    72  #ifdef __alpha__
    73  #define __NR_io_uring_setup 535
    74  #else // !__alpha__
    75  #define __NR_io_uring_setup 425
    76  #endif
    77  #endif // __NR_io_uring_setup
    78  `,
    79  			},
    80  		},
    81  		{
    82  			pattern:     `//[^\s]`,
    83  			suppression: `https?://|//%`,
    84  			message:     "Add a space after //",
    85  			tests: []string{
    86  				`//foo`,
    87  			},
    88  		},
    89  		{
    90  			// This detects C89-style variable declarations in the beginning of block in a best-effort manner.
    91  			// Struct fields look exactly as C89 variable declarations, to filter them out we look for "{"
    92  			// at the beginning of the line.
    93  			pattern: `
    94  {[^{]*
    95  \s+((unsigned )?[a-zA-Z][a-zA-Z0-9_]+\s*\*?|(struct )?[a-zA-Z][a-zA-Z0-9_]+\*)\s+([a-zA-Z][a-zA-Z0-9_]*(,\s*)?)+;
    96  `,
    97  			suppression: `return |goto |va_list |pthread_|zx_`,
    98  			message:     "Don't use C89 var declarations. Declare vars where they are needed and combine with initialization",
    99  			tests: []string{
   100  				`
   101  {
   102  	int i;
   103  `,
   104  				`
   105  {
   106  	socklen_t optlen;
   107  `,
   108  				`
   109  {
   110  	int fd, rv;
   111  `,
   112  				`
   113  {
   114  	int fd, rv;
   115  `,
   116  				`
   117  {
   118  	struct nlattr* attr;
   119  `,
   120  				`
   121  {
   122  	int* i;
   123  `,
   124  				`
   125  {
   126  	DIR* dp;
   127  `,
   128  			},
   129  		},
   130  		{
   131  			pattern: `(fail|exitf)\(".*\\n`,
   132  			message: "Don't use \\n in fail/exitf messages",
   133  			tests: []string{
   134  				`fail("some message with new line\n");`,
   135  			},
   136  		},
   137  		{
   138  			pattern: `fail(msg)?\("[^"]*%`,
   139  			message: "DON'T",
   140  			tests: []string{
   141  				`fail("format %s string")`,
   142  				`failmsg("format %s string", "format")`,
   143  			},
   144  		},
   145  		{
   146  			pattern: `ifn?def\s+SYZ_`,
   147  			message: "SYZ_* are always defined, use #if instead of #ifdef",
   148  			tests: []string{
   149  				`#ifndef SYZ_EXECUTOR_USES_FORK_SERVER`,
   150  				`#ifdef SYZ_EXECUTOR_USES_FORK_SERVER`,
   151  			},
   152  		},
   153  	}
   154  	for _, check := range checks {
   155  		re := regexp.MustCompile(check.pattern)
   156  		for _, test := range check.tests {
   157  			if !re.MatchString(test) {
   158  				t.Fatalf("patter %q does not match test %q", check.pattern, test)
   159  			}
   160  		}
   161  	}
   162  	for _, file := range executorFiles(t) {
   163  		data, err := os.ReadFile(file)
   164  		if err != nil {
   165  			t.Fatal(err)
   166  		}
   167  		for _, check := range checks {
   168  			if check.commonOnly && !strings.Contains(file, "common") {
   169  				continue
   170  			}
   171  			re := regexp.MustCompile(check.pattern)
   172  			supp := regexp.MustCompile(check.suppression)
   173  			for _, match := range re.FindAllIndex(data, -1) {
   174  				end := match[1] - 1
   175  				for end != len(data) && data[end] != '\n' {
   176  					end++
   177  				}
   178  				// Match suppressions against all lines of the match.
   179  				start := match[0] - 1
   180  				for start != 0 && data[start-1] != '\n' {
   181  					start--
   182  				}
   183  				if check.suppression != "" && supp.Match(data[start:end]) {
   184  					continue
   185  				}
   186  				// Locate the last line of the match, that's where we assume the error is.
   187  				start = end - 1
   188  				for start != 0 && data[start-1] != '\n' {
   189  					start--
   190  				}
   191  
   192  				line := bytes.Count(data[:start], []byte{'\n'}) + 1
   193  				t.Errorf("\nexecutor/%v:%v: %v\n%s", file, line, check.message, data[start:end])
   194  			}
   195  		}
   196  	}
   197  }
   198  
   199  func executorFiles(t *testing.T) []string {
   200  	cc, err := filepath.Glob("*.cc")
   201  	if err != nil {
   202  		t.Fatal(err)
   203  	}
   204  	h, err := filepath.Glob("*.h")
   205  	if err != nil {
   206  		t.Fatal(err)
   207  	}
   208  	if len(cc) == 0 || len(h) == 0 {
   209  		t.Fatal("found no executor files")
   210  	}
   211  	res := append(cc, h...)
   212  	sort.Strings(res)
   213  	return res
   214  }