// Copyright 2020 syzkaller project authors. All rights reserved. // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. package executor import ( "bytes" "io/ioutil" "path/filepath" "regexp" "sort" "strings" "testing" ) func TestExecutorMistakes(t *testing.T) { checks := []*struct { pattern string suppression string message string tests []string commonOnly bool }{ { pattern: `\)\n\t*(debug|debug_dump_data)\(`, message: "debug() calls are stripped from C reproducers, this code will break. Use {} around debug() to fix", commonOnly: true, tests: []string{ ` if (foo) debug("foo failed"); `, ` if (x + y) debug_dump_data(data, len); `, }, }, { pattern: `\) {\n[^\n}]+?\n\t*}\n`, suppression: "debug|__except", message: "Don't use single-line compound statements (remove {})", tests: []string{ ` if (foo) { bar(); } `, ` while (x + y) { foo(a, y); } `, }, }, { // These are also not properly stripped by pkg/csource. pattern: `/\*[^{]`, message: "Don't use /* */ block comments. Use // line comments instead", tests: []string{ `/* C++ comment */`, }, }, { pattern: `#define __NR_`, message: "Don't define syscall __NR_foo constants.\n" + "These should be guarded by #ifndef __NR_foo, but this is dependent on the host " + "and may break on other machines (after pkg/csource processing).\n" + "Define sys_foo constants instead.", tests: []string{ ` #ifndef __NR_io_uring_setup #ifdef __alpha__ #define __NR_io_uring_setup 535 #else // !__alpha__ #define __NR_io_uring_setup 425 #endif #endif // __NR_io_uring_setup `, }, }, { pattern: `//[^\s]`, suppression: `https?://`, message: "Add a space after //", tests: []string{ `//foo`, }, }, { // This detects C89-style variable declarations in the beginning of block in a best-effort manner. // Struct fields look exactly as C89 variable declarations, to filter them out we look for "{" // at the beginning of the line. pattern: ` {[^{]* \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*)?)+; `, suppression: `return |goto |va_list |pthread_|zx_`, message: "Don't use C89 var declarations. Declare vars where they are needed and combine with initialization", tests: []string{ ` { int i; `, ` { socklen_t optlen; `, ` { int fd, rv; `, ` { int fd, rv; `, ` { struct nlattr* attr; `, ` { int* i; `, ` { DIR* dp; `, }, }, { pattern: `(fail|exitf)\(".*\\n`, message: "Don't use \\n in fail/exitf messages", tests: []string{ `fail("some message with new line\n");`, }, }, { pattern: `fail(msg)?\("[^"]*%`, message: "DON'T", tests: []string{ `fail("format %s string")`, `failmsg("format %s string", "format")`, }, }, } for _, check := range checks { re := regexp.MustCompile(check.pattern) for _, test := range check.tests { if !re.MatchString(test) { t.Fatalf("patter %q does not match test %q", check.pattern, test) } } } for _, file := range executorFiles(t) { data, err := ioutil.ReadFile(file) if err != nil { t.Fatal(err) } for _, check := range checks { if check.commonOnly && !strings.Contains(file, "common") { continue } re := regexp.MustCompile(check.pattern) supp := regexp.MustCompile(check.suppression) for _, match := range re.FindAllIndex(data, -1) { end := match[1] - 1 for end != len(data) && data[end] != '\n' { end++ } // Match suppressions against all lines of the match. start := match[0] - 1 for start != 0 && data[start-1] != '\n' { start-- } if check.suppression != "" && supp.Match(data[start:end]) { continue } // Locate the last line of the match, that's where we assume the error is. start = end - 1 for start != 0 && data[start-1] != '\n' { start-- } line := bytes.Count(data[:start], []byte{'\n'}) + 1 t.Errorf("\nexecutor/%v:%v: %v\n%s\n", file, line, check.message, data[start:end]) } } } } func executorFiles(t *testing.T) []string { cc, err := filepath.Glob("*.cc") if err != nil { t.Fatal(err) } h, err := filepath.Glob("*.h") if err != nil { t.Fatal(err) } if len(cc) == 0 || len(h) == 0 { t.Fatal("found no executor files") } res := append(cc, h...) sort.Strings(res) return res }