blob: 501b58fea25f52c3ad5939832f2f9200a2c14607 [file] [log] [blame] [edit]
// 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"
"os"
"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.",
commonOnly: true,
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")`,
},
},
{
pattern: `ifn?def\s+SYZ_`,
message: "SYZ_* are always defined, use #if instead of #ifdef",
tests: []string{
`#ifndef SYZ_EXECUTOR_USES_FORK_SERVER`,
`#ifdef SYZ_EXECUTOR_USES_FORK_SERVER`,
},
},
}
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 := os.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
}