]> Cypherpunks repositories - gostls13.git/commitdiff
[dev.fuzz] testing,internal/fuzz: prevent unbounded memory growth
authorRoland Shoemaker <roland@golang.org>
Sun, 2 May 2021 01:46:22 +0000 (18:46 -0700)
committerRoland Shoemaker <roland@golang.org>
Fri, 7 May 2021 14:24:10 +0000 (14:24 +0000)
Usage of f.testContext.match.fullName to generate the test name causes
unbounded memory growth, eventually causing the fuzzer to slow down
as memory pressure increases.

Each time fuzzFn is invoked it generates a unique string and stores it
in a map. With the fuzzer running at around 100k executions per second
this consumed around ~30GB of memory in a handful of minutes.

Instead just use the base name of the test for mutated inputs, a special
name for seeded inputs, and the filename for inputs from the input
corpus.

Change-Id: I083f47df7e82f0c6b0bda244f158233784a13029
Reviewed-on: https://go-review.googlesource.com/c/go/+/316030
Trust: Roland Shoemaker <roland@golang.org>
Trust: Katie Hockman <katie@golang.org>
Run-TryBot: Roland Shoemaker <roland@golang.org>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Katie Hockman <katie@golang.org>
src/cmd/go/testdata/script/test_fuzz_mutate_crash.txt
src/go/build/deps_test.go
src/internal/fuzz/fuzz.go
src/testing/fuzz.go

index 76e7907bf1cd46663eb37fba5f4c2871ffda1ff7..ca2b389321d782b786fb0f2ce37493f6affc6f8b 100644 (file)
@@ -21,6 +21,8 @@ go run check_testdata.go FuzzWithBug
 # Now, the failing bytes should have been added to the seed corpus for
 # the target, and should fail when run without fuzzing.
 ! go test
+stdout 'testdata[/\\]corpus[/\\]FuzzWithBug[/\\][a-f0-9]{64}'
+stdout 'this input caused a crash!'
 
 ! go test -run=FuzzWithNilPanic -fuzz=FuzzWithNilPanic -fuzztime=100x
 stdout 'testdata[/\\]corpus[/\\]FuzzWithNilPanic[/\\]'
index 3511cf41a6b5992673b2d67b79a3fdd68a10781a..a370bc9ac98a064c6a07e14f5b23363668bc0ced 100644 (file)
@@ -501,7 +501,7 @@ var depsRules = `
        FMT, flag, runtime/debug, runtime/trace, internal/sysinfo
        < testing;
 
-       FMT, crypto/sha256, encoding/json, go/ast, go/parser, go/token, math/rand
+       FMT, crypto/sha256, encoding/json, go/ast, go/parser, go/token, math/rand, encoding/hex, crypto/sha256
        < internal/fuzz;
 
        internal/fuzz, internal/testlog, runtime/pprof, regexp
index b9d2d4cd5fb06bccff08578a05259c903f3a810f..d0545bd076d1e7d35cf47372466dac963e45b784 100644 (file)
@@ -512,7 +512,7 @@ func ReadCorpus(dir string, types []reflect.Type) ([]CorpusEntry, error) {
                        errs = append(errs, fmt.Errorf("%q: %v", filename, err))
                        continue
                }
-               corpus = append(corpus, CorpusEntry{Name: file.Name(), Data: data, Values: vals})
+               corpus = append(corpus, CorpusEntry{Name: filename, Data: data, Values: vals})
        }
        if len(errs) > 0 {
                return corpus, &MalformedCorpusError{errs: errs}
index 70e1b414a8b428fc8db93f757f20e62f6fb2ffc4..7afd24d2582c7afbca5576cd33a6766b1e83f159 100644 (file)
@@ -226,7 +226,7 @@ func (f *F) Add(args ...interface{}) {
                }
                values = append(values, args[i])
        }
-       f.corpus = append(f.corpus, corpusEntry{Values: values})
+       f.corpus = append(f.corpus, corpusEntry{Values: values, Name: fmt.Sprintf("seed#%d", len(f.corpus))})
 }
 
 // supportedTypes represents all of the supported types which can be fuzzed.
@@ -298,21 +298,19 @@ func (f *F) Fuzz(ff interface{}) {
        // fn is called in its own goroutine.
        //
        // TODO(jayconrod,katiehockman): dedupe testdata corpus with entries from f.Add
-       // TODO(jayconrod,katiehockman): improve output when running the subtest.
-       // e.g. instead of
-       //    --- FAIL: FuzzSomethingError/#00 (0.00s)
-       // do
-       //    --- FAIL: FuzzSomethingError/<hash> (0.00s)
        run := func(e corpusEntry) error {
                if e.Values == nil {
                        // Every code path should have already unmarshaled Data into Values.
                        // It's our fault if it didn't.
                        panic(fmt.Sprintf("corpus file %q was not unmarshaled", e.Name))
                }
-               testName, ok, _ := f.testContext.match.fullName(&f.common, e.Name)
-               if !ok || shouldFailFast() {
+               if shouldFailFast() {
                        return nil
                }
+               testName := f.common.name
+               if e.Name != "" {
+                       testName = fmt.Sprintf("%s/%s", testName, e.Name)
+               }
                // Record the stack trace at the point of this call so that if the subtest
                // function - which runs in a separate stack - is marked as a helper, we can
                // continue walking the stack into the parent test.