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 }