From: Roland Shoemaker Date: Sun, 2 May 2021 01:46:22 +0000 (-0700) Subject: [dev.fuzz] testing,internal/fuzz: prevent unbounded memory growth X-Git-Tag: go1.18beta1~1282^2~62 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=510e711dd36999f1800678909bb7fdb448aa074f;p=gostls13.git [dev.fuzz] testing,internal/fuzz: prevent unbounded memory growth 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 Trust: Katie Hockman Run-TryBot: Roland Shoemaker TryBot-Result: Go Bot Reviewed-by: Katie Hockman --- diff --git a/src/cmd/go/testdata/script/test_fuzz_mutate_crash.txt b/src/cmd/go/testdata/script/test_fuzz_mutate_crash.txt index 76e7907bf1..ca2b389321 100644 --- a/src/cmd/go/testdata/script/test_fuzz_mutate_crash.txt +++ b/src/cmd/go/testdata/script/test_fuzz_mutate_crash.txt @@ -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[/\\]' diff --git a/src/go/build/deps_test.go b/src/go/build/deps_test.go index 3511cf41a6..a370bc9ac9 100644 --- a/src/go/build/deps_test.go +++ b/src/go/build/deps_test.go @@ -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 diff --git a/src/internal/fuzz/fuzz.go b/src/internal/fuzz/fuzz.go index b9d2d4cd5f..d0545bd076 100644 --- a/src/internal/fuzz/fuzz.go +++ b/src/internal/fuzz/fuzz.go @@ -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} diff --git a/src/testing/fuzz.go b/src/testing/fuzz.go index 70e1b414a8..7afd24d258 100644 --- a/src/testing/fuzz.go +++ b/src/testing/fuzz.go @@ -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/ (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.