summaryrefslogtreecommitdiffstats
path: root/src/testing/fuzz.go
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 19:23:18 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 19:23:18 +0000
commit43a123c1ae6613b3efeed291fa552ecd909d3acf (patch)
treefd92518b7024bc74031f78a1cf9e454b65e73665 /src/testing/fuzz.go
parentInitial commit. (diff)
downloadgolang-1.20-43a123c1ae6613b3efeed291fa552ecd909d3acf.tar.xz
golang-1.20-43a123c1ae6613b3efeed291fa552ecd909d3acf.zip
Adding upstream version 1.20.14.upstream/1.20.14upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--src/testing/fuzz.go729
1 files changed, 729 insertions, 0 deletions
diff --git a/src/testing/fuzz.go b/src/testing/fuzz.go
new file mode 100644
index 0000000..d31a3f8
--- /dev/null
+++ b/src/testing/fuzz.go
@@ -0,0 +1,729 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package testing
+
+import (
+ "errors"
+ "flag"
+ "fmt"
+ "io"
+ "os"
+ "path/filepath"
+ "reflect"
+ "runtime"
+ "strings"
+ "time"
+)
+
+func initFuzzFlags() {
+ matchFuzz = flag.String("test.fuzz", "", "run the fuzz test matching `regexp`")
+ flag.Var(&fuzzDuration, "test.fuzztime", "time to spend fuzzing; default is to run indefinitely")
+ flag.Var(&minimizeDuration, "test.fuzzminimizetime", "time to spend minimizing a value after finding a failing input")
+
+ fuzzCacheDir = flag.String("test.fuzzcachedir", "", "directory where interesting fuzzing inputs are stored (for use only by cmd/go)")
+ isFuzzWorker = flag.Bool("test.fuzzworker", false, "coordinate with the parent process to fuzz random values (for use only by cmd/go)")
+}
+
+var (
+ matchFuzz *string
+ fuzzDuration durationOrCountFlag
+ minimizeDuration = durationOrCountFlag{d: 60 * time.Second, allowZero: true}
+ fuzzCacheDir *string
+ isFuzzWorker *bool
+
+ // corpusDir is the parent directory of the fuzz test's seed corpus within
+ // the package.
+ corpusDir = "testdata/fuzz"
+)
+
+// fuzzWorkerExitCode is used as an exit code by fuzz worker processes after an
+// internal error. This distinguishes internal errors from uncontrolled panics
+// and other failures. Keep in sync with internal/fuzz.workerExitCode.
+const fuzzWorkerExitCode = 70
+
+// InternalFuzzTarget is an internal type but exported because it is
+// cross-package; it is part of the implementation of the "go test" command.
+type InternalFuzzTarget struct {
+ Name string
+ Fn func(f *F)
+}
+
+// F is a type passed to fuzz tests.
+//
+// Fuzz tests run generated inputs against a provided fuzz target, which can
+// find and report potential bugs in the code being tested.
+//
+// A fuzz test runs the seed corpus by default, which includes entries provided
+// by (*F).Add and entries in the testdata/fuzz/<FuzzTestName> directory. After
+// any necessary setup and calls to (*F).Add, the fuzz test must then call
+// (*F).Fuzz to provide the fuzz target. See the testing package documentation
+// for an example, and see the F.Fuzz and F.Add method documentation for
+// details.
+//
+// *F methods can only be called before (*F).Fuzz. Once the test is
+// executing the fuzz target, only (*T) methods can be used. The only *F methods
+// that are allowed in the (*F).Fuzz function are (*F).Failed and (*F).Name.
+type F struct {
+ common
+ fuzzContext *fuzzContext
+ testContext *testContext
+
+ // inFuzzFn is true when the fuzz function is running. Most F methods cannot
+ // be called when inFuzzFn is true.
+ inFuzzFn bool
+
+ // corpus is a set of seed corpus entries, added with F.Add and loaded
+ // from testdata.
+ corpus []corpusEntry
+
+ result fuzzResult
+ fuzzCalled bool
+}
+
+var _ TB = (*F)(nil)
+
+// corpusEntry is an alias to the same type as internal/fuzz.CorpusEntry.
+// We use a type alias because we don't want to export this type, and we can't
+// import internal/fuzz from testing.
+type corpusEntry = struct {
+ Parent string
+ Path string
+ Data []byte
+ Values []any
+ Generation int
+ IsSeed bool
+}
+
+// Helper marks the calling function as a test helper function.
+// When printing file and line information, that function will be skipped.
+// Helper may be called simultaneously from multiple goroutines.
+func (f *F) Helper() {
+ if f.inFuzzFn {
+ panic("testing: f.Helper was called inside the fuzz target, use t.Helper instead")
+ }
+
+ // common.Helper is inlined here.
+ // If we called it, it would mark F.Helper as the helper
+ // instead of the caller.
+ f.mu.Lock()
+ defer f.mu.Unlock()
+ if f.helperPCs == nil {
+ f.helperPCs = make(map[uintptr]struct{})
+ }
+ // repeating code from callerName here to save walking a stack frame
+ var pc [1]uintptr
+ n := runtime.Callers(2, pc[:]) // skip runtime.Callers + Helper
+ if n == 0 {
+ panic("testing: zero callers found")
+ }
+ if _, found := f.helperPCs[pc[0]]; !found {
+ f.helperPCs[pc[0]] = struct{}{}
+ f.helperNames = nil // map will be recreated next time it is needed
+ }
+}
+
+// Fail marks the function as having failed but continues execution.
+func (f *F) Fail() {
+ // (*F).Fail may be called by (*T).Fail, which we should allow. However, we
+ // shouldn't allow direct (*F).Fail calls from inside the (*F).Fuzz function.
+ if f.inFuzzFn {
+ panic("testing: f.Fail was called inside the fuzz target, use t.Fail instead")
+ }
+ f.common.Helper()
+ f.common.Fail()
+}
+
+// Skipped reports whether the test was skipped.
+func (f *F) Skipped() bool {
+ // (*F).Skipped may be called by tRunner, which we should allow. However, we
+ // shouldn't allow direct (*F).Skipped calls from inside the (*F).Fuzz function.
+ if f.inFuzzFn {
+ panic("testing: f.Skipped was called inside the fuzz target, use t.Skipped instead")
+ }
+ f.common.Helper()
+ return f.common.Skipped()
+}
+
+// Add will add the arguments to the seed corpus for the fuzz test. This will be
+// a no-op if called after or within the fuzz target, and args must match the
+// arguments for the fuzz target.
+func (f *F) Add(args ...any) {
+ var values []any
+ for i := range args {
+ if t := reflect.TypeOf(args[i]); !supportedTypes[t] {
+ panic(fmt.Sprintf("testing: unsupported type to Add %v", t))
+ }
+ values = append(values, args[i])
+ }
+ f.corpus = append(f.corpus, corpusEntry{Values: values, IsSeed: true, Path: fmt.Sprintf("seed#%d", len(f.corpus))})
+}
+
+// supportedTypes represents all of the supported types which can be fuzzed.
+var supportedTypes = map[reflect.Type]bool{
+ reflect.TypeOf(([]byte)("")): true,
+ reflect.TypeOf((string)("")): true,
+ reflect.TypeOf((bool)(false)): true,
+ reflect.TypeOf((byte)(0)): true,
+ reflect.TypeOf((rune)(0)): true,
+ reflect.TypeOf((float32)(0)): true,
+ reflect.TypeOf((float64)(0)): true,
+ reflect.TypeOf((int)(0)): true,
+ reflect.TypeOf((int8)(0)): true,
+ reflect.TypeOf((int16)(0)): true,
+ reflect.TypeOf((int32)(0)): true,
+ reflect.TypeOf((int64)(0)): true,
+ reflect.TypeOf((uint)(0)): true,
+ reflect.TypeOf((uint8)(0)): true,
+ reflect.TypeOf((uint16)(0)): true,
+ reflect.TypeOf((uint32)(0)): true,
+ reflect.TypeOf((uint64)(0)): true,
+}
+
+// Fuzz runs the fuzz function, ff, for fuzz testing. If ff fails for a set of
+// arguments, those arguments will be added to the seed corpus.
+//
+// ff must be a function with no return value whose first argument is *T and
+// whose remaining arguments are the types to be fuzzed.
+// For example:
+//
+// f.Fuzz(func(t *testing.T, b []byte, i int) { ... })
+//
+// The following types are allowed: []byte, string, bool, byte, rune, float32,
+// float64, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64.
+// More types may be supported in the future.
+//
+// ff must not call any *F methods, e.g. (*F).Log, (*F).Error, (*F).Skip. Use
+// the corresponding *T method instead. The only *F methods that are allowed in
+// the (*F).Fuzz function are (*F).Failed and (*F).Name.
+//
+// This function should be fast and deterministic, and its behavior should not
+// depend on shared state. No mutatable input arguments, or pointers to them,
+// should be retained between executions of the fuzz function, as the memory
+// backing them may be mutated during a subsequent invocation. ff must not
+// modify the underlying data of the arguments provided by the fuzzing engine.
+//
+// When fuzzing, F.Fuzz does not return until a problem is found, time runs out
+// (set with -fuzztime), or the test process is interrupted by a signal. F.Fuzz
+// should be called exactly once, unless F.Skip or F.Fail is called beforehand.
+func (f *F) Fuzz(ff any) {
+ if f.fuzzCalled {
+ panic("testing: F.Fuzz called more than once")
+ }
+ f.fuzzCalled = true
+ if f.failed {
+ return
+ }
+ f.Helper()
+
+ // ff should be in the form func(*testing.T, ...interface{})
+ fn := reflect.ValueOf(ff)
+ fnType := fn.Type()
+ if fnType.Kind() != reflect.Func {
+ panic("testing: F.Fuzz must receive a function")
+ }
+ if fnType.NumIn() < 2 || fnType.In(0) != reflect.TypeOf((*T)(nil)) {
+ panic("testing: fuzz target must receive at least two arguments, where the first argument is a *T")
+ }
+ if fnType.NumOut() != 0 {
+ panic("testing: fuzz target must not return a value")
+ }
+
+ // Save the types of the function to compare against the corpus.
+ var types []reflect.Type
+ for i := 1; i < fnType.NumIn(); i++ {
+ t := fnType.In(i)
+ if !supportedTypes[t] {
+ panic(fmt.Sprintf("testing: unsupported type for fuzzing %v", t))
+ }
+ types = append(types, t)
+ }
+
+ // Load the testdata seed corpus. Check types of entries in the testdata
+ // corpus and entries declared with F.Add.
+ //
+ // Don't load the seed corpus if this is a worker process; we won't use it.
+ if f.fuzzContext.mode != fuzzWorker {
+ for _, c := range f.corpus {
+ if err := f.fuzzContext.deps.CheckCorpus(c.Values, types); err != nil {
+ // TODO(#48302): Report the source location of the F.Add call.
+ f.Fatal(err)
+ }
+ }
+
+ // Load seed corpus
+ c, err := f.fuzzContext.deps.ReadCorpus(filepath.Join(corpusDir, f.name), types)
+ if err != nil {
+ f.Fatal(err)
+ }
+ for i := range c {
+ c[i].IsSeed = true // these are all seed corpus values
+ if f.fuzzContext.mode == fuzzCoordinator {
+ // If this is the coordinator process, zero the values, since we don't need
+ // to hold onto them.
+ c[i].Values = nil
+ }
+ }
+
+ f.corpus = append(f.corpus, c...)
+ }
+
+ // run calls fn on a given input, as a subtest with its own T.
+ // run is analogous to T.Run. The test filtering and cleanup works similarly.
+ // fn is called in its own goroutine.
+ run := func(captureOut io.Writer, e corpusEntry) (ok bool) {
+ if e.Values == nil {
+ // The corpusEntry must have non-nil Values in order to run the
+ // test. If Values is nil, it is a bug in our code.
+ panic(fmt.Sprintf("corpus file %q was not unmarshaled", e.Path))
+ }
+ if shouldFailFast() {
+ return true
+ }
+ testName := f.name
+ if e.Path != "" {
+ testName = fmt.Sprintf("%s/%s", testName, filepath.Base(e.Path))
+ }
+ if f.testContext.isFuzzing {
+ // Don't preserve subtest names while fuzzing. If fn calls T.Run,
+ // there will be a very large number of subtests with duplicate names,
+ // which will use a large amount of memory. The subtest names aren't
+ // useful since there's no way to re-run them deterministically.
+ f.testContext.match.clearSubNames()
+ }
+
+ // 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.
+ var pc [maxStackLen]uintptr
+ n := runtime.Callers(2, pc[:])
+ t := &T{
+ common: common{
+ barrier: make(chan bool),
+ signal: make(chan bool),
+ name: testName,
+ parent: &f.common,
+ level: f.level + 1,
+ creator: pc[:n],
+ chatty: f.chatty,
+ },
+ context: f.testContext,
+ }
+ if captureOut != nil {
+ // t.parent aliases f.common.
+ t.parent.w = captureOut
+ }
+ t.w = indenter{&t.common}
+ if t.chatty != nil {
+ t.chatty.Updatef(t.name, "=== RUN %s\n", t.name)
+ }
+ f.common.inFuzzFn, f.inFuzzFn = true, true
+ go tRunner(t, func(t *T) {
+ args := []reflect.Value{reflect.ValueOf(t)}
+ for _, v := range e.Values {
+ args = append(args, reflect.ValueOf(v))
+ }
+ // Before resetting the current coverage, defer the snapshot so that
+ // we make sure it is called right before the tRunner function
+ // exits, regardless of whether it was executed cleanly, panicked,
+ // or if the fuzzFn called t.Fatal.
+ if f.testContext.isFuzzing {
+ defer f.fuzzContext.deps.SnapshotCoverage()
+ f.fuzzContext.deps.ResetCoverage()
+ }
+ fn.Call(args)
+ })
+ <-t.signal
+ if t.chatty != nil && t.chatty.json {
+ t.chatty.Updatef(t.parent.name, "=== NAME %s\n", t.parent.name)
+ }
+ f.common.inFuzzFn, f.inFuzzFn = false, false
+ return !t.Failed()
+ }
+
+ switch f.fuzzContext.mode {
+ case fuzzCoordinator:
+ // Fuzzing is enabled, and this is the test process started by 'go test'.
+ // Act as the coordinator process, and coordinate workers to perform the
+ // actual fuzzing.
+ corpusTargetDir := filepath.Join(corpusDir, f.name)
+ cacheTargetDir := filepath.Join(*fuzzCacheDir, f.name)
+ err := f.fuzzContext.deps.CoordinateFuzzing(
+ fuzzDuration.d,
+ int64(fuzzDuration.n),
+ minimizeDuration.d,
+ int64(minimizeDuration.n),
+ *parallel,
+ f.corpus,
+ types,
+ corpusTargetDir,
+ cacheTargetDir)
+ if err != nil {
+ f.result = fuzzResult{Error: err}
+ f.Fail()
+ fmt.Fprintf(f.w, "%v\n", err)
+ if crashErr, ok := err.(fuzzCrashError); ok {
+ crashPath := crashErr.CrashPath()
+ fmt.Fprintf(f.w, "Failing input written to %s\n", crashPath)
+ testName := filepath.Base(crashPath)
+ fmt.Fprintf(f.w, "To re-run:\ngo test -run=%s/%s\n", f.name, testName)
+ }
+ }
+ // TODO(jayconrod,katiehockman): Aggregate statistics across workers
+ // and add to FuzzResult (ie. time taken, num iterations)
+
+ case fuzzWorker:
+ // Fuzzing is enabled, and this is a worker process. Follow instructions
+ // from the coordinator.
+ if err := f.fuzzContext.deps.RunFuzzWorker(func(e corpusEntry) error {
+ // Don't write to f.w (which points to Stdout) if running from a
+ // fuzz worker. This would become very verbose, particularly during
+ // minimization. Return the error instead, and let the caller deal
+ // with the output.
+ var buf strings.Builder
+ if ok := run(&buf, e); !ok {
+ return errors.New(buf.String())
+ }
+ return nil
+ }); err != nil {
+ // Internal errors are marked with f.Fail; user code may call this too, before F.Fuzz.
+ // The worker will exit with fuzzWorkerExitCode, indicating this is a failure
+ // (and 'go test' should exit non-zero) but a failing input should not be recorded.
+ f.Errorf("communicating with fuzzing coordinator: %v", err)
+ }
+
+ default:
+ // Fuzzing is not enabled, or will be done later. Only run the seed
+ // corpus now.
+ for _, e := range f.corpus {
+ name := fmt.Sprintf("%s/%s", f.name, filepath.Base(e.Path))
+ if _, ok, _ := f.testContext.match.fullName(nil, name); ok {
+ run(f.w, e)
+ }
+ }
+ }
+}
+
+func (f *F) report() {
+ if *isFuzzWorker || f.parent == nil {
+ return
+ }
+ dstr := fmtDuration(f.duration)
+ format := "--- %s: %s (%s)\n"
+ if f.Failed() {
+ f.flushToParent(f.name, format, "FAIL", f.name, dstr)
+ } else if f.chatty != nil {
+ if f.Skipped() {
+ f.flushToParent(f.name, format, "SKIP", f.name, dstr)
+ } else {
+ f.flushToParent(f.name, format, "PASS", f.name, dstr)
+ }
+ }
+}
+
+// fuzzResult contains the results of a fuzz run.
+type fuzzResult struct {
+ N int // The number of iterations.
+ T time.Duration // The total time taken.
+ Error error // Error is the error from the failing input
+}
+
+func (r fuzzResult) String() string {
+ if r.Error == nil {
+ return ""
+ }
+ return r.Error.Error()
+}
+
+// fuzzCrashError is satisfied by a failing input detected while fuzzing.
+// These errors are written to the seed corpus and can be re-run with 'go test'.
+// Errors within the fuzzing framework (like I/O errors between coordinator
+// and worker processes) don't satisfy this interface.
+type fuzzCrashError interface {
+ error
+ Unwrap() error
+
+ // CrashPath returns the path of the subtest that corresponds to the saved
+ // crash input file in the seed corpus. The test can be re-run with go test
+ // -run=$test/$name $test is the fuzz test name, and $name is the
+ // filepath.Base of the string returned here.
+ CrashPath() string
+}
+
+// fuzzContext holds fields common to all fuzz tests.
+type fuzzContext struct {
+ deps testDeps
+ mode fuzzMode
+}
+
+type fuzzMode uint8
+
+const (
+ seedCorpusOnly fuzzMode = iota
+ fuzzCoordinator
+ fuzzWorker
+)
+
+// runFuzzTests runs the fuzz tests matching the pattern for -run. This will
+// only run the (*F).Fuzz function for each seed corpus without using the
+// fuzzing engine to generate or mutate inputs.
+func runFuzzTests(deps testDeps, fuzzTests []InternalFuzzTarget, deadline time.Time) (ran, ok bool) {
+ ok = true
+ if len(fuzzTests) == 0 || *isFuzzWorker {
+ return ran, ok
+ }
+ m := newMatcher(deps.MatchString, *match, "-test.run", *skip)
+ var mFuzz *matcher
+ if *matchFuzz != "" {
+ mFuzz = newMatcher(deps.MatchString, *matchFuzz, "-test.fuzz", *skip)
+ }
+
+ for _, procs := range cpuList {
+ runtime.GOMAXPROCS(procs)
+ for i := uint(0); i < *count; i++ {
+ if shouldFailFast() {
+ break
+ }
+
+ tctx := newTestContext(*parallel, m)
+ tctx.deadline = deadline
+ fctx := &fuzzContext{deps: deps, mode: seedCorpusOnly}
+ root := common{w: os.Stdout} // gather output in one place
+ if Verbose() {
+ root.chatty = newChattyPrinter(root.w)
+ }
+ for _, ft := range fuzzTests {
+ if shouldFailFast() {
+ break
+ }
+ testName, matched, _ := tctx.match.fullName(nil, ft.Name)
+ if !matched {
+ continue
+ }
+ if mFuzz != nil {
+ if _, fuzzMatched, _ := mFuzz.fullName(nil, ft.Name); fuzzMatched {
+ // If this will be fuzzed, then don't run the seed corpus
+ // right now. That will happen later.
+ continue
+ }
+ }
+ f := &F{
+ common: common{
+ signal: make(chan bool),
+ barrier: make(chan bool),
+ name: testName,
+ parent: &root,
+ level: root.level + 1,
+ chatty: root.chatty,
+ },
+ testContext: tctx,
+ fuzzContext: fctx,
+ }
+ f.w = indenter{&f.common}
+ if f.chatty != nil {
+ f.chatty.Updatef(f.name, "=== RUN %s\n", f.name)
+ }
+ go fRunner(f, ft.Fn)
+ <-f.signal
+ if f.chatty != nil && f.chatty.json {
+ f.chatty.Updatef(f.parent.name, "=== NAME %s\n", f.parent.name)
+ }
+ ok = ok && !f.Failed()
+ ran = ran || f.ran
+ }
+ if !ran {
+ // There were no tests to run on this iteration.
+ // This won't change, so no reason to keep trying.
+ break
+ }
+ }
+ }
+
+ return ran, ok
+}
+
+// runFuzzing runs the fuzz test matching the pattern for -fuzz. Only one such
+// fuzz test must match. This will run the fuzzing engine to generate and
+// mutate new inputs against the fuzz target.
+//
+// If fuzzing is disabled (-test.fuzz is not set), runFuzzing
+// returns immediately.
+func runFuzzing(deps testDeps, fuzzTests []InternalFuzzTarget) (ok bool) {
+ if len(fuzzTests) == 0 || *matchFuzz == "" {
+ return true
+ }
+ m := newMatcher(deps.MatchString, *matchFuzz, "-test.fuzz", *skip)
+ tctx := newTestContext(1, m)
+ tctx.isFuzzing = true
+ fctx := &fuzzContext{
+ deps: deps,
+ }
+ root := common{w: os.Stdout}
+ if *isFuzzWorker {
+ root.w = io.Discard
+ fctx.mode = fuzzWorker
+ } else {
+ fctx.mode = fuzzCoordinator
+ }
+ if Verbose() && !*isFuzzWorker {
+ root.chatty = newChattyPrinter(root.w)
+ }
+ var fuzzTest *InternalFuzzTarget
+ var testName string
+ var matched []string
+ for i := range fuzzTests {
+ name, ok, _ := tctx.match.fullName(nil, fuzzTests[i].Name)
+ if !ok {
+ continue
+ }
+ matched = append(matched, name)
+ fuzzTest = &fuzzTests[i]
+ testName = name
+ }
+ if len(matched) == 0 {
+ fmt.Fprintln(os.Stderr, "testing: warning: no fuzz tests to fuzz")
+ return true
+ }
+ if len(matched) > 1 {
+ fmt.Fprintf(os.Stderr, "testing: will not fuzz, -fuzz matches more than one fuzz test: %v\n", matched)
+ return false
+ }
+
+ f := &F{
+ common: common{
+ signal: make(chan bool),
+ barrier: nil, // T.Parallel has no effect when fuzzing.
+ name: testName,
+ parent: &root,
+ level: root.level + 1,
+ chatty: root.chatty,
+ },
+ fuzzContext: fctx,
+ testContext: tctx,
+ }
+ f.w = indenter{&f.common}
+ if f.chatty != nil {
+ f.chatty.Updatef(f.name, "=== RUN %s\n", f.name)
+ }
+ go fRunner(f, fuzzTest.Fn)
+ <-f.signal
+ if f.chatty != nil {
+ f.chatty.Updatef(f.parent.name, "=== NAME %s\n", f.parent.name)
+ }
+ return !f.failed
+}
+
+// fRunner wraps a call to a fuzz test and ensures that cleanup functions are
+// called and status flags are set. fRunner should be called in its own
+// goroutine. To wait for its completion, receive from f.signal.
+//
+// fRunner is analogous to tRunner, which wraps subtests started with T.Run.
+// Unit tests and fuzz tests work a little differently, so for now, these
+// functions aren't consolidated. In particular, because there are no F.Run and
+// F.Parallel methods, i.e., no fuzz sub-tests or parallel fuzz tests, a few
+// simplifications are made. We also require that F.Fuzz, F.Skip, or F.Fail is
+// called.
+func fRunner(f *F, fn func(*F)) {
+ // When this goroutine is done, either because runtime.Goexit was called, a
+ // panic started, or fn returned normally, record the duration and send
+ // t.signal, indicating the fuzz test is done.
+ defer func() {
+ // Detect whether the fuzz test panicked or called runtime.Goexit
+ // without calling F.Fuzz, F.Fail, or F.Skip. If it did, panic (possibly
+ // replacing a nil panic value). Nothing should recover after fRunner
+ // unwinds, so this should crash the process and print stack.
+ // Unfortunately, recovering here adds stack frames, but the location of
+ // the original panic should still be
+ // clear.
+ if f.Failed() {
+ numFailed.Add(1)
+ }
+ err := recover()
+ if err == nil {
+ f.mu.RLock()
+ fuzzNotCalled := !f.fuzzCalled && !f.skipped && !f.failed
+ if !f.finished && !f.skipped && !f.failed {
+ err = errNilPanicOrGoexit
+ }
+ f.mu.RUnlock()
+ if fuzzNotCalled && err == nil {
+ f.Error("returned without calling F.Fuzz, F.Fail, or F.Skip")
+ }
+ }
+
+ // Use a deferred call to ensure that we report that the test is
+ // complete even if a cleanup function calls F.FailNow. See issue 41355.
+ didPanic := false
+ defer func() {
+ if !didPanic {
+ // Only report that the test is complete if it doesn't panic,
+ // as otherwise the test binary can exit before the panic is
+ // reported to the user. See issue 41479.
+ f.signal <- true
+ }
+ }()
+
+ // If we recovered a panic or inappropriate runtime.Goexit, fail the test,
+ // flush the output log up to the root, then panic.
+ doPanic := func(err any) {
+ f.Fail()
+ if r := f.runCleanup(recoverAndReturnPanic); r != nil {
+ f.Logf("cleanup panicked with %v", r)
+ }
+ for root := &f.common; root.parent != nil; root = root.parent {
+ root.mu.Lock()
+ root.duration += time.Since(root.start)
+ d := root.duration
+ root.mu.Unlock()
+ root.flushToParent(root.name, "--- FAIL: %s (%s)\n", root.name, fmtDuration(d))
+ }
+ didPanic = true
+ panic(err)
+ }
+ if err != nil {
+ doPanic(err)
+ }
+
+ // No panic or inappropriate Goexit.
+ f.duration += time.Since(f.start)
+
+ if len(f.sub) > 0 {
+ // Unblock inputs that called T.Parallel while running the seed corpus.
+ // This only affects fuzz tests run as normal tests.
+ // While fuzzing, T.Parallel has no effect, so f.sub is empty, and this
+ // branch is not taken. f.barrier is nil in that case.
+ f.testContext.release()
+ close(f.barrier)
+ // Wait for the subtests to complete.
+ for _, sub := range f.sub {
+ <-sub.signal
+ }
+ cleanupStart := time.Now()
+ err := f.runCleanup(recoverAndReturnPanic)
+ f.duration += time.Since(cleanupStart)
+ if err != nil {
+ doPanic(err)
+ }
+ }
+
+ // Report after all subtests have finished.
+ f.report()
+ f.done = true
+ f.setRan()
+ }()
+ defer func() {
+ if len(f.sub) == 0 {
+ f.runCleanup(normalPanic)
+ }
+ }()
+
+ f.start = time.Now()
+ fn(f)
+
+ // Code beyond this point will not be executed when FailNow or SkipNow
+ // is invoked.
+ f.mu.Lock()
+ f.finished = true
+ f.mu.Unlock()
+}