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 }