]> Cypherpunks.ru repositories - gostls13.git/commitdiff
[dev.fuzz] all: merge master (dd7ba3b) into dev.fuzz
authorKatie Hockman <katie@golang.org>
Wed, 2 Jun 2021 22:37:38 +0000 (18:37 -0400)
committerKatie Hockman <katie@golang.org>
Wed, 2 Jun 2021 22:39:15 +0000 (22:39 +0000)
Merge List:

+ 2021-06-02 dd7ba3ba2c net: don't rely on system hosts in TestCVE202133195
+ 2021-06-02 4f572d7076 io/fs: minor corrections to Sub docs
+ 2021-06-02 e11d14225c doc/go1.17: remove runtime section
+ 2021-06-02 6e189afd3e doc/go1.17: mention SYS_WAIT6/WEXITED on NetBSD
+ 2021-06-02 ff9f5fb859 cmd/link: recognize clang linker error in testCGOLTO
+ 2021-06-02 1c6a2ea2ea doc/go1.17: document time changes for Go1.17
+ 2021-06-02 d743e67e06 doc/go1.17: document flag changes for Go 1.17
+ 2021-06-02 dc8f87b749 runtime/internal/sys: generate //go:build lines in gengoos.go
+ 2021-06-02 84c0e5d47f cmd/link: move issue 43830 tests out of TestScript
+ 2021-06-02 cae68700cc runtime: fix formatting
+ 2021-06-01 567ee865f6 cmd/go: add declaration to cgo_lto_issue43830 test
+ 2021-06-01 24e9707cbf cmd/link, cmd/cgo: support -flto in CFLAGS
+ 2021-06-01 272552275f A+C: update name
+ 2021-06-01 2bec019fb5 doc/go1.17: add release notes for register ABI
+ 2021-06-01 2e59cc5fb4 cmd/go: add [-src] to documentation
+ 2021-06-01 0b80cf1136 cmd/go: make 'go get' save sums for incidentally updated modules
+ 2021-05-30 3b770f2ccb go/types: don't declare 'comparable' when typeparams are disabled
+ 2021-05-30 1607c28172 go/types: unexport the GoVersion configuration option for Go 1.17
+ 2021-05-29 79bda65041 doc/go1.17: mention time.Layout
+ 2021-05-29 f6cc392d1d doc/go1.17: document text/template/parse.SkipFuncCheck
+ 2021-05-28 1419ca7cea doc/go1.17: mention new definitions of MSG_CMSG_CLOEXEC
+ 2021-05-28 6624771c83 doc/go1.17: mention testing.[TB].Setenv methods
+ 2021-05-28 bbda923592 doc/go1.17: mention new Windows SysProcAttr fields
+ 2021-05-28 6f58088bd8 doc/go1.17: document new go/build/BuildContext.ToolTags field
+ 2021-05-28 c295107708 doc/go1.17: mention new encoding/csv/Reader.FieldPos method
+ 2021-05-28 ccd9784edf doc/go1.17: document new debug/elf constant
+ 2021-05-28 3de3440fb9 go/ast: remove FuncDecl.IsMethod for Go 1.17
+ 2021-05-27 639acdc833 doc/go1.17: clarify that compress/lzw Reader and Writer types are new
+ 2021-05-27 193d514131 net/http: correct Client.Do doc about context cancelation
+ 2021-05-27 ab2ef4aaa7 doc/go1.17: document reflect changes
+ 2021-05-27 0ece95a0fe cmd/go: don't let 'go mod download' save sums for inconsistent requirements
+ 2021-05-27 cdcd02842d net: verify results from Lookup* are valid domain names
+ 2021-05-27 8bf5bf5173 cmd/compile: improve debug locations for partially live in-params
+ 2021-05-27 56af34f875 cmd/compile: place reg spills after OpArg{Int,Float}Reg ops
+ 2021-05-27 db66e9e15d cmd/link: accept Windows line-ending in TestTrampolineCgo
+ 2021-05-27 6b8c94b6c5 go/types: guard against check==nil in newNamed
+ 2021-05-27 fca7b8f3e6 Revert "net: verify results from Lookup* are valid domain names"
+ 2021-05-27 950fa11c4c net/http/httputil: always remove hop-by-hop headers
+ 2021-05-27 9bc52686da cmd/go,cmd/link: do not check for staleness in most tests
+ 2021-05-27 6ff0ae2aa4 crypto/elliptic: fix typo in p521Point type name
+ 2021-05-26 3075ffc93e os: deflake TestFdReadRace
+ 2021-05-26 a62c08734f src/os: revert accidentally submitted change
+ 2021-05-26 1d5298d46a doc/go1.17: document net/... changes
+ 2021-05-26 0fbecece98 doc/go1.17: document syscall changes
+ 2021-05-26 02beecb397 mime: document use of the Shared MIME-Info Database
+ 2021-05-26 a92460fd2f doc/go1.17: add release notes for runtime/metrics package
+ 2021-05-26 55aefbb268 doc/go1.17: mention enabling frame pointer on all ARM64
+ 2021-05-26 39da9ae513 go/types: ensure that Named.check is nilled out once it is expanded
+ 2021-05-26 bfd7798a6c runtime,cmd/link/internal/ld: fix typos
+ 2021-05-26 e4615ad74d math/big: move division into natdiv.go
+ 2021-05-26 d050238bb6 doc/go1.17: fix formatting for time changes
+ 2021-05-25 74242baa41 archive/zip: only preallocate File slice if reasonably sized
+ 2021-05-25 f22ec51deb doc: add Go 1.17 release note about inlining functions with closures
+ 2021-05-25 8b462d7567 cmd/go: add a -compat flag to 'go mod tidy'
+ 2021-05-24 c89f1224a5 net: verify results from Lookup* are valid domain names
+ 2021-05-24 08a8fa9c47 misc/wasm: ensure correct stack pointer in catch clauses
+ 2021-05-24 32b73ae180 cmd/go: align checks of module path during initialization.
+ 2021-05-24 15d9d4a009 cmd/go: add tests illustrating what happens when Go 1.16 is used in a Go 1.17 main module
+ 2021-05-24 873401df5b cmd/compile: ensure equal functions don't do unaligned loads
+ 2021-05-24 b83610699a cmd/compile: record regabi status in DW_AT_producer
+ 2021-05-24 a22e317220 cmd/compile: always include underlying type for map types
+ 2021-05-24 4356e7e85f runtime: account for spill slots in Windows callback compilation
+ 2021-05-24 52d7033ff6 cmd/go/internal/modload: set the default GoVersion in a single location
+ 2021-05-24 05819bc104 cmd/go/internal/modcmd: factor out a type for flags whose arguments are Go versions
+ 2021-05-22 cca23a7373 cmd/compile: revert CL/316890
+ 2021-05-21 f87194cbd7 doc/go1.17: document changes to net/http package
+ 2021-05-21 217f5dd496 doc: document additional atomic.Value methods
+ 2021-05-21 3c656445f1 cmd/go: in TestScript/mod_replace, download an explicit module path
+ 2021-05-21 76b2d6afed os: document that StartProcess puts files into blocking mode
+ 2021-05-21 e4d7525c3e cmd/dist: display first class port status in json output
+ 2021-05-21 4fb10b2118 cmd/go: in 'go mod download' without args, don't save module zip sums
+ 2021-05-21 4fda54ce3f doc/go1.17: document database/sql changes for Go 1.17
+ 2021-05-21 8876b9bd6a doc/go1.17: document io/fs changes for Go 1.17
+ 2021-05-21 5fee772c87 doc/go1.17: document archive/zip changes for Go 1.17
+ 2021-05-21 3148694f60 cmd/go: remove warning from module deprecation notice printing
+ 2021-05-21 7e63c8b765 runtime: wait for Go runtime to initialize in Windows signal test
+ 2021-05-21 831573cd21 io/fs: added an example for io/fs.WalkDir
+ 2021-05-20 baa934d26d cmd: go get golang.org/x/tools/analysis@49064d23 && go mod vendor
+ 2021-05-20 7c692cc7ea doc/go1.17: document changes to os package
+ 2021-05-20 ce9a3b79d5 crypto/x509: add new FreeBSD 12.2+ trusted certificate folder
+ 2021-05-20 f8be906d74 test: re-enable test on riscv64 now that it supports external linking
+ 2021-05-20 def5360541 doc/go1.17: add release notes for OpenBSD ports
+ 2021-05-20 ef1f52cc38 doc/go1.17: add release note for windows/arm64 port
+ 2021-05-20 bb7495a46d doc/go1.17: document new math constants
+ 2021-05-20 f07e4dae3c syscall: document NewCallback and NewCallbackCDecl limitations
+ 2021-05-20 a8d85918b6 misc/cgo/testplugin: skip TestIssue25756pie on darwin/arm64 builder
+ 2021-05-19 6c1c055d1e cmd/internal/moddeps: use filepath.SkipDir only on directories
+ 2021-05-19 658b5e66ec net: return nil UDPAddr from ReadFromUDP
+ 2021-05-19 15a374d5c1 test: check portable error message on issue46234.go
+ 2021-05-18 eeadce2d87 go/build/constraint: fix parsing of "// +build" (with no args)
+ 2021-05-18 6d2ef2ef2a cmd/compile: don't emit inltree for closure within body of inlined func
+ 2021-05-18 048cb4ceee crypto/x509: remove duplicate import
+ 2021-05-18 690a8c3fb1 make.bash: fix misuse of continue
+ 2021-05-18 8b0901fd32 doc/go1.17: fix typo "avoding" -> "avoiding"
+ 2021-05-18 5e191f8f48 time: rewrite the documentation for layout strings
+ 2021-05-17 bfe3573d58 go/token: correct the interval notation used in some panic messages
+ 2021-05-17 a2c07a9a1a all: update golang.org/x/net to latest
+ 2021-05-17 b9b2bed893 syscall: some containers may fail syscall.TestSetuidEtc
+ 2021-05-17 b1aff42900 cmd/go: don't print 'go get' deprecation notices in the main module
+ 2021-05-17 bade680867 runtime/cgo: fix crosscall2 on ppc64x
+ 2021-05-15 ce92a2023c cmd/go: error out of 'go mod tidy' if the go version is newer than supported
+ 2021-05-14 02699f810a runtime: mark osyield nosplit on OpenBSD
+ 2021-05-14 3d324f127d net/http: prevent infinite wait during TestMissingStatusNoPanic
+ 2021-05-14 0eb38f2b16 cmd/go/internal/load: override Package.Root in module mode
+ 2021-05-14 a938e52986 cmd/go: fix a portability issue in the cd script command

Change-Id: I72e621368c4435396eb727d40287d1e318505308

54 files changed:
api/except.txt
api/next.txt
codereview.cfg
src/cmd/go/alldocs.go
src/cmd/go/internal/cache/cache.go
src/cmd/go/internal/cfg/cfg.go
src/cmd/go/internal/clean/clean.go
src/cmd/go/internal/load/flag.go
src/cmd/go/internal/load/pkg.go
src/cmd/go/internal/load/test.go
src/cmd/go/internal/test/flagdefs.go
src/cmd/go/internal/test/flagdefs_test.go
src/cmd/go/internal/test/genflags.go
src/cmd/go/internal/test/test.go
src/cmd/go/internal/test/testflag.go
src/cmd/go/internal/work/init.go
src/cmd/go/main.go
src/cmd/go/testdata/script/test_fuzz.txt [new file with mode: 0644]
src/cmd/go/testdata/script/test_fuzz_cache.txt [new file with mode: 0644]
src/cmd/go/testdata/script/test_fuzz_chatty.txt [new file with mode: 0644]
src/cmd/go/testdata/script/test_fuzz_cleanup.txt [new file with mode: 0644]
src/cmd/go/testdata/script/test_fuzz_fuzztime.txt [new file with mode: 0644]
src/cmd/go/testdata/script/test_fuzz_io_error.txt [new file with mode: 0644]
src/cmd/go/testdata/script/test_fuzz_match.txt [new file with mode: 0644]
src/cmd/go/testdata/script/test_fuzz_minimize.txt [new file with mode: 0644]
src/cmd/go/testdata/script/test_fuzz_mutate_crash.txt [new file with mode: 0644]
src/cmd/go/testdata/script/test_fuzz_mutate_fail.txt [new file with mode: 0644]
src/cmd/go/testdata/script/test_fuzz_mutator.txt [new file with mode: 0644]
src/cmd/go/testdata/script/test_fuzz_parallel.txt [new file with mode: 0644]
src/cmd/go/testdata/script/test_fuzz_tag.txt [new file with mode: 0644]
src/cmd/link/internal/ld/data.go
src/go/build/deps_test.go
src/go/doc/example.go
src/go/doc/example_test.go
src/internal/fuzz/coverage.go [new file with mode: 0644]
src/internal/fuzz/encoding.go [new file with mode: 0644]
src/internal/fuzz/encoding_test.go [new file with mode: 0644]
src/internal/fuzz/fuzz.go [new file with mode: 0644]
src/internal/fuzz/mem.go [new file with mode: 0644]
src/internal/fuzz/minimize.go [new file with mode: 0644]
src/internal/fuzz/minimize_test.go [new file with mode: 0644]
src/internal/fuzz/mutator.go [new file with mode: 0644]
src/internal/fuzz/mutator_test.go [new file with mode: 0644]
src/internal/fuzz/pcg.go [new file with mode: 0644]
src/internal/fuzz/sys_posix.go [new file with mode: 0644]
src/internal/fuzz/sys_unimplemented.go [new file with mode: 0644]
src/internal/fuzz/sys_windows.go [new file with mode: 0644]
src/internal/fuzz/trace.go [new file with mode: 0644]
src/internal/fuzz/worker.go [new file with mode: 0644]
src/testing/benchmark.go
src/testing/fuzz.go [new file with mode: 0644]
src/testing/internal/testdeps/deps.go
src/testing/sub_test.go
src/testing/testing.go

index 14fe7785fa54d3bc2a484c992113edf1d13c2d1f..b9972c121cfa4addbfdbfe82644b567745da846e 100644 (file)
@@ -492,6 +492,7 @@ pkg syscall (windows-amd64), type CertRevocationInfo struct, OidSpecificInfo uin
 pkg syscall (windows-amd64), type CertSimpleChain struct, TrustListInfo uintptr
 pkg syscall (windows-amd64), type RawSockaddrAny struct, Pad [96]int8
 pkg testing, func MainStart(func(string, string) (bool, error), []InternalTest, []InternalBenchmark, []InternalExample) *M
+pkg testing, func MainStart(testDeps, []InternalTest, []InternalBenchmark, []InternalExample) *M
 pkg testing, func RegisterCover(Cover)
 pkg text/scanner, const GoTokens = 1012
 pkg text/template/parse, type DotNode bool
index 9e996005c62d258c58ffcb0ea1172091e1fff5db..564f672c690b9ed12e64623280b835ae34fab0c8 100644 (file)
@@ -89,7 +89,40 @@ pkg syscall (windows-386), type SysProcAttr struct, ParentProcess Handle
 pkg syscall (windows-amd64), type SysProcAttr struct, AdditionalInheritedHandles []Handle
 pkg syscall (windows-amd64), type SysProcAttr struct, ParentProcess Handle
 pkg testing, method (*B) Setenv(string, string)
+pkg testing, func Fuzz(func(*F)) FuzzResult
+pkg testing, func MainStart(testDeps, []InternalTest, []InternalBenchmark, []InternalFuzzTarget, []InternalExample) *M
+pkg testing, func RunFuzzTargets(func(string, string) (bool, error), []InternalFuzzTarget) bool
+pkg testing, func RunFuzzing(func(string, string) (bool, error), []InternalFuzzTarget) bool
+pkg testing, method (*F) Add(...interface{})
+pkg testing, method (*F) Cleanup(func())
+pkg testing, method (*F) Error(...interface{})
+pkg testing, method (*F) Errorf(string, ...interface{})
+pkg testing, method (*F) Fail()
+pkg testing, method (*F) FailNow()
+pkg testing, method (*F) Failed() bool
+pkg testing, method (*F) Fatal(...interface{})
+pkg testing, method (*F) Fatalf(string, ...interface{})
+pkg testing, method (*F) Fuzz(interface{})
+pkg testing, method (*F) Helper()
+pkg testing, method (*F) Log(...interface{})
+pkg testing, method (*F) Logf(string, ...interface{})
+pkg testing, method (*F) Name() string
+pkg testing, method (*F) Skip(...interface{})
+pkg testing, method (*F) SkipNow()
+pkg testing, method (*F) Skipf(string, ...interface{})
+pkg testing, method (*F) Skipped() bool
+pkg testing, method (*F) TempDir() string
 pkg testing, method (*T) Setenv(string, string)
+pkg testing, method (FuzzResult) String() string
+pkg testing, type F struct
+pkg testing, type FuzzResult struct
+pkg testing, type FuzzResult struct, Crasher entry
+pkg testing, type FuzzResult struct, Error error
+pkg testing, type FuzzResult struct, N int
+pkg testing, type FuzzResult struct, T time.Duration
+pkg testing, type InternalFuzzTarget struct
+pkg testing, type InternalFuzzTarget struct, Fn func(*F)
+pkg testing, type InternalFuzzTarget struct, Name string
 pkg text/template/parse, const SkipFuncCheck = 2
 pkg text/template/parse, const SkipFuncCheck Mode
 pkg time, func UnixMicro(int64) Time
@@ -97,3 +130,4 @@ pkg time, func UnixMilli(int64) Time
 pkg time, method (*Time) IsDST() bool
 pkg time, method (Time) UnixMicro() int64
 pkg time, method (Time) UnixMilli() int64
+>>>>>>> origin/master
index 77a74f108eae362626abcdbfcf41ddb9a33b02cd..bed9bcf7eea1b3bb6fe9b1ac0d0110bb3c10a9ac 100644 (file)
@@ -1 +1,2 @@
-branch: master
+branch: dev.fuzz
+parent-branch: master
\ No newline at end of file
index ab61017c4eb5598acfd00af405267027a5de5f2f..0655153969ee0883d2722d89d5b1e359caa250d9 100644 (file)
@@ -53,6 +53,7 @@
 //     private         configuration for downloading non-public code
 //     testflag        testing flags
 //     testfunc        testing functions
+//     fuzz            fuzzing
 //     vcs             controlling version control with GOVCS
 //
 // Use "go help <topic>" for more information about that topic.
 // download cache, including unpacked source code of versioned
 // dependencies.
 //
+// The -fuzzcache flag causes clean to remove values used for fuzz testing.
+//
 // For more about build flags, see 'go help build'.
 //
 // For more about specifying packages, see 'go help packages'.
 //
 // 'Go test' recompiles each package along with any files with names matching
 // the file pattern "*_test.go".
-// These additional files can contain test functions, benchmark functions, and
-// example functions. See 'go help testfunc' for more.
+// These additional files can contain test functions, benchmark functions, fuzz
+// targets and example functions. See 'go help testfunc' for more.
 // Each listed package causes the execution of a separate test binary.
 // Files whose names begin with "_" (including "_test.go") or "." are ignored.
 //
 // so a successful package test result will be cached and reused
 // regardless of -timeout setting.
 //
+// Run 'go help fuzz' for details around how the go command handles fuzz targets.
+//
 // In addition to the build flags, the flags handled by 'go test' itself are:
 //
 //     -args
 //         (for example, -benchtime 100x).
 //
 //     -count n
-//         Run each test and benchmark n times (default 1).
+//         Run each test, benchmark, and fuzz targets' seed corpora n times
+//         (default 1).
 //         If -cpu is set, run n times for each GOMAXPROCS value.
 //         Examples are always run once.
 //
 //         Sets -cover.
 //
 //     -cpu 1,2,4
-//         Specify a list of GOMAXPROCS values for which the tests or
-//         benchmarks should be executed. The default is the current value
+//         Specify a list of GOMAXPROCS values for which the tests, benchmarks or
+//         fuzz targets should be executed. The default is the current value
 //         of GOMAXPROCS.
 //
 //     -failfast
 //         Do not start new tests after the first test failure.
 //
+//     -fuzz name
+//         Run the fuzz target with the given regexp. Must match exactly one fuzz
+//         target. This is an experimental feature.
+//
+//     -fuzztime t
+//         Run enough iterations of the fuzz test to take t, specified as a
+//         time.Duration (for example, -fuzztime 1h30s). The default is to run
+//         forever.
+//         The special syntax Nx means to run the fuzz test N times
+//         (for example, -fuzztime 100x).
+//
+//     -keepfuzzing
+//         Keep running the fuzz target if a crasher is found.
+//
 //     -list regexp
-//         List tests, benchmarks, or examples matching the regular expression.
-//         No tests, benchmarks or examples will be run. This will only
-//         list top-level tests. No subtest or subbenchmarks will be shown.
+//         List tests, benchmarks, fuzz targets, or examples matching the regular
+//         expression. No tests, benchmarks, fuzz targets, or examples will be run.
+//         This will only list top-level tests. No subtest or subbenchmarks will be
+//         shown.
 //
 //     -parallel n
-//         Allow parallel execution of test functions that call t.Parallel.
+//         Allow parallel execution of test functions that call t.Parallel, and
+//         f.Fuzz functions that call t.Parallel when running the seed corpus.
 //         The value of this flag is the maximum number of tests to run
-//         simultaneously; by default, it is set to the value of GOMAXPROCS.
+//         simultaneously. While fuzzing, the value of this flag is the
+//         maximum number of workers to run the fuzz function simultaneously,
+//         regardless of whether t.Parallel has been called; by default, it is set
+//         to the value of GOMAXPROCS.
 //         Note that -parallel only applies within a single test binary.
 //         The 'go test' command may run tests for different packages
 //         in parallel as well, according to the setting of the -p flag
 //         (see 'go help build').
 //
 //     -run regexp
-//         Run only those tests and examples matching the regular expression.
-//         For tests, the regular expression is split by unbracketed slash (/)
-//         characters into a sequence of regular expressions, and each part
-//         of a test's identifier must match the corresponding element in
+//         Run only those tests, examples, and fuzz targets matching the regular
+//         expression. For tests, the regular expression is split by unbracketed
+//         slash (/) characters into a sequence of regular expressions, and each
+//         part of a test's identifier must match the corresponding element in
 //         the sequence, if any. Note that possible parents of matches are
 //         run too, so that -run=X/Y matches and runs and reports the result
 //         of all tests matching X, even those without sub-tests matching Y,
 //
 //     func BenchmarkXxx(b *testing.B) { ... }
 //
+// A fuzz target is one named FuzzXxx and should have the signature,
+//
+//     func FuzzXxx(f *testing.F) { ... }
+//
 // An example function is similar to a test function but, instead of using
 // *testing.T to report success or failure, prints output to os.Stdout.
 // If the last comment in the function starts with "Output:" then the output
 //
 // The entire test file is presented as the example when it contains a single
 // example function, at least one other function, type, variable, or constant
-// declaration, and no test or benchmark functions.
+// declaration, and no fuzz targets or test or benchmark functions.
 //
 // See the documentation of the testing package for more information.
 //
 //
+// Fuzzing
+//
+// By default, go test will build and run the fuzz targets using the target's seed
+// corpus only. Any generated corpora in $GOCACHE that were previously written by
+// the fuzzing engine will not be run by default.
+//
+// When -fuzz is set, the binary will be instrumented for coverage. After all
+// tests, examples, benchmark functions, and the seed corpora for all fuzz targets
+// have been run, go test will begin to fuzz the specified fuzz target.
+// Note that this feature is experimental.
+//
+// -run can be used for testing a single seed corpus entry for a fuzz target. The
+// regular expression value of -run can be in the form $target/$name, where $target
+// is the name of the fuzz target, and $name is the name of the file (ignoring file
+// extensions) to run. For example, -run=FuzzFoo/497b6f87.
+//
+// See https://golang.org/s/draft-fuzzing-design for more details.
+//
+//
 // Controlling version control with GOVCS
 //
 // The 'go get' command can run version control commands like git
index d592d7049786ce9d3fd5d15a67f2884327f149a0..596f22e8fc1fca11debc31466408ae5dc50bc34d 100644 (file)
@@ -533,3 +533,13 @@ func (c *Cache) copyFile(file io.ReadSeeker, out OutputID, size int64) error {
 
        return nil
 }
+
+// FuzzDir returns a subdirectory within the cache for storing fuzzing data.
+// The subdirectory may not exist.
+//
+// This directory is managed by the internal/fuzz package. Files in this
+// directory aren't removed by the 'go clean -cache' command or by Trim.
+// They may be removed with 'go clean -fuzzcache'.
+func (c *Cache) FuzzDir() string {
+       return filepath.Join(c.dir, "fuzz")
+}
index b47eb812b59ea54d6911fa687ba01fbf6469bcb6..21a56d6df67ef088cbbdae14b79b0618792f3f93 100644 (file)
@@ -58,6 +58,10 @@ var (
 
 func defaultContext() build.Context {
        ctxt := build.Default
+
+       // TODO(b/187972950): remove this tag before merging to master.
+       ctxt.BuildTags = []string{"gofuzzbeta"}
+
        ctxt.JoinPath = filepath.Join // back door to say "do not use go command"
 
        ctxt.GOROOT = findGOROOT()
index fd4cb205591105d0ad527e25e811cd62dfadf5a8..e0d3c9e0c618586a6bf5bcb6be33d0d6594dcd6e 100644 (file)
@@ -75,6 +75,8 @@ The -modcache flag causes clean to remove the entire module
 download cache, including unpacked source code of versioned
 dependencies.
 
+The -fuzzcache flag causes clean to remove values used for fuzz testing.
+
 For more about build flags, see 'go help build'.
 
 For more about specifying packages, see 'go help packages'.
@@ -85,6 +87,7 @@ var (
        cleanI         bool // clean -i flag
        cleanR         bool // clean -r flag
        cleanCache     bool // clean -cache flag
+       cleanFuzzcache bool // clean -fuzzcache flag
        cleanModcache  bool // clean -modcache flag
        cleanTestcache bool // clean -testcache flag
 )
@@ -96,6 +99,7 @@ func init() {
        CmdClean.Flag.BoolVar(&cleanI, "i", false, "")
        CmdClean.Flag.BoolVar(&cleanR, "r", false, "")
        CmdClean.Flag.BoolVar(&cleanCache, "cache", false, "")
+       CmdClean.Flag.BoolVar(&cleanFuzzcache, "fuzzcache", false, "")
        CmdClean.Flag.BoolVar(&cleanModcache, "modcache", false, "")
        CmdClean.Flag.BoolVar(&cleanTestcache, "testcache", false, "")
 
@@ -206,6 +210,18 @@ func runClean(ctx context.Context, cmd *base.Command, args []string) {
                        }
                }
        }
+
+       if cleanFuzzcache {
+               fuzzDir := cache.Default().FuzzDir()
+               if cfg.BuildN || cfg.BuildX {
+                       b.Showcmd("", "rm -rf %s", fuzzDir)
+               }
+               if !cfg.BuildN {
+                       if err := os.RemoveAll(fuzzDir); err != nil {
+                               base.Errorf("go clean -fuzzcache: %v", err)
+                       }
+               }
+       }
 }
 
 var cleaned = map[*load.Package]bool{}
