Revert "[tools] Improvements to 'fuzz'"
This reverts commit 340b2d93d7364766e9287916935277752080629b.
Reason for revert: broke fuzzer check due to DXC failures
Original change's description:
> [tools] Improvements to 'fuzz'
>
> Add support for running multiple fuzzer targets (only 'wgsl' right now).
> Copy filtered files out of tint/test to the corpus directory, so that we don't fuzz uninteresting files.
> Pass --dxc flag to fuzzers
> Add a flag for ignoring non-security crashes.
>
> Change-Id: I1a49c61c642118880da71ea2eaa42b1734ec44df
> Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/190922
> Reviewed-by: Ryan Harrison <rharrison@chromium.org>
> Commit-Queue: Ryan Harrison <rharrison@chromium.org>
> Auto-Submit: Ben Clayton <bclayton@google.com>
TBR=rharrison@chromium.org,bclayton@google.com,dawn-scoped@luci-project-accounts.iam.gserviceaccount.com
Change-Id: Iefa757426e4f27f33ce4a232867b5924917bd99d
No-Presubmit: true
No-Tree-Checks: true
No-Try: true
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/190941
Reviewed-by: James Price <jrprice@google.com>
Reviewed-by: Ryan Harrison <rharrison@chromium.org>
diff --git a/tools/src/cmd/fuzz/main.go b/tools/src/cmd/fuzz/main.go
index 2fd4736..7bddaee 100644
--- a/tools/src/cmd/fuzz/main.go
+++ b/tools/src/cmd/fuzz/main.go
@@ -38,7 +38,6 @@
"os"
"os/exec"
"path/filepath"
- "regexp"
"runtime"
"strings"
"sync/atomic"
@@ -51,6 +50,10 @@
"dawn.googlesource.com/dawn/tools/src/utils"
)
+const (
+ wgslDictionaryRelPath = "src/tint/cmd/fuzz/wgsl/dictionary.txt"
+)
+
func main() {
if err := run(); err != nil {
fmt.Println(err)
@@ -58,64 +61,34 @@
}
}
-type fuzzerInfo struct {
- name string // Short name of the fuzzer
- path string // Path to the fuzzer executable
- ext string // File extensions used by the fuzzer
- dict string // Optional path to a dictionary file for the fuzzer
-}
-
-func run() error {
- t := tool{}
-
- allFuzzers := []fuzzerInfo{
- {
- name: "wgsl",
- path: "tint_wgsl_fuzzer",
- ext: ".wgsl",
- dict: "src/tint/cmd/fuzz/wgsl/dictionary.txt",
- },
- }
- fuzzerByName := map[string]fuzzerInfo{}
- for _, fuzzer := range allFuzzers {
- fuzzerByName[fuzzer.name] = fuzzer
- }
- allFuzzerNames := transform.SliceNoErr(allFuzzers, func(f fuzzerInfo) string { return f.name })
-
- check := false
- build := ""
- flag.BoolVar(&t.verbose, "verbose", false, "print additional output")
- flag.BoolVar(&check, "check", false, "check that all the end-to-end test do not fail")
- flag.BoolVar(&t.securityOnly, "security-only", false, "ignore issues that are not considered security impacting")
- flag.StringVar(&t.filter, "filter", "", "filter the fuzzers run to those with this substring")
- flag.StringVar(&t.corpus, "corpus", defaultCorpusDir(), "the corpus directory")
- flag.StringVar(&build, "build", defaultBuildDir(), "the build directory")
- flag.StringVar(&t.out, "out", "<tmp>", "the directory to hold generated test files")
- flag.IntVar(&t.numProcesses, "j", runtime.NumCPU(), "number of concurrent fuzzers to run")
- flag.Usage = func() {
- fmt.Printf(`
+func showUsage() {
+ fmt.Println(`
fuzz is a helper for running the tint fuzzer executables
fuzz can check that the corpus does not trigger any issues with the fuzzers, and
simplify running of the fuzzers locally.
usage:
- fuzz [fuzzers] [flags...]
+ fuzz [flags...]`)
+ flag.PrintDefaults()
+ fmt.Println(``)
+ os.Exit(1)
+}
-fuzzers are the fuzzer types to run, defaults to all.
- Possible values: ` + strings.Join(allFuzzerNames, ", ") + `
-`)
- flag.PrintDefaults()
- fmt.Println(``)
- os.Exit(1)
- }
+func run() error {
+ t := tool{}
+
+ check := false
+ build := ""
+ flag.BoolVar(&t.verbose, "verbose", false, "print additional output")
+ flag.BoolVar(&check, "check", false, "check that all the end-to-end test do not fail")
+ flag.StringVar(&t.filter, "filter", "", "filter the fuzzers run to those with this substring")
+ flag.StringVar(&t.corpus, "corpus", defaultCorpusDir(), "the corpus directory")
+ flag.StringVar(&build, "build", defaultBuildDir(), "the build directory")
+ flag.StringVar(&t.out, "out", "<tmp>", "the directory to hold generated test files")
+ flag.IntVar(&t.numProcesses, "j", runtime.NumCPU(), "number of concurrent fuzzers to run")
flag.Parse()
- selectedFuzzers := flag.Args()
- if len(selectedFuzzers) == 0 {
- selectedFuzzers = allFuzzerNames
- }
-
if t.numProcesses < 1 {
t.numProcesses = 1
}
@@ -137,31 +110,17 @@
return fmt.Errorf("output directory '%v' does not exist", t.out)
}
- // Register all the fuzzers
- for _, name := range selectedFuzzers {
- fuzzer, ok := fuzzerByName[name]
- if !ok {
- return fmt.Errorf("unknown fuzzer '%v'. Possible values: %v", name, strings.Join(allFuzzerNames, ", "))
+ // Verify all of the fuzzer executables are present
+ for _, fuzzer := range []struct {
+ name string
+ path *string
+ }{
+ {"tint_wgsl_fuzzer", &t.wgslFuzzer},
+ } {
+ *fuzzer.path = filepath.Join(build, fuzzer.name)
+ if !fileutils.IsExe(*fuzzer.path) {
+ return fmt.Errorf("fuzzer not found at '%v'", *fuzzer.path)
}
-
- fuzzer.path = filepath.Join(build, fuzzer.path)
- if !fileutils.IsExe(fuzzer.path) {
- return fmt.Errorf("fuzzer not found at '%v'", fuzzer.path)
- }
-
- if fuzzer.dict != "" {
- dictPath, err := filepath.Abs(filepath.Join(fileutils.DawnRoot(), fuzzer.dict))
- if err != nil || !fileutils.IsFile(dictPath) {
- return fmt.Errorf("failed to obtain the dictionary.txt path: %w", err)
- }
- fuzzer.dict = dictPath
- }
-
- t.fuzzers = append(t.fuzzers, fuzzer)
- }
-
- if dxc := filepath.Join(build, dxcFileName()); fileutils.IsFile(dxc) {
- t.dxc = dxc
}
// If --check was passed, then just ensure that all the files in the corpus
@@ -176,39 +135,27 @@
type tool struct {
verbose bool
- filter string // filter fuzzers to those with this substring
- corpus string // directory of test files
- out string // where to emit new test files
- dxc string // path to the DXC DLL / so
- fuzzers []fuzzerInfo // the fuzzers to run
- numProcesses int // number of concurrent processes to spawn
- securityOnly bool // Ignore non-security crashes
+ filter string // filter fuzzers to those with this substring
+ corpus string // directory of test files
+ out string // where to emit new test files
+ wgslFuzzer string // path to tint_wgsl_fuzzer
+ numProcesses int // number of concurrent processes to spawn
}
// check() runs the fuzzers against all the .wgsl files under to the corpus directory,
// ensuring that the fuzzers do not error for the given file.
func (t tool) check() error {
- type job struct {
- file string
- exe string
+ wgslFiles, err := glob.Glob(filepath.Join(t.corpus, "**.wgsl"))
+ if err != nil {
+ return err
}
- jobs := []job{}
+ // Remove '*.expected.wgsl'
+ wgslFiles = transform.Filter(wgslFiles, func(s string) bool { return !strings.Contains(s, ".expected.") })
- for _, fuzzer := range t.fuzzers {
- files, err := t.fuzzerCorpusFiles(fuzzer)
- if err != nil {
- return err
- }
+ log.Printf("checking %v files...\n", len(wgslFiles))
- log.Printf("%v: checking %v files...\n", fuzzer.name, len(files))
-
- for _, file := range files {
- jobs = append(jobs, job{file: file, exe: fuzzer.path})
- }
- }
-
- remaining := transform.SliceToChan(jobs)
+ remaining := transform.SliceToChan(wgslFiles)
var pb *progressbar.ProgressBar
if term.CanUseAnsiEscapeSequences() {
@@ -218,32 +165,26 @@
var numDone uint32
routine := func() error {
- for job := range remaining {
+ for file := range remaining {
atomic.AddUint32(&numDone, 1)
if pb != nil {
pb.Update(progressbar.Status{
- Total: len(jobs),
+ Total: len(wgslFiles),
Segments: []progressbar.Segment{
{Count: int(atomic.LoadUint32(&numDone))},
},
})
}
- args := []string{}
- if t.dxc != "" {
- args = append(args, "--dxc="+t.dxc)
- }
- args = append(args, job.file)
-
- if out, err := exec.Command(job.exe, args...).CombinedOutput(); err != nil {
- _, fuzzer := filepath.Split(job.exe)
- return fmt.Errorf("%v run with %v failed with %v\n\n%v", fuzzer, job, err, string(out))
+ if out, err := exec.Command(t.wgslFuzzer, file).CombinedOutput(); err != nil {
+ _, fuzzer := filepath.Split(t.wgslFuzzer)
+ return fmt.Errorf("%v run with %v failed with %v\n\n%v", fuzzer, file, err, string(out))
}
}
return nil
}
- if err := utils.RunConcurrent(t.numProcesses, routine); err != nil {
+ if err = utils.RunConcurrent(t.numProcesses, routine); err != nil {
return err
}
@@ -256,105 +197,55 @@
// New cases are written to t.out.
// Blocks until a fuzzer errors, or the process is interrupted.
func (t tool) run() error {
- // Regular expression used to identify the crash file written by libfuzzer
- var reCrashFile = regexp.MustCompile("crash-[a-z0-9]{40}")
-
ctx := utils.CancelOnInterruptContext(context.Background())
ctx, cancel := context.WithCancel(ctx)
defer cancel()
- routinesPerFuzzer := t.numProcesses / len(t.fuzzers)
- if routinesPerFuzzer == 0 {
- routinesPerFuzzer = 1
+ dictPath, err := filepath.Abs(filepath.Join(fileutils.DawnRoot(), wgslDictionaryRelPath))
+ if err != nil || !fileutils.IsFile(dictPath) {
+ return fmt.Errorf("failed to obtain the dictionary.txt path: %w", err)
}
- errs := make(chan error, 8)
+ args := []string{t.out, t.corpus,
+ "-dict=" + dictPath,
+ }
+ if t.verbose {
+ args = append(args, "--verbose")
+ }
+ if t.filter != "" {
+ args = append(args, "--filter="+t.filter)
+ }
- for _, fuzzer := range t.fuzzers {
- fuzzer := fuzzer
-
- corpusFiles, err := t.fuzzerCorpusFiles(fuzzer)
- if err != nil {
- return err
- }
-
- log.Println("copying", len(corpusFiles), fuzzer.ext, "files to", t.out+"...")
- for _, path := range corpusFiles {
- _, file := filepath.Split(path)
- if err := fileutils.CopyFile(filepath.Join(t.out, file), path); err != nil {
- return err
+ fmt.Println("running", t.numProcesses, "fuzzer instances")
+ errs := make(chan error, t.numProcesses)
+ for i := 0; i < t.numProcesses; i++ {
+ go func() {
+ cmd := exec.CommandContext(ctx, t.wgslFuzzer, args...)
+ out := bytes.Buffer{}
+ cmd.Stdout = &out
+ cmd.Stderr = &out
+ if t.verbose {
+ cmd.Stdout = io.MultiWriter(&out, os.Stdout)
+ cmd.Stderr = io.MultiWriter(&out, os.Stderr)
}
- }
-
- args := []string{t.out}
- if fuzzer.dict != "" {
- args = append(args, "-dict="+fuzzer.dict)
- }
- if t.verbose {
- args = append(args, "--verbose")
- }
- if t.filter != "" {
- args = append(args, "--filter="+t.filter)
- }
- if t.dxc != "" {
- args = append(args, "--dxc="+t.dxc)
- }
-
- log.Println("running", routinesPerFuzzer, fuzzer.name, "fuzzer instances...")
- for i := 0; i < routinesPerFuzzer; i++ {
- go func() {
- for {
- cmd := exec.CommandContext(ctx, fuzzer.path, args...)
- out := bytes.Buffer{}
- cmd.Stdout = &out
- cmd.Stderr = &out
- if t.verbose {
- cmd.Stdout = io.MultiWriter(&out, os.Stdout)
- cmd.Stderr = io.MultiWriter(&out, os.Stderr)
- }
- if err := cmd.Run(); err != nil {
- if ctxErr := ctx.Err(); ctxErr != nil {
- errs <- ctxErr
- } else {
- if t.securityOnly && isFailureNonSecurity(out.String()) {
- log.Println("non-security crash found. restarting...")
- if file := reCrashFile.FindString(out.String()); file != "" {
- os.Remove(file)
- }
- continue
- }
- _, fuzzer := filepath.Split(fuzzer.ext)
- errs <- fmt.Errorf("%v failed with %v\n\n%v", fuzzer, err, out.String())
- }
- } else {
- errs <- fmt.Errorf("fuzzer unexpectedly terminated without error:\n%v", out.String())
- }
- break
+ if err := cmd.Run(); err != nil {
+ if ctxErr := ctx.Err(); ctxErr != nil {
+ errs <- ctxErr
+ } else {
+ _, fuzzer := filepath.Split(t.wgslFuzzer)
+ errs <- fmt.Errorf("%v failed with %v\n\n%v", fuzzer, err, out.String())
}
- }()
- }
+ } else {
+ errs <- fmt.Errorf("fuzzer unexpectedly terminated without error:\n%v", out.String())
+ }
+ }()
}
-
for err := range errs {
return err
}
return nil
}
-func (t tool) fuzzerCorpusFiles(f fuzzerInfo) ([]string, error) {
- files, err := glob.Glob(filepath.Join(t.corpus, "**"+f.ext))
- if err != nil {
- return nil, err
- }
-
- // Remove '*.expected.wgsl'
- if f.name == "wgsl" {
- files = transform.Filter(files, func(s string) bool { return !strings.Contains(s, ".expected.wgsl") })
- }
-
- return files, nil
-}
-
func defaultCorpusDir() string {
return filepath.Join(fileutils.DawnRoot(), "test/tint")
}
@@ -362,26 +253,3 @@
func defaultBuildDir() string {
return filepath.Join(fileutils.DawnRoot(), "out/active")
}
-
-func dxcFileName() string {
- switch runtime.GOOS {
- case "windows":
- return "dxcompiler.dll"
- case "darwin":
- return "libdxcompiler.dylib"
- default:
- return "libdxcompiler.so"
- }
-}
-
-func isFailureNonSecurity(out string) bool {
- for _, str := range []string{
- "AddressSanitizer: SEGV on unknown address 0x000000000000",
- "ICE while running fuzzer",
- } {
- if strings.Contains(out, str) {
- return true
- }
- }
- return false
-}