index 440cb86134489a7e023ce516629fe9bccc00fe0c..274c0f23e27b1dd11e79202f0ff73705b1536440 100644 (file)
@@ -22,8 +22,9 @@ var (
 // that allows specifying different effective flags for different packages.
 // See 'go help build' for more details about per-package flags.
 type PerPackageFlag struct {
-       present bool
-       values  []ppfValue
+       present      bool
+       values       []ppfValue
+       seenPackages map[*Package]bool // the packages for which the flags have already been set
 }
 
 // A ppfValue is a single <pattern>=<flags> per-package flag value.
index 738904865e245d0aea2ba0805024a3d817c51ce9..26115ff6a431911dba10d4358e0e4b5bf7162126 100644 (file)
@@ -2629,10 +2629,20 @@ func (e *mainPackageError) ImportPath() string {
 
 func setToolFlags(pkgs ...*Package) {
        for _, p := range PackageList(pkgs) {
-               p.Internal.Asmflags = BuildAsmflags.For(p)
-               p.Internal.Gcflags = BuildGcflags.For(p)
-               p.Internal.Ldflags = BuildLdflags.For(p)
-               p.Internal.Gccgoflags = BuildGccgoflags.For(p)
+               appendFlags(p, &p.Internal.Asmflags, &BuildAsmflags)
+               appendFlags(p, &p.Internal.Gcflags, &BuildGcflags)
+               appendFlags(p, &p.Internal.Ldflags, &BuildLdflags)
+               appendFlags(p, &p.Internal.Gccgoflags, &BuildGccgoflags)
+       }
+}
+
+func appendFlags(p *Package, flags *[]string, packageFlag *PerPackageFlag) {
+       if !packageFlag.seenPackages[p] {
+               if packageFlag.seenPackages == nil {
+                       packageFlag.seenPackages = make(map[*Package]bool)
+               }
+               packageFlag.seenPackages[p] = true
+               *flags = append(*flags, packageFlag.For(p)...)
        }
 }
 
index 6baa1db14f063ef983502c4088d5a24c690f9709..a84e3e6a6fe41525941c26b03d9f0e61a0eeb6cc 100644 (file)
@@ -533,6 +533,7 @@ func formatTestmain(t *testFuncs) ([]byte, error) {
 type testFuncs struct {
        Tests       []testFunc
        Benchmarks  []testFunc
+       FuzzTargets []testFunc
        Examples    []testFunc
        TestMain    *testFunc
        Package     *Package
@@ -631,6 +632,13 @@ func (t *testFuncs) load(filename, pkg string, doImport, seen *bool) error {
                        }
                        t.Benchmarks = append(t.Benchmarks, testFunc{pkg, name, "", false})
                        *doImport, *seen = true, true
+               case isTest(name, "Fuzz"):
+                       err := checkTestFunc(n, "F")
+                       if err != nil {
+                               return err
+                       }
+                       t.FuzzTargets = append(t.FuzzTargets, testFunc{pkg, name, "", false})
+                       *doImport, *seen = true, true
                }
        }
        ex := doc.Examples(f)
@@ -694,6 +702,12 @@ var benchmarks = []testing.InternalBenchmark{
 {{end}}
 }
 
+var fuzzTargets = []testing.InternalFuzzTarget{
+{{range .FuzzTargets}}
+       {"{{.Name}}", {{.Package}}.{{.Name}}},
+{{end}}
+}
+
 var examples = []testing.InternalExample{
 {{range .Examples}}
        {"{{.Name}}", {{.Package}}.{{.Name}}, {{.Output | printf "%q"}}, {{.Unordered}}},
@@ -752,7 +766,7 @@ func main() {
                CoveredPackages: {{printf "%q" .Covered}},
        })
 {{end}}
-       m := testing.MainStart(testdeps.TestDeps{}, tests, benchmarks, examples)
+       m := testing.MainStart(testdeps.TestDeps{}, tests, benchmarks, fuzzTargets, examples)
 {{with .TestMain}}
        {{.Package}}.{{.Name}}(m)
        os.Exit(int(reflect.ValueOf(m).Elem().FieldByName("exitCode").Int()))
index 37ac81c26782ae226be515bc2f65a42700de978d..3148074d57441513e74db77b52216b1dd406aa14 100644 (file)
@@ -19,6 +19,9 @@ var passFlagToTest = map[string]bool{
        "cpu":                  true,
        "cpuprofile":           true,
        "failfast":             true,
+       "fuzz":                 true,
+       "fuzzminimizetime":     true,
+       "fuzztime":             true,
        "list":                 true,
        "memprofile":           true,
        "memprofilerate":       true,
index ab5440b3801f15af1124d3ce1738035a894926a0..f238fc7d335e639a55e3dd466d927a9e87ed27b2 100644 (file)
@@ -17,7 +17,7 @@ func TestPassFlagToTestIncludesAllTestFlags(t *testing.T) {
                }
                name := strings.TrimPrefix(f.Name, "test.")
                switch name {
-               case "testlogfile", "paniconexit0":
+               case "testlogfile", "paniconexit0", "fuzzcachedir", "fuzzworker":
                        // These are internal flags.
                default:
                        if !passFlagToTest[name] {
index 9277de7fee839e216f8c70b31b8720d839f16c5e..645aae68b17d7ec25d67de3b8991eca79e02d8eb 100644 (file)
@@ -64,7 +64,7 @@ func testFlags() []string {
                name := strings.TrimPrefix(f.Name, "test.")
 
                switch name {
-               case "testlogfile", "paniconexit0":
+               case "testlogfile", "paniconexit0", "fuzzcachedir", "fuzzworker":
                        // These flags are only for use by cmd/go.
                default:
                        names = append(names, name)
index 59ea1ef5445178f052006dd37af2d8b0b209b9ef..012a75123b51be16994fbf10c5d9ae86375cf6a5 100644 (file)
@@ -60,8 +60,8 @@ followed by detailed output for each failed package.
 
 'Go test' recompiles each package along with any files with names matching
 the file pattern "*_test.go".
-These additional files can contain test functions, benchmark functions, and
-example functions. See 'go help testfunc' for more.
+These additional files can contain test functions, benchmark functions, fuzz
+targets and example functions. See 'go help testfunc' for more.
 Each listed package causes the execution of a separate test binary.
 Files whose names begin with "_" (including "_test.go") or "." are ignored.
 
@@ -130,6 +130,8 @@ A cached test result is treated as executing in no time at all,
 so a successful package test result will be cached and reused
 regardless of -timeout setting.
 
+Run 'go help fuzz' for details around how the go command handles fuzz targets.
+
 In addition to the build flags, the flags handled by 'go test' itself are:
 
        -args
@@ -206,7 +208,8 @@ control the execution of any test:
            (for example, -benchtime 100x).
 
        -count n
-           Run each test and benchmark n times (default 1).
+           Run each test, benchmark, and fuzz targets' seed corpora n times
+           (default 1).
            If -cpu is set, run n times for each GOMAXPROCS value.
            Examples are always run once.
 
@@ -235,32 +238,51 @@ control the execution of any test:
            Sets -cover.
 
        -cpu 1,2,4
-           Specify a list of GOMAXPROCS values for which the tests or
-           benchmarks should be executed. The default is the current value
+           Specify a list of GOMAXPROCS values for which the tests, benchmarks or
+           fuzz targets should be executed. The default is the current value
            of GOMAXPROCS.
 
        -failfast
            Do not start new tests after the first test failure.
 
+       -fuzz name
+           Run the fuzz target with the given regexp. Must match exactly one fuzz
+           target. This is an experimental feature.
+
+       -fuzztime t
+           Run enough iterations of the fuzz test to take t, specified as a
+           time.Duration (for example, -fuzztime 1h30s). The default is to run
+           forever.
+           The special syntax Nx means to run the fuzz test N times
+           (for example, -fuzztime 100x).
+
+       -keepfuzzing
+           Keep running the fuzz target if a crasher is found.
+
        -list regexp
-           List tests, benchmarks, or examples matching the regular expression.
-           No tests, benchmarks or examples will be run. This will only
-           list top-level tests. No subtest or subbenchmarks will be shown.
+           List tests, benchmarks, fuzz targets, or examples matching the regular
+           expression. No tests, benchmarks, fuzz targets, or examples will be run.
+           This will only list top-level tests. No subtest or subbenchmarks will be
+           shown.
 
        -parallel n
-           Allow parallel execution of test functions that call t.Parallel.
+           Allow parallel execution of test functions that call t.Parallel, and
+           f.Fuzz functions that call t.Parallel when running the seed corpus.
            The value of this flag is the maximum number of tests to run
-           simultaneously; by default, it is set to the value of GOMAXPROCS.
+           simultaneously. While fuzzing, the value of this flag is the
+           maximum number of workers to run the fuzz function simultaneously,
+           regardless of whether t.Parallel has been called; by default, it is set
+           to the value of GOMAXPROCS.
            Note that -parallel only applies within a single test binary.
            The 'go test' command may run tests for different packages
            in parallel as well, according to the setting of the -p flag
            (see 'go help build').
 
        -run regexp
-           Run only those tests and examples matching the regular expression.
-           For tests, the regular expression is split by unbracketed slash (/)
-           characters into a sequence of regular expressions, and each part
-           of a test's identifier must match the corresponding element in
+           Run only those tests, examples, and fuzz targets matching the regular
+           expression. For tests, the regular expression is split by unbracketed
+           slash (/) characters into a sequence of regular expressions, and each
+           part of a test's identifier must match the corresponding element in
            the sequence, if any. Note that possible parents of matches are
            run too, so that -run=X/Y matches and runs and reports the result
            of all tests matching X, even those without sub-tests matching Y,
@@ -430,6 +452,10 @@ A benchmark function is one named BenchmarkXxx and should have the signature,
 
        func BenchmarkXxx(b *testing.B) { ... }
 
+A fuzz target is one named FuzzXxx and should have the signature,
+
+       func FuzzXxx(f *testing.F) { ... }
+
 An example function is similar to a test function but, instead of using
 *testing.T to report success or failure, prints output to os.Stdout.
 If the last comment in the function starts with "Output:" then the output
@@ -469,12 +495,34 @@ Here is another example where the ordering of the output is ignored:
 
 The entire test file is presented as the example when it contains a single
 example function, at least one other function, type, variable, or constant
-declaration, and no test or benchmark functions.
+declaration, and no fuzz targets or test or benchmark functions.
 
 See the documentation of the testing package for more information.
 `,
 }
 
+var HelpFuzz = &base.Command{
+       UsageLine: "fuzz",
+       Short:     "fuzzing",
+       Long: `
+By default, go test will build and run the fuzz targets using the target's seed
+corpus only. Any generated corpora in $GOCACHE that were previously written by
+the fuzzing engine will not be run by default.
+
+When -fuzz is set, the binary will be instrumented for coverage. After all
+tests, examples, benchmark functions, and the seed corpora for all fuzz targets
+have been run, go test will begin to fuzz the specified fuzz target.
+Note that this feature is experimental.
+
+-run can be used for testing a single seed corpus entry for a fuzz target. The
+regular expression value of -run can be in the form $target/$name, where $target
+is the name of the fuzz target, and $name is the name of the file (ignoring file
+extensions) to run. For example, -run=FuzzFoo/497b6f87.
+
+See https://golang.org/s/draft-fuzzing-design for more details.
+`,
+}
+
 var (
        testBench        string                            // -bench flag
        testC            bool                              // -c flag
@@ -483,6 +531,7 @@ var (
        testCoverPaths   []string                          // -coverpkg flag
        testCoverPkgs    []*load.Package                   // -coverpkg flag
        testCoverProfile string                            // -coverprofile flag
+       testFuzz         string                            // -fuzz flag
        testJSON         bool                              // -json flag
        testList         string                            // -list flag
        testO            string                            // -o flag
@@ -625,7 +674,9 @@ func runTest(ctx context.Context, cmd *base.Command, args []string) {
        // to that timeout plus one minute. This is a backup alarm in case
        // the test wedges with a goroutine spinning and its background
        // timer does not get a chance to fire.
-       if testTimeout > 0 {
+       // Don't set this if fuzzing, since it should be able to run
+       // indefinitely.
+       if testTimeout > 0 && testFuzz == "" {
                testKillTimeout = testTimeout + 1*time.Minute
        }
 
@@ -775,6 +826,25 @@ func runTest(ctx context.Context, cmd *base.Command, args []string) {
                }
        }
 
+       // Inform the compiler that it should instrument the binary at
+       // build-time when fuzzing is enabled.
+       fuzzFlags := work.FuzzInstrumentFlags()
+       if testFuzz != "" && fuzzFlags != nil {
+               // Don't instrument packages which may affect coverage guidance but are
+               // unlikely to be useful.
+               var fuzzNoInstrument = map[string]bool{
+                       "testing":       true,
+                       "internal/fuzz": true,
+                       "runtime":       true,
+               }
+               for _, p := range load.TestPackageList(ctx, pkgOpts, pkgs) {
+                       if fuzzNoInstrument[p.ImportPath] {
+                               continue
+                       }
+                       p.Internal.Gcflags = append(p.Internal.Gcflags, fuzzFlags...)
+               }
+       }
+
        // Prepare build + run + print actions for all packages being tested.
        for _, p := range pkgs {
                // sync/atomic import is inserted by the cover tool. See #18486
@@ -1080,6 +1150,8 @@ func declareCoverVars(p *load.Package, files ...string) map[string]*load.CoverVa
 }
 
 var noTestsToRun = []byte("\ntesting: warning: no tests to run\n")
+var noTargetsToFuzz = []byte("\ntesting: warning: no targets to fuzz\n")
+var tooManyTargetsToFuzz = []byte("\ntesting: warning: -fuzz matches more than one target, won't fuzz\n")
 
 type runCache struct {
        disableCache bool // cache should be disabled for this run
@@ -1183,7 +1255,12 @@ func (c *runCache) builderRunTest(b *work.Builder, ctx context.Context, a *work.
                testlogArg = []string{"-test.testlogfile=" + a.Objdir + "testlog.txt"}
        }
        panicArg := "-test.paniconexit0"
-       args := str.StringList(execCmd, a.Deps[0].BuiltTarget(), testlogArg, panicArg, testArgs)
+       fuzzArg := []string{}
+       if testFuzz != "" {
+               fuzzCacheDir := filepath.Join(cache.Default().FuzzDir(), a.Package.ImportPath)
+               fuzzArg = []string{"-test.fuzzcachedir=" + fuzzCacheDir}
+       }
+       args := str.StringList(execCmd, a.Deps[0].BuiltTarget(), testlogArg, panicArg, fuzzArg, testArgs)
 
        if testCoverProfile != "" {
                // Write coverage to temporary profile, for merging later.
@@ -1276,6 +1353,12 @@ func (c *runCache) builderRunTest(b *work.Builder, ctx context.Context, a *work.
                if bytes.HasPrefix(out, noTestsToRun[1:]) || bytes.Contains(out, noTestsToRun) {
                        norun = " [no tests to run]"
                }
+               if bytes.HasPrefix(out, noTargetsToFuzz[1:]) || bytes.Contains(out, noTargetsToFuzz) {
+                       norun = " [no targets to fuzz]"
+               }
+               if bytes.HasPrefix(out, tooManyTargetsToFuzz[1:]) || bytes.Contains(out, tooManyTargetsToFuzz) {
+                       norun = " [will not fuzz, -fuzz matches more than one target]"
+               }
                fmt.Fprintf(cmd.Stdout, "ok  \t%s\t%s%s%s\n", a.Package.ImportPath, t, coveragePercentage(out), norun)
                c.saveOutput(a)
        } else {
index 08f1efa2c0d26a0cf398f2b778c0adde90fc97ae..e3eca9249be859cf71a78a355b75f659d627c2a6 100644 (file)
@@ -57,6 +57,7 @@ func init() {
        cf.String("cpu", "", "")
        cf.StringVar(&testCPUProfile, "cpuprofile", "", "")
        cf.Bool("failfast", false, "")
+       cf.StringVar(&testFuzz, "fuzz", "", "")
        cf.StringVar(&testList, "list", "", "")
        cf.StringVar(&testMemProfile, "memprofile", "", "")
        cf.String("memprofilerate", "", "")
@@ -67,6 +68,8 @@ func init() {
        cf.String("run", "", "")
        cf.Bool("short", false, "")
        cf.DurationVar(&testTimeout, "timeout", 10*time.Minute, "")
+       cf.String("fuzztime", "", "")
+       cf.String("fuzzminimizetime", "", "")
        cf.StringVar(&testTrace, "trace", "", "")
        cf.BoolVar(&testV, "v", false, "")
        cf.Var(&testShuffle, "shuffle", "")
index 37a3e2d0ffd1232368863992603432fbd783e828..5f53c20245f4e2e92acab15978f8ad6426d244db 100644 (file)
@@ -47,6 +47,14 @@ func BuildInit() {
        }
 }
 
+func FuzzInstrumentFlags() []string {
+       if cfg.Goarch != "amd64" && cfg.Goarch != "arm64" {
+               // Instrumentation is only supported on 64-bit architectures.
+               return nil
+       }
+       return []string{"-d=libfuzzer"}
+}
+
 func instrumentInit() {
        if !cfg.BuildRace && !cfg.BuildMSan {
                return
index 02174a56ff0bef40871f3ef7301588233d7bb355..452673dd3462c38ca831624b070536ea22effd53 100644 (file)
@@ -80,6 +80,7 @@ func init() {
                modfetch.HelpPrivate,
                test.HelpTestflag,
                test.HelpTestfunc,
+               test.HelpFuzz,
                modget.HelpVCS,
        }
 }
diff --git a/src/cmd/go/testdata/script/test_fuzz.txt b/src/cmd/go/testdata/script/test_fuzz.txt
new file mode 100644 (file)
index 0000000..0b1b85f
--- /dev/null
@@ -0,0 +1,409 @@
+# TODO(jayconrod): support shared memory on more platforms.
+[!darwin] [!linux] [!windows] skip
+
+# Test that running a fuzz target that returns without failing or calling
+# f.Fuzz fails and causes a non-zero exit status.
+! go test noop_fuzz_test.go
+! stdout ^ok
+stdout FAIL
+
+# Test that fuzzing a fuzz target that returns without failing or calling
+# f.Fuzz fails and causes a non-zero exit status.
+! go test -fuzz=Fuzz -fuzztime=1x noop_fuzz_test.go
+! stdout ^ok
+stdout FAIL
+
+# Test that calling f.Error in a fuzz target causes a non-zero exit status.
+! go test -fuzz=Fuzz -fuzztime=1x error_fuzz_test.go
+! stdout ^ok
+stdout FAIL
+
+# Test that calling f.Fatal in a fuzz target causes a non-zero exit status.
+! go test fatal_fuzz_test.go
+! stdout ^ok
+stdout FAIL
+
+# Test that successful test exits cleanly.
+go test success_fuzz_test.go
+stdout ^ok
+! stdout FAIL
+
+# Test that successful fuzzing exits cleanly.
+go test -fuzz=Fuzz -fuzztime=1x success_fuzz_test.go
+stdout ok
+! stdout FAIL
+
+# Test that calling f.Fatal while fuzzing causes a non-zero exit status.
+! go test -fuzz=Fuzz -fuzztime=1x fatal_fuzz_test.go
+! stdout ^ok
+stdout FAIL
+
+# Test error with seed corpus in f.Fuzz
+! go test -run FuzzError fuzz_add_test.go
+! stdout ^ok
+stdout FAIL
+stdout 'error here'
+
+[short] stop
+
+# Test that calling panic(nil) in a fuzz target causes a non-zero exit status.
+! go test panic_fuzz_test.go
+! stdout ^ok
+stdout FAIL
+
+# Test that skipped test exits cleanly.
+go test skipped_fuzz_test.go
+stdout ok
+! stdout FAIL
+
+# Test that f.Fatal within f.Fuzz panics
+! go test fatal_fuzz_fn_fuzz_test.go
+! stdout ^ok
+! stdout 'fatal here'
+stdout FAIL
+stdout 'f.Fuzz function'
+
+# Test that f.Error within f.Fuzz panics
+! go test error_fuzz_fn_fuzz_test.go
+! stdout ^ok
+! stdout 'error here'
+stdout FAIL
+stdout 'f.Fuzz function'
+
+# Test that f.Skip within f.Fuzz panics
+! go test skip_fuzz_fn_fuzz_test.go
+! stdout ^ok
+! stdout 'skip here'
+stdout FAIL
+stdout 'f.Fuzz function'
+
+# Test that a call to f.Fatal after the Fuzz func is never executed.
+go test fatal_after_fuzz_func_fuzz_test.go
+stdout ok
+! stdout FAIL
+
+# Test that missing *T in f.Fuzz causes a non-zero exit status.
+! go test incomplete_fuzz_call_fuzz_test.go
+! stdout ^ok
+stdout FAIL
+
+# Test that a panic in the Cleanup func is executed.
+! go test cleanup_fuzz_test.go
+! stdout ^ok
+stdout FAIL
+stdout 'failed some precondition'
+
+# Test success with seed corpus in f.Fuzz
+go test -run FuzzPass fuzz_add_test.go
+stdout ok
+! stdout FAIL
+! stdout 'off by one error'
+
+# Test fatal with seed corpus in f.Fuzz
+! go test -run FuzzFatal fuzz_add_test.go
+! stdout ^ok
+stdout FAIL
+stdout 'fatal here'
+
+# Test panic with seed corpus in f.Fuzz
+! go test -run FuzzPanic fuzz_add_test.go
+! stdout ^ok
+stdout FAIL
+stdout 'off by one error'
+
+# Test panic(nil) with seed corpus in f.Fuzz
+! go test -run FuzzNilPanic fuzz_add_test.go
+! stdout ^ok
+stdout FAIL
+
+# Test panic with unsupported seed corpus
+! go test -run FuzzUnsupported fuzz_add_test.go
+! stdout ^ok
+stdout FAIL
+
+# Test panic with different number of args to f.Add
+! go test -run FuzzAddDifferentNumber fuzz_add_test.go
+! stdout ^ok
+stdout FAIL
+
+# Test panic with different type of args to f.Add
+! go test -run FuzzAddDifferentType fuzz_add_test.go
+! stdout ^ok
+stdout FAIL
+
+# Test fatal with testdata seed corpus
+! go test -run FuzzFail corpustesting/fuzz_testdata_corpus_test.go
+! stdout ^ok
+stdout FAIL
+stdout 'fatal here'
+
+# Test pass with testdata seed corpus
+go test -run FuzzPass corpustesting/fuzz_testdata_corpus_test.go
+stdout ok
+! stdout FAIL
+! stdout 'fatal here'
+
+# Test panic with malformed seed corpus
+! go test -run FuzzFail corpustesting/fuzz_testdata_corpus_test.go
+! stdout ^ok
+stdout FAIL
+
+# Test pass with file in other nested testdata directory
+go test -run FuzzInNestedDir corpustesting/fuzz_testdata_corpus_test.go
+stdout ok
+! stdout FAIL
+! stdout 'fatal here'
+
+# Test fails with file containing wrong type
+! go test -run FuzzWrongType corpustesting/fuzz_testdata_corpus_test.go
+! stdout ^ok
+stdout FAIL
+
+-- noop_fuzz_test.go --
+package noop_fuzz
+
+import "testing"
+
+func Fuzz(f *testing.F) {}
+
+-- error_fuzz_test.go --
+package error_fuzz
+
+import "testing"
+
+func Fuzz(f *testing.F) {
+    f.Error("error in target")
+}
+
+-- fatal_fuzz_test.go --
+package fatal_fuzz
+
+import "testing"
+
+func Fuzz(f *testing.F) {
+    f.Fatal("fatal in target")
+}
+
+-- panic_fuzz_test.go --
+package panic_fuzz
+
+import "testing"
+
+func FuzzPanic(f *testing.F) {
+    panic(nil)
+}
+
+-- success_fuzz_test.go --
+package success_fuzz
+
+import "testing"
+
+func Fuzz(f *testing.F) {
+    f.Fuzz(func (*testing.T, []byte) {})
+}
+
+-- skipped_fuzz_test.go --
+package skipped_fuzz
+
+import "testing"
+
+func Fuzz(f *testing.F) {
+    f.Skip()
+}
+
+-- fatal_fuzz_fn_fuzz_test.go --
+package fatal_fuzz_fn_fuzz
+
+import "testing"
+
+func Fuzz(f *testing.F) {
+    f.Add([]byte("aa"))
+    f.Fuzz(func(t *testing.T, b []byte) {
+        f.Fatal("fatal here")
+    })
+}
+
+-- error_fuzz_fn_fuzz_test.go --
+package error_fuzz_fn_fuzz
+
+import "testing"
+
+func Fuzz(f *testing.F) {
+    f.Add([]byte("aa"))
+    f.Fuzz(func(t *testing.T, b []byte) {
+        f.Error("error here")
+    })
+}
+
+-- skip_fuzz_fn_fuzz_test.go --
+package skip_fuzz_fn_fuzz
+
+import "testing"
+
+func Fuzz(f *testing.F) {
+    f.Add([]byte("aa"))
+    f.Fuzz(func(t *testing.T, b []byte) {
+        f.Skip("skip here")
+    })
+}
+
+-- fatal_after_fuzz_func_fuzz_test.go --
+package fatal_after_fuzz_func_fuzz
+
+import "testing"
+
+func Fuzz(f *testing.F) {
+    f.Fuzz(func(t *testing.T, b []byte) {
+        // no-op
+    })
+    f.Fatal("this shouldn't be called")
+}
+
+-- incomplete_fuzz_call_fuzz_test.go --
+package incomplete_fuzz_call_fuzz
+
+import "testing"
+
+func Fuzz(f *testing.F) {
+    f.Fuzz(func(b []byte) {
+        // this is missing *testing.T as the first param, so should panic
+    })
+}
+
+-- cleanup_fuzz_test.go --
+package cleanup_fuzz_test
+
+import "testing"
+
+func Fuzz(f *testing.F) {
+    f.Cleanup(func() {
+        panic("failed some precondition")
+    })
+    f.Fuzz(func(t *testing.T, b []byte) {
+        // no-op
+    })
+}
+
+-- fuzz_add_test.go --
+package fuzz_add
+
+import "testing"
+
+func add(f *testing.F) {
+    f.Helper()
+    f.Add([]byte("123"))
+    f.Add([]byte("12345"))
+    f.Add([]byte(""))
+}
+
+func FuzzPass(f *testing.F) {
+    add(f)
+    f.Fuzz(func(t *testing.T, b []byte) {
+        if len(b) == -1 {
+            t.Fatal("fatal here") // will not be executed
+        }
+    })
+}
+
+func FuzzError(f *testing.F) {
+    add(f)
+    f.Fuzz(func(t *testing.T, b []byte) {
+        if len(b) == 3 {
+            t.Error("error here")
+        }
+    })
+}
+
+func FuzzFatal(f *testing.F) {
+    add(f)
+    f.Fuzz(func(t *testing.T, b []byte) {
+        if len(b) == 0 {
+            t.Fatal("fatal here")
+        }
+    })
+}
+
+func FuzzPanic(f *testing.F) {
+    add(f)
+    f.Fuzz(func(t *testing.T, b []byte) {
+        if len(b) == 5 {
+            panic("off by one error")
+        }
+    })
+}
+
+func FuzzNilPanic(f *testing.F) {
+    add(f)
+    f.Fuzz(func(t *testing.T, b []byte) {
+        if len(b) == 3 {
+            panic(nil)
+        }
+    })
+}
+
+func FuzzUnsupported(f *testing.F) {
+    m := make(map[string]bool)
+    f.Add(m)
+    f.Fuzz(func(t *testing.T, b []byte) {})
+}
+
+func FuzzAddDifferentNumber(f *testing.F) {
+    f.Add([]byte("a"))
+    f.Add([]byte("a"), []byte("b"))
+    f.Fuzz(func(t *testing.T, b []byte) {})
+}
+
+func FuzzAddDifferentType(f *testing.F) {
+    f.Add(false)
+    f.Add(1234)
+    f.Fuzz(func(t *testing.T, b []byte) {})
+}
+
+-- corpustesting/fuzz_testdata_corpus_test.go --
+package fuzz_testdata_corpus
+
+import "testing"
+
+func fuzzFn(f *testing.F) {
+    f.Helper()
+    f.Fuzz(func(t *testing.T, b []byte) {
+        if string(b) == "12345" {
+            t.Fatal("fatal here")
+        }
+    })
+}
+
+func FuzzFail(f *testing.F) {
+    fuzzFn(f)
+}
+
+func FuzzPass(f *testing.F) {
+    fuzzFn(f)
+}
+
+func FuzzPanic(f *testing.F) {
+    f.Fuzz(func(t *testing.T, b []byte) {})
+}
+
+func FuzzInNestedDir(f *testing.F) {
+    f.Fuzz(func(t *testing.T, b []byte) {})
+}
+
+func FuzzWrongType(f *testing.F) {
+    f.Fuzz(func(t *testing.T, b []byte) {})
+}
+
+-- corpustesting/testdata/corpus/FuzzFail/1 --
+go test fuzz v1
+[]byte("12345")
+-- corpustesting/testdata/corpus/FuzzPass/1 --
+go test fuzz v1
+[]byte("00000")
+-- corpustesting/testdata/corpus/FuzzPanic/1 --
+malformed
+-- corpustesting/testdata/corpus/FuzzInNestedDir/anotherdir/1 --
+go test fuzz v1
+[]byte("12345")
+-- corpustesting/testdata/corpus/FuzzWrongType/1 --
+go test fuzz v1
+int("00000")
\ No newline at end of file
diff --git a/src/cmd/go/testdata/script/test_fuzz_cache.txt b/src/cmd/go/testdata/script/test_fuzz_cache.txt
new file mode 100644 (file)
index 0000000..a6c9caf
--- /dev/null
@@ -0,0 +1,73 @@
+# TODO(jayconrod): support shared memory on more platforms.
+[!darwin] [!linux] [!windows] skip
+
+[short] skip
+env GOCACHE=$WORK/cache
+
+# Fuzz cache should not exist after a regular test run.
+go test .
+exists $GOCACHE
+! exists $GOCACHE/fuzz
+
+# Fuzzing should write interesting values to the cache.
+go test -fuzz=FuzzY -fuzztime=100x .
+go run ./contains_files $GOCACHE/fuzz/example.com/y/FuzzY
+
+# 'go clean -cache' should not delete the fuzz cache.
+go clean -cache
+exists $GOCACHE/fuzz
+
+# 'go clean -fuzzcache' should delete the fuzz cache but not the build cache.
+go list -f {{.Stale}} ./empty
+stdout true
+go install ./empty
+go list -f {{.Stale}} ./empty
+stdout false
+go clean -fuzzcache
+! exists $GOCACHE/fuzz
+go list -f {{.Stale}} ./empty
+stdout false
+
+-- go.mod --
+module example.com/y
+
+go 1.16
+-- y_test.go --
+package y
+
+import "testing"
+
+func FuzzY(f *testing.F) {
+       f.Add([]byte("y"))
+       f.Fuzz(func(t *testing.T, b []byte) { Y(b) })
+}
+-- y.go --
+package y
+
+import "bytes"
+
+func Y(b []byte) bool {
+       return bytes.Equal(b, []byte("y"))
+}
+-- empty/empty.go --
+package empty
+-- contains_files/contains_files.go --
+package main
+
+import (
+       "fmt"
+       "path/filepath"
+       "io/ioutil"
+       "os"
+)
+
+func main() {
+       infos, err := ioutil.ReadDir(filepath.Clean(os.Args[1]))
+       if err != nil {
+               fmt.Fprintln(os.Stderr, err)
+               os.Exit(1)
+       }
+       if len(infos) == 0 {
+               os.Exit(1)
+       }
+}
diff --git a/src/cmd/go/testdata/script/test_fuzz_chatty.txt b/src/cmd/go/testdata/script/test_fuzz_chatty.txt
new file mode 100644 (file)
index 0000000..9ebd480
--- /dev/null
@@ -0,0 +1,106 @@
+# TODO(jayconrod): support shared memory on more platforms.
+[!darwin] [!linux] [!windows] skip
+
+[short] skip
+
+# Run chatty fuzz targets with an error.
+! go test -v chatty_error_fuzz_test.go
+! stdout '^ok'
+stdout 'FAIL'
+stdout 'error in target'
+
+# Run chatty fuzz targets with a fatal.
+! go test -v chatty_fatal_fuzz_test.go
+! stdout '^ok'
+stdout 'FAIL'
+stdout 'fatal in target'
+
+# Run chatty fuzz target with a panic
+! go test -v chatty_panic_fuzz_test.go
+! stdout ^ok
+stdout FAIL
+stdout 'this is bad'
+
+# Run skipped chatty fuzz targets.
+go test -v chatty_skipped_fuzz_test.go
+stdout ok
+stdout SKIP
+! stdout FAIL
+
+# Run successful chatty fuzz targets.
+go test -v chatty_fuzz_test.go
+stdout ok
+stdout PASS
+stdout 'all good here'
+! stdout FAIL
+
+# Fuzz successful chatty fuzz target that includes a separate unit test.
+go test -v chatty_with_test_fuzz_test.go -fuzz=Fuzz -fuzztime=1x
+stdout ok
+stdout PASS
+! stdout FAIL
+# TODO: It's currently the case that it's logged twice. Fix that, and change
+# this check to verify it.
+stdout 'all good here'
+# Verify that the unit test is only run once.
+! stdout '(?s)logged foo.*logged foo'
+
+-- chatty_error_fuzz_test.go --
+package chatty_error_fuzz
+
+import "testing"
+
+func Fuzz(f *testing.F) {
+    f.Error("error in target")
+}
+
+-- chatty_fatal_fuzz_test.go --
+package chatty_fatal_fuzz
+
+import "testing"
+
+func Fuzz(f *testing.F) {
+    f.Fatal("fatal in target")
+}
+
+-- chatty_panic_fuzz_test.go --
+package chatty_panic_fuzz
+
+import "testing"
+
+func Fuzz(f *testing.F) {
+    panic("this is bad")
+}
+
+-- chatty_skipped_fuzz_test.go --
+package chatty_skipped_fuzz
+
+import "testing"
+
+func Fuzz(f *testing.F) {
+    f.Skip()
+}
+
+-- chatty_fuzz_test.go --
+package chatty_fuzz
+
+import "testing"
+
+func Fuzz(f *testing.F) {
+    f.Log("all good here")
+    f.Fuzz(func(*testing.T, []byte) {})
+}
+
+-- chatty_with_test_fuzz_test.go --
+package chatty_with_test_fuzz
+
+import "testing"
+
+func TestFoo(t *testing.T) {
+    t.Log("logged foo")
+}
+
+func Fuzz(f *testing.F) {
+    f.Log("all good here")
+    f.Fuzz(func(*testing.T, []byte) {})
+}
diff --git a/src/cmd/go/testdata/script/test_fuzz_cleanup.txt b/src/cmd/go/testdata/script/test_fuzz_cleanup.txt
new file mode 100644 (file)
index 0000000..8862591
--- /dev/null
@@ -0,0 +1,67 @@
+# TODO(jayconrod): support shared memory on more platforms.
+[!darwin] [!linux] [!windows] skip
+[short] skip
+
+# Cleanup should run after F.Skip.
+go test -run=FuzzTargetSkip
+stdout cleanup
+
+# Cleanup should run after F.Fatal.
+! go test -run=FuzzTargetFatal
+stdout cleanup
+
+# Cleanup should run after an unexpected runtime.Goexit.
+! go test -run=FuzzTargetGoexit
+stdout cleanup
+
+# Cleanup should run after panic.
+! go test -run=FuzzTargetPanic
+stdout cleanup
+
+# Cleanup should run in fuzz function on seed corpus.
+go test -v -run=FuzzFunction
+stdout '(?s)inner.*outer'
+
+# TODO(jayconrod): test cleanup while fuzzing. For now, the worker process's
+# stdout and stderr is connected to the coordinator's, but it should eventually
+# be connected to os.DevNull, so we wouldn't see t.Log output.
+
+-- go.mod --
+module cleanup
+
+go 1.15
+-- cleanup_test.go --
+package cleanup
+
+import (
+       "runtime"
+       "testing"
+)
+
+func FuzzTargetSkip(f *testing.F) {
+       f.Cleanup(func() { f.Log("cleanup") })
+       f.Skip()
+}
+
+func FuzzTargetFatal(f *testing.F) {
+       f.Cleanup(func() { f.Log("cleanup") })
+       f.Fatal()
+}
+
+func FuzzTargetGoexit(f *testing.F) {
+       f.Cleanup(func() { f.Log("cleanup") })
+       runtime.Goexit()
+}
+
+func FuzzTargetPanic(f *testing.F) {
+       f.Cleanup(func() { f.Log("cleanup") })
+       panic("oh no")
+}
+
+func FuzzFunction(f *testing.F) {
+       f.Add([]byte{0})
+       f.Cleanup(func() { f.Log("outer") })
+       f.Fuzz(func(t *testing.T, b []byte) {
+               t.Cleanup(func() { t.Logf("inner") })
+       })
+}
diff --git a/src/cmd/go/testdata/script/test_fuzz_fuzztime.txt b/src/cmd/go/testdata/script/test_fuzz_fuzztime.txt
new file mode 100644 (file)
index 0000000..617980e
--- /dev/null
@@ -0,0 +1,77 @@
+# TODO(jayconrod): support shared memory on more platforms.
+[!darwin] [!linux] [!windows] skip
+
+[short] skip
+
+# There are no seed values, so 'go test' should finish quickly.
+go test
+
+# Fuzzing should exit 0 when after fuzztime, even if timeout is short.
+go test -timeout=10ms -fuzz=FuzzFast -fuzztime=5s
+
+# We should see the same behavior when invoking the test binary directly.
+go test -c
+exec ./fuzz.test$GOEXE -test.timeout=10ms -test.fuzz=FuzzFast -test.fuzztime=5s -test.parallel=1 -test.fuzzcachedir=$WORK/cache
+
+# Timeout should not cause inputs to be written as crashers.
+! exists testdata/corpus
+
+# When we use fuzztime with an "x" suffix, it runs a specific number of times.
+# This fuzz function creates a file with a unique name ($pid.$count) on each run.
+# We count the files to find the number of runs.
+mkdir count
+env GOCACHE=$WORK/tmp
+go test -fuzz=FuzzCount -fuzztime=1000x
+go run count_files.go
+stdout '^1000$'
+
+-- go.mod --
+module fuzz
+
+go 1.16
+-- fuzz_fast_test.go --
+package fuzz_test
+
+import "testing"
+
+func FuzzFast(f *testing.F) {
+       f.Fuzz(func (*testing.T, []byte) {})
+}
+-- fuzz_count_test.go --
+package fuzz
+
+import (
+       "fmt"
+       "os"
+       "testing"
+)
+
+func FuzzCount(f *testing.F) {
+       pid := os.Getpid()
+       n := 0
+       f.Fuzz(func(t *testing.T, _ []byte) {
+               name := fmt.Sprintf("count/%v.%d", pid, n)
+               if err := os.WriteFile(name, nil, 0666); err != nil {
+                       t.Fatal(err)
+               }
+               n++
+       })
+}
+-- count_files.go --
+// +build ignore
+
+package main
+
+import (
+       "fmt"
+       "os"
+)
+
+func main() {
+       dir, err := os.ReadDir("count")
+       if err != nil {
+               fmt.Fprintln(os.Stderr, err)
+               os.Exit(1)
+       }
+       fmt.Println(len(dir))
+}
diff --git a/src/cmd/go/testdata/script/test_fuzz_io_error.txt b/src/cmd/go/testdata/script/test_fuzz_io_error.txt
new file mode 100644 (file)
index 0000000..4c7ab4c
--- /dev/null
@@ -0,0 +1,101 @@
+# Test that when the coordinator experiences an I/O error communicating
+# with a worker, the coordinator stops the worker and reports the error.
+# The coordinator should not record a crasher.
+#
+# We simulate an I/O error in the test by writing garbage to fuzz_out.
+# This is unlikely, but possible. It's difficult to simulate interruptions
+# due to ^C and EOF errors which are more common. We don't report those.
+[short] skip
+[!darwin] [!linux] [!windows] skip
+
+# If the I/O error occurs before F.Fuzz is called, the coordinator should
+# stop the worker and say that.
+! go test -fuzz=FuzzClosePipeBefore -parallel=1
+stdout '\s*fuzzing process terminated without fuzzing:'
+! stdout 'communicating with fuzzing process'
+! exists testdata
+
+# If the I/O error occurs after F.Fuzz is called (unlikely), just exit.
+# It's hard to distinguish this case from the worker being interrupted by ^C
+# or exiting with status 0 (which it should do when interrupted by ^C).
+! go test -fuzz=FuzzClosePipeAfter -parallel=1
+stdout '^\s*communicating with fuzzing process: invalid character ''!'' looking for beginning of value$'
+! exists testdata
+
+-- go.mod --
+module test
+
+go 1.17
+-- io_error_test.go --
+package io_error
+
+import (
+       "flag"
+       "testing"
+       "time"
+)
+
+func isWorker() bool {
+       f := flag.Lookup("test.fuzzworker")
+       if f == nil {
+               return false
+       }
+       get, ok := f.Value.(flag.Getter)
+       if !ok {
+               return false
+       }
+       return get.Get() == interface{}(true)
+}
+
+func FuzzClosePipeBefore(f *testing.F) {
+       if isWorker() {
+               sendGarbageToCoordinator(f)
+               time.Sleep(3600 * time.Second) // pause until coordinator terminates the process
+       }
+       f.Fuzz(func(*testing.T, []byte) {})
+}
+
+func FuzzClosePipeAfter(f *testing.F) {
+       f.Fuzz(func(t *testing.T, _ []byte) {
+               if isWorker() {
+                       sendGarbageToCoordinator(t)
+                       time.Sleep(3600 * time.Second) // pause until coordinator terminates the process
+               }
+       })
+}
+-- io_error_windows_test.go --
+package io_error
+
+import (
+       "fmt"
+       "os"
+       "testing"
+)
+
+func sendGarbageToCoordinator(tb testing.TB) {
+       v := os.Getenv("GO_TEST_FUZZ_WORKER_HANDLES")
+       var fuzzInFD, fuzzOutFD uintptr
+       if _, err := fmt.Sscanf(v, "%x,%x", &fuzzInFD, &fuzzOutFD); err != nil {
+               tb.Fatalf("parsing GO_TEST_FUZZ_WORKER_HANDLES: %v", err)
+       }
+       f := os.NewFile(fuzzOutFD, "fuzz_out")
+       if _, err := f.Write([]byte("!!")); err != nil {
+               tb.Fatalf("writing fuzz_out: %v", err)
+       }
+}
+-- io_error_notwindows_test.go --
+// +build !windows
+
+package io_error
+
+import (
+       "os"
+       "testing"
+)
+
+func sendGarbageToCoordinator(tb testing.TB) {
+       f := os.NewFile(4, "fuzz_out")
+       if _, err := f.Write([]byte("!!")); err != nil {
+               tb.Fatalf("writing fuzz_out: %v", err)
+       }
+}
diff --git a/src/cmd/go/testdata/script/test_fuzz_match.txt b/src/cmd/go/testdata/script/test_fuzz_match.txt
new file mode 100644 (file)
index 0000000..ab8bebf
--- /dev/null
@@ -0,0 +1,60 @@
+# TODO(jayconrod): support shared memory on more platforms.
+[!darwin] [!linux] [!windows] skip
+
+# Matches only fuzz targets to test.
+go test standalone_fuzz_test.go
+! stdout '^ok.*\[no tests to run\]'
+stdout '^ok'
+
+# Matches only for fuzzing.
+go test -fuzz Fuzz -fuzztime 1x standalone_fuzz_test.go
+! stdout '^ok.*\[no tests to run\]'
+stdout '^ok'
+
+# Matches none for fuzzing but will run the fuzz target as a test.
+go test -fuzz ThisWillNotMatch -fuzztime 1x standalone_fuzz_test.go
+! stdout '^ok.*\[no tests to run\]'
+stdout '^ok'
+stdout '\[no targets to fuzz\]'
+
+[short] stop
+
+# Matches only fuzz targets to test with -run.
+go test -run Fuzz standalone_fuzz_test.go
+! stdout '^ok.*\[no tests to run\]'
+stdout '^ok'
+
+# Matches no fuzz targets.
+go test -run ThisWillNotMatch standalone_fuzz_test.go
+stdout '^ok.*\[no tests to run\]'
+! stdout '\[no targets to fuzz\]'
+
+# Matches more than one fuzz target for fuzzing.
+go test -fuzz Fuzz -fuzztime 1x multiple_fuzz_test.go
+# The tests should run, but not be fuzzed
+! stdout '\[no tests to run\]'
+! stdout '\[no targets to fuzz\]'
+stdout ok
+stdout '\[will not fuzz, -fuzz matches more than one target\]'
+
+-- standalone_fuzz_test.go --
+package standalone_fuzz
+
+import "testing"
+
+func Fuzz(f *testing.F) {
+       f.Fuzz(func (*testing.T, []byte) {})
+}
+
+-- multiple_fuzz_test.go --
+package multiple_fuzz
+
+import "testing"
+
+func FuzzA(f *testing.F) {
+       f.Fuzz(func (*testing.T, []byte) {})
+}
+
+func FuzzB(f *testing.F) {
+       f.Fuzz(func (*testing.T, []byte) {})
+}
diff --git a/src/cmd/go/testdata/script/test_fuzz_minimize.txt b/src/cmd/go/testdata/script/test_fuzz_minimize.txt
new file mode 100644 (file)
index 0000000..215ce04
--- /dev/null
@@ -0,0 +1,153 @@
+# TODO(jayconrod): support shared memory on more platforms.
+[!darwin] [!linux] [!windows] skip
+
+[short] skip
+
+# We clean the fuzz cache during this test. Don't clean the user's cache.
+env GOCACHE=$WORK/gocache
+
+# Test that minimization is working for recoverable errors.
+! go test -fuzz=FuzzMinimizerRecoverable -run=FuzzMinimizerRecoverable -fuzztime=100x -fuzzminimizetime=10000x minimizer_test.go
+! stdout '^ok'
+stdout 'got the minimum size!'
+stdout 'contains a non-zero byte'
+stdout FAIL
+
+# Check that the bytes written to testdata are of length 50 (the minimum size)
+go run check_testdata.go FuzzMinimizerRecoverable 50
+
+# Test that re-running the minimized value causes a crash.
+! go test -run=FuzzMinimizerRecoverable minimizer_test.go
+rm testdata
+
+# Test that minimization is working for non-recoverable errors.
+! go test -fuzz=FuzzMinimizerNonrecoverable -run=FuzzMinimizerNonrecoverable -fuzztime=100x -fuzzminimizetime=10000x minimizer_test.go
+! stdout '^ok'
+stdout 'found a crash, minimizing'
+stdout 'fuzzing process terminated unexpectedly while minimizing: exit status 99'
+stdout FAIL
+
+# Check that re-running the value causes a crash.
+! go test -run=FuzzMinimizerNonrecoverable minimizer_test.go
+rm testdata
+
+# Clear the fuzzing cache. There may already be minimized inputs that would
+# interfere with the next stage of the test.
+go clean -fuzzcache
+
+# Test that minimization can be cancelled by fuzzminimizetime and the latest
+# crash will still be logged and written to testdata.
+! go test -fuzz=FuzzMinimizerRecoverable -run=FuzzMinimizerRecoverable -fuzztime=100x -fuzzminimizetime=1x minimizer_test.go
+! stdout '^ok'
+stdout 'testdata[/\\]corpus[/\\]FuzzMinimizerRecoverable[/\\]'
+! stdout 'got the minimum size!'  # it shouldn't have had enough time to minimize it
+stdout FAIL
+
+# Test that re-running the unminimized value causes a crash.
+! go test -run=FuzzMinimizerRecoverable minimizer_test.go
+
+# TODO(jayconrod,katiehockman): add a test which verifies that the right bytes
+# are written to testdata in the case of an interrupt during minimization.
+
+-- go.mod --
+module m
+
+go 1.16
+-- minimizer_test.go --
+package fuzz_test
+
+import (
+       "os"
+       "testing"
+)
+
+func FuzzMinimizerRecoverable(f *testing.F) {
+       f.Add(make([]byte, 100))
+       f.Fuzz(func(t *testing.T, b []byte) {
+               if len(b) < 50 {
+                       // Make sure that b is large enough that it can be minimized
+                       return
+               }
+               // Given the randomness of the mutations, this should allow the
+               // minimizer to trim down the value a bit.
+               for _, n := range b {
+                       if n != 0 {
+                               if len(b) == 50 {
+                                       t.Log("got the minimum size!")
+                               }
+                               t.Fatal("contains a non-zero byte")
+                       }
+               }
+       })
+}
+
+func FuzzMinimizerNonrecoverable(f *testing.F) {
+       f.Add(make([]byte, 100))
+       f.Fuzz(func(t *testing.T, b []byte) {
+               if len(b) < 50 {
+                       // Make sure that b is large enough that it can be minimized
+                       return
+               }
+               // Given the randomness of the mutations, this should allow the
+               // minimizer to trim down the value a bit.
+               for _, n := range b {
+                       if n != 0 {
+                               t.Log("contains a non-zero byte")
+                               os.Exit(99)
+                       }
+               }
+       })
+}
+-- check_testdata.go --
+// +build ignore
+
+package main
+
+import (
+       "bytes"
+       "fmt"
+       "io/ioutil"
+       "os"
+       "path/filepath"
+       "strconv"
+)
+
+func main() {
+       target := os.Args[1]
+       numBytes, err := strconv.Atoi(os.Args[2])
+       if err != nil {
+               fmt.Fprintln(os.Stderr, err)
+               os.Exit(1)
+       }
+
+       // Open the file in testdata (there should only be one)
+       dir := fmt.Sprintf("testdata/corpus/%s", target)
+       files, err := ioutil.ReadDir(dir)
+       if err != nil {
+               fmt.Fprintln(os.Stderr, err)
+               os.Exit(1)
+       }
+       if len(files) != 1 {
+               fmt.Fprintf(os.Stderr, "expected one file, got %d", len(files))
+               os.Exit(1)
+       }
+       got, err := ioutil.ReadFile(filepath.Join(dir, files[0].Name()))
+       if err != nil {
+               fmt.Fprintln(os.Stderr, err)
+               os.Exit(1)
+       }
+
+       // Make sure that there were exactly 100 bytes written to the corpus entry
+       prefix := []byte("[]byte(")
+       i := bytes.Index(got, prefix)
+       gotBytes := got[i+len(prefix) : len(got)-1]
+       s, err := strconv.Unquote(string(gotBytes))
+       if err != nil {
+               fmt.Fprintln(os.Stderr, err)
+               os.Exit(1)
+       }
+       if want, got := numBytes, len(s); want != got {
+               fmt.Fprintf(os.Stderr, "want %d bytes, got %d\n", want, got)
+               os.Exit(1)
+       }
+}
diff --git a/src/cmd/go/testdata/script/test_fuzz_mutate_crash.txt b/src/cmd/go/testdata/script/test_fuzz_mutate_crash.txt
new file mode 100644 (file)
index 0000000..cba91a9
--- /dev/null
@@ -0,0 +1,295 @@
+# TODO(jayconrod): support shared memory on more platforms.
+[!darwin] [!linux] [!windows] skip
+
+# Tests that a crash caused by a mutator-discovered input writes the bad input
+# to testdata, and fails+reports correctly. This tests the end-to-end behavior
+# of the mutator finding a crash while fuzzing, adding it as a regression test
+# to the seed corpus in testdata, and failing the next time the test is run.
+
+[short] skip
+
+# Running the seed corpus for all of the targets should pass the first
+# time, since nothing in the seed corpus will cause a crash.
+go test
+
+# Running the fuzzer should find a crashing input quickly.
+! go test -fuzz=FuzzWithBug -fuzztime=100x
+stdout 'testdata[/\\]corpus[/\\]FuzzWithBug[/\\]'
+stdout 'this input caused a crash!'
+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[/\\]'
+stdout 'runtime.Goexit'
+go run check_testdata.go FuzzWithNilPanic
+
+! go test -run=FuzzWithFail -fuzz=FuzzWithFail -fuzztime=100x
+stdout 'testdata[/\\]corpus[/\\]FuzzWithFail[/\\]'
+go run check_testdata.go FuzzWithFail
+
+! go test -run=FuzzWithLogFail -fuzz=FuzzWithLogFail -fuzztime=100x
+stdout 'testdata[/\\]corpus[/\\]FuzzWithLogFail[/\\]'
+stdout 'logged something'
+go run check_testdata.go FuzzWithLogFail
+
+! go test -run=FuzzWithErrorf -fuzz=FuzzWithErrorf -fuzztime=100x
+stdout 'testdata[/\\]corpus[/\\]FuzzWithErrorf[/\\]'
+stdout 'errorf was called here'
+go run check_testdata.go FuzzWithErrorf
+
+! go test -run=FuzzWithFatalf -fuzz=FuzzWithFatalf -fuzztime=100x
+stdout 'testdata[/\\]corpus[/\\]FuzzWithFatalf[/\\]'
+stdout 'fatalf was called here'
+go run check_testdata.go FuzzWithFatalf
+
+! go test -run=FuzzWithBadExit -fuzz=FuzzWithBadExit -fuzztime=100x
+stdout 'testdata[/\\]corpus[/\\]FuzzWithBadExit[/\\]'
+stdout 'unexpectedly'
+go run check_testdata.go FuzzWithBadExit
+
+# Running the fuzzer should find a crashing input quickly for fuzzing two types.
+! go test -run=FuzzWithTwoTypes -fuzz=FuzzWithTwoTypes -fuzztime=100x
+stdout 'testdata[/\\]corpus[/\\]FuzzWithTwoTypes[/\\]'
+stdout 'these inputs caused a crash!'
+go run check_testdata.go FuzzWithTwoTypes
+
+# Running the fuzzer should find a crashing input quickly for an integer.
+! go test -run=FuzzInt -fuzz=FuzzInt -fuzztime=100x
+stdout 'testdata[/\\]corpus[/\\]FuzzInt[/\\]'
+stdout 'this input caused a crash!'
+go run check_testdata.go FuzzInt
+
+! go test -run=FuzzUint -fuzz=FuzzUint -fuzztime=100x
+stdout 'testdata[/\\]corpus[/\\]FuzzUint[/\\]'
+stdout 'this input caused a crash!'
+go run check_testdata.go FuzzUint
+
+# Running the fuzzer should find a crashing input quickly for a bool.
+! go test -run=FuzzBool -fuzz=FuzzBool -fuzztime=100x
+stdout 'testdata[/\\]corpus[/\\]FuzzBool[/\\]'
+stdout 'this input caused a crash!'
+go run check_testdata.go FuzzBool
+
+# Running the fuzzer should find a crashing input quickly for a float.
+! go test -run=FuzzFloat -fuzz=FuzzFloat -fuzztime=100x
+stdout 'testdata[/\\]corpus[/\\]FuzzFloat[/\\]'
+stdout 'this input caused a crash!'
+go run check_testdata.go FuzzFloat
+
+# Running the fuzzer should find a crashing input quickly for a byte.
+! go test -run=FuzzByte -fuzz=FuzzByte -fuzztime=100x
+stdout 'testdata[/\\]corpus[/\\]FuzzByte[/\\]'
+stdout 'this input caused a crash!'
+go run check_testdata.go FuzzByte
+
+# Running the fuzzer should find a crashing input quickly for a rune.
+! go test -run=FuzzRune -fuzz=FuzzRune -fuzztime=100x
+stdout 'testdata[/\\]corpus[/\\]FuzzRune[/\\]'
+stdout 'this input caused a crash!'
+go run check_testdata.go FuzzRune
+
+# Running the fuzzer should find a crashing input quickly for a string.
+! go test -run=FuzzString -fuzz=FuzzString -fuzztime=100x
+stdout 'testdata[/\\]corpus[/\\]FuzzString[/\\]'
+stdout 'this input caused a crash!'
+go run check_testdata.go FuzzString
+
+-- go.mod --
+module m
+
+go 1.16
+-- fuzz_crash_test.go --
+package fuzz_crash
+
+import (
+    "os"
+       "testing"
+)
+
+func FuzzWithBug(f *testing.F) {
+       f.Add([]byte("aa"))
+       f.Fuzz(func(t *testing.T, b []byte) {
+               if string(b) != "aa" {
+                       panic("this input caused a crash!")
+               }
+       })
+}
+
+func FuzzWithNilPanic(f *testing.F) {
+       f.Add([]byte("aa"))
+       f.Fuzz(func(t *testing.T, b []byte) {
+               if string(b) != "aa" {
+                       panic(nil)
+               }
+       })
+}
+
+func FuzzWithFail(f *testing.F) {
+       f.Add([]byte("aa"))
+       f.Fuzz(func(t *testing.T, b []byte) {
+               if string(b) != "aa" {
+                       t.Fail()
+               }
+       })
+}
+
+func FuzzWithLogFail(f *testing.F) {
+       f.Add([]byte("aa"))
+       f.Fuzz(func(t *testing.T, b []byte) {
+               if string(b) != "aa" {
+                       t.Log("logged something")
+                       t.Fail()
+               }
+       })
+}
+
+func FuzzWithErrorf(f *testing.F) {
+       f.Add([]byte("aa"))
+       f.Fuzz(func(t *testing.T, b []byte) {
+               if string(b) != "aa" {
+                       t.Errorf("errorf was called here")
+               }
+       })
+}
+
+func FuzzWithFatalf(f *testing.F) {
+       f.Add([]byte("aa"))
+       f.Fuzz(func(t *testing.T, b []byte) {
+               if string(b) != "aa" {
+                       t.Fatalf("fatalf was called here")
+               }
+       })
+}
+
+func FuzzWithBadExit(f *testing.F) {
+       f.Add([]byte("aa"))
+       f.Fuzz(func(t *testing.T, b []byte) {
+               if string(b) != "aa" {
+                       os.Exit(1)
+               }
+       })
+}
+
+func FuzzWithTwoTypes(f *testing.F) {
+       f.Fuzz(func(t *testing.T, a, b []byte) {
+               if len(a) > 0 && len(b) > 0 {
+                       panic("these inputs caused a crash!")
+               }
+       })
+}
+
+func FuzzInt(f *testing.F) {
+       f.Add(0)
+       f.Fuzz(func(t *testing.T, a int) {
+               if a != 0 {
+                       panic("this input caused a crash!")
+               }
+       })
+}
+
+func FuzzUint(f *testing.F) {
+       f.Add(uint(0))
+       f.Fuzz(func(t *testing.T, a uint) {
+               if a != 0 {
+                       panic("this input caused a crash!")
+               }
+       })
+}
+
+func FuzzBool(f *testing.F) {
+       f.Add(false)
+       f.Fuzz(func(t *testing.T, a bool) {
+               if a {
+                       panic("this input caused a crash!")
+               }
+       })
+}
+
+func FuzzFloat(f *testing.F) {
+       f.Fuzz(func(t *testing.T, a float64) {
+               if a != float64(int64(a)) {
+                       // It has a decimal, so it was mutated by division
+                       panic("this input caused a crash!")
+               }
+       })
+}
+
+func FuzzByte(f *testing.F) {
+       f.Add(byte(0))
+       f.Fuzz(func(t *testing.T, a byte) {
+               if a != 0 {
+                       panic("this input caused a crash!")
+               }
+       })
+}
+
+func FuzzRune(f *testing.F) {
+       f.Add(rune(0))
+       f.Fuzz(func(t *testing.T, a rune) {
+               if a != 0 {
+                       panic("this input caused a crash!")
+               }
+       })
+}
+
+func FuzzString(f *testing.F) {
+       f.Add("")
+       f.Fuzz(func(t *testing.T, a string) {
+               if a != "" {
+                       panic("this input caused a crash!")
+               }
+       })
+}
+
+-- check_testdata.go --
+// +build ignore
+
+package main
+
+import (
+       "bytes"
+       "crypto/sha256"
+       "fmt"
+       "io/ioutil"
+       "os"
+       "path/filepath"
+)
+
+func main() {
+       target := os.Args[1]
+       dir := filepath.Join("testdata/corpus", target)
+
+       files, err := ioutil.ReadDir(dir)
+       if err != nil {
+               fmt.Fprintln(os.Stderr, err)
+               os.Exit(1)
+       }
+
+       if len(files) == 0 {
+               fmt.Fprintf(os.Stderr, "expect at least one new mutation to be written to testdata\n")
+               os.Exit(1)
+       }
+
+       fname := files[0].Name()
+       contents, err := ioutil.ReadFile(filepath.Join(dir, fname))
+       if err != nil {
+               fmt.Fprintln(os.Stderr, err)
+               os.Exit(1)
+       }
+       if bytes.Equal(contents, []byte("aa")) {
+               fmt.Fprintf(os.Stderr, "newly written testdata entry was not mutated\n")
+               os.Exit(1)
+       }
+       // The hash of the bytes in the file should match the filename.
+       h := []byte(fmt.Sprintf("%x", sha256.Sum256(contents)))
+       if !bytes.Equal([]byte(fname), h) {
+               fmt.Fprintf(os.Stderr, "hash of bytes %q does not match filename %q\n", h, fname)
+               os.Exit(1)
+       }
+}
diff --git a/src/cmd/go/testdata/script/test_fuzz_mutate_fail.txt b/src/cmd/go/testdata/script/test_fuzz_mutate_fail.txt
new file mode 100644 (file)
index 0000000..935c22a
--- /dev/null
@@ -0,0 +1,103 @@
+# TODO(jayconrod): support shared memory on more platforms.
+[!darwin] [!linux] [!windows] skip
+
+# Check that if a worker does not call F.Fuzz or calls F.Fail first,
+# 'go test' exits non-zero and no crasher is recorded.
+
+[short] skip
+
+! go test -fuzz=FuzzReturn
+! exists testdata
+
+! go test -fuzz=FuzzSkip
+! exists testdata
+
+! go test -fuzz=FuzzFail
+! exists testdata
+
+! go test -fuzz=FuzzPanic
+! exists testdata
+
+! go test -fuzz=FuzzNilPanic
+! exists testdata
+
+! go test -fuzz=FuzzGoexit
+! exists testdata
+
+! go test -fuzz=FuzzExit
+! exists testdata
+
+-- go.mod --
+module m
+
+go 1.17
+-- fuzz_fail_test.go --
+package fuzz_fail
+
+import (
+       "flag"
+       "os"
+       "runtime"
+       "testing"
+)
+
+func isWorker() bool {
+       f := flag.Lookup("test.fuzzworker")
+       if f == nil {
+               return false
+       }
+       get, ok := f.Value.(flag.Getter)
+       if !ok {
+               return false
+       }
+       return get.Get() == interface{}(true)
+}
+
+func FuzzReturn(f *testing.F) {
+       if isWorker() {
+               return
+       }
+       f.Fuzz(func(*testing.T, []byte) {})
+}
+
+func FuzzSkip(f *testing.F) {
+       if isWorker() {
+               f.Skip()
+       }
+       f.Fuzz(func(*testing.T, []byte) {})
+}
+
+func FuzzFail(f *testing.F) {
+       if isWorker() {
+               f.Fail()
+       }
+       f.Fuzz(func(*testing.T, []byte) {})
+}
+
+func FuzzPanic(f *testing.F) {
+       if isWorker() {
+               panic("nope")
+       }
+       f.Fuzz(func(*testing.T, []byte) {})
+}
+
+func FuzzNilPanic(f *testing.F) {
+       if isWorker() {
+               panic(nil)
+       }
+       f.Fuzz(func(*testing.T, []byte) {})
+}
+
+func FuzzGoexit(f *testing.F) {
+       if isWorker() {
+               runtime.Goexit()
+       }
+       f.Fuzz(func(*testing.T, []byte) {})
+}
+
+func FuzzExit(f *testing.F) {
+       if isWorker() {
+               os.Exit(99)
+       }
+       f.Fuzz(func(*testing.T, []byte) {})
+}
diff --git a/src/cmd/go/testdata/script/test_fuzz_mutator.txt b/src/cmd/go/testdata/script/test_fuzz_mutator.txt
new file mode 100644 (file)
index 0000000..fb7984c
--- /dev/null
@@ -0,0 +1,188 @@
+# TODO(jayconrod): support shared memory on more platforms.
+[!darwin] [!linux] [!windows] skip
+
+# Test basic fuzzing mutator behavior.
+#
+# fuzz_test.go has two fuzz targets (FuzzA, FuzzB) which both add a seed value.
+# Each fuzz function writes the input to a log file. The coordinator and worker
+# use separate log files. check_logs.go verifies that the coordinator only
+# tests seed values and the worker tests mutated values on the fuzz target.
+
+[short] skip
+
+go test -fuzz=FuzzA -fuzztime=100x -parallel=1 -log=fuzz
+go run check_logs.go fuzz fuzz.worker
+
+# TODO(b/181800488): remove -parallel=1, here and below. For now, when a
+# crash is found, all workers keep running, wasting resources and reducing
+# the number of executions available to the minimizer, increasing flakiness.
+
+# Test that the mutator is good enough to find several unique mutations.
+! go test -fuzz=FuzzMutator -parallel=1 -fuzztime=100x mutator_test.go
+! stdout '^ok'
+stdout FAIL
+stdout 'mutator found enough unique mutations'
+
+-- go.mod --
+module m
+
+go 1.16
+-- fuzz_test.go --
+package fuzz_test
+
+import (
+       "flag"
+       "fmt"
+       "os"
+       "testing"
+)
+
+var (
+       logPath = flag.String("log", "", "path to log file")
+       logFile *os.File
+)
+
+func TestMain(m *testing.M) {
+       flag.Parse()
+       var err error
+       logFile, err = os.OpenFile(*logPath, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666)
+       if os.IsExist(err) {
+               *logPath += ".worker"
+               logFile, err = os.OpenFile(*logPath, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666)
+       }
+       if err != nil {
+               fmt.Fprintln(os.Stderr, err)
+               os.Exit(1)
+       }
+       os.Exit(m.Run())
+}
+
+func FuzzA(f *testing.F) {
+       f.Add([]byte("seed"))
+       f.Fuzz(func(t *testing.T, b []byte) {
+               fmt.Fprintf(logFile, "FuzzA %q\n", b)
+       })
+}
+
+func FuzzB(f *testing.F) {
+       f.Add([]byte("seed"))
+       f.Fuzz(func(t *testing.T, b []byte) {
+               fmt.Fprintf(logFile, "FuzzB %q\n", b)
+       })
+}
+
+-- check_logs.go --
+// +build ignore
+
+package main
+
+import (
+       "bufio"
+       "fmt"
+       "io"
+       "os"
+       "strings"
+)
+
+func main() {
+       coordPath, workerPath := os.Args[1], os.Args[2]
+
+       coordLog, err := os.Open(coordPath)
+       if err != nil {
+               fmt.Fprintln(os.Stderr, err)
+               os.Exit(1)
+       }
+       defer coordLog.Close()
+       if err := checkCoordLog(coordLog); err != nil {
+               fmt.Fprintln(os.Stderr, err)
+               os.Exit(1)
+       }
+
+       workerLog, err := os.Open(workerPath)
+       if err != nil {
+               fmt.Fprintln(os.Stderr, err)
+               os.Exit(1)
+       }
+       defer workerLog.Close()
+       if err := checkWorkerLog(workerLog); err != nil {
+               fmt.Fprintln(os.Stderr, err)
+               os.Exit(1)
+       }
+}
+
+func checkCoordLog(r io.Reader) error {
+       scan := bufio.NewScanner(r)
+       var sawASeed, sawBSeed bool
+       for scan.Scan() {
+               line := scan.Text()
+               switch {
+               case line == `FuzzA "seed"`:
+                       if sawASeed {
+                               return fmt.Errorf("coordinator: tested FuzzA seed multiple times")
+                       }
+                       sawASeed = true
+
+               case line == `FuzzB "seed"`:
+                       if sawBSeed {
+                               return fmt.Errorf("coordinator: tested FuzzB seed multiple times")
+                       }
+                       sawBSeed = true
+
+               default:
+                       return fmt.Errorf("coordinator: tested something other than seeds: %s", line)
+               }
+       }
+       if err := scan.Err(); err != nil {
+               return err
+       }
+       if !sawASeed {
+               return fmt.Errorf("coordinator: did not test FuzzA seed")
+       }
+       if !sawBSeed {
+               return fmt.Errorf("coordinator: did not test FuzzB seed")
+       }
+       return nil
+}
+
+func checkWorkerLog(r io.Reader) error {
+       scan := bufio.NewScanner(r)
+       var sawAMutant bool
+       for scan.Scan() {
+               line := scan.Text()
+               if !strings.HasPrefix(line, "FuzzA ") {
+                       return fmt.Errorf("worker: tested something other than target: %s", line)
+               }
+               if strings.TrimPrefix(line, "FuzzA ") != `"seed"` {
+                       sawAMutant = true
+               }
+       }
+       if err := scan.Err(); err != nil && err != bufio.ErrTooLong {
+               return err
+       }
+       if !sawAMutant {
+               return fmt.Errorf("worker: did not test any mutants")
+       }
+       return nil
+}
+-- mutator_test.go --
+package fuzz_test
+
+import (
+       "testing"
+)
+
+// TODO(katiehockman): re-work this test once we have a better fuzzing engine
+// (ie. more mutations, and compiler instrumentation)
+func FuzzMutator(f *testing.F) {
+       // TODO(katiehockman): simplify this once we can dedupe crashes (e.g.
+       // replace map with calls to panic, and simply count the number of crashes
+       // that were added to testdata)
+       crashes := make(map[string]bool)
+       // No seed corpus initiated
+       f.Fuzz(func(t *testing.T, b []byte) {
+               crashes[string(b)] = true
+               if len(crashes) >= 10 {
+                       panic("mutator found enough unique mutations")
+               }
+       })
+}
diff --git a/src/cmd/go/testdata/script/test_fuzz_parallel.txt b/src/cmd/go/testdata/script/test_fuzz_parallel.txt
new file mode 100644 (file)
index 0000000..d9f6cc7
--- /dev/null
@@ -0,0 +1,61 @@
+# TODO(jayconrod): support shared memory on more platforms.
+[!darwin] [!linux] [!windows] skip
+
+[short] skip
+
+# When running seed inputs, T.Parallel should let multiple inputs run in
+# parallel.
+go test -run=FuzzSeed
+
+# When fuzzing, T.Parallel should be safe to call, but it should have no effect.
+# We just check that it doesn't hang, which would be the most obvious
+# failure mode.
+# TODO(jayconrod): check for the string "after T.Parallel". It's not printed
+# by 'go test', so we can't distinguish that crasher from some other panic.
+! go test -run=FuzzMutate -fuzz=FuzzMutate
+exists testdata/corpus/FuzzMutate
+
+-- go.mod --
+module fuzz_parallel
+
+go 1.17
+-- fuzz_parallel_test.go --
+package fuzz_parallel
+
+import (
+       "sort"
+       "sync"
+       "testing"
+)
+
+func FuzzSeed(f *testing.F) {
+       for _, v := range [][]byte{{'a'}, {'b'}, {'c'}} {
+               f.Add(v)
+       }
+
+       var mu sync.Mutex
+       var before, after []byte
+       f.Cleanup(func() {
+               sort.Slice(after, func(i, j int) bool { return after[i] < after[j] })
+               got := string(before) + string(after)
+               want := "abcabc"
+               if got != want {
+                       f.Fatalf("got %q; want %q", got, want)
+               }
+       })
+
+       f.Fuzz(func(t *testing.T, b []byte) {
+               before = append(before, b...)
+               t.Parallel()
+               mu.Lock()
+               after = append(after, b...)
+               mu.Unlock()
+       })
+}
+
+func FuzzMutate(f *testing.F) {
+       f.Fuzz(func(t *testing.T, _ []byte) {
+               t.Parallel()
+               t.Error("after T.Parallel")
+       })
+}
diff --git a/src/cmd/go/testdata/script/test_fuzz_tag.txt b/src/cmd/go/testdata/script/test_fuzz_tag.txt
new file mode 100644 (file)
index 0000000..07ed5d6
--- /dev/null
@@ -0,0 +1,31 @@
+# Check that the gofuzzbeta tag is enabled by default and can be disabled.
+# TODO(jayconrod,katiehockman): before merging to master, restore the old
+# default and delete this test.
+
+[short] skip
+
+go test -list=.
+stdout Test
+stdout Fuzz
+
+go test -tags=
+
+-- go.mod --
+module fuzz
+
+go 1.17
+-- fuzz_test.go --
+// +build gofuzzbeta
+
+package fuzz
+
+import "testing"
+
+func Fuzz(f *testing.F) {
+       f.Add([]byte(nil))
+       f.Fuzz(func(*testing.T, []byte) {})
+}
+
+func Test(*testing.T) {}
+-- empty_test.go --
+package fuzz
index 70fbb9dc4e24cf2d47c78c89b072684625735784..12ece3e233b0cceaaad67fdee1b95d310c1baded 100644 (file)
@@ -1789,7 +1789,9 @@ func (state *dodataState) allocateDataSections(ctxt *Link) {
 
        // Coverage instrumentation counters for libfuzzer.
        if len(state.data[sym.SLIBFUZZER_EXTRA_COUNTER]) > 0 {
-               state.allocateNamedSectionAndAssignSyms(&Segdata, "__libfuzzer_extra_counters", sym.SLIBFUZZER_EXTRA_COUNTER, sym.Sxxx, 06)
+               sect := state.allocateNamedSectionAndAssignSyms(&Segdata, "__libfuzzer_extra_counters", sym.SLIBFUZZER_EXTRA_COUNTER, sym.Sxxx, 06)
+               ldr.SetSymSect(ldr.LookupOrCreateSym("internal/fuzz._counters", 0), sect)
+               ldr.SetSymSect(ldr.LookupOrCreateSym("internal/fuzz._ecounters", 0), sect)
        }
 
        if len(state.data[sym.STLSBSS]) > 0 {
@@ -2529,6 +2531,7 @@ func (ctxt *Link) address() []*sym.Segment {
        var noptr *sym.Section
        var bss *sym.Section
        var noptrbss *sym.Section
+       var fuzzCounters *sym.Section
        for i, s := range Segdata.Sections {
                if (ctxt.IsELF || ctxt.HeadType == objabi.Haix) && s.Name == ".tbss" {
                        continue
@@ -2540,17 +2543,17 @@ func (ctxt *Link) address() []*sym.Segment {
                s.Vaddr = va
                va += uint64(vlen)
                Segdata.Length = va - Segdata.Vaddr
-               if s.Name == ".data" {
+               switch s.Name {
+               case ".data":
                        data = s
-               }
-               if s.Name == ".noptrdata" {
+               case ".noptrdata":
                        noptr = s
-               }
-               if s.Name == ".bss" {
+               case ".bss":
                        bss = s
-               }
-               if s.Name == ".noptrbss" {
+               case ".noptrbss":
                        noptrbss = s
+               case "__libfuzzer_extra_counters":
+                       fuzzCounters = s
                }
        }
 
@@ -2667,6 +2670,11 @@ func (ctxt *Link) address() []*sym.Segment {
        ctxt.xdefine("runtime.enoptrbss", sym.SNOPTRBSS, int64(noptrbss.Vaddr+noptrbss.Length))
        ctxt.xdefine("runtime.end", sym.SBSS, int64(Segdata.Vaddr+Segdata.Length))
 
+       if fuzzCounters != nil {
+               ctxt.xdefine("internal/fuzz._counters", sym.SLIBFUZZER_EXTRA_COUNTER, int64(fuzzCounters.Vaddr))
+               ctxt.xdefine("internal/fuzz._ecounters", sym.SLIBFUZZER_EXTRA_COUNTER, int64(fuzzCounters.Vaddr+fuzzCounters.Length))
+       }
+
        if ctxt.IsSolaris() {
                // On Solaris, in the runtime it sets the external names of the
                // end symbols. Unset them and define separate symbols, so we
index 5d1cf7f4c97cfd7a3755a7801921321908b81406..67989d2e38d2028aee5a4b8435880a0ddaef84ce 100644 (file)
@@ -509,7 +509,10 @@ var depsRules = `
        FMT, flag, runtime/debug, runtime/trace, internal/sysinfo, math/rand
        < testing;
 
-       internal/testlog, runtime/pprof, regexp
+       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
        < testing/internal/testdeps;
 
        OS, flag, testing, internal/cfg
index 274000cecb7a4b194193cbe2f87e7a1d5753f867..fbbd846354608c80b9eb247bd3f5cfeb33e3474e 100644 (file)
@@ -44,13 +44,13 @@ type Example struct {
 //     identifiers from other packages (or predeclared identifiers, such as
 //     "int") and the test file does not include a dot import.
 //   - The entire test file is the example: the file contains exactly one
-//     example function, zero test or benchmark functions, and at least one
-//     top-level function, type, variable, or constant declaration other
-//     than the example function.
+//     example function, zero test, fuzz target, or benchmark function, and at
+//     least one top-level function, type, variable, or constant declaration
+//     other than the example function.
 func Examples(testFiles ...*ast.File) []*Example {
        var list []*Example
        for _, file := range testFiles {
-               hasTests := false // file contains tests or benchmarks
+               hasTests := false // file contains tests, fuzz targets, or benchmarks
                numDecl := 0      // number of non-import declarations in the file
                var flist []*Example
                for _, decl := range file.Decls {
@@ -64,7 +64,7 @@ func Examples(testFiles ...*ast.File) []*Example {
                        }
                        numDecl++
                        name := f.Name.Name
-                       if isTest(name, "Test") || isTest(name, "Benchmark") {
+                       if isTest(name, "Test") || isTest(name, "Benchmark") || isTest(name, "Fuzz") {
                                hasTests = true
                                continue
                        }
@@ -133,9 +133,9 @@ func exampleOutput(b *ast.BlockStmt, comments []*ast.CommentGroup) (output strin
        return "", false, false // no suitable comment found
 }
 
-// isTest tells whether name looks like a test, example, or benchmark.
-// It is a Test (say) if there is a character after Test that is not a
-// lower-case letter. (We don't want Testiness.)
+// isTest tells whether name looks like a test, example, fuzz target, or
+// benchmark. It is a Test (say) if there is a character after Test that is not
+// lower-case letter. (We don't want Testiness.)
 func isTest(name, prefix string) bool {
        if !strings.HasPrefix(name, prefix) {
                return false
index cf1b702549e5242e57d60e4998873c558d5fed72..21b71290f7d4e64ed0e9c9423932ea3e2c3d6eb6 100644 (file)
@@ -307,6 +307,9 @@ func (X) TestBlah() {
 func (X) BenchmarkFoo() {
 }
 
+func (X) FuzzFoo() {
+}
+
 func Example() {
        fmt.Println("Hello, world!")
        // Output: Hello, world!
@@ -326,6 +329,9 @@ func (X) TestBlah() {
 func (X) BenchmarkFoo() {
 }
 
+func (X) FuzzFoo() {
+}
+
 func main() {
        fmt.Println("Hello, world!")
 }
diff --git a/src/internal/fuzz/coverage.go b/src/internal/fuzz/coverage.go
new file mode 100644 (file)
index 0000000..316aa14
--- /dev/null
@@ -0,0 +1,53 @@
+// Copyright 2021 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 fuzz
+
+import (
+       "internal/unsafeheader"
+       "unsafe"
+)
+
+// coverage returns a []byte containing unique 8-bit counters for each edge of
+// the instrumented source code. This coverage data will only be generated if
+// `-d=libfuzzer` is set at build time. This can be used to understand the code
+// coverage of a test execution.
+func coverage() []byte {
+       addr := unsafe.Pointer(&_counters)
+       size := uintptr(unsafe.Pointer(&_ecounters)) - uintptr(addr)
+
+       var res []byte
+       *(*unsafeheader.Slice)(unsafe.Pointer(&res)) = unsafeheader.Slice{
+               Data: addr,
+               Len:  int(size),
+               Cap:  int(size),
+       }
+       return res
+}
+
+// ResetCovereage sets all of the counters for each edge of the instrumented
+// source code to 0.
+func ResetCoverage() {
+       cov := coverage()
+       for i := range cov {
+               cov[i] = 0
+       }
+}
+
+// SnapshotCoverage copies the current counter values into coverageSnapshot,
+// preserving them for later inspection.
+func SnapshotCoverage() {
+       cov := coverage()
+       if coverageSnapshot == nil {
+               coverageSnapshot = make([]byte, len(cov))
+       }
+       copy(coverageSnapshot, cov)
+}
+
+var coverageSnapshot []byte
+
+// _counters and _ecounters mark the start and end, respectively, of where
+// the 8-bit coverage counters reside in memory. They're known to cmd/link,
+// which specially assigns their addresses for this purpose.
+var _counters, _ecounters [0]byte
diff --git a/src/internal/fuzz/encoding.go b/src/internal/fuzz/encoding.go
new file mode 100644 (file)
index 0000000..c2f7d22
--- /dev/null
@@ -0,0 +1,240 @@
+// Copyright 2021 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 fuzz
+
+import (
+       "bytes"
+       "fmt"
+       "go/ast"
+       "go/parser"
+       "go/token"
+       "strconv"
+)
+
+// encVersion1 will be the first line of a file with version 1 encoding.
+var encVersion1 = "go test fuzz v1"
+
+// marshalCorpusFile encodes an arbitrary number of arguments into the file format for the
+// corpus.
+func marshalCorpusFile(vals ...interface{}) []byte {
+       if len(vals) == 0 {
+               panic("must have at least one value to marshal")
+       }
+       b := bytes.NewBuffer([]byte(encVersion1))
+       // TODO(katiehockman): keep uint8 and int32 encoding where applicable,
+       // instead of changing to byte and rune respectively.
+       for _, val := range vals {
+               switch t := val.(type) {
+               case int, int8, int16, int64, uint, uint16, uint32, uint64, float32, float64, bool:
+                       fmt.Fprintf(b, "\n%T(%v)", t, t)
+               case string:
+                       fmt.Fprintf(b, "\nstring(%q)", t)
+               case rune: // int32
+                       fmt.Fprintf(b, "\nrune(%q)", t)
+               case byte: // uint8
+                       fmt.Fprintf(b, "\nbyte(%q)", t)
+               case []byte: // []uint8
+                       fmt.Fprintf(b, "\n[]byte(%q)", t)
+               default:
+                       panic(fmt.Sprintf("unsupported type: %T", t))
+               }
+       }
+       return b.Bytes()
+}
+
+// unmarshalCorpusFile decodes corpus bytes into their respective values.
+func unmarshalCorpusFile(b []byte) ([]interface{}, error) {
+       if len(b) == 0 {
+               return nil, fmt.Errorf("cannot unmarshal empty string")
+       }
+       lines := bytes.Split(b, []byte("\n"))
+       if len(lines) < 2 {
+               return nil, fmt.Errorf("must include version and at least one value")
+       }
+       if string(lines[0]) != encVersion1 {
+               return nil, fmt.Errorf("unknown encoding version: %s", lines[0])
+       }
+       var vals []interface{}
+       for _, line := range lines[1:] {
+               line = bytes.TrimSpace(line)
+               if len(line) == 0 {
+                       continue
+               }
+               v, err := parseCorpusValue(line)
+               if err != nil {
+                       return nil, fmt.Errorf("malformed line %q: %v", line, err)
+               }
+               vals = append(vals, v)
+       }
+       return vals, nil
+}
+
+func parseCorpusValue(line []byte) (interface{}, error) {
+       fs := token.NewFileSet()
+       expr, err := parser.ParseExprFrom(fs, "(test)", line, 0)
+       if err != nil {
+               return nil, err
+       }
+       call, ok := expr.(*ast.CallExpr)
+       if !ok {
+               return nil, fmt.Errorf("expected call expression")
+       }
+       if len(call.Args) != 1 {
+               return nil, fmt.Errorf("expected call expression with 1 argument; got %d", len(call.Args))
+       }
+       arg := call.Args[0]
+
+       if arrayType, ok := call.Fun.(*ast.ArrayType); ok {
+               if arrayType.Len != nil {
+                       return nil, fmt.Errorf("expected []byte or primitive type")
+               }
+               elt, ok := arrayType.Elt.(*ast.Ident)
+               if !ok || elt.Name != "byte" {
+                       return nil, fmt.Errorf("expected []byte")
+               }
+               lit, ok := arg.(*ast.BasicLit)
+               if !ok || lit.Kind != token.STRING {
+                       return nil, fmt.Errorf("string literal required for type []byte")
+               }
+               s, err := strconv.Unquote(lit.Value)
+               if err != nil {
+                       return nil, err
+               }
+               return []byte(s), nil
+       }
+
+       idType, ok := call.Fun.(*ast.Ident)
+       if !ok {
+               return nil, fmt.Errorf("expected []byte or primitive type")
+       }
+       if idType.Name == "bool" {
+               id, ok := arg.(*ast.Ident)
+               if !ok {
+                       return nil, fmt.Errorf("malformed bool")
+               }
+               if id.Name == "true" {
+                       return true, nil
+               } else if id.Name == "false" {
+                       return false, nil
+               } else {
+                       return nil, fmt.Errorf("true or false required for type bool")
+               }
+       }
+       var (
+               val  string
+               kind token.Token
+       )
+       if op, ok := arg.(*ast.UnaryExpr); ok {
+               // Special case for negative numbers.
+               lit, ok := op.X.(*ast.BasicLit)
+               if !ok || (lit.Kind != token.INT && lit.Kind != token.FLOAT) {
+                       return nil, fmt.Errorf("expected operation on int or float type")
+               }
+               if op.Op != token.SUB {
+                       return nil, fmt.Errorf("unsupported operation on int: %v", op.Op)
+               }
+               val = op.Op.String() + lit.Value // e.g. "-" + "124"
+               kind = lit.Kind
+       } else {
+               lit, ok := arg.(*ast.BasicLit)
+               if !ok {
+                       return nil, fmt.Errorf("literal value required for primitive type")
+               }
+               val, kind = lit.Value, lit.Kind
+       }
+
+       switch typ := idType.Name; typ {
+       case "string":
+               if kind != token.STRING {
+                       return nil, fmt.Errorf("string literal value required for type string")
+               }
+               return strconv.Unquote(val)
+       case "byte", "rune":
+               if kind != token.CHAR {
+                       return nil, fmt.Errorf("character literal required for byte/rune types")
+               }
+               n := len(val)
+               if n < 2 {
+                       return nil, fmt.Errorf("malformed character literal, missing single quotes")
+               }
+               code, _, _, err := strconv.UnquoteChar(val[1:n-1], '\'')
+               if err != nil {
+                       return nil, err
+               }
+               if typ == "rune" {
+                       return code, nil
+               }
+               if code >= 256 {
+                       return nil, fmt.Errorf("can only encode single byte to a byte type")
+               }
+               return byte(code), nil
+       case "int", "int8", "int16", "int32", "int64":
+               if kind != token.INT {
+                       return nil, fmt.Errorf("integer literal required for int types")
+               }
+               return parseInt(val, typ)
+       case "uint", "uint8", "uint16", "uint32", "uint64":
+               if kind != token.INT {
+                       return nil, fmt.Errorf("integer literal required for uint types")
+               }
+               return parseUint(val, typ)
+       case "float32":
+               if kind != token.FLOAT && kind != token.INT {
+                       return nil, fmt.Errorf("float or integer literal required for float32 type")
+               }
+               v, err := strconv.ParseFloat(val, 32)
+               return float32(v), err
+       case "float64":
+               if kind != token.FLOAT && kind != token.INT {
+                       return nil, fmt.Errorf("float or integer literal required for float64 type")
+               }
+               return strconv.ParseFloat(val, 64)
+       default:
+               return nil, fmt.Errorf("expected []byte or primitive type")
+       }
+}
+
+// parseInt returns an integer of value val and type typ.
+func parseInt(val, typ string) (interface{}, error) {
+       switch typ {
+       case "int":
+               return strconv.Atoi(val)
+       case "int8":
+               i, err := strconv.ParseInt(val, 10, 8)
+               return int8(i), err
+       case "int16":
+               i, err := strconv.ParseInt(val, 10, 16)
+               return int16(i), err
+       case "int32":
+               i, err := strconv.ParseInt(val, 10, 32)
+               return int32(i), err
+       case "int64":
+               return strconv.ParseInt(val, 10, 64)
+       default:
+               panic("unreachable")
+       }
+}
+
+// parseInt returns an unsigned integer of value val and type typ.
+func parseUint(val, typ string) (interface{}, error) {
+       switch typ {
+       case "uint":
+               i, err := strconv.ParseUint(val, 10, 0)
+               return uint(i), err
+       case "uint8":
+               i, err := strconv.ParseUint(val, 10, 8)
+               return uint8(i), err
+       case "uint16":
+               i, err := strconv.ParseUint(val, 10, 16)
+               return uint16(i), err
+       case "uint32":
+               i, err := strconv.ParseUint(val, 10, 32)
+               return uint32(i), err
+       case "uint64":
+               return strconv.ParseUint(val, 10, 64)
+       default:
+               panic("unreachable")
+       }
+}
diff --git a/src/internal/fuzz/encoding_test.go b/src/internal/fuzz/encoding_test.go
new file mode 100644 (file)
index 0000000..3cd8d0e
--- /dev/null
@@ -0,0 +1,122 @@
+// Copyright 2021 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 fuzz
+
+import (
+       "strings"
+       "testing"
+)
+
+func TestUnmarshalMarshal(t *testing.T) {
+       var tests = []struct {
+               in string
+               ok bool
+       }{
+               {
+                       in: "int(1234)",
+                       ok: false, // missing version
+               },
+               {
+                       in: `go test fuzz v1
+string("a"bcad")`,
+                       ok: false, // malformed
+               },
+               {
+                       in: `go test fuzz v1
+int()`,
+                       ok: false, // empty value
+               },
+               {
+                       in: `go test fuzz v1
+uint(-32)`,
+                       ok: false, // invalid negative uint
+               },
+               {
+                       in: `go test fuzz v1
+int8(1234456)`,
+                       ok: false, // int8 too large
+               },
+               {
+                       in: `go test fuzz v1
+int(20*5)`,
+                       ok: false, // expression in int value
+               },
+               {
+                       in: `go test fuzz v1
+int(--5)`,
+                       ok: false, // expression in int value
+               },
+               {
+                       in: `go test fuzz v1
+bool(0)`,
+                       ok: false, // malformed bool
+               },
+               {
+                       in: `go test fuzz v1
+byte('aa)`,
+                       ok: false, // malformed byte
+               },
+               {
+                       in: `go test fuzz v1
+byte('☃')`,
+                       ok: false, // byte out of range
+               },
+               {
+                       in: `go test fuzz v1
+string("extra")
+[]byte("spacing")  
+    `,
+                       ok: true,
+               },
+               {
+                       in: `go test fuzz v1
+float64(0)
+float32(0)
+`,
+                       ok: true, // will be an integer literal since there is no decimal
+               },
+               {
+                       in: `go test fuzz v1
+int(-23)
+int8(-2)
+int64(2342425)
+uint(1)
+uint16(234)
+uint32(352342)
+uint64(123)
+rune('œ')
+byte('K')
+byte('ÿ')
+[]byte("hello¿")
+[]byte("a")
+bool(true)
+string("hello\\xbd\\xb2=\\xbc ⌘")
+float64(-12.5)
+float32(2.5)`,
+                       ok: true,
+               },
+       }
+       for _, test := range tests {
+               t.Run(test.in, func(t *testing.T) {
+                       vals, err := unmarshalCorpusFile([]byte(test.in))
+                       if test.ok && err != nil {
+                               t.Fatalf("unmarshal unexpected error: %v", err)
+                       } else if !test.ok && err == nil {
+                               t.Fatalf("unmarshal unexpected success")
+                       }
+                       if !test.ok {
+                               return // skip the rest of the test
+                       }
+                       newB := marshalCorpusFile(vals...)
+                       if err != nil {
+                               t.Fatalf("marshal unexpected error: %v", err)
+                       }
+                       want := strings.TrimSpace(test.in)
+                       if want != string(newB) {
+                               t.Errorf("values changed after unmarshal then marshal\nbefore: %q\nafter:  %q", want, newB)
+                       }
+               })
+       }
+}
diff --git a/src/internal/fuzz/fuzz.go b/src/internal/fuzz/fuzz.go
new file mode 100644 (file)
index 0000000..4bcfbee
--- /dev/null
@@ -0,0 +1,718 @@
+// 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 fuzz provides common fuzzing functionality for tests built with
+// "go test" and for programs that use fuzzing functionality in the testing
+// package.
+package fuzz
+
+import (
+       "context"
+       "crypto/sha256"
+       "errors"
+       "fmt"
+       "io"
+       "io/ioutil"
+       "os"
+       "path/filepath"
+       "reflect"
+       "runtime"
+       "strings"
+       "time"
+)
+
+// CoordinateFuzzingOpts is a set of arguments for CoordinateFuzzing.
+// The zero value is valid for each field unless specified otherwise.
+type CoordinateFuzzingOpts struct {
+       // Log is a writer for logging progress messages and warnings.
+       // If nil, io.Discard will be used instead.
+       Log io.Writer
+
+       // Timeout is the amount of wall clock time to spend fuzzing after the corpus
+       // has loaded. If zero, there will be no time limit.
+       Timeout time.Duration
+
+       // Limit is the number of random values to generate and test. If zero,
+       // there will be no limit on the number of generated values.
+       Limit int64
+
+       // MinimizeTimeout is the amount of wall clock time to spend minimizing
+       // after discovering a crasher. If zero, there will be no time limit.
+       MinimizeTimeout time.Duration
+
+       // MinimizeLimit is the maximum number of calls to the fuzz function to be
+       // made while minimizing after finding a crash. If zero, there will be
+       // no limit.
+       MinimizeLimit int64
+
+       // parallel is the number of worker processes to run in parallel. If zero,
+       // CoordinateFuzzing will run GOMAXPROCS workers.
+       Parallel int
+
+       // Seed is a list of seed values added by the fuzz target with testing.F.Add
+       // and in testdata.
+       Seed []CorpusEntry
+
+       // Types is the list of types which make up a corpus entry.
+       // Types must be set and must match values in Seed.
+       Types []reflect.Type
+
+       // CorpusDir is a directory where files containing values that crash the
+       // code being tested may be written. CorpusDir must be set.
+       CorpusDir string
+
+       // CacheDir is a directory containing additional "interesting" values.
+       // The fuzzer may derive new values from these, and may write new values here.
+       CacheDir string
+}
+
+// CoordinateFuzzing creates several worker processes and communicates with
+// them to test random inputs that could trigger crashes and expose bugs.
+// The worker processes run the same binary in the same directory with the
+// same environment variables as the coordinator process. Workers also run
+// with the same arguments as the coordinator, except with the -test.fuzzworker
+// flag prepended to the argument list.
+//
+// If a crash occurs, the function will return an error containing information
+// about the crash, which can be reported to the user.
+func CoordinateFuzzing(ctx context.Context, opts CoordinateFuzzingOpts) (err error) {
+       if err := ctx.Err(); err != nil {
+               return err
+       }
+       if opts.Log == nil {
+               opts.Log = io.Discard
+       }
+       if opts.Parallel == 0 {
+               opts.Parallel = runtime.GOMAXPROCS(0)
+       }
+       if opts.Limit > 0 && int64(opts.Parallel) > opts.Limit {
+               // Don't start more workers than we need.
+               opts.Parallel = int(opts.Limit)
+       }
+       canMinimize := false
+       for _, t := range opts.Types {
+               if isMinimizable(t) {
+                       canMinimize = true
+                       break
+               }
+       }
+
+       c, err := newCoordinator(opts)
+       if err != nil {
+               return err
+       }
+
+       if opts.Timeout > 0 {
+               var cancel func()
+               ctx, cancel = context.WithTimeout(ctx, opts.Timeout)
+               defer cancel()
+       }
+
+       // TODO(jayconrod): do we want to support fuzzing different binaries?
+       dir := "" // same as self
+       binPath := os.Args[0]
+       args := append([]string{"-test.fuzzworker"}, os.Args[1:]...)
+       env := os.Environ() // same as self
+
+       // newWorker creates a worker but doesn't start it yet.
+       newWorker := func() (*worker, error) {
+               mem, err := sharedMemTempFile(workerSharedMemSize)
+               if err != nil {
+                       return nil, err
+               }
+               memMu := make(chan *sharedMem, 1)
+               memMu <- mem
+               return &worker{
+                       dir:         dir,
+                       binPath:     binPath,
+                       args:        args,
+                       env:         env[:len(env):len(env)], // copy on append to ensure workers don't overwrite each other.
+                       coordinator: c,
+                       memMu:       memMu,
+               }, nil
+       }
+
+       // fuzzCtx is used to stop workers, for example, after finding a crasher.
+       fuzzCtx, cancelWorkers := context.WithCancel(ctx)
+       defer cancelWorkers()
+       doneC := ctx.Done()
+       inputC := c.inputC
+
+       // stop is called when a worker encounters a fatal error.
+       var fuzzErr error
+       stopping := false
+       stop := func(err error) {
+               if err == fuzzCtx.Err() || isInterruptError(err) {
+                       // Suppress cancellation errors and terminations due to SIGINT.
+                       // The messages are not helpful since either the user triggered the error
+                       // (with ^C) or another more helpful message will be printed (a crasher).
+                       err = nil
+               }
+               if err != nil && (fuzzErr == nil || fuzzErr == ctx.Err()) {
+                       fuzzErr = err
+               }
+               if stopping {
+                       return
+               }
+               stopping = true
+               cancelWorkers()
+               doneC = nil
+               inputC = nil
+       }
+
+       // Start workers.
+       errC := make(chan error)
+       workers := make([]*worker, opts.Parallel)
+       for i := range workers {
+               var err error
+               workers[i], err = newWorker()
+               if err != nil {
+                       return err
+               }
+       }
+       for i := range workers {
+               w := workers[i]
+               go func() {
+                       err := w.coordinate(fuzzCtx)
+                       if fuzzCtx.Err() != nil || isInterruptError(err) {
+                               err = nil
+                       }
+                       cleanErr := w.cleanup()
+                       if err == nil {
+                               err = cleanErr
+                       }
+                       errC <- err
+               }()
+       }
+
+       // Main event loop.
+       // Do not return until all workers have terminated. We avoid a deadlock by
+       // receiving messages from workers even after ctx is cancelled.
+       activeWorkers := len(workers)
+       input, ok := c.nextInput()
+       if !ok {
+               panic("no input")
+       }
+       statTicker := time.NewTicker(3 * time.Second)
+       defer statTicker.Stop()
+       defer c.logStats()
+       crashMinimizing := false
+       crashWritten := false
+
+       for {
+               select {
+               case <-doneC:
+                       // Interrupted, cancelled, or timed out.
+                       // stop sets doneC to nil so we don't busy wait here.
+                       stop(ctx.Err())
+
+               case result := <-c.resultC:
+                       // Received response from worker.
+                       c.updateStats(result)
+                       if c.opts.Limit > 0 && c.count >= c.opts.Limit {
+                               stop(nil)
+                       }
+
+                       if result.crasherMsg != "" {
+                               if canMinimize && !result.minimized {
+                                       // Found a crasher but haven't yet attempted to minimize it.
+                                       // Send it back to a worker for minimization. Disable inputC so
+                                       // other workers don't continue fuzzing.
+                                       if crashMinimizing {
+                                               break
+                                       }
+                                       crashMinimizing = true
+                                       inputC = nil
+                                       fmt.Fprintf(c.opts.Log, "found a crash, minimizing...\n")
+                                       c.minimizeC <- result
+                               } else if !crashWritten {
+                                       // Found a crasher that's either minimized or not minimizable.
+                                       // Write to corpus and stop.
+                                       fileName, err := writeToCorpus(result.entry.Data, opts.CorpusDir)
+                                       if err == nil {
+                                               crashWritten = true
+                                               err = &crashError{
+                                                       name: filepath.Base(fileName),
+                                                       err:  errors.New(result.crasherMsg),
+                                               }
+                                       }
+                                       stop(err)
+                               }
+                       } else if result.coverageData != nil {
+                               foundNew := c.updateCoverage(result.coverageData)
+                               if foundNew && !c.coverageOnlyRun() {
+                                       // Found an interesting value that expanded coverage.
+                                       // This is not a crasher, but we should add it to the
+                                       // on-disk corpus, and prioritize it for future fuzzing.
+                                       // TODO(jayconrod, katiehockman): Prioritize fuzzing these
+                                       // values which expanded coverage, perhaps based on the
+                                       // number of new edges that this result expanded.
+                                       // TODO(jayconrod, katiehockman): Don't write a value that's already
+                                       // in the corpus.
+                                       c.interestingCount++
+                                       c.corpus.entries = append(c.corpus.entries, result.entry)
+                                       if opts.CacheDir != "" {
+                                               if _, err := writeToCorpus(result.entry.Data, opts.CacheDir); err != nil {
+                                                       stop(err)
+                                               }
+                                       }
+                               } else if c.coverageOnlyRun() {
+                                       c.covOnlyInputs--
+                                       if c.covOnlyInputs == 0 {
+                                               // The coordinator has finished getting a baseline for
+                                               // coverage. Tell all of the workers to inialize their
+                                               // baseline coverage data (by setting interestingCount
+                                               // to 0).
+                                               c.interestingCount = 0
+                                       }
+                               }
+                       }
+                       if inputC == nil && !crashMinimizing && !stopping && !c.coverageOnlyRun() {
+                               // Re-enable inputC if it was disabled earlier because we hit the limit
+                               // on the number of inputs to fuzz (nextInput returned false). Workers
+                               // can do less work than requested, so after receiving a result above,
+                               // we might be below the limit now.
+                               if input, ok = c.nextInput(); ok {
+                                       inputC = c.inputC
+                               }
+                       }
+
+               case err := <-errC:
+                       // A worker terminated, possibly after encountering a fatal error.
+                       stop(err)
+                       activeWorkers--
+                       if activeWorkers == 0 {
+                               return fuzzErr
+                       }
+
+               case inputC <- input:
+                       // Send the next input to any worker.
+                       if c.corpusIndex == 0 && c.coverageOnlyRun() {
+                               // The coordinator is currently trying to run all of the corpus
+                               // entries to gather baseline coverage data, and all of the
+                               // inputs have been passed to inputC. Block any more inputs from
+                               // being passed to the workers for now.
+                               inputC = nil
+                       } else if input, ok = c.nextInput(); !ok {
+                               inputC = nil
+                       }
+
+               case <-statTicker.C:
+                       c.logStats()
+               }
+       }
+
+       // TODO(jayconrod,katiehockman): if a crasher can't be written to the corpus,
+       // write to the cache instead.
+}
+
+// crashError wraps a crasher written to the seed corpus. It saves the name
+// of the file where the input causing the crasher was saved. The testing
+// framework uses this to report a command to re-run that specific input.
+type crashError struct {
+       name string
+       err  error
+}
+
+func (e *crashError) Error() string {
+       return e.err.Error()
+}
+
+func (e *crashError) Unwrap() error {
+       return e.err
+}
+
+func (e *crashError) CrashName() string {
+       return e.name
+}
+
+type corpus struct {
+       entries []CorpusEntry
+}
+
+// CorpusEntry represents an individual input for fuzzing.
+//
+// We must use an equivalent type in the testing and testing/internal/testdeps
+// packages, but testing can't import this package directly, and we don't want
+// to export this type from testing. Instead, we use the same struct type and
+// use a type alias (not a defined type) for convenience.
+//
+// TODO: split marshalled and unmarshalled types. In most places, we only need
+// one or the other.
+type CorpusEntry = struct {
+       // Name is the name of the corpus file, if the entry was loaded from the
+       // seed corpus. It can be used with -run. For entries added with f.Add and
+       // entries generated by the mutator, Name is empty.
+       Name string
+
+       // Data is the raw data loaded from a corpus file.
+       Data []byte
+
+       // Values is the unmarshaled values from a corpus file.
+       Values []interface{}
+}
+
+type fuzzInput struct {
+       // entry is the value to test initially. The worker will randomly mutate
+       // values from this starting point.
+       entry CorpusEntry
+
+       // countRequested is the number of values to test. If non-zero, the worker
+       // will stop after testing this many values, if it hasn't already stopped.
+       countRequested int64
+
+       // coverageOnly indicates whether this input is for a coverage-only run. If
+       // true, the input should not be fuzzed.
+       coverageOnly bool
+
+       // interestingCount reflects the coordinator's current interestingCount
+       // value.
+       interestingCount int64
+
+       // coverageData reflects the coordinator's current coverageData.
+       coverageData []byte
+}
+
+type fuzzResult struct {
+       // entry is an interesting value or a crasher.
+       entry CorpusEntry
+
+       // crasherMsg is an error message from a crash. It's "" if no crash was found.
+       crasherMsg string
+
+       // minimized is true if a worker attempted to minimize entry.
+       // Minimization may not have actually been completed.
+       minimized bool
+
+       // coverageData is set if the worker found new coverage.
+       coverageData []byte
+
+       // countRequested is the number of values the coordinator asked the worker
+       // to test. 0 if there was no limit.
+       countRequested int64
+
+       // count is the number of values the worker actually tested.
+       count int64
+
+       // duration is the time the worker spent testing inputs.
+       duration time.Duration
+}
+
+// coordinator holds channels that workers can use to communicate with
+// the coordinator.
+type coordinator struct {
+       opts CoordinateFuzzingOpts
+
+       // startTime is the time we started the workers after loading the corpus.
+       // Used for logging.
+       startTime time.Time
+
+       // inputC is sent values to fuzz by the coordinator. Any worker may receive
+       // values from this channel. Workers send results to resultC.
+       inputC chan fuzzInput
+
+       // minimizeC is sent values to minimize by the coordinator. Any worker may
+       // receive values from this channel. Workers send results to resultC.
+       minimizeC chan fuzzResult
+
+       // resultC is sent results of fuzzing by workers. The coordinator
+       // receives these. Multiple types of messages are allowed.
+       resultC chan fuzzResult
+
+       // count is the number of values fuzzed so far.
+       count int64
+
+       // interestingCount is the number of unique interesting values which have
+       // been found this execution.
+       interestingCount int64
+
+       // covOnlyInputs is the number of entries in the corpus which still need to
+       // be sent to a worker to gather baseline coverage data.
+       covOnlyInputs int
+
+       // duration is the time spent fuzzing inside workers, not counting time
+       // starting up or tearing down.
+       duration time.Duration
+
+       // countWaiting is the number of values the coordinator is currently waiting
+       // for workers to fuzz.
+       countWaiting int64
+
+       // corpus is a set of interesting values, including the seed corpus and
+       // generated values that workers reported as interesting.
+       corpus corpus
+
+       // corpusIndex is the next value to send to workers.
+       // TODO(jayconrod,katiehockman): need a scheduling algorithm that chooses
+       // which corpus value to send next (or generates something new).
+       corpusIndex int
+
+       coverageData []byte
+}
+
+func newCoordinator(opts CoordinateFuzzingOpts) (*coordinator, error) {
+       // Make sure all of the seed corpus has marshalled data.
+       for i := range opts.Seed {
+               if opts.Seed[i].Data == nil {
+                       opts.Seed[i].Data = marshalCorpusFile(opts.Seed[i].Values...)
+               }
+       }
+       corpus, err := readCache(opts.Seed, opts.Types, opts.CacheDir)
+       if err != nil {
+               return nil, err
+       }
+       covOnlyInputs := len(corpus.entries)
+       if len(corpus.entries) == 0 {
+               var vals []interface{}
+               for _, t := range opts.Types {
+                       vals = append(vals, zeroValue(t))
+               }
+               corpus.entries = append(corpus.entries, CorpusEntry{Data: marshalCorpusFile(vals...), Values: vals})
+       }
+       c := &coordinator{
+               opts:          opts,
+               startTime:     time.Now(),
+               inputC:        make(chan fuzzInput),
+               minimizeC:     make(chan fuzzResult),
+               resultC:       make(chan fuzzResult),
+               corpus:        corpus,
+               covOnlyInputs: covOnlyInputs,
+       }
+
+       covSize := len(coverage())
+       if covSize == 0 {
+               fmt.Fprintf(c.opts.Log, "warning: coverage-guided fuzzing is not supported on this platform\n")
+               c.covOnlyInputs = 0
+       } else {
+               // Set c.coverageData to a clean []byte full of zeros.
+               c.coverageData = make([]byte, covSize)
+       }
+
+       if c.covOnlyInputs > 0 {
+               // Set c.interestingCount to -1 so the workers know when the coverage
+               // run is finished and can update their local coverage data.
+               c.interestingCount = -1
+       }
+
+       return c, nil
+}
+
+func (c *coordinator) updateStats(result fuzzResult) {
+       // Adjust total stats.
+       c.count += result.count
+       c.countWaiting -= result.countRequested
+       c.duration += result.duration
+}
+
+func (c *coordinator) logStats() {
+       // TODO(jayconrod,katiehockman): consider printing the amount of coverage
+       // that has been reached so far (perhaps a percentage of edges?)
+       elapsed := time.Since(c.startTime)
+       if c.coverageOnlyRun() {
+               fmt.Fprintf(c.opts.Log, "gathering baseline coverage, elapsed: %.1fs, workers: %d, left: %d\n", elapsed.Seconds(), c.opts.Parallel, c.covOnlyInputs)
+       } else {
+               rate := float64(c.count) / elapsed.Seconds()
+               fmt.Fprintf(c.opts.Log, "fuzzing, elapsed: %.1fs, execs: %d (%.0f/sec), workers: %d, interesting: %d\n", elapsed.Seconds(), c.count, rate, c.opts.Parallel, c.interestingCount)
+       }
+}
+
+// nextInput returns the next value that should be sent to workers.
+// If the number of executions is limited, the returned value includes
+// a limit for one worker. If there are no executions left, nextInput returns
+// a zero value and false.
+func (c *coordinator) nextInput() (fuzzInput, bool) {
+       if c.opts.Limit > 0 && c.count+c.countWaiting >= c.opts.Limit {
+               // Workers already testing all requested inputs.
+               return fuzzInput{}, false
+       }
+       input := fuzzInput{
+               entry:            c.corpus.entries[c.corpusIndex],
+               interestingCount: c.interestingCount,
+               coverageData:     c.coverageData,
+       }
+       c.corpusIndex = (c.corpusIndex + 1) % (len(c.corpus.entries))
+
+       if c.coverageOnlyRun() {
+               // This is a coverage-only run, so this input shouldn't be fuzzed,
+               // and shouldn't be included in the count of generated values.
+               input.coverageOnly = true
+               return input, true
+       }
+
+       if c.opts.Limit > 0 {
+               input.countRequested = c.opts.Limit / int64(c.opts.Parallel)
+               if c.opts.Limit%int64(c.opts.Parallel) > 0 {
+                       input.countRequested++
+               }
+               remaining := c.opts.Limit - c.count - c.countWaiting
+               if input.countRequested > remaining {
+                       input.countRequested = remaining
+               }
+               c.countWaiting += input.countRequested
+       }
+       return input, true
+}
+
+func (c *coordinator) coverageOnlyRun() bool {
+       return c.covOnlyInputs > 0
+}
+
+// updateCoverage updates c.coverageData for all edges that have a higher
+// counter value in newCoverage. It return true if a new edge was hit.
+func (c *coordinator) updateCoverage(newCoverage []byte) bool {
+       if len(newCoverage) != len(c.coverageData) {
+               panic(fmt.Sprintf("num edges changed at runtime: %d, expected %d", len(newCoverage), len(c.coverageData)))
+       }
+       newEdge := false
+       for i := range newCoverage {
+               if newCoverage[i] > c.coverageData[i] {
+                       if c.coverageData[i] == 0 {
+                               newEdge = true
+                       }
+                       c.coverageData[i] = newCoverage[i]
+               }
+       }
+       return newEdge
+}
+
+// readCache creates a combined corpus from seed values and values in the cache
+// (in GOCACHE/fuzz).
+//
+// TODO(jayconrod,katiehockman): need a mechanism that can remove values that
+// aren't useful anymore, for example, because they have the wrong type.
+func readCache(seed []CorpusEntry, types []reflect.Type, cacheDir string) (corpus, error) {
+       var c corpus
+       c.entries = append(c.entries, seed...)
+       entries, err := ReadCorpus(cacheDir, types)
+       if err != nil {
+               if _, ok := err.(*MalformedCorpusError); !ok {
+                       // It's okay if some files in the cache directory are malformed and
+                       // are not included in the corpus, but fail if it's an I/O error.
+                       return corpus{}, err
+               }
+               // TODO(jayconrod,katiehockman): consider printing some kind of warning
+               // indicating the number of files which were skipped because they are
+               // malformed.
+       }
+       c.entries = append(c.entries, entries...)
+       return c, nil
+}
+
+// MalformedCorpusError is an error found while reading the corpus from the
+// filesystem. All of the errors are stored in the errs list. The testing
+// framework uses this to report malformed files in testdata.
+type MalformedCorpusError struct {
+       errs []error
+}
+
+func (e *MalformedCorpusError) Error() string {
+       var msgs []string
+       for _, s := range e.errs {
+               msgs = append(msgs, s.Error())
+       }
+       return strings.Join(msgs, "\n")
+}
+
+// ReadCorpus reads the corpus from the provided dir. The returned corpus
+// entries are guaranteed to match the given types. Any malformed files will
+// be saved in a MalformedCorpusError and returned, along with the most recent
+// error.
+func ReadCorpus(dir string, types []reflect.Type) ([]CorpusEntry, error) {
+       files, err := ioutil.ReadDir(dir)
+       if os.IsNotExist(err) {
+               return nil, nil // No corpus to read
+       } else if err != nil {
+               return nil, fmt.Errorf("reading seed corpus from testdata: %v", err)
+       }
+       var corpus []CorpusEntry
+       var errs []error
+       for _, file := range files {
+               // TODO(jayconrod,katiehockman): determine when a file is a fuzzing input
+               // based on its name. We should only read files created by writeToCorpus.
+               // If we read ALL files, we won't be able to change the file format by
+               // changing the extension. We also won't be able to add files like
+               // README.txt explaining why the directory exists.
+               if file.IsDir() {
+                       continue
+               }
+               filename := filepath.Join(dir, file.Name())
+               data, err := ioutil.ReadFile(filename)
+               if err != nil {
+                       return nil, fmt.Errorf("failed to read corpus file: %v", err)
+               }
+               var vals []interface{}
+               vals, err = readCorpusData(data, types)
+               if err != nil {
+                       errs = append(errs, fmt.Errorf("%q: %v", filename, err))
+                       continue
+               }
+               corpus = append(corpus, CorpusEntry{Name: filename, Data: data, Values: vals})
+       }
+       if len(errs) > 0 {
+               return corpus, &MalformedCorpusError{errs: errs}
+       }
+       return corpus, nil
+}
+
+func readCorpusData(data []byte, types []reflect.Type) ([]interface{}, error) {
+       vals, err := unmarshalCorpusFile(data)
+       if err != nil {
+               return nil, fmt.Errorf("unmarshal: %v", err)
+       }
+       if len(vals) != len(types) {
+               return nil, fmt.Errorf("wrong number of values in corpus file: %d, want %d", len(vals), len(types))
+       }
+       for i := range types {
+               if reflect.TypeOf(vals[i]) != types[i] {
+                       return nil, fmt.Errorf("mismatched types in corpus file: %v, want %v", vals, types)
+               }
+       }
+       return vals, nil
+}
+
+// writeToCorpus atomically writes the given bytes to a new file in testdata.
+// If the directory does not exist, it will create one. If the file already
+// exists, writeToCorpus will not rewrite it. writeToCorpus returns the
+// file's name, or an error if it failed.
+func writeToCorpus(b []byte, dir string) (name string, err error) {
+       sum := fmt.Sprintf("%x", sha256.Sum256(b))
+       name = filepath.Join(dir, sum)
+       if err := os.MkdirAll(dir, 0777); err != nil {
+               return "", err
+       }
+       if err := ioutil.WriteFile(name, b, 0666); err != nil {
+               os.Remove(name) // remove partially written file
+               return "", err
+       }
+       return name, nil
+}
+
+func zeroValue(t reflect.Type) interface{} {
+       for _, v := range zeroVals {
+               if reflect.TypeOf(v) == t {
+                       return v
+               }
+       }
+       panic(fmt.Sprintf("unsupported type: %v", t))
+}
+
+var zeroVals []interface{} = []interface{}{
+       []byte(""),
+       string(""),
+       false,
+       byte(0),
+       rune(0),
+       float32(0),
+       float64(0),
+       int(0),
+       int8(0),
+       int16(0),
+       int32(0),
+       int64(0),
+       uint(0),
+       uint8(0),
+       uint16(0),
+       uint32(0),
+       uint64(0),
+}
diff --git a/src/internal/fuzz/mem.go b/src/internal/fuzz/mem.go
new file mode 100644 (file)
index 0000000..a779232
--- /dev/null
@@ -0,0 +1,131 @@
+// 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 fuzz
+
+import (
+       "fmt"
+       "io/ioutil"
+       "os"
+       "unsafe"
+)
+
+// sharedMem manages access to a region of virtual memory mapped from a file,
+// shared between multiple processes. The region includes space for a header and
+// a value of variable length.
+//
+// When fuzzing, the coordinator creates a sharedMem from a temporary file for
+// each worker. This buffer is used to pass values to fuzz between processes.
+// Care must be taken to manage access to shared memory across processes;
+// sharedMem provides no synchronization on its own. See workerComm for an
+// explanation.
+type sharedMem struct {
+       // f is the file mapped into memory.
+       f *os.File
+
+       // region is the mapped region of virtual memory for f. The content of f may
+       // be read or written through this slice.
+       region []byte
+
+       // removeOnClose is true if the file should be deleted by Close.
+       removeOnClose bool
+
+       // sys contains OS-specific information.
+       sys sharedMemSys
+}
+
+// sharedMemHeader stores metadata in shared memory.
+type sharedMemHeader struct {
+       // count is the number of times the worker has called the fuzz function.
+       // May be reset by coordinator.
+       count int64
+
+       // valueLen is the length of the value that was last fuzzed.
+       valueLen int
+}
+
+// sharedMemSize returns the size needed for a shared memory buffer that can
+// contain values of the given size.
+func sharedMemSize(valueSize int) int {
+       // TODO(jayconrod): set a reasonable maximum size per platform.
+       return int(unsafe.Sizeof(sharedMemHeader{})) + valueSize
+}
+
+// sharedMemTempFile creates a new temporary file of the given size, then maps
+// it into memory. The file will be removed when the Close method is called.
+func sharedMemTempFile(size int) (m *sharedMem, err error) {
+       // Create a temporary file.
+       f, err := ioutil.TempFile("", "fuzz-*")
+       if err != nil {
+               return nil, err
+       }
+       defer func() {
+               if err != nil {
+                       f.Close()
+                       os.Remove(f.Name())
+               }
+       }()
+
+       // Resize it to the correct size.
+       totalSize := sharedMemSize(size)
+       if err := f.Truncate(int64(totalSize)); err != nil {
+               return nil, err
+       }
+
+       // Map the file into memory.
+       removeOnClose := true
+       return sharedMemMapFile(f, totalSize, removeOnClose)
+}
+
+// header returns a pointer to metadata within the shared memory region.
+func (m *sharedMem) header() *sharedMemHeader {
+       return (*sharedMemHeader)(unsafe.Pointer(&m.region[0]))
+}
+
+// valueRef returns the value currently stored in shared memory. The returned
+// slice points to shared memory; it is not a copy.
+func (m *sharedMem) valueRef() []byte {
+       length := m.header().valueLen
+       valueOffset := int(unsafe.Sizeof(sharedMemHeader{}))
+       return m.region[valueOffset : valueOffset+length]
+}
+
+// valueCopy returns a copy of the value stored in shared memory.
+func (m *sharedMem) valueCopy() []byte {
+       ref := m.valueRef()
+       b := make([]byte, len(ref))
+       copy(b, ref)
+       return b
+}
+
+// setValue copies the data in b into the shared memory buffer and sets
+// the length. len(b) must be less than or equal to the capacity of the buffer
+// (as returned by cap(m.value())).
+func (m *sharedMem) setValue(b []byte) {
+       v := m.valueRef()
+       if len(b) > cap(v) {
+               panic(fmt.Sprintf("value length %d larger than shared memory capacity %d", len(b), cap(v)))
+       }
+       m.header().valueLen = len(b)
+       copy(v[:cap(v)], b)
+}
+
+// setValueLen sets the length of the shared memory buffer returned by valueRef
+// to n, which may be at most the cap of that slice.
+//
+// Note that we can only store the length in the shared memory header. The full
+// slice header contains a pointer, which is likely only valid for one process,
+// since each process can map shared memory at a different virtual address.
+func (m *sharedMem) setValueLen(n int) {
+       v := m.valueRef()
+       if n > cap(v) {
+               panic(fmt.Sprintf("length %d larger than shared memory capacity %d", n, cap(v)))
+       }
+       m.header().valueLen = n
+}
+
+// TODO(jayconrod): add method to resize the buffer. We'll need that when the
+// mutator can increase input length. Only the coordinator will be able to
+// do it, since we'll need to send a message to the worker telling it to
+// remap the file.
diff --git a/src/internal/fuzz/minimize.go b/src/internal/fuzz/minimize.go
new file mode 100644 (file)
index 0000000..5164c34
--- /dev/null
@@ -0,0 +1,113 @@
+// Copyright 2021 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 fuzz
+
+import (
+       "math"
+       "reflect"
+)
+
+func isMinimizable(t reflect.Type) bool {
+       for _, v := range zeroVals {
+               if t == reflect.TypeOf(v) {
+                       return true
+               }
+       }
+       return false
+}
+
+func minimizeBytes(v []byte, stillCrashes func(interface{}) bool, shouldStop func() bool) {
+       // First, try to cut the tail.
+       for n := 1024; n != 0; n /= 2 {
+               for len(v) > n {
+                       if shouldStop() {
+                               return
+                       }
+                       candidate := v[:len(v)-n]
+                       if !stillCrashes(candidate) {
+                               break
+                       }
+                       // Set v to the new value to continue iterating.
+                       v = candidate
+               }
+       }
+
+       // Then, try to remove each individual byte.
+       tmp := make([]byte, len(v))
+       for i := 0; i < len(v)-1; i++ {
+               if shouldStop() {
+                       return
+               }
+               candidate := tmp[:len(v)-1]
+               copy(candidate[:i], v[:i])
+               copy(candidate[i:], v[i+1:])
+               if !stillCrashes(candidate) {
+                       continue
+               }
+               // Update v to delete the value at index i.
+               copy(v[i:], v[i+1:])
+               v = v[:len(candidate)]
+               // v[i] is now different, so decrement i to redo this iteration
+               // of the loop with the new value.
+               i--
+       }
+
+       // Then, try to remove each possible subset of bytes.
+       for i := 0; i < len(v)-1; i++ {
+               copy(tmp, v[:i])
+               for j := len(v); j > i+1; j-- {
+                       if shouldStop() {
+                               return
+                       }
+                       candidate := tmp[:len(v)-j+i]
+                       copy(candidate[i:], v[j:])
+                       if !stillCrashes(candidate) {
+                               continue
+                       }
+                       // Update v and reset the loop with the new length.
+                       copy(v[i:], v[j:])
+                       v = v[:len(candidate)]
+                       j = len(v)
+               }
+       }
+
+       return
+}
+
+func minimizeInteger(v uint, stillCrashes func(interface{}) bool, shouldStop func() bool) {
+       // TODO(rolandshoemaker): another approach could be either unsetting/setting all bits
+       // (depending on signed-ness), or rotating bits? When operating on cast signed integers
+       // this would probably be more complex though.
+       for ; v != 0; v /= 10 {
+               if shouldStop() {
+                       return
+               }
+               // We ignore the return value here because there is no point
+               // advancing the loop, since there is nothing after this check,
+               // and we don't return early because a smaller value could
+               // re-trigger the crash.
+               stillCrashes(v)
+       }
+       return
+}
+
+func minimizeFloat(v float64, stillCrashes func(interface{}) bool, shouldStop func() bool) {
+       if math.IsNaN(v) {
+               return
+       }
+       minimized := float64(0)
+       for div := 10.0; minimized < v; div *= 10 {
+               if shouldStop() {
+                       return
+               }
+               minimized = float64(int(v*div)) / div
+               if !stillCrashes(minimized) {
+                       // Since we are searching from least precision -> highest precision we
+                       // can return early since we've already found the smallest value
+                       return
+               }
+       }
+       return
+}
diff --git a/src/internal/fuzz/minimize_test.go b/src/internal/fuzz/minimize_test.go
new file mode 100644 (file)
index 0000000..d786cf8
--- /dev/null
@@ -0,0 +1,210 @@
+// Copyright 2021 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.
+
+//go:build darwin || linux || windows
+// +build darwin linux windows
+
+package fuzz
+
+import (
+       "context"
+       "fmt"
+       "reflect"
+       "testing"
+)
+
+func TestMinimizeInput(t *testing.T) {
+       type testcase struct {
+               fn       func(CorpusEntry) error
+               input    []interface{}
+               expected []interface{}
+       }
+       cases := []testcase{
+               {
+                       fn: func(e CorpusEntry) error {
+                               b := e.Values[0].([]byte)
+                               ones := 0
+                               for _, v := range b {
+                                       if v == 1 {
+                                               ones++
+                                       }
+                               }
+                               if ones == 3 {
+                                       return fmt.Errorf("bad %v", e.Values[0])
+                               }
+                               return nil
+                       },
+                       input:    []interface{}{[]byte{0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}},
+                       expected: []interface{}{[]byte{1, 1, 1}},
+               },
+               {
+                       fn: func(e CorpusEntry) error {
+                               b := e.Values[0].(string)
+                               ones := 0
+                               for _, v := range b {
+                                       if v == '1' {
+                                               ones++
+                                       }
+                               }
+                               if ones == 3 {
+                                       return fmt.Errorf("bad %v", e.Values[0])
+                               }
+                               return nil
+                       },
+                       input:    []interface{}{"001010001000000000000000000"},
+                       expected: []interface{}{"111"},
+               },
+               {
+                       fn: func(e CorpusEntry) error {
+                               i := e.Values[0].(int)
+                               if i > 100 {
+                                       return fmt.Errorf("bad %v", e.Values[0])
+                               }
+                               return nil
+                       },
+                       input:    []interface{}{123456},
+                       expected: []interface{}{123},
+               },
+               {
+                       fn: func(e CorpusEntry) error {
+                               i := e.Values[0].(int8)
+                               if i > 10 {
+                                       return fmt.Errorf("bad %v", e.Values[0])
+                               }
+                               return nil
+                       },
+                       input:    []interface{}{int8(1<<7 - 1)},
+                       expected: []interface{}{int8(12)},
+               },
+               {
+                       fn: func(e CorpusEntry) error {
+                               i := e.Values[0].(int16)
+                               if i > 10 {
+                                       return fmt.Errorf("bad %v", e.Values[0])
+                               }
+                               return nil
+                       },
+                       input:    []interface{}{int16(1<<15 - 1)},
+                       expected: []interface{}{int16(32)},
+               },
+               {
+                       fn: func(e CorpusEntry) error {
+                               i := e.Values[0].(int32)
+                               if i > 10 {
+                                       return fmt.Errorf("bad %v", e.Values[0])
+                               }
+                               return nil
+                       },
+                       input:    []interface{}{int32(1<<31 - 1)},
+                       expected: []interface{}{int32(21)},
+               },
+               {
+                       fn: func(e CorpusEntry) error {
+                               i := e.Values[0].(uint)
+                               if i > 10 {
+                                       return fmt.Errorf("bad %v", e.Values[0])
+                               }
+                               return nil
+                       },
+                       input:    []interface{}{uint(123456)},
+                       expected: []interface{}{uint(12)},
+               },
+               {
+                       fn: func(e CorpusEntry) error {
+                               i := e.Values[0].(uint8)
+                               if i > 10 {
+                                       return fmt.Errorf("bad %v", e.Values[0])
+                               }
+                               return nil
+                       },
+                       input:    []interface{}{uint8(1<<8 - 1)},
+                       expected: []interface{}{uint8(25)},
+               },
+               {
+                       fn: func(e CorpusEntry) error {
+                               i := e.Values[0].(uint16)
+                               if i > 10 {
+                                       return fmt.Errorf("bad %v", e.Values[0])
+                               }
+                               return nil
+                       },
+                       input:    []interface{}{uint16(1<<16 - 1)},
+                       expected: []interface{}{uint16(65)},
+               },
+               {
+                       fn: func(e CorpusEntry) error {
+                               i := e.Values[0].(uint32)
+                               if i > 10 {
+                                       return fmt.Errorf("bad %v", e.Values[0])
+                               }
+                               return nil
+                       },
+                       input:    []interface{}{uint32(1<<32 - 1)},
+                       expected: []interface{}{uint32(42)},
+               },
+               {
+                       fn: func(e CorpusEntry) error {
+                               if i := e.Values[0].(float32); i == 1.23 {
+                                       return nil
+                               }
+                               return fmt.Errorf("bad %v", e.Values[0])
+                       },
+                       input:    []interface{}{float32(1.23456789)},
+                       expected: []interface{}{float32(1.2)},
+               },
+               {
+                       fn: func(e CorpusEntry) error {
+                               if i := e.Values[0].(float64); i == 1.23 {
+                                       return nil
+                               }
+                               return fmt.Errorf("bad %v", e.Values[0])
+                       },
+                       input:    []interface{}{float64(1.23456789)},
+                       expected: []interface{}{float64(1.2)},
+               },
+       }
+
+       // If we are on a 64 bit platform add int64 and uint64 tests
+       if v := int64(1<<63 - 1); int64(int(v)) == v {
+               cases = append(cases, testcase{
+                       fn: func(e CorpusEntry) error {
+                               i := e.Values[0].(int64)
+                               if i > 10 {
+                                       return fmt.Errorf("bad %v", e.Values[0])
+                               }
+                               return nil
+                       },
+                       input:    []interface{}{int64(1<<63 - 1)},
+                       expected: []interface{}{int64(92)},
+               }, testcase{
+                       fn: func(e CorpusEntry) error {
+                               i := e.Values[0].(uint64)
+                               if i > 10 {
+                                       return fmt.Errorf("bad %v", e.Values[0])
+                               }
+                               return nil
+                       },
+                       input:    []interface{}{uint64(1<<64 - 1)},
+                       expected: []interface{}{uint64(18)},
+               })
+       }
+
+       for _, tc := range cases {
+               ws := &workerServer{
+                       fuzzFn: tc.fn,
+               }
+               count := int64(0)
+               vals := tc.input
+               err := ws.minimizeInput(context.Background(), vals, &count, 0)
+               if err == nil {
+                       t.Error("minimizeInput didn't fail")
+               }
+               if expected := fmt.Sprintf("bad %v", tc.input[0]); err.Error() != expected {
+                       t.Errorf("unexpected error: got %s, want %s", err, expected)
+               }
+               if !reflect.DeepEqual(vals, tc.expected) {
+                       t.Errorf("unexpected results: got %v, want %v", vals, tc.expected)
+               }
+       }
+}
diff --git a/src/internal/fuzz/mutator.go b/src/internal/fuzz/mutator.go
new file mode 100644 (file)
index 0000000..d4ca31e
--- /dev/null
@@ -0,0 +1,525 @@
+// 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 fuzz
+
+import (
+       "encoding/binary"
+       "fmt"
+       "math"
+       "reflect"
+       "unsafe"
+)
+
+type mutator struct {
+       r *pcgRand
+}
+
+func newMutator() *mutator {
+       return &mutator{r: newPcgRand()}
+}
+
+func (m *mutator) rand(n int) int {
+       return m.r.intn(n)
+}
+
+func (m *mutator) randByteOrder() binary.ByteOrder {
+       if m.r.bool() {
+               return binary.LittleEndian
+       }
+       return binary.BigEndian
+}
+
+// chooseLen chooses length of range mutation in range [0,n]. It gives
+// preference to shorter ranges.
+func (m *mutator) chooseLen(n int) int {
+       switch x := m.rand(100); {
+       case x < 90:
+               return m.rand(min(8, n)) + 1
+       case x < 99:
+               return m.rand(min(32, n)) + 1
+       default:
+               return m.rand(n) + 1
+       }
+}
+
+func min(a, b int) int {
+       if a < b {
+               return a
+       }
+       return b
+}
+
+// mutate performs several mutations on the provided values.
+func (m *mutator) mutate(vals []interface{}, maxBytes int) {
+       // TODO(katiehockman): pull some of these functions into helper methods and
+       // test that each case is working as expected.
+       // TODO(katiehockman): perform more types of mutations for []byte.
+
+       // maxPerVal will represent the maximum number of bytes that each value be
+       // allowed after mutating, giving an equal amount of capacity to each line.
+       // Allow a little wiggle room for the encoding.
+       maxPerVal := maxBytes/len(vals) - 100
+
+       // Pick a random value to mutate.
+       // TODO: consider mutating more than one value at a time.
+       i := m.rand(len(vals))
+       switch v := vals[i].(type) {
+       case int:
+               vals[i] = int(m.mutateInt(int64(v), maxInt))
+       case int8:
+               vals[i] = int8(m.mutateInt(int64(v), math.MaxInt8))
+       case int16:
+               vals[i] = int16(m.mutateInt(int64(v), math.MaxInt16))
+       case int64:
+               vals[i] = m.mutateInt(v, maxInt)
+       case uint:
+               vals[i] = uint(m.mutateUInt(uint64(v), maxUint))
+       case uint16:
+               vals[i] = uint16(m.mutateUInt(uint64(v), math.MaxUint16))
+       case uint32:
+               vals[i] = uint32(m.mutateUInt(uint64(v), math.MaxUint32))
+       case uint64:
+               vals[i] = m.mutateUInt(uint64(v), maxUint)
+       case float32:
+               vals[i] = float32(m.mutateFloat(float64(v), math.MaxFloat32))
+       case float64:
+               vals[i] = m.mutateFloat(v, math.MaxFloat64)
+       case bool:
+               if m.rand(2) == 1 {
+                       vals[i] = !v // 50% chance of flipping the bool
+               }
+       case rune: // int32
+               vals[i] = rune(m.mutateInt(int64(v), math.MaxInt32))
+       case byte: // uint8
+               vals[i] = byte(m.mutateUInt(uint64(v), math.MaxUint8))
+       case string:
+               // TODO(jayconrod,katiehockman): Keep a []byte somewhere (maybe in
+               // mutator) that we mutate repeatedly to avoid re-allocating the data
+               // every time.
+               if len(v) > maxPerVal {
+                       panic(fmt.Sprintf("cannot mutate bytes of length %d", len(v)))
+               }
+               b := []byte(v)
+               if cap(b) < maxPerVal {
+                       b = append(make([]byte, 0, maxPerVal), b...)
+               }
+               m.mutateBytes(&b)
+               vals[i] = string(b)
+       case []byte:
+               if len(v) > maxPerVal {
+                       panic(fmt.Sprintf("cannot mutate bytes of length %d", len(v)))
+               }
+               if cap(v) < maxPerVal {
+                       v = append(make([]byte, 0, maxPerVal), v...)
+               }
+               m.mutateBytes(&v)
+               vals[i] = v
+       default:
+               panic(fmt.Sprintf("type not supported for mutating: %T", vals[i]))
+       }
+}
+
+func (m *mutator) mutateInt(v, maxValue int64) int64 {
+       numIters := 1 + m.r.exp2()
+       var max int64
+       for iter := 0; iter < numIters; iter++ {
+               max = 100
+               switch m.rand(2) {
+               case 0:
+                       // Add a random number
+                       if v >= maxValue {
+                               iter--
+                               continue
+                       }
+                       if v > 0 && maxValue-v < max {
+                               // Don't let v exceed maxValue
+                               max = maxValue - v
+                       }
+                       v += int64(1 + m.rand(int(max)))
+               case 1:
+                       // Subtract a random number
+                       if v <= -maxValue {
+                               iter--
+                               continue
+                       }
+                       if v < 0 && maxValue+v < max {
+                               // Don't let v drop below -maxValue
+                               max = maxValue + v
+                       }
+                       v -= int64(1 + m.rand(int(max)))
+               }
+       }
+       return v
+}
+
+func (m *mutator) mutateUInt(v, maxValue uint64) uint64 {
+       numIters := 1 + m.r.exp2()
+       var max uint64
+       for iter := 0; iter < numIters; iter++ {
+               max = 100
+               switch m.rand(2) {
+               case 0:
+                       // Add a random number
+                       if v >= maxValue {
+                               iter--
+                               continue
+                       }
+                       if v > 0 && maxValue-v < max {
+                               // Don't let v exceed maxValue
+                               max = maxValue - v
+                       }
+
+                       v += uint64(1 + m.rand(int(max)))
+               case 1:
+                       // Subtract a random number
+                       if v <= 0 {
+                               iter--
+                               continue
+                       }
+                       if v < max {
+                               // Don't let v drop below 0
+                               max = v
+                       }
+                       v -= uint64(1 + m.rand(int(max)))
+               }
+       }
+       return v
+}
+
+func (m *mutator) mutateFloat(v, maxValue float64) float64 {
+       numIters := 1 + m.r.exp2()
+       var max float64
+       for iter := 0; iter < numIters; iter++ {
+               switch m.rand(4) {
+               case 0:
+                       // Add a random number
+                       if v >= maxValue {
+                               iter--
+                               continue
+                       }
+                       max = 100
+                       if v > 0 && maxValue-v < max {
+                               // Don't let v exceed maxValue
+                               max = maxValue - v
+                       }
+                       v += float64(1 + m.rand(int(max)))
+               case 1:
+                       // Subtract a random number
+                       if v <= -maxValue {
+                               iter--
+                               continue
+                       }
+                       max = 100
+                       if v < 0 && maxValue+v < max {
+                               // Don't let v drop below -maxValue
+                               max = maxValue + v
+                       }
+                       v -= float64(1 + m.rand(int(max)))
+               case 2:
+                       // Multiply by a random number
+                       absV := math.Abs(v)
+                       if v == 0 || absV >= maxValue {
+                               iter--
+                               continue
+                       }
+                       max = 10
+                       if maxValue/absV < max {
+                               // Don't let v go beyond the minimum or maximum value
+                               max = maxValue / absV
+                       }
+                       v *= float64(1 + m.rand(int(max)))
+               case 3:
+                       // Divide by a random number
+                       if v == 0 {
+                               iter--
+                               continue
+                       }
+                       v /= float64(1 + m.rand(10))
+               }
+       }
+       return v
+}
+
+func (m *mutator) mutateBytes(ptrB *[]byte) {
+       b := *ptrB
+       defer func() {
+               oldHdr := (*reflect.SliceHeader)(unsafe.Pointer(ptrB))
+               newHdr := (*reflect.SliceHeader)(unsafe.Pointer(&b))
+               if oldHdr.Data != newHdr.Data {
+                       panic("data moved to new address")
+               }
+               *ptrB = b
+       }()
+
+       numIters := 1 + m.r.exp2()
+       for iter := 0; iter < numIters; iter++ {
+               switch m.rand(18) {
+               case 0:
+                       // Remove a range of bytes.
+                       if len(b) <= 1 {
+                               iter--
+                               continue
+                       }
+                       pos0 := m.rand(len(b))
+                       pos1 := pos0 + m.chooseLen(len(b)-pos0)
+                       copy(b[pos0:], b[pos1:])
+                       b = b[:len(b)-(pos1-pos0)]
+               case 1:
+                       // Insert a range of random bytes.
+                       pos := m.rand(len(b) + 1)
+                       n := m.chooseLen(1024)
+                       if len(b)+n >= cap(b) {
+                               iter--
+                               continue
+                       }
+                       b = b[:len(b)+n]
+                       copy(b[pos+n:], b[pos:])
+                       for i := 0; i < n; i++ {
+                               b[pos+i] = byte(m.rand(256))
+                       }
+               case 2:
+                       // Duplicate a range of bytes and insert it into
+                       // a random position
+                       if len(b) <= 1 {
+                               iter--
+                               continue
+                       }
+                       src := m.rand(len(b))
+                       dst := m.rand(len(b))
+                       for dst == src {
+                               dst = m.rand(len(b))
+                       }
+                       n := m.chooseLen(len(b) - src)
+                       if len(b)+n >= cap(b) {
+                               iter--
+                               continue
+                       }
+                       tmp := make([]byte, n)
+                       copy(tmp, b[src:])
+                       b = b[:len(b)+n]
+                       copy(b[dst+n:], b[dst:])
+                       copy(b[dst:], tmp)
+               case 3:
+                       // Overwrite a range of bytes with a randomly selected
+                       // chunk
+                       if len(b) <= 1 {
+                               iter--
+                               continue
+                       }
+                       src := m.rand(len(b))
+                       dst := m.rand(len(b))
+                       for dst == src {
+                               dst = m.rand(len(b))
+                       }
+                       n := m.chooseLen(len(b) - src)
+                       copy(b[dst:], b[src:src+n])
+               case 4:
+                       // Bit flip.
+                       if len(b) == 0 {
+                               iter--
+                               continue
+                       }
+                       pos := m.rand(len(b))
+                       b[pos] ^= 1 << uint(m.rand(8))
+               case 5:
+                       // Set a byte to a random value.
+                       if len(b) == 0 {
+                               iter--
+                               continue
+                       }
+                       pos := m.rand(len(b))
+                       // In order to avoid a no-op (where the random value matches
+                       // the existing value), use XOR instead of just setting to
+                       // the random value.
+                       b[pos] ^= byte(1 + m.rand(255))
+               case 6:
+                       // Swap 2 bytes.
+                       if len(b) <= 1 {
+                               iter--
+                               continue
+                       }
+                       src := m.rand(len(b))
+                       dst := m.rand(len(b))
+                       for dst == src {
+                               dst = m.rand(len(b))
+                       }
+                       b[src], b[dst] = b[dst], b[src]
+               case 7:
+                       // Add/subtract from a byte.
+                       if len(b) == 0 {
+                               iter--
+                               continue
+                       }
+                       pos := m.rand(len(b))
+                       v := byte(m.rand(35) + 1)
+                       if m.r.bool() {
+                               b[pos] += v
+                       } else {
+                               b[pos] -= v
+                       }
+               case 8:
+                       // Add/subtract from a uint16.
+                       if len(b) < 2 {
+                               iter--
+                               continue
+                       }
+                       v := uint16(m.rand(35) + 1)
+                       if m.r.bool() {
+                               v = 0 - v
+                       }
+                       pos := m.rand(len(b) - 1)
+                       enc := m.randByteOrder()
+                       enc.PutUint16(b[pos:], enc.Uint16(b[pos:])+v)
+               case 9:
+                       // Add/subtract from a uint32.
+                       if len(b) < 4 {
+                               iter--
+                               continue
+                       }
+                       v := uint32(m.rand(35) + 1)
+                       if m.r.bool() {
+                               v = 0 - v
+                       }
+                       pos := m.rand(len(b) - 3)
+                       enc := m.randByteOrder()
+                       enc.PutUint32(b[pos:], enc.Uint32(b[pos:])+v)
+               case 10:
+                       // Add/subtract from a uint64.
+                       if len(b) < 8 {
+                               iter--
+                               continue
+                       }
+                       v := uint64(m.rand(35) + 1)
+                       if m.r.bool() {
+                               v = 0 - v
+                       }
+                       pos := m.rand(len(b) - 7)
+                       enc := m.randByteOrder()
+                       enc.PutUint64(b[pos:], enc.Uint64(b[pos:])+v)
+               case 11:
+                       // Replace a byte with an interesting value.
+                       if len(b) == 0 {
+                               iter--
+                               continue
+                       }
+                       pos := m.rand(len(b))
+                       b[pos] = byte(interesting8[m.rand(len(interesting8))])
+               case 12:
+                       // Replace a uint16 with an interesting value.
+                       if len(b) < 2 {
+                               iter--
+                               continue
+                       }
+                       pos := m.rand(len(b) - 1)
+                       v := uint16(interesting16[m.rand(len(interesting16))])
+                       m.randByteOrder().PutUint16(b[pos:], v)
+               case 13:
+                       // Replace a uint32 with an interesting value.
+                       if len(b) < 4 {
+                               iter--
+                               continue
+                       }
+                       pos := m.rand(len(b) - 3)
+                       v := uint32(interesting32[m.rand(len(interesting32))])
+                       m.randByteOrder().PutUint32(b[pos:], v)
+               case 14:
+                       // Insert a range of constant bytes.
+                       if len(b) <= 1 {
+                               iter--
+                               continue
+                       }
+                       dst := m.rand(len(b))
+                       // TODO(rolandshoemaker,katiehockman): 4096 was mainly picked
+                       // randomly. We may want to either pick a much larger value
+                       // (AFL uses 32768, paired with a similar impl to chooseLen
+                       // which biases towards smaller lengths that grow over time),
+                       // or set the max based on characteristics of the corpus
+                       // (libFuzzer sets a min/max based on the min/max size of
+                       // entries in the corpus and then picks uniformly from
+                       // that range).
+                       n := m.chooseLen(4096)
+                       if len(b)+n >= cap(b) {
+                               iter--
+                               continue
+                       }
+                       b = b[:len(b)+n]
+                       copy(b[dst+n:], b[dst:])
+                       rb := byte(m.rand(256))
+                       for i := dst; i < dst+n; i++ {
+                               b[i] = rb
+                       }
+               case 15:
+                       // Overwrite a range of bytes with a chunk of
+                       // constant bytes.
+                       if len(b) <= 1 {
+                               iter--
+                               continue
+                       }
+                       dst := m.rand(len(b))
+                       n := m.chooseLen(len(b) - dst)
+                       rb := byte(m.rand(256))
+                       for i := dst; i < dst+n; i++ {
+                               b[i] = rb
+                       }
+               case 16:
+                       // Shuffle a range of bytes
+                       if len(b) <= 1 {
+                               iter--
+                               continue
+                       }
+                       dst := m.rand(len(b))
+                       n := m.chooseLen(len(b) - dst)
+                       if n <= 2 {
+                               iter--
+                               continue
+                       }
+                       // Start at the end of the range, and iterate backwards
+                       // to dst, swapping each element with another element in
+                       // dst:dst+n (Fisher-Yates shuffle).
+                       for i := n - 1; i > 0; i-- {
+                               j := m.rand(i + 1)
+                               b[dst+i], b[dst+j] = b[dst+j], b[dst+i]
+                       }
+               case 17:
+                       // Swap two chunks
+                       if len(b) <= 1 {
+                               iter--
+                               continue
+                       }
+                       src := m.rand(len(b))
+                       dst := m.rand(len(b))
+                       for dst == src {
+                               dst = m.rand(len(b))
+                       }
+                       n := m.chooseLen(len(b) - src)
+                       tmp := make([]byte, n)
+                       copy(tmp, b[dst:])
+                       copy(b[dst:], b[src:src+n])
+                       copy(b[src:], tmp)
+               default:
+                       panic("unknown mutator")
+               }
+       }
+}
+
+var (
+       interesting8  = []int8{-128, -1, 0, 1, 16, 32, 64, 100, 127}
+       interesting16 = []int16{-32768, -129, 128, 255, 256, 512, 1000, 1024, 4096, 32767}
+       interesting32 = []int32{-2147483648, -100663046, -32769, 32768, 65535, 65536, 100663045, 2147483647}
+)
+
+const (
+       maxUint = uint64(^uint(0))
+       maxInt  = int64(maxUint >> 1)
+)
+
+func init() {
+       for _, v := range interesting8 {
+               interesting16 = append(interesting16, int16(v))
+       }
+       for _, v := range interesting16 {
+               interesting32 = append(interesting32, int32(v))
+       }
+}
diff --git a/src/internal/fuzz/mutator_test.go b/src/internal/fuzz/mutator_test.go
new file mode 100644 (file)
index 0000000..b1b5311
--- /dev/null
@@ -0,0 +1,30 @@
+// Copyright 2021 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 fuzz
+
+import (
+       "strconv"
+       "testing"
+)
+
+func BenchmarkMutatorBytes(b *testing.B) {
+       for _, size := range []int{
+               1,
+               10,
+               100,
+               1000,
+               10000,
+               100000,
+       } {
+               size := size
+               b.Run(strconv.Itoa(size), func(b *testing.B) {
+                       vals := []interface{}{make([]byte, size)}
+                       m := newMutator()
+                       for i := 0; i < b.N; i++ {
+                               m.mutate(vals, workerSharedMemSize)
+                       }
+               })
+       }
+}
diff --git a/src/internal/fuzz/pcg.go b/src/internal/fuzz/pcg.go
new file mode 100644 (file)
index 0000000..18e553b
--- /dev/null
@@ -0,0 +1,124 @@
+// 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 fuzz
+
+import (
+       "math/bits"
+       "os"
+       "strconv"
+       "strings"
+       "sync/atomic"
+       "time"
+)
+
+// The functions in pcg implement a 32 bit PRNG with a 64 bit period: pcg xsh rr
+// 64 32. See https://www.pcg-random.org/ for more information. This
+// implementation is geared specifically towards the needs of fuzzing: Simple
+// creation and use, no reproducibility, no concurrency safety, just the
+// necessary methods, optimized for speed.
+
+var globalInc uint64 // PCG stream
+
+const multiplier uint64 = 6364136223846793005
+
+// pcgRand is a PRNG. It should not be copied or shared. No Rand methods are
+// concurrency safe.
+type pcgRand struct {
+       noCopy noCopy // help avoid mistakes: ask vet to ensure that we don't make a copy
+       state  uint64
+       inc    uint64
+}
+
+func godebugSeed() *int {
+       debug := strings.Split(os.Getenv("GODEBUG"), ",")
+       for _, f := range debug {
+               if strings.HasPrefix(f, "fuzzseed=") {
+                       seed, err := strconv.Atoi(strings.TrimPrefix(f, "fuzzseed="))
+                       if err != nil {
+                               panic("malformed fuzzseed")
+                       }
+                       return &seed
+               }
+       }
+       return nil
+}
+
+// newPcgRand generates a new, seeded Rand, ready for use.
+func newPcgRand() *pcgRand {
+       r := new(pcgRand)
+       now := uint64(time.Now().UnixNano())
+       if seed := godebugSeed(); seed != nil {
+               now = uint64(*seed)
+       }
+       inc := atomic.AddUint64(&globalInc, 1)
+       r.state = now
+       r.inc = (inc << 1) | 1
+       r.step()
+       r.state += now
+       r.step()
+       return r
+}
+
+func (r *pcgRand) step() {
+       r.state *= multiplier
+       r.state += r.inc
+}
+
+// uint32 returns a pseudo-random uint32.
+func (r *pcgRand) uint32() uint32 {
+       x := r.state
+       r.step()
+       return bits.RotateLeft32(uint32(((x>>18)^x)>>27), -int(x>>59))
+}
+
+// intn returns a pseudo-random number in [0, n).
+// n must fit in a uint32.
+func (r *pcgRand) intn(n int) int {
+       if int(uint32(n)) != n {
+               panic("large Intn")
+       }
+       return int(r.uint32n(uint32(n)))
+}
+
+// uint32n returns a pseudo-random number in [0, n).
+//
+// For implementation details, see:
+// https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction
+// https://lemire.me/blog/2016/06/30/fast-random-shuffling
+func (r *pcgRand) uint32n(n uint32) uint32 {
+       v := r.uint32()
+       prod := uint64(v) * uint64(n)
+       low := uint32(prod)
+       if low < n {
+               thresh := uint32(-int32(n)) % n
+               for low < thresh {
+                       v = r.uint32()
+                       prod = uint64(v) * uint64(n)
+                       low = uint32(prod)
+               }
+       }
+       return uint32(prod >> 32)
+}
+
+// exp2 generates n with probability 1/2^(n+1).
+func (r *pcgRand) exp2() int {
+       return bits.TrailingZeros32(r.uint32())
+}
+
+// bool generates a random bool.
+func (r *pcgRand) bool() bool {
+       return r.uint32()&1 == 0
+}
+
+// noCopy may be embedded into structs which must not be copied
+// after the first use.
+//
+// See https://golang.org/issues/8005#issuecomment-190753527
+// for details.
+type noCopy struct{}
+
+// lock is a no-op used by -copylocks checker from `go vet`.
+func (*noCopy) lock()   {}
+func (*noCopy) unlock() {}
diff --git a/src/internal/fuzz/sys_posix.go b/src/internal/fuzz/sys_posix.go
new file mode 100644 (file)
index 0000000..8ea84d2
--- /dev/null
@@ -0,0 +1,92 @@
+// 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.
+
+//go:build darwin || linux
+// +build darwin linux
+
+package fuzz
+
+import (
+       "fmt"
+       "os"
+       "os/exec"
+       "syscall"
+)
+
+type sharedMemSys struct{}
+
+func sharedMemMapFile(f *os.File, size int, removeOnClose bool) (*sharedMem, error) {
+       prot := syscall.PROT_READ | syscall.PROT_WRITE
+       flags := syscall.MAP_FILE | syscall.MAP_SHARED
+       region, err := syscall.Mmap(int(f.Fd()), 0, size, prot, flags)
+       if err != nil {
+               return nil, err
+       }
+
+       return &sharedMem{f: f, region: region, removeOnClose: removeOnClose}, nil
+}
+
+// Close unmaps the shared memory and closes the temporary file. If this
+// sharedMem was created with sharedMemTempFile, Close also removes the file.
+func (m *sharedMem) Close() error {
+       // Attempt all operations, even if we get an error for an earlier operation.
+       // os.File.Close may fail due to I/O errors, but we still want to delete
+       // the temporary file.
+       var errs []error
+       errs = append(errs,
+               syscall.Munmap(m.region),
+               m.f.Close())
+       if m.removeOnClose {
+               errs = append(errs, os.Remove(m.f.Name()))
+       }
+       for _, err := range errs {
+               if err != nil {
+                       return err
+               }
+       }
+       return nil
+}
+
+// setWorkerComm configures communciation channels on the cmd that will
+// run a worker process.
+func setWorkerComm(cmd *exec.Cmd, comm workerComm) {
+       mem := <-comm.memMu
+       memFile := mem.f
+       comm.memMu <- mem
+       cmd.ExtraFiles = []*os.File{comm.fuzzIn, comm.fuzzOut, memFile}
+}
+
+// getWorkerComm returns communication channels in the worker process.
+func getWorkerComm() (comm workerComm, err error) {
+       fuzzIn := os.NewFile(3, "fuzz_in")
+       fuzzOut := os.NewFile(4, "fuzz_out")
+       memFile := os.NewFile(5, "fuzz_mem")
+       fi, err := memFile.Stat()
+       if err != nil {
+               return workerComm{}, err
+       }
+       size := int(fi.Size())
+       if int64(size) != fi.Size() {
+               return workerComm{}, fmt.Errorf("fuzz temp file exceeds maximum size")
+       }
+       removeOnClose := false
+       mem, err := sharedMemMapFile(memFile, size, removeOnClose)
+       if err != nil {
+               return workerComm{}, err
+       }
+       memMu := make(chan *sharedMem, 1)
+       memMu <- mem
+       return workerComm{fuzzIn: fuzzIn, fuzzOut: fuzzOut, memMu: memMu}, nil
+}
+
+// isInterruptError returns whether an error was returned by a process that
+// was terminated by an interrupt signal (SIGINT).
+func isInterruptError(err error) bool {
+       exitErr, ok := err.(*exec.ExitError)
+       if !ok || exitErr.ExitCode() >= 0 {
+               return false
+       }
+       status := exitErr.Sys().(syscall.WaitStatus)
+       return status.Signal() == syscall.SIGINT || status.Signal() == syscall.SIGKILL
+}
diff --git a/src/internal/fuzz/sys_unimplemented.go b/src/internal/fuzz/sys_unimplemented.go
new file mode 100644 (file)
index 0000000..5f80379
--- /dev/null
@@ -0,0 +1,36 @@
+// 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.
+
+// TODO(jayconrod): support more platforms.
+//go:build !darwin && !linux && !windows
+// +build !darwin,!linux,!windows
+
+package fuzz
+
+import (
+       "os"
+       "os/exec"
+)
+
+type sharedMemSys struct{}
+
+func sharedMemMapFile(f *os.File, size int, removeOnClose bool) (*sharedMem, error) {
+       panic("not implemented")
+}
+
+func (m *sharedMem) Close() error {
+       panic("not implemented")
+}
+
+func setWorkerComm(cmd *exec.Cmd, comm workerComm) {
+       panic("not implemented")
+}
+
+func getWorkerComm() (comm workerComm, err error) {
+       panic("not implemented")
+}
+
+func isInterruptError(err error) bool {
+       panic("not implemented")
+}
diff --git a/src/internal/fuzz/sys_windows.go b/src/internal/fuzz/sys_windows.go
new file mode 100644 (file)
index 0000000..286501b
--- /dev/null
@@ -0,0 +1,142 @@
+// 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 fuzz
+
+import (
+       "fmt"
+       "os"
+       "os/exec"
+       "reflect"
+       "syscall"
+       "unsafe"
+)
+
+type sharedMemSys struct {
+       mapObj syscall.Handle
+}
+
+func sharedMemMapFile(f *os.File, size int, removeOnClose bool) (mem *sharedMem, err error) {
+       defer func() {
+               if err != nil {
+                       err = fmt.Errorf("mapping temporary file %s: %w", f.Name(), err)
+               }
+       }()
+
+       // Create a file mapping object. The object itself is not shared.
+       mapObj, err := syscall.CreateFileMapping(
+               syscall.Handle(f.Fd()), // fhandle
+               nil,                    // sa
+               syscall.PAGE_READWRITE, // prot
+               0,                      // maxSizeHigh
+               0,                      // maxSizeLow
+               nil,                    // name
+       )
+       if err != nil {
+               return nil, err
+       }
+
+       // Create a view from the file mapping object.
+       access := uint32(syscall.FILE_MAP_READ | syscall.FILE_MAP_WRITE)
+       addr, err := syscall.MapViewOfFile(
+               mapObj,        // handle
+               access,        // access
+               0,             // offsetHigh
+               0,             // offsetLow
+               uintptr(size), // length
+       )
+       if err != nil {
+               syscall.CloseHandle(mapObj)
+               return nil, err
+       }
+
+       var region []byte
+       header := (*reflect.SliceHeader)(unsafe.Pointer(&region))
+       header.Data = addr
+       header.Len = size
+       header.Cap = size
+       return &sharedMem{
+               f:             f,
+               region:        region,
+               removeOnClose: removeOnClose,
+               sys:           sharedMemSys{mapObj: mapObj},
+       }, nil
+}
+
+// Close unmaps the shared memory and closes the temporary file. If this
+// sharedMem was created with sharedMemTempFile, Close also removes the file.
+func (m *sharedMem) Close() error {
+       // Attempt all operations, even if we get an error for an earlier operation.
+       // os.File.Close may fail due to I/O errors, but we still want to delete
+       // the temporary file.
+       var errs []error
+       errs = append(errs,
+               syscall.UnmapViewOfFile(uintptr(unsafe.Pointer(&m.region[0]))),
+               syscall.CloseHandle(m.sys.mapObj),
+               m.f.Close())
+       if m.removeOnClose {
+               errs = append(errs, os.Remove(m.f.Name()))
+       }
+       for _, err := range errs {
+               if err != nil {
+                       return err
+               }
+       }
+       return nil
+}
+
+// setWorkerComm configures communciation channels on the cmd that will
+// run a worker process.
+func setWorkerComm(cmd *exec.Cmd, comm workerComm) {
+       mem := <-comm.memMu
+       memName := mem.f.Name()
+       comm.memMu <- mem
+       syscall.SetHandleInformation(syscall.Handle(comm.fuzzIn.Fd()), syscall.HANDLE_FLAG_INHERIT, 1)
+       syscall.SetHandleInformation(syscall.Handle(comm.fuzzOut.Fd()), syscall.HANDLE_FLAG_INHERIT, 1)
+       cmd.Env = append(cmd.Env, fmt.Sprintf("GO_TEST_FUZZ_WORKER_HANDLES=%x,%x,%q", comm.fuzzIn.Fd(), comm.fuzzOut.Fd(), memName))
+       cmd.SysProcAttr = &syscall.SysProcAttr{AdditionalInheritedHandles: []syscall.Handle{syscall.Handle(comm.fuzzIn.Fd()), syscall.Handle(comm.fuzzOut.Fd())}}
+}
+
+// getWorkerComm returns communication channels in the worker process.
+func getWorkerComm() (comm workerComm, err error) {
+       v := os.Getenv("GO_TEST_FUZZ_WORKER_HANDLES")
+       if v == "" {
+               return workerComm{}, fmt.Errorf("GO_TEST_FUZZ_WORKER_HANDLES not set")
+       }
+       var fuzzInFD, fuzzOutFD uintptr
+       var memName string
+       if _, err := fmt.Sscanf(v, "%x,%x,%q", &fuzzInFD, &fuzzOutFD, &memName); err != nil {
+               return workerComm{}, fmt.Errorf("parsing GO_TEST_FUZZ_WORKER_HANDLES=%s: %v", v, err)
+       }
+
+       fuzzIn := os.NewFile(fuzzInFD, "fuzz_in")
+       fuzzOut := os.NewFile(fuzzOutFD, "fuzz_out")
+       tmpFile, err := os.OpenFile(memName, os.O_RDWR, 0)
+       if err != nil {
+               return workerComm{}, fmt.Errorf("worker opening temp file: %w", err)
+       }
+       fi, err := tmpFile.Stat()
+       if err != nil {
+               return workerComm{}, fmt.Errorf("worker checking temp file size: %w", err)
+       }
+       size := int(fi.Size())
+       if int64(size) != fi.Size() {
+               return workerComm{}, fmt.Errorf("fuzz temp file exceeds maximum size")
+       }
+       removeOnClose := false
+       mem, err := sharedMemMapFile(tmpFile, size, removeOnClose)
+       if err != nil {
+               return workerComm{}, err
+       }
+       memMu := make(chan *sharedMem, 1)
+       memMu <- mem
+
+       return workerComm{fuzzIn: fuzzIn, fuzzOut: fuzzOut, memMu: memMu}, nil
+}
+
+func isInterruptError(err error) bool {
+       // On Windows, we can't tell whether the process was interrupted by the error
+       // returned by Wait. It looks like an ExitError with status 1.
+       return false
+}
diff --git a/src/internal/fuzz/trace.go b/src/internal/fuzz/trace.go
new file mode 100644 (file)
index 0000000..f70b1a6
--- /dev/null
@@ -0,0 +1,29 @@
+// Copyright 2021 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.
+
+// +build !libfuzzer
+
+package fuzz
+
+import _ "unsafe" // for go:linkname
+
+//go:linkname libfuzzerTraceCmp1 runtime.libfuzzerTraceCmp1
+//go:linkname libfuzzerTraceCmp2 runtime.libfuzzerTraceCmp2
+//go:linkname libfuzzerTraceCmp4 runtime.libfuzzerTraceCmp4
+//go:linkname libfuzzerTraceCmp8 runtime.libfuzzerTraceCmp8
+
+//go:linkname libfuzzerTraceConstCmp1 runtime.libfuzzerTraceConstCmp1
+//go:linkname libfuzzerTraceConstCmp2 runtime.libfuzzerTraceConstCmp2
+//go:linkname libfuzzerTraceConstCmp4 runtime.libfuzzerTraceConstCmp4
+//go:linkname libfuzzerTraceConstCmp8 runtime.libfuzzerTraceConstCmp8
+
+func libfuzzerTraceCmp1(arg0, arg1 uint8)  {}
+func libfuzzerTraceCmp2(arg0, arg1 uint16) {}
+func libfuzzerTraceCmp4(arg0, arg1 uint32) {}
+func libfuzzerTraceCmp8(arg0, arg1 uint64) {}
+
+func libfuzzerTraceConstCmp1(arg0, arg1 uint8)  {}
+func libfuzzerTraceConstCmp2(arg0, arg1 uint16) {}
+func libfuzzerTraceConstCmp4(arg0, arg1 uint32) {}
+func libfuzzerTraceConstCmp8(arg0, arg1 uint64) {}
diff --git a/src/internal/fuzz/worker.go b/src/internal/fuzz/worker.go
new file mode 100644 (file)
index 0000000..2bfd9fc
--- /dev/null
@@ -0,0 +1,978 @@
+// 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 fuzz
+
+import (
+       "context"
+       "encoding/json"
+       "errors"
+       "fmt"
+       "io"
+       "io/ioutil"
+       "os"
+       "os/exec"
+       "runtime"
+       "sync"
+       "time"
+)
+
+const (
+       // workerFuzzDuration is the amount of time a worker can spend testing random
+       // variations of an input given by the coordinator.
+       workerFuzzDuration = 100 * time.Millisecond
+
+       // workerTimeoutDuration is the amount of time a worker can go without
+       // responding to the coordinator before being stopped.
+       workerTimeoutDuration = 1 * time.Second
+
+       // workerExitCode is used as an exit code by fuzz worker processes after an internal error.
+       // This distinguishes internal errors from uncontrolled panics and other crashes.
+       // Keep in sync with internal/fuzz.workerExitCode.
+       workerExitCode = 70
+
+       // workerSharedMemSize is the maximum size of the shared memory file used to
+       // communicate with workers. This limits the size of fuzz inputs.
+       workerSharedMemSize = 100 << 20 // 100 MB
+)
+
+// worker manages a worker process running a test binary. The worker object
+// exists only in the coordinator (the process started by 'go test -fuzz').
+// workerClient is used by the coordinator to send RPCs to the worker process,
+// which handles them with workerServer.
+type worker struct {
+       dir     string   // working directory, same as package directory
+       binPath string   // path to test executable
+       args    []string // arguments for test executable
+       env     []string // environment for test executable
+
+       coordinator *coordinator
+
+       memMu chan *sharedMem // mutex guarding shared memory with worker; persists across processes.
+
+       cmd         *exec.Cmd     // current worker process
+       client      *workerClient // used to communicate with worker process
+       waitErr     error         // last error returned by wait, set before termC is closed.
+       interrupted bool          // true after stop interrupts a running worker.
+       termC       chan struct{} // closed by wait when worker process terminates
+}
+
+// cleanup releases persistent resources associated with the worker.
+func (w *worker) cleanup() error {
+       mem := <-w.memMu
+       if mem == nil {
+               return nil
+       }
+       close(w.memMu)
+       return mem.Close()
+}
+
+// coordinate runs the test binary to perform fuzzing.
+//
+// coordinate loops until ctx is cancelled or a fatal error is encountered.
+// If a test process terminates unexpectedly while fuzzing, coordinate will
+// attempt to restart and continue unless the termination can be attributed
+// to an interruption (from a timer or the user).
+//
+// While looping, coordinate receives inputs from the coordinator, passes
+// those inputs to the worker process, then passes the results back to
+// the coordinator.
+func (w *worker) coordinate(ctx context.Context) error {
+       // interestingCount starts at -1, like the coordinator does, so that the
+       // worker client's coverage data is updated after a coverage-only run.
+       interestingCount := int64(-1)
+
+       // Main event loop.
+       for {
+               // Start or restart the worker if it's not running.
+               if !w.isRunning() {
+                       if err := w.startAndPing(ctx); err != nil {
+                               return err
+                       }
+               }
+
+               select {
+               case <-ctx.Done():
+                       // Worker was told to stop.
+                       err := w.stop()
+                       if err != nil && !w.interrupted && !isInterruptError(err) {
+                               return err
+                       }
+                       return ctx.Err()
+
+               case <-w.termC:
+                       // Worker process terminated unexpectedly while waiting for input.
+                       err := w.stop()
+                       if w.interrupted {
+                               panic("worker interrupted after unexpected termination")
+                       }
+                       if err == nil || isInterruptError(err) {
+                               // Worker stopped, either by exiting with status 0 or after being
+                               // interrupted with a signal that was not sent by the coordinator.
+                               //
+                               // When the user presses ^C, on POSIX platforms, SIGINT is delivered to
+                               // all processes in the group concurrently, and the worker may see it
+                               // before the coordinator. The worker should exit 0 gracefully (in
+                               // theory).
+                               //
+                               // This condition is probably intended by the user, so suppress
+                               // the error.
+                               return nil
+                       }
+                       if exitErr, ok := err.(*exec.ExitError); ok && exitErr.ExitCode() == workerExitCode {
+                               // Worker exited with a code indicating F.Fuzz was not called correctly,
+                               // for example, F.Fail was called first.
+                               return fmt.Errorf("fuzzing process exited unexpectedly due to an internal failure: %w", err)
+                       }
+                       // Worker exited non-zero or was terminated by a non-interrupt signal
+                       // (for example, SIGSEGV) while fuzzing.
+                       return fmt.Errorf("fuzzing process terminated unexpectedly: %w", err)
+                       // TODO(jayconrod,katiehockman): if -keepfuzzing, restart worker.
+
+               case input := <-w.coordinator.inputC:
+                       // Received input from coordinator.
+                       args := fuzzArgs{Limit: input.countRequested, Timeout: workerFuzzDuration, CoverageOnly: input.coverageOnly}
+                       if interestingCount < input.interestingCount {
+                               // The coordinator's coverage data has changed, so send the data
+                               // to the client.
+                               args.CoverageData = input.coverageData
+                       }
+                       value, resp, err := w.client.fuzz(ctx, input.entry.Data, args)
+                       if err != nil {
+                               // Error communicating with worker.
+                               w.stop()
+                               if ctx.Err() != nil {
+                                       // Timeout or interruption.
+                                       return ctx.Err()
+                               }
+                               if w.interrupted {
+                                       // Communication error before we stopped the worker.
+                                       // Report an error, but don't record a crasher.
+                                       return fmt.Errorf("communicating with fuzzing process: %v", err)
+                               }
+                               if w.waitErr == nil || isInterruptError(w.waitErr) {
+                                       // Worker stopped, either by exiting with status 0 or after being
+                                       // interrupted with a signal (not sent by coordinator). See comment in
+                                       // termC case above.
+                                       //
+                                       // Since we expect I/O errors around interrupts, ignore this error.
+                                       return nil
+                               }
+                               // Unexpected termination. Set error message and fall through.
+                               // We'll restart the worker on the next iteration.
+                               resp.Err = fmt.Sprintf("fuzzing process terminated unexpectedly: %v", w.waitErr)
+                       }
+                       result := fuzzResult{
+                               countRequested: input.countRequested,
+                               count:          resp.Count,
+                               duration:       resp.Duration,
+                       }
+                       if resp.Err != "" {
+                               result.entry = CorpusEntry{Data: value}
+                               result.crasherMsg = resp.Err
+                       } else if resp.CoverageData != nil {
+                               result.entry = CorpusEntry{Data: value}
+                               result.coverageData = resp.CoverageData
+                       }
+                       w.coordinator.resultC <- result
+
+               case crasher := <-w.coordinator.minimizeC:
+                       // Received input to minimize from coordinator.
+                       minRes, err := w.minimize(ctx, crasher)
+                       if err != nil {
+                               // Failed to minimize. Send back the original crash.
+                               fmt.Fprintln(w.coordinator.opts.Log, err)
+                               minRes = crasher
+                               minRes.minimized = true
+                       }
+                       w.coordinator.resultC <- minRes
+               }
+       }
+}
+
+// minimize tells a worker process to attempt to find a smaller value that
+// causes an error. minimize may restart the worker repeatedly if the error
+// causes (or already caused) the worker process to terminate.
+//
+// TODO: support minimizing inputs that expand coverage in a specific way,
+// for example, by ensuring that an input activates a specific set of counters.
+func (w *worker) minimize(ctx context.Context, input fuzzResult) (min fuzzResult, err error) {
+       if w.coordinator.opts.MinimizeTimeout != 0 {
+               var cancel func()
+               ctx, cancel = context.WithTimeout(ctx, w.coordinator.opts.MinimizeTimeout)
+               defer cancel()
+       }
+
+       min = input
+       min.minimized = true
+
+       args := minimizeArgs{
+               Limit:   w.coordinator.opts.MinimizeLimit,
+               Timeout: w.coordinator.opts.MinimizeTimeout,
+       }
+       value, resp, err := w.client.minimize(ctx, input.entry.Data, args)
+       if err != nil {
+               // Error communicating with worker.
+               w.stop()
+               if ctx.Err() != nil || w.interrupted || isInterruptError(w.waitErr) {
+                       // Worker was interrupted, possibly by the user pressing ^C.
+                       // Normally, workers can handle interrupts and timeouts gracefully and
+                       // will return without error. An error here indicates the worker
+                       // may not have been in a good state, but the error won't be meaningful
+                       // to the user. Just return the original crasher without logging anything.
+                       return min, nil
+               }
+               return fuzzResult{}, fmt.Errorf("fuzzing process terminated unexpectedly while minimizing: %w", w.waitErr)
+       }
+
+       if resp.Err == "" {
+               // Minimization did not find a smaller input that caused a crash.
+               return min, nil
+       }
+       min.crasherMsg = resp.Err
+       min.count = resp.Count
+       min.duration = resp.Duration
+       min.entry.Data = value
+       return min, nil
+}
+
+func (w *worker) isRunning() bool {
+       return w.cmd != nil
+}
+
+// startAndPing starts the worker process and sends it a message to make sure it
+// can communicate.
+//
+// startAndPing returns an error if any part of this didn't work, including if
+// the context is expired or the worker process was interrupted before it
+// responded. Errors that happen after start but before the ping response
+// likely indicate that the worker did not call F.Fuzz or called F.Fail first.
+// We don't record crashers for these errors.
+func (w *worker) startAndPing(ctx context.Context) error {
+       if ctx.Err() != nil {
+               return ctx.Err()
+       }
+       if err := w.start(); err != nil {
+               return err
+       }
+       if err := w.client.ping(ctx); err != nil {
+               w.stop()
+               if ctx.Err() != nil {
+                       return ctx.Err()
+               }
+               if isInterruptError(err) {
+                       // User may have pressed ^C before worker responded.
+                       return err
+               }
+               // TODO: record and return stderr.
+               return fmt.Errorf("fuzzing process terminated without fuzzing: %w", err)
+       }
+       return nil
+}
+
+// start runs a new worker process.
+//
+// If the process couldn't be started, start returns an error. Start won't
+// return later termination errors from the process if they occur.
+//
+// If the process starts successfully, start returns nil. stop must be called
+// once later to clean up, even if the process terminates on its own.
+//
+// When the process terminates, w.waitErr is set to the error (if any), and
+// w.termC is closed.
+func (w *worker) start() (err error) {
+       if w.isRunning() {
+               panic("worker already started")
+       }
+       w.waitErr = nil
+       w.interrupted = false
+       w.termC = nil
+
+       cmd := exec.Command(w.binPath, w.args...)
+       cmd.Dir = w.dir
+       cmd.Env = w.env[:len(w.env):len(w.env)] // copy on append to ensure workers don't overwrite each other.
+       cmd.Stdout = os.Stdout
+       cmd.Stderr = os.Stderr
+
+       // Create the "fuzz_in" and "fuzz_out" pipes so we can communicate with
+       // the worker. We don't use stdin and stdout, since the test binary may
+       // do something else with those.
+       //
+       // Each pipe has a reader and a writer. The coordinator writes to fuzzInW
+       // and reads from fuzzOutR. The worker inherits fuzzInR and fuzzOutW.
+       // The coordinator closes fuzzInR and fuzzOutW after starting the worker,
+       // since we have no further need of them.
+       fuzzInR, fuzzInW, err := os.Pipe()
+       if err != nil {
+               return err
+       }
+       defer fuzzInR.Close()
+       fuzzOutR, fuzzOutW, err := os.Pipe()
+       if err != nil {
+               fuzzInW.Close()
+               return err
+       }
+       defer fuzzOutW.Close()
+       setWorkerComm(cmd, workerComm{fuzzIn: fuzzInR, fuzzOut: fuzzOutW, memMu: w.memMu})
+
+       // Start the worker process.
+       if err := cmd.Start(); err != nil {
+               fuzzInW.Close()
+               fuzzOutR.Close()
+               return err
+       }
+
+       // Worker started successfully.
+       // After this, w.client owns fuzzInW and fuzzOutR, so w.client.Close must be
+       // called later by stop.
+       w.cmd = cmd
+       w.termC = make(chan struct{})
+       w.client = newWorkerClient(workerComm{fuzzIn: fuzzInW, fuzzOut: fuzzOutR, memMu: w.memMu})
+
+       go func() {
+               w.waitErr = w.cmd.Wait()
+               close(w.termC)
+       }()
+
+       return nil
+}
+
+// stop tells the worker process to exit by closing w.client, then blocks until
+// it terminates. If the worker doesn't terminate after a short time, stop
+// signals it with os.Interrupt (where supported), then os.Kill.
+//
+// stop returns the error the process terminated with, if any (same as
+// w.waitErr).
+//
+// stop must be called at least once after start returns successfully, even if
+// the worker process terminates unexpectedly.
+func (w *worker) stop() error {
+       if w.termC == nil {
+               panic("worker was not started successfully")
+       }
+       select {
+       case <-w.termC:
+               // Worker already terminated.
+               if w.client == nil {
+                       // stop already called.
+                       return w.waitErr
+               }
+               // Possible unexpected termination.
+               w.client.Close()
+               w.cmd = nil
+               w.client = nil
+               return w.waitErr
+       default:
+               // Worker still running.
+       }
+
+       // Tell the worker to stop by closing fuzz_in. It won't actually stop until it
+       // finishes with earlier calls.
+       closeC := make(chan struct{})
+       go func() {
+               w.client.Close()
+               close(closeC)
+       }()
+
+       sig := os.Interrupt
+       if runtime.GOOS == "windows" {
+               // Per https://golang.org/pkg/os/#Signal, “Interrupt is not implemented on
+               // Windows; using it with os.Process.Signal will return an error.”
+               // Fall back to Kill instead.
+               sig = os.Kill
+       }
+
+       t := time.NewTimer(workerTimeoutDuration)
+       for {
+               select {
+               case <-w.termC:
+                       // Worker terminated.
+                       t.Stop()
+                       <-closeC
+                       w.cmd = nil
+                       w.client = nil
+                       return w.waitErr
+
+               case <-t.C:
+                       // Timer fired before worker terminated.
+                       w.interrupted = true
+                       switch sig {
+                       case os.Interrupt:
+                               // Try to stop the worker with SIGINT and wait a little longer.
+                               w.cmd.Process.Signal(sig)
+                               sig = os.Kill
+                               t.Reset(workerTimeoutDuration)
+
+                       case os.Kill:
+                               // Try to stop the worker with SIGKILL and keep waiting.
+                               w.cmd.Process.Signal(sig)
+                               sig = nil
+                               t.Reset(workerTimeoutDuration)
+
+                       case nil:
+                               // Still waiting. Print a message to let the user know why.
+                               fmt.Fprintf(w.coordinator.opts.Log, "waiting for fuzzing process to terminate...\n")
+                       }
+               }
+       }
+}
+
+// RunFuzzWorker is called in a worker process to communicate with the
+// coordinator process in order to fuzz random inputs. RunFuzzWorker loops
+// until the coordinator tells it to stop.
+//
+// fn is a wrapper on the fuzz function. It may return an error to indicate
+// a given input "crashed". The coordinator will also record a crasher if
+// the function times out or terminates the process.
+//
+// RunFuzzWorker returns an error if it could not communicate with the
+// coordinator process.
+func RunFuzzWorker(ctx context.Context, fn func(CorpusEntry) error) error {
+       comm, err := getWorkerComm()
+       if err != nil {
+               return err
+       }
+       srv := &workerServer{workerComm: comm, fuzzFn: fn, m: newMutator()}
+       return srv.serve(ctx)
+}
+
+// call is serialized and sent from the coordinator on fuzz_in. It acts as
+// a minimalist RPC mechanism. Exactly one of its fields must be set to indicate
+// which method to call.
+type call struct {
+       Ping     *pingArgs
+       Fuzz     *fuzzArgs
+       Minimize *minimizeArgs
+}
+
+// minimizeArgs contains arguments to workerServer.minimize. The value to
+// minimize is already in shared memory.
+type minimizeArgs struct {
+       // Timeout is the time to spend minimizing. This may include time to start up,
+       // especially if the input causes the worker process to terminated, requiring
+       // repeated restarts.
+       Timeout time.Duration
+
+       // Limit is the maximum number of values to test, without spending more time
+       // than Duration. 0 indicates no limit.
+       Limit int64
+}
+
+// minimizeResponse contains results from workerServer.minimize.
+type minimizeResponse struct {
+       // Err is the error string caused by the value in shared memory.
+       // If Err is empty, minimize was unable to find any shorter values that
+       // caused errors, and the value in shared memory is the original value.
+       Err string
+
+       // Duration is the time spent minimizing, not including starting or cleaning up.
+       Duration time.Duration
+
+       // Count is the number of values tested.
+       Count int64
+}
+
+// fuzzArgs contains arguments to workerServer.fuzz. The value to fuzz is
+// passed in shared memory.
+type fuzzArgs struct {
+       // Timeout is the time to spend fuzzing, not including starting or
+       // cleaning up.
+       Timeout time.Duration
+
+       // Limit is the maximum number of values to test, without spending more time
+       // than Duration. 0 indicates no limit.
+       Limit int64
+
+       // CoverageOnly indicates whether this is a coverage-only run (ie. fuzzing
+       // should not occur).
+       CoverageOnly bool
+
+       // CoverageData is the coverage data. If set, the worker should update its
+       // local coverage data prior to fuzzing.
+       CoverageData []byte
+}
+
+// fuzzResponse contains results from workerServer.fuzz.
+type fuzzResponse struct {
+       // Duration is the time spent fuzzing, not including starting or cleaning up.
+       Duration time.Duration
+
+       // Count is the number of values tested.
+       Count int64
+
+       // CoverageData is set if the value in shared memory expands coverage
+       // and therefore may be interesting to the coordinator.
+       CoverageData []byte
+
+       // Err is the error string caused by the value in shared memory, which is
+       // non-empty if the value in shared memory caused a crash.
+       Err string
+}
+
+// pingArgs contains arguments to workerServer.ping.
+type pingArgs struct{}
+
+// pingResponse contains results from workerServer.ping.
+type pingResponse struct{}
+
+// workerComm holds pipes and shared memory used for communication
+// between the coordinator process (client) and a worker process (server).
+// These values are unique to each worker; they are shared only with the
+// coordinator, not with other workers.
+//
+// Access to shared memory is synchronized implicitly over the RPC protocol
+// implemented in workerServer and workerClient. During a call, the client
+// (worker) has exclusive access to shared memory; at other times, the server
+// (coordinator) has exclusive access.
+type workerComm struct {
+       fuzzIn, fuzzOut *os.File
+       memMu           chan *sharedMem // mutex guarding shared memory
+}
+
+// workerServer is a minimalist RPC server, run by fuzz worker processes.
+// It allows the coordinator process (using workerClient) to call methods in a
+// worker process. This system allows the coordinator to run multiple worker
+// processes in parallel and to collect inputs that caused crashes from shared
+// memory after a worker process terminates unexpectedly.
+type workerServer struct {
+       workerComm
+       m *mutator
+
+       // coverageData is the local coverage data for the worker. It is
+       // periodically updated to reflect the data in the coordinator when new
+       // edges are hit.
+       coverageData []byte
+
+       // fuzzFn runs the worker's fuzz function on the given input and returns
+       // an error if it finds a crasher (the process may also exit or crash).
+       fuzzFn func(CorpusEntry) error
+}
+
+// serve reads serialized RPC messages on fuzzIn. When serve receives a message,
+// it calls the corresponding method, then sends the serialized result back
+// on fuzzOut.
+//
+// serve handles RPC calls synchronously; it will not attempt to read a message
+// until the previous call has finished.
+//
+// serve returns errors that occurred when communicating over pipes. serve
+// does not return errors from method calls; those are passed through serialized
+// responses.
+func (ws *workerServer) serve(ctx context.Context) error {
+       // This goroutine may stay blocked after serve returns because the underlying
+       // read blocks, even after the file descriptor in this process is closed. The
+       // pipe must be closed by the client, too.
+       errC := make(chan error, 1)
+       go func() {
+               enc := json.NewEncoder(ws.fuzzOut)
+               dec := json.NewDecoder(ws.fuzzIn)
+               for {
+                       if ctx.Err() != nil {
+                               return
+                       }
+
+                       var c call
+                       if err := dec.Decode(&c); err == io.EOF {
+                               return
+                       } else if err != nil {
+                               errC <- err
+                               return
+                       }
+                       if ctx.Err() != nil {
+                               return
+                       }
+
+                       var resp interface{}
+                       switch {
+                       case c.Fuzz != nil:
+                               resp = ws.fuzz(ctx, *c.Fuzz)
+                       case c.Minimize != nil:
+                               resp = ws.minimize(ctx, *c.Minimize)
+                       case c.Ping != nil:
+                               resp = ws.ping(ctx, *c.Ping)
+                       default:
+                               errC <- errors.New("no arguments provided for any call")
+                               return
+                       }
+
+                       if err := enc.Encode(resp); err != nil {
+                               errC <- err
+                               return
+                       }
+               }
+       }()
+
+       select {
+       case <-ctx.Done():
+               // Stop handling messages when ctx.Done() is closed. This normally happens
+               // when the worker process receives a SIGINT signal, which on POSIX platforms
+               // is sent to the process group when ^C is pressed.
+               return ctx.Err()
+       case err := <-errC:
+               return err
+       }
+}
+
+// fuzz runs the test function on random variations of a given input value for
+// a given amount of time. fuzz returns early if it finds an input that crashes
+// the fuzz function or an input that expands coverage.
+func (ws *workerServer) fuzz(ctx context.Context, args fuzzArgs) (resp fuzzResponse) {
+       if args.CoverageData != nil {
+               ws.coverageData = args.CoverageData
+       }
+       start := time.Now()
+       defer func() { resp.Duration = time.Since(start) }()
+
+       fuzzCtx, cancel := context.WithTimeout(ctx, args.Timeout)
+       defer cancel()
+       mem := <-ws.memMu
+       defer func() {
+               resp.Count = mem.header().count
+               ws.memMu <- mem
+       }()
+
+       vals, err := unmarshalCorpusFile(mem.valueCopy())
+       if err != nil {
+               panic(err)
+       }
+
+       if args.CoverageOnly {
+               ws.fuzzFn(CorpusEntry{Values: vals})
+               resp.CoverageData = coverageSnapshot
+               return resp
+       }
+
+       if cov := coverage(); len(cov) != len(ws.coverageData) {
+               panic(fmt.Sprintf("num edges changed at runtime: %d, expected %d", len(cov), len(ws.coverageData)))
+       }
+       for {
+               select {
+               case <-fuzzCtx.Done():
+                       return resp
+
+               default:
+                       mem.header().count++
+                       ws.m.mutate(vals, cap(mem.valueRef()))
+                       writeToMem(vals, mem)
+                       if err := ws.fuzzFn(CorpusEntry{Values: vals}); err != nil {
+                               resp.Err = err.Error()
+                               if resp.Err == "" {
+                                       resp.Err = "fuzz function failed with no output"
+                               }
+                               return resp
+                       }
+                       for i := range coverageSnapshot {
+                               if ws.coverageData[i] == 0 && coverageSnapshot[i] > ws.coverageData[i] {
+                                       // TODO(jayconrod,katie): minimize this.
+                                       resp.CoverageData = coverageSnapshot
+                                       return resp
+                               }
+                       }
+                       if args.Limit > 0 && mem.header().count == args.Limit {
+                               return resp
+                       }
+               }
+       }
+}
+
+func (ws *workerServer) minimize(ctx context.Context, args minimizeArgs) (resp minimizeResponse) {
+       start := time.Now()
+       defer func() { resp.Duration = time.Now().Sub(start) }()
+       mem := <-ws.memMu
+       defer func() { ws.memMu <- mem }()
+       vals, err := unmarshalCorpusFile(mem.valueCopy())
+       if err != nil {
+               panic(err)
+       }
+       if args.Timeout != 0 {
+               var cancel func()
+               ctx, cancel = context.WithTimeout(ctx, args.Timeout)
+               defer cancel()
+       }
+
+       // Minimize the values in vals, then write to shared memory. We only write
+       // to shared memory after completing minimization. If the worker terminates
+       // unexpectedly before then, the coordinator will use the original input.
+       err = ws.minimizeInput(ctx, vals, &mem.header().count, args.Limit)
+       writeToMem(vals, mem)
+       if err != nil {
+               resp.Err = err.Error()
+       }
+       return resp
+}
+
+// minimizeInput applies a series of minimizing transformations on the provided
+// vals, ensuring that each minimization still causes an error in fuzzFn. Before
+// every call to fuzzFn, it marshals the new vals and writes it to the provided
+// mem just in case an unrecoverable error occurs. It uses the context to
+// determine how long to run, stopping once closed. It returns the last error it
+// found.
+func (ws *workerServer) minimizeInput(ctx context.Context, vals []interface{}, count *int64, limit int64) error {
+       shouldStop := func() bool {
+               return ctx.Err() != nil || (limit > 0 && *count >= limit)
+       }
+       if shouldStop() {
+               return nil
+       }
+
+       var valI int
+       var retErr error
+       tryMinimized := func(candidate interface{}) bool {
+               prev := vals[valI]
+               // Set vals[valI] to the candidate after it has been
+               // properly cast. We know that candidate must be of
+               // the same type as prev, so use that as a reference.
+               switch c := candidate.(type) {
+               case float64:
+                       switch prev.(type) {
+                       case float32:
+                               vals[valI] = float32(c)
+                       case float64:
+                               vals[valI] = c
+                       default:
+                               panic("impossible")
+                       }
+               case uint:
+                       switch prev.(type) {
+                       case uint:
+                               vals[valI] = c
+                       case uint8:
+                               vals[valI] = uint8(c)
+                       case uint16:
+                               vals[valI] = uint16(c)
+                       case uint32:
+                               vals[valI] = uint32(c)
+                       case uint64:
+                               vals[valI] = uint64(c)
+                       case int:
+                               vals[valI] = int(c)
+                       case int8:
+                               vals[valI] = int8(c)
+                       case int16:
+                               vals[valI] = int16(c)
+                       case int32:
+                               vals[valI] = int32(c)
+                       case int64:
+                               vals[valI] = int64(c)
+                       default:
+                               panic("impossible")
+                       }
+               case []byte:
+                       switch prev.(type) {
+                       case []byte:
+                               vals[valI] = c
+                       case string:
+                               vals[valI] = string(c)
+                       default:
+                               panic("impossible")
+                       }
+               default:
+                       panic("impossible")
+               }
+               err := ws.fuzzFn(CorpusEntry{Values: vals})
+               if err != nil {
+                       retErr = err
+                       return true
+               }
+               *count++
+               vals[valI] = prev
+               return false
+       }
+
+       for valI = range vals {
+               if shouldStop() {
+                       return retErr
+               }
+               switch v := vals[valI].(type) {
+               case bool:
+                       continue // can't minimize
+               case float32:
+                       minimizeFloat(float64(v), tryMinimized, shouldStop)
+               case float64:
+                       minimizeFloat(v, tryMinimized, shouldStop)
+               case uint:
+                       minimizeInteger(v, tryMinimized, shouldStop)
+               case uint8:
+                       minimizeInteger(uint(v), tryMinimized, shouldStop)
+               case uint16:
+                       minimizeInteger(uint(v), tryMinimized, shouldStop)
+               case uint32:
+                       minimizeInteger(uint(v), tryMinimized, shouldStop)
+               case uint64:
+                       if uint64(uint(v)) != v {
+                               // Skip minimizing a uint64 on 32 bit platforms, since we'll truncate the
+                               // value when casting
+                               continue
+                       }
+                       minimizeInteger(uint(v), tryMinimized, shouldStop)
+               case int:
+                       minimizeInteger(uint(v), tryMinimized, shouldStop)
+               case int8:
+                       minimizeInteger(uint(v), tryMinimized, shouldStop)
+               case int16:
+                       minimizeInteger(uint(v), tryMinimized, shouldStop)
+               case int32:
+                       minimizeInteger(uint(v), tryMinimized, shouldStop)
+               case int64:
+                       if int64(int(v)) != v {
+                               // Skip minimizing a int64 on 32 bit platforms, since we'll truncate the
+                               // value when casting
+                               continue
+                       }
+                       minimizeInteger(uint(v), tryMinimized, shouldStop)
+               case string:
+                       minimizeBytes([]byte(v), tryMinimized, shouldStop)
+               case []byte:
+                       minimizeBytes(v, tryMinimized, shouldStop)
+               default:
+                       panic("unreachable")
+               }
+       }
+       return retErr
+}
+
+func writeToMem(vals []interface{}, mem *sharedMem) {
+       b := marshalCorpusFile(vals...)
+       mem.setValue(b)
+}
+
+// ping does nothing. The coordinator calls this method to ensure the worker
+// has called F.Fuzz and can communicate.
+func (ws *workerServer) ping(ctx context.Context, args pingArgs) pingResponse {
+       return pingResponse{}
+}
+
+// workerClient is a minimalist RPC client. The coordinator process uses a
+// workerClient to call methods in each worker process (handled by
+// workerServer).
+type workerClient struct {
+       workerComm
+
+       mu  sync.Mutex
+       enc *json.Encoder
+       dec *json.Decoder
+}
+
+func newWorkerClient(comm workerComm) *workerClient {
+       return &workerClient{
+               workerComm: comm,
+               enc:        json.NewEncoder(comm.fuzzIn),
+               dec:        json.NewDecoder(comm.fuzzOut),
+       }
+}
+
+// Close shuts down the connection to the RPC server (the worker process) by
+// closing fuzz_in. Close drains fuzz_out (avoiding a SIGPIPE in the worker),
+// and closes it after the worker process closes the other end.
+func (wc *workerClient) Close() error {
+       wc.mu.Lock()
+       defer wc.mu.Unlock()
+
+       // Close fuzzIn. This signals to the server that there are no more calls,
+       // and it should exit.
+       if err := wc.fuzzIn.Close(); err != nil {
+               wc.fuzzOut.Close()
+               return err
+       }
+
+       // Drain fuzzOut and close it. When the server exits, the kernel will close
+       // its end of fuzzOut, and we'll get EOF.
+       if _, err := io.Copy(ioutil.Discard, wc.fuzzOut); err != nil {
+               wc.fuzzOut.Close()
+               return err
+       }
+       return wc.fuzzOut.Close()
+}
+
+// errSharedMemClosed is returned by workerClient methods that cannot access
+// shared memory because it was closed and unmapped by another goroutine. That
+// can happen when worker.cleanup is called in the worker goroutine while a
+// workerClient.fuzz call runs concurrently.
+//
+// This error should not be reported. It indicates the operation was
+// interrupted.
+var errSharedMemClosed = errors.New("internal error: shared memory was closed and unmapped")
+
+// minimize tells the worker to call the minimize method. See
+// workerServer.minimize.
+func (wc *workerClient) minimize(ctx context.Context, valueIn []byte, args minimizeArgs) (valueOut []byte, resp minimizeResponse, err error) {
+       wc.mu.Lock()
+       defer wc.mu.Unlock()
+
+       mem, ok := <-wc.memMu
+       if !ok {
+               return nil, minimizeResponse{}, errSharedMemClosed
+       }
+       mem.header().count = 0
+       mem.setValue(valueIn)
+       wc.memMu <- mem
+
+       c := call{Minimize: &args}
+       err = wc.call(ctx, c, &resp)
+       mem, ok = <-wc.memMu
+       if !ok {
+               return nil, minimizeResponse{}, errSharedMemClosed
+       }
+       valueOut = mem.valueCopy()
+       resp.Count = mem.header().count
+       wc.memMu <- mem
+
+       return valueOut, resp, err
+}
+
+// fuzz tells the worker to call the fuzz method. See workerServer.fuzz.
+func (wc *workerClient) fuzz(ctx context.Context, valueIn []byte, args fuzzArgs) (valueOut []byte, resp fuzzResponse, err error) {
+       wc.mu.Lock()
+       defer wc.mu.Unlock()
+
+       mem, ok := <-wc.memMu
+       if !ok {
+               return nil, fuzzResponse{}, errSharedMemClosed
+       }
+       mem.header().count = 0
+       mem.setValue(valueIn)
+       wc.memMu <- mem
+
+       c := call{Fuzz: &args}
+       err = wc.call(ctx, c, &resp)
+       mem, ok = <-wc.memMu
+       if !ok {
+               return nil, fuzzResponse{}, errSharedMemClosed
+       }
+       valueOut = mem.valueCopy()
+       resp.Count = mem.header().count
+       wc.memMu <- mem
+
+       return valueOut, resp, err
+}
+
+// ping tells the worker to call the ping method. See workerServer.ping.
+func (wc *workerClient) ping(ctx context.Context) error {
+       c := call{Ping: &pingArgs{}}
+       var resp pingResponse
+       return wc.call(ctx, c, &resp)
+}
+
+// call sends an RPC from the coordinator to the worker process and waits for
+// the response. The call may be cancelled with ctx.
+func (wc *workerClient) call(ctx context.Context, c call, resp interface{}) (err error) {
+       // This goroutine may stay blocked after call returns because the underlying
+       // read blocks, even after the file descriptor in this process is closed. The
+       // pipe must be closed by the server, too.
+       errC := make(chan error, 1)
+       go func() {
+               if err := wc.enc.Encode(c); err != nil {
+                       errC <- err
+                       return
+               }
+               errC <- wc.dec.Decode(resp)
+       }()
+
+       select {
+       case <-ctx.Done():
+               return ctx.Err()
+       case err := <-errC:
+               return err
+       }
+}
index 15b4426c5a544cbc081bde34d7647bd3eb6f9365..c8571a5f5a77427bf4343b092d4f37b36dd2e137 100644 (file)
@@ -32,35 +32,35 @@ var (
        matchBenchmarks *string
        benchmarkMemory *bool
 
-       benchTime = benchTimeFlag{d: 1 * time.Second} // changed during test of testing package
+       benchTime = durationOrCountFlag{d: 1 * time.Second} // changed during test of testing package
 )
 
-type benchTimeFlag struct {
+type durationOrCountFlag struct {
        d time.Duration
        n int
 }
 
-func (f *benchTimeFlag) String() string {
+func (f *durationOrCountFlag) String() string {
        if f.n > 0 {
                return fmt.Sprintf("%dx", f.n)
        }
        return time.Duration(f.d).String()
 }
 
-func (f *benchTimeFlag) Set(s string) error {
+func (f *durationOrCountFlag) Set(s string) error {
        if strings.HasSuffix(s, "x") {
                n, err := strconv.ParseInt(s[:len(s)-1], 10, 0)
                if err != nil || n <= 0 {
                        return fmt.Errorf("invalid count")
                }
-               *f = benchTimeFlag{n: int(n)}
+               *f = durationOrCountFlag{n: int(n)}
                return nil
        }
        d, err := time.ParseDuration(s)
        if err != nil || d <= 0 {
                return fmt.Errorf("invalid duration")
        }
-       *f = benchTimeFlag{d: d}
+       *f = durationOrCountFlag{d: d}
        return nil
 }
 
@@ -98,7 +98,7 @@ type B struct {
        previousN        int           // number of iterations in the previous run
        previousDuration time.Duration // total duration of the previous run
        benchFunc        func(b *B)
-       benchTime        benchTimeFlag
+       benchTime        durationOrCountFlag
        bytes            int64
        missingBytes     bool // one of the subbenchmarks does not have bytes set.
        timerOn          bool
diff --git a/src/testing/fuzz.go b/src/testing/fuzz.go
new file mode 100644 (file)
index 0000000..78a0a60
--- /dev/null
@@ -0,0 +1,704 @@
+// 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"
+       "sync/atomic"
+       "time"
+)
+
+func initFuzzFlags() {
+       matchFuzz = flag.String("test.fuzz", "", "run the fuzz target 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 crash; default is to minimize for 60s")
+       fuzzCacheDir = flag.String("test.fuzzcachedir", "", "directory where interesting fuzzing inputs are stored")
+       isFuzzWorker = flag.Bool("test.fuzzworker", false, "coordinate with the parent process to fuzz random values")
+}
+
+var (
+       matchFuzz        *string
+       fuzzDuration     durationOrCountFlag
+       minimizeDuration = durationOrCountFlag{d: 60 * time.Second}
+       fuzzCacheDir     *string
+       isFuzzWorker     *bool
+
+       // corpusDir is the parent directory of the target's seed corpus within
+       // the package.
+       corpusDir = "testdata/corpus"
+)
+
+// fuzzWorkerExitCode is used as an exit code by fuzz worker processes after an internal error.
+// This distinguishes internal errors from uncontrolled panics and other crashes.
+// 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 targets for fuzz testing.
+type F struct {
+       common
+       fuzzContext *fuzzContext
+       testContext *testContext
+       inFuzzFn    bool          // set to true when fuzz function is running
+       corpus      []corpusEntry // corpus is the in-memory corpus
+       result      FuzzResult    // result is the result of running the fuzz target
+       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
+// importing internal/fuzz from testing.
+type corpusEntry = struct {
+       Name   string
+       Data   []byte
+       Values []interface{}
+}
+
+// Cleanup registers a function to be called when the test and all its
+// subtests complete. Cleanup functions will be called in last added,
+// first called order.
+func (f *F) Cleanup(fn func()) {
+       if f.inFuzzFn {
+               panic("testing: f.Cleanup was called inside the f.Fuzz function, use t.Cleanup instead")
+       }
+       f.common.Helper()
+       f.common.Cleanup(fn)
+}
+
+// Error is equivalent to Log followed by Fail.
+func (f *F) Error(args ...interface{}) {
+       if f.inFuzzFn {
+               panic("testing: f.Error was called inside the f.Fuzz function, use t.Error instead")
+       }
+       f.common.Helper()
+       f.common.Error(args...)
+}
+
+// Errorf is equivalent to Logf followed by Fail.
+func (f *F) Errorf(format string, args ...interface{}) {
+       if f.inFuzzFn {
+               panic("testing: f.Errorf was called inside the f.Fuzz function, use t.Errorf instead")
+       }
+       f.common.Helper()
+       f.common.Errorf(format, args...)
+}
+
+// Fail marks the function as having failed but continues execution.
+func (f *F) Fail() {
+       if f.inFuzzFn {
+               panic("testing: f.Fail was called inside the f.Fuzz function, use t.Fail instead")
+       }
+       f.common.Helper()
+       f.common.Fail()
+}
+
+// FailNow marks the function as having failed and stops its execution
+// by calling runtime.Goexit (which then runs all deferred calls in the
+// current goroutine).
+// Execution will continue at the next test or benchmark.
+// FailNow must be called from the goroutine running the
+// test or benchmark function, not from other goroutines
+// created during the test. Calling FailNow does not stop
+// those other goroutines.
+func (f *F) FailNow() {
+       if f.inFuzzFn {
+               panic("testing: f.FailNow was called inside the f.Fuzz function, use t.FailNow instead")
+       }
+       f.common.Helper()
+       f.common.FailNow()
+}
+
+// Fatal is equivalent to Log followed by FailNow.
+func (f *F) Fatal(args ...interface{}) {
+       if f.inFuzzFn {
+               panic("testing: f.Fatal was called inside the f.Fuzz function, use t.Fatal instead")
+       }
+       f.common.Helper()
+       f.common.Fatal(args...)
+}
+
+// Fatalf is equivalent to Logf followed by FailNow.
+func (f *F) Fatalf(format string, args ...interface{}) {
+       if f.inFuzzFn {
+               panic("testing: f.Fatalf was called inside the f.Fuzz function, use t.Fatalf instead")
+       }
+       f.common.Helper()
+       f.common.Fatalf(format, args...)
+}
+
+// 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 f.Fuzz function, 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
+       }
+}
+
+// Skip is equivalent to Log followed by SkipNow.
+func (f *F) Skip(args ...interface{}) {
+       if f.inFuzzFn {
+               panic("testing: f.Skip was called inside the f.Fuzz function, use t.Skip instead")
+       }
+       f.common.Helper()
+       f.common.Skip(args...)
+}
+
+// SkipNow marks the test as having been skipped and stops its execution
+// by calling runtime.Goexit.
+// If a test fails (see Error, Errorf, Fail) and is then skipped,
+// it is still considered to have failed.
+// Execution will continue at the next test or benchmark. See also FailNow.
+// SkipNow must be called from the goroutine running the test, not from
+// other goroutines created during the test. Calling SkipNow does not stop
+// those other goroutines.
+func (f *F) SkipNow() {
+       if f.inFuzzFn {
+               panic("testing: f.SkipNow was called inside the f.Fuzz function, use t.SkipNow instead")
+       }
+       f.common.Helper()
+       f.common.SkipNow()
+}
+
+// Skipf is equivalent to Logf followed by SkipNow.
+func (f *F) Skipf(format string, args ...interface{}) {
+       if f.inFuzzFn {
+               panic("testing: f.Skipf was called inside the f.Fuzz function, use t.Skipf instead")
+       }
+       f.common.Helper()
+       f.common.Skipf(format, args...)
+}
+
+// TempDir returns a temporary directory for the test to use.
+// The directory is automatically removed by Cleanup when the test and
+// all its subtests complete.
+// Each subsequent call to t.TempDir returns a unique directory;
+// if the directory creation fails, TempDir terminates the test by calling Fatal.
+func (f *F) TempDir() string {
+       if f.inFuzzFn {
+               panic("testing: f.TempDir was called inside the f.Fuzz function, use t.TempDir instead")
+       }
+       f.common.Helper()
+       return f.common.TempDir()
+}
+
+// Add will add the arguments to the seed corpus for the fuzz target. This will
+// be a no-op if called after or within the Fuzz function. The args must match
+// those in the Fuzz function.
+func (f *F) Add(args ...interface{}) {
+       var values []interface{}
+       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, Name: 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.
+//
+// This is a terminal function which will terminate the currently running fuzz
+// target by calling runtime.Goexit. To run any code after this function, use
+// Cleanup.
+func (f *F) Fuzz(ff interface{}) {
+       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: F.Fuzz function must receive at least two arguments, where the first argument is a *T")
+       }
+
+       // 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 seed corpus
+       c, err := f.fuzzContext.readCorpus(filepath.Join(corpusDir, f.name), types)
+       if err != nil {
+               f.Fatal(err)
+       }
+       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.
+       //
+       // TODO(jayconrod,katiehockman): dedupe testdata corpus with entries from f.Add
+       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))
+               }
+               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.
+               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,
+                               fuzzing: true,
+                       },
+                       context: f.testContext,
+               }
+               t.w = indenter{&t.common}
+               if t.chatty != nil {
+                       t.chatty.Updatef(t.name, "=== RUN  %s\n", t.name)
+               }
+               f.inFuzzFn = true
+               go tRunner(t, func(t *T) {
+                       args := []reflect.Value{reflect.ValueOf(t)}
+                       for _, v := range e.Values {
+                               args = append(args, reflect.ValueOf(v))
+                       }
+                       f.fuzzContext.resetCoverage()
+                       fn.Call(args)
+                       f.fuzzContext.snapshotCoverage()
+               })
+               <-t.signal
+               f.inFuzzFn = false
+               if t.Failed() {
+                       return errors.New(string(f.output))
+               }
+               return nil
+       }
+
+       switch {
+       case f.fuzzContext.coordinateFuzzing != nil:
+               // 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.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 {
+                               crashName := crashErr.CrashName()
+                               fmt.Fprintf(f.w, "Crash written to %s\n", filepath.Join("testdata/corpus", f.name, crashName))
+                               fmt.Fprintf(f.w, "To re-run:\ngo test %s -run=%s/%s\n", f.fuzzContext.importPath(), f.name, crashName)
+                       }
+               }
+               // TODO(jayconrod,katiehockman): Aggregate statistics across workers
+               // and add to FuzzResult (ie. time taken, num iterations)
+
+       case f.fuzzContext.runFuzzWorker != nil:
+               // Fuzzing is enabled, and this is a worker process. Follow instructions
+               // from the coordinator.
+               if err := f.fuzzContext.runFuzzWorker(run); 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 crasher 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 {
+                       run(e)
+               }
+       }
+
+       // Record that the fuzz function (or coordinateFuzzing or runFuzzWorker)
+       // returned normally. This is used to distinguish runtime.Goexit below
+       // from panic(nil).
+       f.finished = true
+
+       // Terminate the goroutine. F.Fuzz should not return.
+       // We cannot call runtime.Goexit from a deferred function: if there is a
+       // panic, that would replace the panic value with nil.
+       runtime.Goexit()
+}
+
+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 crash
+}
+
+func (r FuzzResult) String() string {
+       s := ""
+       if r.Error == nil {
+               return s
+       }
+       s = fmt.Sprintf("%s", r.Error.Error())
+       return s
+}
+
+// fuzzCrashError is satisfied by a crash detected within the fuzz function.
+// 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
+
+       // CrashName returns the name of the subtest that corresponds to the saved
+       // crash input file in the seed corpus. The test can be re-run with
+       // go test $pkg -run=$target/$name where $pkg is the package's import path,
+       // $target is the fuzz target name, and $name is the string returned here.
+       CrashName() string
+}
+
+// fuzzContext holds all fields that are common to all fuzz targets.
+type fuzzContext struct {
+       importPath        func() string
+       coordinateFuzzing func(time.Duration, int64, time.Duration, int64, int, []corpusEntry, []reflect.Type, string, string) error
+       runFuzzWorker     func(func(corpusEntry) error) error
+       readCorpus        func(string, []reflect.Type) ([]corpusEntry, error)
+       resetCoverage     func()
+       snapshotCoverage  func()
+}
+
+// runFuzzTargets runs the fuzz targets 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 runFuzzTargets(deps testDeps, fuzzTargets []InternalFuzzTarget) (ran, ok bool) {
+       ok = true
+       if len(fuzzTargets) == 0 || *isFuzzWorker {
+               return ran, ok
+       }
+       m := newMatcher(deps.MatchString, *match, "-test.run")
+       tctx := newTestContext(*parallel, m)
+       fctx := &fuzzContext{
+               importPath:       deps.ImportPath,
+               readCorpus:       deps.ReadCorpus,
+               resetCoverage:    deps.ResetCoverage,
+               snapshotCoverage: deps.SnapshotCoverage,
+       }
+       root := common{w: os.Stdout} // gather output in one place
+       if Verbose() {
+               root.chatty = newChattyPrinter(root.w)
+       }
+       for _, ft := range fuzzTargets {
+               if shouldFailFast() {
+                       break
+               }
+               testName, matched, _ := tctx.match.fullName(nil, ft.Name)
+               if !matched {
+                       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
+       }
+       return root.ran, !root.Failed()
+}
+
+// runFuzzing runs the fuzz target matching the pattern for -fuzz. Only one such
+// fuzz target must match. This will run the fuzzing engine to generate and
+// mutate new inputs against the f.Fuzz function.
+//
+// If fuzzing is disabled (-test.fuzz is not set), runFuzzing
+// returns immediately.
+func runFuzzing(deps testDeps, fuzzTargets []InternalFuzzTarget) (ran, ok bool) {
+       // TODO(katiehockman,jayconrod): Should we do something special to make sure
+       // we don't print f.Log statements again with runFuzzing, since we already
+       // would have printed them when we ran runFuzzTargets (ie. seed corpus run)?
+       if len(fuzzTargets) == 0 || *matchFuzz == "" {
+               return false, true
+       }
+       m := newMatcher(deps.MatchString, *matchFuzz, "-test.fuzz")
+       tctx := newTestContext(1, m)
+       fctx := &fuzzContext{
+               importPath:       deps.ImportPath,
+               readCorpus:       deps.ReadCorpus,
+               resetCoverage:    deps.ResetCoverage,
+               snapshotCoverage: deps.SnapshotCoverage,
+       }
+       root := common{w: os.Stdout}
+       if *isFuzzWorker {
+               root.w = io.Discard
+               fctx.runFuzzWorker = deps.RunFuzzWorker
+       } else {
+               fctx.coordinateFuzzing = deps.CoordinateFuzzing
+       }
+       if Verbose() && !*isFuzzWorker {
+               root.chatty = newChattyPrinter(root.w)
+       }
+       var target *InternalFuzzTarget
+       var f *F
+       for i := range fuzzTargets {
+               ft := &fuzzTargets[i]
+               testName, matched, _ := tctx.match.fullName(nil, ft.Name)
+               if !matched {
+                       continue
+               }
+               if target != nil {
+                       fmt.Fprintln(os.Stderr, "testing: warning: -fuzz matches more than one target, won't fuzz")
+                       return false, true
+               }
+               target = ft
+               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 target == nil {
+               return false, true
+       }
+       if f.chatty != nil {
+               f.chatty.Updatef(f.name, "=== FUZZ  %s\n", f.name)
+       }
+       go fRunner(f, target.Fn)
+       <-f.signal
+       return f.ran, !f.failed
+}
+
+// fRunner wraps a call to a fuzz target 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 f.signal.
+//
+// fRunner is analogous with tRunner, which wraps subtests started with T.Run.
+// Tests and fuzz targets 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-targets or parallel fuzz targets, 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 target is done.
+       defer func() {
+               // Detect whether the fuzz target 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 with a stack. Unfortunately, recovering
+               // here adds stack frames, but the location of the original panic should
+               // still be clear.
+               if f.Failed() {
+                       atomic.AddUint32(&numFailed, 1)
+               }
+               err := recover()
+               f.mu.RLock()
+               ok := f.skipped || f.failed || (f.fuzzCalled && f.finished)
+               f.mu.RUnlock()
+               if err == nil && !ok {
+                       err = errNilPanicOrGoexit
+               }
+
+               // Use a deferred call to ensure that we report that the test is
+               // complete even if a cleanup function calls t.FailNow. See issue 41355.
+               didPanic := false
+               defer func() {
+                       if didPanic {
+                               return
+                       }
+                       if err != nil {
+                               panic(err)
+                       }
+                       // 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 interface{}) {
+                       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 {
+                       // Run parallel inputs.
+                       // Release the parallel subtests.
+                       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 is only executed if fn returned normally.
+       // That means fn did not call F.Fuzz or F.Skip. It should have called F.Fail.
+       f.mu.Lock()
+       defer f.mu.Unlock()
+       if !f.failed {
+               panic(f.name + " returned without calling F.Fuzz, F.Fail, or F.Skip")
+       }
+}
index 3608d332946e2ebee0558a35c35c7930a65e923a..01390f51d3eaf8d7afde915256f9e135f2cc78b9 100644 (file)
@@ -12,12 +12,18 @@ package testdeps
 
 import (
        "bufio"
+       "context"
+       "internal/fuzz"
        "internal/testlog"
        "io"
+       "os"
+       "os/signal"
+       "reflect"
        "regexp"
        "runtime/pprof"
        "strings"
        "sync"
+       "time"
 )
 
 // TestDeps is an implementation of the testing.testDeps interface,
@@ -126,3 +132,64 @@ func (TestDeps) StopTestLog() error {
 func (TestDeps) SetPanicOnExit0(v bool) {
        testlog.SetPanicOnExit0(v)
 }
+
+func (TestDeps) CoordinateFuzzing(
+       timeout time.Duration,
+       limit int64,
+       minimizeTimeout time.Duration,
+       minimizeLimit int64,
+       parallel int,
+       seed []fuzz.CorpusEntry,
+       types []reflect.Type,
+       corpusDir,
+       cacheDir string) (err error) {
+       // Fuzzing may be interrupted with a timeout or if the user presses ^C.
+       // In either case, we'll stop worker processes gracefully and save
+       // crashers and interesting values.
+       ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
+       defer cancel()
+       err = fuzz.CoordinateFuzzing(ctx, fuzz.CoordinateFuzzingOpts{
+               Log:             os.Stderr,
+               Timeout:         timeout,
+               Limit:           limit,
+               MinimizeTimeout: minimizeTimeout,
+               MinimizeLimit:   minimizeLimit,
+               Parallel:        parallel,
+               Seed:            seed,
+               Types:           types,
+               CorpusDir:       corpusDir,
+               CacheDir:        cacheDir,
+       })
+       if err == ctx.Err() {
+               return nil
+       }
+       return err
+}
+
+func (TestDeps) RunFuzzWorker(fn func(fuzz.CorpusEntry) error) error {
+       // Worker processes may or may not receive a signal when the user presses ^C
+       // On POSIX operating systems, a signal sent to a process group is delivered
+       // to all processes in that group. This is not the case on Windows.
+       // If the worker is interrupted, return quickly and without error.
+       // If only the coordinator process is interrupted, it tells each worker
+       // process to stop by closing its "fuzz_in" pipe.
+       ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
+       defer cancel()
+       err := fuzz.RunFuzzWorker(ctx, fn)
+       if err == ctx.Err() {
+               return nil
+       }
+       return err
+}
+
+func (TestDeps) ReadCorpus(dir string, types []reflect.Type) ([]fuzz.CorpusEntry, error) {
+       return fuzz.ReadCorpus(dir, types)
+}
+
+func (TestDeps) ResetCoverage() {
+       fuzz.ResetCoverage()
+}
+
+func (TestDeps) SnapshotCoverage() {
+       fuzz.SnapshotCoverage()
+}
index 6c7d83aac2c036af89e65de2a1f35f44243f52a7..6a5add6f4e89f5f259d8b94ad962ffd955f612d2 100644 (file)
@@ -480,9 +480,10 @@ func TestTRun(t *T) {
                        buf := &bytes.Buffer{}
                        root := &T{
                                common: common{
-                                       signal: make(chan bool),
-                                       name:   "Test",
-                                       w:      buf,
+                                       signal:  make(chan bool),
+                                       barrier: make(chan bool),
+                                       name:    "Test",
+                                       w:       buf,
                                },
                                context: ctx,
                        }
@@ -669,7 +670,7 @@ func TestBRun(t *T) {
                                        w:      buf,
                                },
                                benchFunc: func(b *B) { ok = b.Run("test", tc.f) }, // Use Run to catch failure.
-                               benchTime: benchTimeFlag{d: 1 * time.Microsecond},
+                               benchTime: durationOrCountFlag{d: 1 * time.Microsecond},
                        }
                        if tc.chatty {
                                root.chatty = newChattyPrinter(root.w)
index 85a7fec65f19fc03e25e8f195c654bb14f005892..82b422a41476a88b055c4de22f03509af0cbbe7a 100644 (file)
@@ -34,7 +34,7 @@
 // its -bench flag is provided. Benchmarks are run sequentially.
 //
 // For a description of the testing flags, see
-// https://golang.org/cmd/go/#hdr-Testing_flags
+// https://golang.org/cmd/go/#hdr-Testing_flags.
 //
 // A sample benchmark function looks like this:
 //     func BenchmarkRandInt(b *testing.B) {
 // example function, at least one other function, type, variable, or constant
 // declaration, and no test or benchmark functions.
 //
+// Fuzzing
+//
+// Functions of the form
+//     func FuzzXxx(*testing.F)
+// are considered fuzz targets, and are executed by the "go test" command. When
+// the -fuzz flag is provided, the functions will be fuzzed.
+//
+// For a description of the testing flags, see
+// https://golang.org/cmd/go/#hdr-Testing_flags.
+//
+// For a description of fuzzing, see golang.org/s/draft-fuzzing-design.
+//
+// A sample fuzz target looks like this:
+//     func FuzzBytesCmp(f *testing.F) {
+//         f.Fuzz(func(t *testing.T, a, b []byte) {
+//             if bytes.HasPrefix(a, b) && !bytes.Contains(a, b) {
+//                 t.Error("HasPrefix is true, but Contains is false")
+//             }
+//         })
+//     }
+//
 // Skipping
 //
 // Tests or benchmarks may be skipped at run time with a call to
 //         ...
 //     }
 //
+// The Skip method of *T can be used in a fuzz target if the input is invalid,
+// but should not be considered a crash. For example:
+//
+//     func FuzzJSONMarshalling(f *testing.F) {
+//         f.Fuzz(func(t *testing.T, b []byte) {
+//             var v interface{}
+//             if err := json.Unmarshal(b, &v); err != nil {
+//                 t.Skip()
+//             }
+//             if _, err := json.Marshal(v); err != nil {
+//                 t.Error("Marshal: %v", err)
+//             }
+//         })
+//     }
+//
 // Subtests and Sub-benchmarks
 //
 // The Run methods of T and B allow defining subtests and sub-benchmarks,
 // of the top-level test and the sequence of names passed to Run, separated by
 // slashes, with an optional trailing sequence number for disambiguation.
 //
-// The argument to the -run and -bench command-line flags is an unanchored regular
+// The argument to the -run, -bench, and -fuzz command-line flags is an unanchored regular
 // expression that matches the test's name. For tests with multiple slash-separated
 // elements, such as subtests, the argument is itself slash-separated, with
 // expressions matching each name element in turn. Because it is unanchored, an
 // empty expression matches any string.
 // For example, using "matching" to mean "whose name contains":
 //
-//     go test -run ''      # Run all tests.
-//     go test -run Foo     # Run top-level tests matching "Foo", such as "TestFooBar".
-//     go test -run Foo/A=  # For top-level tests matching "Foo", run subtests matching "A=".
-//     go test -run /A=1    # For all top-level tests, run subtests matching "A=1".
+//     go test -run ''        # Run all tests.
+//     go test -run Foo       # Run top-level tests matching "Foo", such as "TestFooBar".
+//     go test -run Foo/A=    # For top-level tests matching "Foo", run subtests matching "A=".
+//     go test -run /A=1      # For all top-level tests, run subtests matching "A=1".
+//     go test -fuzz FuzzFoo  # Fuzz the target matching "FuzzFoo"
+//
+// The -run argument can also be used to run a specific value in the seed
+// corpus, for debugging. For example:
+//     go test -run=FuzzFoo/9ddb952d9814
+//
+// The -fuzz and -run flags can both be set, in order to fuzz a target but
+// skip the execution of all other tests.
 //
 // Subtests can also be used to control parallelism. A parent test will only
 // complete once all of its subtests complete. In this example, all tests are
@@ -244,6 +288,7 @@ import (
        "io"
        "math/rand"
        "os"
+       "reflect"
        "runtime"
        "runtime/debug"
        "runtime/trace"
@@ -303,6 +348,7 @@ func Init() {
        shuffle = flag.String("test.shuffle", "off", "randomize the execution order of tests and benchmarks")
 
        initBenchmarkFlags()
+       initFuzzFlags()
 }
 
 var (
@@ -402,6 +448,7 @@ type common struct {
 
        chatty     *chattyPrinter // A copy of chattyPrinter, if the chatty flag is set.
        bench      bool           // Whether the current test is a benchmark.
+       fuzzing    bool           // Whether the current test is a fuzzing target.
        hasSub     int32          // Written atomically.
        raceErrors int            // Number of races detected during test.
        runner     string         // Function name of tRunner running the test.
@@ -531,6 +578,15 @@ func (c *common) frameSkip(skip int) runtime.Frame {
 // and inserts the final newline if needed and indentation spaces for formatting.
 // This function must be called with c.mu held.
 func (c *common) decorate(s string, skip int) string {
+       // TODO(jayconrod,katiehockman): Consider refactoring the logging logic.
+       // If more helper PCs have been added since we last did the conversion
+       if c.helperNames == nil {
+               c.helperNames = make(map[string]struct{})
+               for pc := range c.helperPCs {
+                       c.helperNames[pcToName(pc)] = struct{}{}
+               }
+       }
+
        frame := c.frameSkip(skip)
        file := frame.File
        line := frame.Line
@@ -600,6 +656,20 @@ func (c *common) flushToParent(testName, format string, args ...interface{}) {
        }
 }
 
+// isFuzzing returns whether the current context, or any of the parent contexts,
+// are a fuzzing target
+func (c *common) isFuzzing() bool {
+       if c.fuzzing {
+               return true
+       }
+       for parent := c.parent; parent != nil; parent = parent.parent {
+               if parent.fuzzing {
+                       return true
+               }
+       }
+       return false
+}
+
 type indenter struct {
        c *common
 }
@@ -1063,6 +1133,12 @@ func (t *T) Parallel() {
                panic("testing: t.Parallel called after t.Setenv; cannot set environment variables in parallel tests")
        }
        t.isParallel = true
+       if t.parent.barrier == nil {
+               // T.Parallel has no effect when fuzzing.
+               // Multiple processes may run in parallel, but only one input can run at a
+               // time per process so we can attribute crashes to specific inputs.
+               return
+       }
 
        // We don't want to include the time we spend waiting for serial tests
        // in the test duration. Record the elapsed time thus far and reset the
@@ -1160,10 +1236,11 @@ func tRunner(t *T, fn func(t *T)) {
                // complete even if a cleanup function calls t.FailNow. See issue 41355.
                didPanic := false
                defer func() {
-                       if didPanic {
+                       isFuzzing := t.common.isFuzzing()
+                       if didPanic && !isFuzzing {
                                return
                        }
-                       if err != nil {
+                       if err != nil && !isFuzzing {
                                panic(err)
                        }
                        // Only report that the test is complete if it doesn't panic,
@@ -1189,6 +1266,12 @@ func tRunner(t *T, fn func(t *T)) {
                                }
                        }
                        didPanic = true
+                       if t.common.fuzzing {
+                               for root := &t.common; root.parent != nil; root = root.parent {
+                                       fmt.Fprintf(root.parent.w, "panic: %s\n%s\n", err, string(debug.Stack()))
+                               }
+                               return
+                       }
                        panic(err)
                }
                if err != nil {
@@ -1373,6 +1456,15 @@ func (f matchStringOnly) ImportPath() string                          { return "
 func (f matchStringOnly) StartTestLog(io.Writer)                      {}
 func (f matchStringOnly) StopTestLog() error                          { return errMain }
 func (f matchStringOnly) SetPanicOnExit0(bool)                        {}
+func (f matchStringOnly) CoordinateFuzzing(time.Duration, int64, time.Duration, int64, int, []corpusEntry, []reflect.Type, string, string) error {
+       return errMain
+}
+func (f matchStringOnly) RunFuzzWorker(func(corpusEntry) error) error { return errMain }
+func (f matchStringOnly) ReadCorpus(string, []reflect.Type) ([]corpusEntry, error) {
+       return nil, errMain
+}
+func (f matchStringOnly) ResetCoverage()    {}
+func (f matchStringOnly) SnapshotCoverage() {}
 
 // Main is an internal function, part of the implementation of the "go test" command.
 // It was exported because it is cross-package and predates "internal" packages.
@@ -1381,15 +1473,16 @@ func (f matchStringOnly) SetPanicOnExit0(bool)                        {}
 // new functionality is added to the testing package.
 // Systems simulating "go test" should be updated to use MainStart.
 func Main(matchString func(pat, str string) (bool, error), tests []InternalTest, benchmarks []InternalBenchmark, examples []InternalExample) {
-       os.Exit(MainStart(matchStringOnly(matchString), tests, benchmarks, examples).Run())
+       os.Exit(MainStart(matchStringOnly(matchString), tests, benchmarks, nil, examples).Run())
 }
 
 // M is a type passed to a TestMain function to run the actual tests.
 type M struct {
-       deps       testDeps
-       tests      []InternalTest
-       benchmarks []InternalBenchmark
-       examples   []InternalExample
+       deps        testDeps
+       tests       []InternalTest
+       benchmarks  []InternalBenchmark
+       fuzzTargets []InternalFuzzTarget
+       examples    []InternalExample
 
        timer     *time.Timer
        afterOnce sync.Once
@@ -1414,18 +1507,24 @@ type testDeps interface {
        StartTestLog(io.Writer)
        StopTestLog() error
        WriteProfileTo(string, io.Writer, int) error
+       CoordinateFuzzing(time.Duration, int64, time.Duration, int64, int, []corpusEntry, []reflect.Type, string, string) error
+       RunFuzzWorker(func(corpusEntry) error) error
+       ReadCorpus(string, []reflect.Type) ([]corpusEntry, error)
+       ResetCoverage()
+       SnapshotCoverage()
 }
 
 // MainStart is meant for use by tests generated by 'go test'.
 // It is not meant to be called directly and is not subject to the Go 1 compatibility document.
 // It may change signature from release to release.
-func MainStart(deps testDeps, tests []InternalTest, benchmarks []InternalBenchmark, examples []InternalExample) *M {
+func MainStart(deps testDeps, tests []InternalTest, benchmarks []InternalBenchmark, fuzzTargets []InternalFuzzTarget, examples []InternalExample) *M {
        Init()
        return &M{
-               deps:       deps,
-               tests:      tests,
-               benchmarks: benchmarks,
-               examples:   examples,
+               deps:        deps,
+               tests:       tests,
+               benchmarks:  benchmarks,
+               fuzzTargets: fuzzTargets,
+               examples:    examples,
        }
 }
 
@@ -1452,9 +1551,15 @@ func (m *M) Run() (code int) {
                m.exitCode = 2
                return
        }
+       if *matchFuzz != "" && *fuzzCacheDir == "" {
+               fmt.Fprintln(os.Stderr, "testing: internal error: -test.fuzzcachedir must be set if -test.fuzz is set")
+               flag.Usage()
+               m.exitCode = 2
+               return
+       }
 
        if len(*matchList) != 0 {
-               listTests(m.deps.MatchString, m.tests, m.benchmarks, m.examples)
+               listTests(m.deps.MatchString, m.tests, m.benchmarks, m.fuzzTargets, m.examples)
                m.exitCode = 0
                return
        }
@@ -1482,22 +1587,43 @@ func (m *M) Run() (code int) {
 
        m.before()
        defer m.after()
-       deadline := m.startAlarm()
-       haveExamples = len(m.examples) > 0
-       testRan, testOk := runTests(m.deps.MatchString, m.tests, deadline)
-       exampleRan, exampleOk := runExamples(m.deps.MatchString, m.examples)
-       m.stopAlarm()
-       if !testRan && !exampleRan && *matchBenchmarks == "" {
-               fmt.Fprintln(os.Stderr, "testing: warning: no tests to run")
+       if !*isFuzzWorker {
+               // The fuzzing coordinator will already run all tests, examples,
+               // and benchmarks. Don't make the workers do redundant work.
+               deadline := m.startAlarm()
+               haveExamples = len(m.examples) > 0
+               testRan, testOk := runTests(m.deps.MatchString, m.tests, deadline)
+               fuzzTargetsRan, fuzzTargetsOk := runFuzzTargets(m.deps, m.fuzzTargets)
+               exampleRan, exampleOk := runExamples(m.deps.MatchString, m.examples)
+               m.stopAlarm()
+               if !testRan && !exampleRan && !fuzzTargetsRan && *matchBenchmarks == "" && *matchFuzz == "" {
+                       fmt.Fprintln(os.Stderr, "testing: warning: no tests to run")
+               }
+               if !testOk || !exampleOk || !fuzzTargetsOk || !runBenchmarks(m.deps.ImportPath(), m.deps.MatchString, m.benchmarks) || race.Errors() > 0 {
+                       fmt.Println("FAIL")
+                       m.exitCode = 1
+                       return
+               }
+       }
+
+       fuzzingRan, fuzzingOk := runFuzzing(m.deps, m.fuzzTargets)
+       if *matchFuzz != "" && !fuzzingRan {
+               fmt.Fprintln(os.Stderr, "testing: warning: no targets to fuzz")
        }
-       if !testOk || !exampleOk || !runBenchmarks(m.deps.ImportPath(), m.deps.MatchString, m.benchmarks) || race.Errors() > 0 {
+       if !*isFuzzWorker && !fuzzingOk {
                fmt.Println("FAIL")
-               m.exitCode = 1
+               if *isFuzzWorker {
+                       m.exitCode = fuzzWorkerExitCode
+               } else {
+                       m.exitCode = 1
+               }
                return
        }
 
-       fmt.Println("PASS")
        m.exitCode = 0
+       if !*isFuzzWorker {
+               fmt.Println("PASS")
+       }
        return
 }
 
@@ -1518,7 +1644,7 @@ func (t *T) report() {
        }
 }
 
-func listTests(matchString func(pat, str string) (bool, error), tests []InternalTest, benchmarks []InternalBenchmark, examples []InternalExample) {
+func listTests(matchString func(pat, str string) (bool, error), tests []InternalTest, benchmarks []InternalBenchmark, fuzzTargets []InternalFuzzTarget, examples []InternalExample) {
        if _, err := matchString(*matchList, "non-empty"); err != nil {
                fmt.Fprintf(os.Stderr, "testing: invalid regexp in -test.list (%q): %s\n", *matchList, err)
                os.Exit(1)
@@ -1534,6 +1660,11 @@ func listTests(matchString func(pat, str string) (bool, error), tests []Internal
                        fmt.Println(bench.Name)
                }
        }
+       for _, fuzzTarget := range fuzzTargets {
+               if ok, _ := matchString(*matchList, fuzzTarget.Name); ok {
+                       fmt.Println(fuzzTarget.Name)
+               }
+       }
        for _, example := range examples {
                if ok, _ := matchString(*matchList, example.Name); ok {
                        fmt.Println(example.Name)