From 1c377fbe940a7135c455baf710f821f7d307c72f Mon Sep 17 00:00:00 2001 From: thepudds Date: Thu, 16 Dec 2021 15:20:41 -0500 Subject: [PATCH] all: support cmd/go 1.18 fuzzing --- README.md | 232 +++-- cmd/fzgen/README.md | 3 + cmd/fzgen/main.go | 11 + examples/inputs/arg-reuse/argreuse.go | 44 + examples/inputs/race-xsync-map/go.mod | 15 + examples/inputs/race-xsync-map/go.sum | 49 + examples/inputs/race-xsync-map/xsyncmap.go | 52 ++ examples/inputs/race-xsync-mpmcqueue/go.mod | 15 + examples/inputs/race-xsync-mpmcqueue/go.sum | 49 + .../inputs/race-xsync-mpmcqueue/xsyncqueue.go | 33 + examples/inputs/race/race.go | 46 + examples/inputs/return-reuse/returnreuse.go | 47 + examples/inputs/test-chain-uuid/inputuuid.go | 24 + .../test-constructor-injection/inputctors.go | 89 ++ .../inputs/test-exported/inputexported.go | 43 + examples/inputs/test-types/intputtypes.go | 108 +++ .../outputs/arg-reuse/autofuzzchain_test.go | 35 + .../outputs/go-fuzz-perf/cmd-go/fuzz_test.go | 24 + examples/outputs/go-fuzz-perf/go-fuzz/fuzz.go | 52 ++ examples/outputs/go.mod | 32 + examples/outputs/go.sum | 58 ++ examples/outputs/google-uuid/autofuzz_test.go | 377 ++++++++ .../outputs/google-uuid/autofuzzchain_test.go | 156 ++++ examples/outputs/goroar/autofuzzchain_test.go | 101 ++ examples/outputs/lz4/autofuzz_test.go | 282 ++++++ .../outputs/netlink/autofuzzchain_test.go | 112 +++ .../standalone_repro1_test.go | 86 ++ .../standalone_repro2_test.go | 86 ++ .../race-xsync-map/autofuzzchain_test.go | 57 ++ .../race-xsync-mpmcqueue-repro/repro_test.go | 26 + .../autofuzzchain_test.go | 90 ++ examples/outputs/race/autofuzzchain_test.go | 40 + examples/outputs/randpanic/autofuzz_test.go | 36 + examples/outputs/randpanic/rand.go | 34 + .../outputs/roaring/autofuzzchain_test.go | 293 ++++++ .../stdlib-encoding-ascii85/autofuzz_test.go | 58 ++ .../stdlib-net-url/autofuzzchain_test.go | 96 ++ .../outputs/stdlib-netip/autofuzz_test.go | 656 +++++++++++++ .../outputs/stdlib-strconv/autofuzz_test.go | 246 +++++ fuzzer/fuzzer.go | 821 ++++++++++++++++ fuzzer/fuzzer_race_test.go | 191 ++++ fuzzer/fuzzer_test.go | 309 +++++++ fuzzer/internal/plan/plan.go | 32 + fuzzer/internal/randparam/bytes2rand.go | 18 +- fuzzer/internal/randparam/bytes2rand_test.go | 1 - fuzzer/internal/randparam/randparam.go | 629 ++++++++++--- fuzzer/internal/randparam/randparam_test.go | 225 ++++- fuzzer/marshall.go | 89 ++ fuzzer/marshall_test.go | 153 +++ gen/fzgen.go | 94 +- gen/genfuncs.go | 875 ++++++++++-------- gen/genfuncs_stdlib_test.go | 88 ++ gen/genfuncs_test.go | 245 +++++ gen/genfuncsloop.go | 641 +++++++++++++ gen/genfuncsloop_test.go | 213 +++++ gen/internal/mod/packages.go | 121 +-- gen/packages.go | 189 ++++ go.mod | 18 + go.sum | 52 ++ script_test.go | 58 +- test.sh | 21 + testdata/exported_and_private_local_pkg.go | 133 +++ .../exported_and_private_not_local_pkg.go | 134 +++ testdata/exported_local_pkg.go | 52 ++ testdata/exported_not_local_pkg.go | 53 ++ .../inject_ctor_false_exported_local_pkg.go | 200 ++++ ...nject_ctor_false_exported_not_local_pkg.go | 201 ++++ .../inject_ctor_true_exported_local_pkg.go | 155 ++++ ...inject_ctor_true_exported_not_local_pkg.go | 156 ++++ testdata/nil_checks_exported_not_local_pkg.go | 42 + testdata/race_exported_local_pkg.go | 34 + testdata/race_exported_not_local_pkg.go | 35 + ...gs_inject_ctor_false_exported_local_pkg.go | 517 +++++++++++ ...nject_ctor_false_exported_not_local_pkg.go | 590 ++++++++++++ ...inject_ctor_true_exported_not_local_pkg.go | 518 +++++++++++ testdata/types_exported_not_local_pkg.go | 212 +++++ testdata/uuid_exported_local_pkg.go | 67 ++ testdata/uuid_exported_not_local_pkg.go | 68 ++ testscripts/arg_reuse_fuzzing.txt | 65 ++ testscripts/help.txt | 31 +- testscripts/race_fuzzing.txt | 85 ++ testscripts/return_reuse_fuzzing.txt | 41 + 82 files changed, 11475 insertions(+), 890 deletions(-) create mode 100644 cmd/fzgen/README.md create mode 100644 cmd/fzgen/main.go create mode 100644 examples/inputs/arg-reuse/argreuse.go create mode 100644 examples/inputs/race-xsync-map/go.mod create mode 100644 examples/inputs/race-xsync-map/go.sum create mode 100644 examples/inputs/race-xsync-map/xsyncmap.go create mode 100644 examples/inputs/race-xsync-mpmcqueue/go.mod create mode 100644 examples/inputs/race-xsync-mpmcqueue/go.sum create mode 100644 examples/inputs/race-xsync-mpmcqueue/xsyncqueue.go create mode 100644 examples/inputs/race/race.go create mode 100644 examples/inputs/return-reuse/returnreuse.go create mode 100644 examples/inputs/test-chain-uuid/inputuuid.go create mode 100644 examples/inputs/test-constructor-injection/inputctors.go create mode 100644 examples/inputs/test-exported/inputexported.go create mode 100644 examples/inputs/test-types/intputtypes.go create mode 100644 examples/outputs/arg-reuse/autofuzzchain_test.go create mode 100644 examples/outputs/go-fuzz-perf/cmd-go/fuzz_test.go create mode 100644 examples/outputs/go-fuzz-perf/go-fuzz/fuzz.go create mode 100644 examples/outputs/go.mod create mode 100644 examples/outputs/go.sum create mode 100644 examples/outputs/google-uuid/autofuzz_test.go create mode 100644 examples/outputs/google-uuid/autofuzzchain_test.go create mode 100644 examples/outputs/goroar/autofuzzchain_test.go create mode 100644 examples/outputs/lz4/autofuzz_test.go create mode 100644 examples/outputs/netlink/autofuzzchain_test.go create mode 100644 examples/outputs/race-xsync-map-repro/standalone_repro1_test.go create mode 100644 examples/outputs/race-xsync-map-repro/standalone_repro2_test.go create mode 100644 examples/outputs/race-xsync-map/autofuzzchain_test.go create mode 100644 examples/outputs/race-xsync-mpmcqueue-repro/repro_test.go create mode 100644 examples/outputs/race-xsync-mpmcqueue/autofuzzchain_test.go create mode 100644 examples/outputs/race/autofuzzchain_test.go create mode 100644 examples/outputs/randpanic/autofuzz_test.go create mode 100644 examples/outputs/randpanic/rand.go create mode 100644 examples/outputs/roaring/autofuzzchain_test.go create mode 100644 examples/outputs/stdlib-encoding-ascii85/autofuzz_test.go create mode 100644 examples/outputs/stdlib-net-url/autofuzzchain_test.go create mode 100644 examples/outputs/stdlib-netip/autofuzz_test.go create mode 100644 examples/outputs/stdlib-strconv/autofuzz_test.go create mode 100644 fuzzer/fuzzer.go create mode 100644 fuzzer/fuzzer_race_test.go create mode 100644 fuzzer/fuzzer_test.go create mode 100644 fuzzer/internal/plan/plan.go create mode 100644 fuzzer/marshall.go create mode 100644 fuzzer/marshall_test.go create mode 100644 gen/genfuncs_stdlib_test.go create mode 100644 gen/genfuncs_test.go create mode 100644 gen/genfuncsloop.go create mode 100644 gen/genfuncsloop_test.go create mode 100644 gen/packages.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 test.sh create mode 100644 testdata/exported_and_private_local_pkg.go create mode 100644 testdata/exported_and_private_not_local_pkg.go create mode 100644 testdata/exported_local_pkg.go create mode 100644 testdata/exported_not_local_pkg.go create mode 100644 testdata/inject_ctor_false_exported_local_pkg.go create mode 100644 testdata/inject_ctor_false_exported_not_local_pkg.go create mode 100644 testdata/inject_ctor_true_exported_local_pkg.go create mode 100644 testdata/inject_ctor_true_exported_not_local_pkg.go create mode 100644 testdata/nil_checks_exported_not_local_pkg.go create mode 100644 testdata/race_exported_local_pkg.go create mode 100644 testdata/race_exported_not_local_pkg.go create mode 100644 testdata/strings_inject_ctor_false_exported_local_pkg.go create mode 100644 testdata/strings_inject_ctor_false_exported_not_local_pkg.go create mode 100644 testdata/strings_inject_ctor_true_exported_not_local_pkg.go create mode 100644 testdata/types_exported_not_local_pkg.go create mode 100644 testdata/uuid_exported_local_pkg.go create mode 100644 testdata/uuid_exported_not_local_pkg.go create mode 100644 testscripts/arg_reuse_fuzzing.txt create mode 100644 testscripts/race_fuzzing.txt create mode 100644 testscripts/return_reuse_fuzzing.txt diff --git a/README.md b/README.md index cba68a9..d33b658 100644 --- a/README.md +++ b/README.md @@ -1,133 +1,169 @@ -[![Build Status](https://travis-ci.org/thepudds/fzgo.svg?branch=master)](https://travis-ci.org/thepudds/fzgo) [![Go Report Card](https://goreportcard.com/badge/github.com/thepudds/fzgo)](https://goreportcard.com/report/github.com/thepudds/fzgo) +# fzgen +fzgen auto-generates fuzzing wrappers for Go 1.18, optionally finds problematic API call sequences, can automatically wire outputs to inputs across API calls, and supports rich types such as structs, maps, slices, named types, and common interfaces. -## fzgo: go-fuzz + 'go test' = fewer bugs +## Why? -If you are not familiar with fuzzing, this [motivation](http://tiny.cc/why-go-fuzz) document provides a quick 1-page introduction. +Fewer bugs, happy Gophers. -`fzgo` is a prototype of [golang/go#19109](https://golang.org/issue/19109) **"cmd/go: make fuzzing a first class citizen, like tests or benchmarks"**. +Modern fuzzing has had a large amount of [success](https://events.linuxfoundation.org/wp-content/uploads/2017/11/Syzbot-and-the-Tale-of-Thousand-Kernel-Bugs-Dmitry-Vyukov-Google.pdf) and can be almost [eerily smart](https://lcamtuf.blogspot.com/2014/11/pulling-jpegs-out-of-thin-air.html), but has been most heavily used in the realm of security, often with a focus on [parsing untrusted inputs](https://google.github.io/oss-fuzz/faq/#what-kind-of-projects-are-you-accepting). -`fzgo` supports some conveniences like fuzzing rich signatures and auto-generation of fuzzing functions. +Security is critical, but: -The basic approach is that `fzgo` integrates [dvyukov/go-fuzz](https://github.com/dvyukov/go-fuzz) -into `go test`, with the heavy lifting being done by `go-fuzz`, `go-fuzz-build`, and the `go` tool. The focus -is on step 1 of a tentative list of "Draft goals for a prototype" outlined in [this -comment](https://github.com/golang/go/issues/19109#issuecomment-441442080) on [#19109](https://golang.org/issue/19109): + 1. Eventually, the bigger success for fuzzing might be **finding correctness & stability problems** in a broader set of code bases, and beyond today's more common security focus. + 2. There is also a large opportunity to **make fuzzing easier to pick up** by a broader community. - _Step 1. Prototype proposed CLI, including interaction with existing 'go test'._ - -`fzgo` supports the `-fuzz` flag and several other related flags proposed in the March 2017 -[#19109 proposal document](https://github.com/golang/go/issues/19109#issuecomment-285456008). `fzgo` also supports typical `go` commands -such as `fzgo build`, `fzgo test`, or `fzgo env` (which are implemented by wrapping the `go` tool). +This project aims to pitch in on those two fronts. -Any and all feedback is welcome! +If enough people work to make the fuzzing ecosystem accessible enough, "coffee break fuzzing" might eventually become as common as unit tests. And of course, increased adoption of fuzzing helps security as well. :blush: -### Features +## Quick Start: Install & Automatically Create Fuzz Targets -* Rich signatures like `FuzzRegexp(re string, input []byte, posix bool)` are supported, as well as the classic `Fuzz(data []byte) int` form used by `go-fuzz`. -* The corpus is automatically used as deterministic input to unit tests when running a normal `go test`. -* Individual corpus files can be unit tested via `fzgo test -fuzz=. -run=TestCorpus/`. -* `go-fuzz` requires a two step process. `fzgo` eliminates the separate manual preparation step. -* `fzgo` automatically caches instrumented binaries in `GOPATH/pkg/fuzz` and re-uses them if possible. -* The fuzzing corpus defaults to `GOPATH/pkg/fuzz/corpus`. -* The `-fuzzdir=/some/path` flag allows the corpus to be stored elsewhere (e.g., a separate corpus repo); `-fuzzdir=testdata` stores the corpus under `/testdata/fuzz/fuzzname` (hence typically in VCS with the code under test). -* `fuzz` and `gofuzz` build tags are allowed but not required. -* An optional [genfuzzfuncs](https://github.com/thepudds/fzgo/blob/master/genfuzzfuncs/README.md) utility can automatically create fuzzing functions for all of the public functions and methods in a package of interest. This makes it quicker and easier to start fuzzing. +Starting from an empty directory, create a module and install the dev version of Go 1.18 via gotip: +``` +$ go mod init example +$ go install golang.org/dl/gotip@latest +$ gotip download +``` -## Usage +Download, compile, and install the fzgen binary from source: +``` +$ go install github.com/thepudds/fzgen/cmd/fzgen@latest ``` -Usage: fzgo test [build/test flags] [packages] [build/test flags] -Examples: +Use fzgen to automatically create a set of fuzz targets -- in this case for the encoding/ascii85 package from the Go standard library: +``` +$ fzgen encoding/ascii85 +fzgen: created autofuzz_test.go +``` - fzgo test # normal 'go test' of current package, plus run any corpus as unit tests - fzgo test -fuzz=. # fuzz the current package with a function starting with 'Fuzz' - fzgo test -fuzz=FuzzFoo # fuzz the current package with a function matching 'FuzzFoo' - fzgo test ./... -fuzz=FuzzFoo # fuzz a package in ./... with a function matching 'FuzzFoo' - fzgo test sample/pkg -fuzz=FuzzFoo # fuzz 'sample/pkg' with a function matching 'FuzzFoo' +That's it -- now we can start fuzzing! +``` +$ gotip test -fuzz=Fuzz_Encode +``` -Rich signatures like Fuzz(re string, input []byte, posix bool)` are supported, as well Fuzz(data []byte) int. -Fuzz functions must start with 'Fuzz'. +Within a few seconds, you should get a crash: +``` +[...] +fuzz: minimizing 56-byte failing input file +fuzz: elapsed: 0s, minimizing +--- FAIL: Fuzz_Encode (0.06s) +``` -The following flags work with 'fzgo test -fuzz': +Without any manual work, you just found a bug in the standard library. (It's a very minor bug though -- probably at the level of "perhaps the doc could be more explicit about an expected panic"). - -fuzz regexp - fuzz at most one function matching regexp - -fuzzdir dir - store fuzz artifacts in dir (default pkgpath/testdata/fuzz) - -fuzztime d - fuzz for duration d (default unlimited) - -parallel n - start n fuzzing operations (default GOMAXPROCS) - -timeout d - fail an individual call to a fuzz function after duration d (default 10s, minimum 1s) - -c - compile the instrumented code but do not run it - -v - verbose: print additional output -``` +That's enough for you to get started on your own, but let's also briefly look at a more interesting example. -## Install +## Example: Easily Finding a Data Race +Again starting from an empty directory, we'll set up a module, and this time also add fzgen to the go.mod: ``` -$ go get -u github.com/thepudds/fzgo/... -$ go get -u github.com/dvyukov/go-fuzz/... +$ go mod init temp +$ go get go get github.com/thepudds/fzgen ``` -Note: if you already have an older `dvyukov/go-fuzz`, you might need to first delete it from GOPATH as described in -the [dvyukov/go-fuzz](https://github.com/dvyukov/go-fuzz#history-rewrite) repo. +Next, we automatically create a new fuzz target. This time: + * We ask fzgen to "chain" a set of methods together in a calling sequence controlled by fzgen.Fuzzer (via the `-chain` argument). + * We also tell fzgen that it should in theory be safe to do parallel execution of those methods across multiple goroutines (via the `-parallel` argument). -The `go-fuzz` source code must be in your GOPATH, and the `go-fuzz` and `go-fuzz-build` binaries must be -in your path environment variable. +``` +$ fzgen -chain -parallel github.com/thepudds/fzgen/examples/inputs/race +fzgen: created autofuzzchain_test.go +``` -**Note**: Module-mode is not supported ([#15](https://github.com/thepudds/fzgo/issues/15)), but you can fuzz a module with `fzgo` as long as the code under test is in GOPATH and you set `GO111MODULE=off` env variable. +That's it! Let's get fuzzing. -## Status +This time, we also enable the race detector as we fuzz: +``` +$ gotip test -fuzz=. -race +``` -This is a simple prototype. Don't expect great things. ;-) +This is a harder challenge than our first example, but within several minutes or so, you should get a data race detected: +``` +--- FAIL: Fuzz_NewMySafeMap (4.26s) + --- FAIL: Fuzz_NewMySafeMap (0.01s) + testing.go:1282: race detected during execution of test +``` + +If we want to see what exact calls triggered this, along with their input arguments, we can set a fzgen debug flag asking it to show us a reproducer, and then ask 'go test' to re-run the failing input that was just found. (Your failing example will almost certainly have a different filename and show a different pattern of calls). + +``` +$ export FZDEBUG=repro=1 # On Windows: set FZDEBUG=repro=1 +$ gotip test -run=./9800b52 -race +``` -That said, there is reasonable test coverage and `fzgo` is hopefully beta quality. Automatically generating fuzz functions is implemented in a separate [genfuzzfuncs](https://github.com/thepudds/fzgo/blob/master/genfuzzfuncs/README.md) utility that is more alpha quality. +This will output a snippet of valid Go code that represents the reproducer: +```go + // PLANNED STEPS (loop count: 1, spin: true) + + Fuzz_MySafeMap_Store( + [16]uint8{152,152,152,152,152,152,152,152,152,152,152,152,152,152,152,152}, + &raceexample.Request{Answer:42,}, + ) + + var wg sync.WaitGroup + wg.Add(2) + + // Execute next steps in parallel. + go func() { + defer wg.Done() + Fuzz_MySafeMap_Load( + [16]uint8{152,152,152,152,152,152,152,152,152,152,152,152,152,152,152,152}, + ) + }() + go func() { + defer wg.Done() + Fuzz_MySafeMap_Load( + [16]uint8{152,152,152,152,152,152,152,152,152,152,152,152,152,152,152,152}, + ) + }() + wg.Wait() + + // Resume sequential execution. + Fuzz_MySafeMap_Load( + [16]uint8{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, + ) + +[...] + --- FAIL: Fuzz_NewMySafeMap (0.01s) + testing.go:1282: race detected during execution of test +``` -Testing is primarily done with the nice internal `testscripts` package used by the core Go team to test the `go` tool -and extracted at [rogpeppe/go-internal/testscript](https://github.com/rogpeppe/go-internal/tree/master/testscript). +Note that just running a regular test under the race detector might not catch this bug, including because the race detector [only finds data races that happen at runtime](https://go.dev/blog/race-detector), which means a diversity of code paths and input data is imporant for the race detector to do its job. Fzgen helps supply those code paths and data. -#### Changes from the proposal document +For this particular [bug](https://github.com/thepudds/fzgen/blob/master/examples/inputs/race/race.go#L34-L36) to be observable by the race detector, it requires: -The primary changes between the current fzgo prototype vs. the March 2017 [proposal document](https://github.com/golang/go/issues/19109#issuecomment-285456008): + 1. A Store must complete, then be followed by two Loads, and all three must use the same key. + 2. The Store must have certain payload data (`Answer: 42`). + 3. The two Loads must happen concurrently. + 4. Prior to the two Loads, no other Store can update the key to have a non-matching payload. -1. fzgo supports rich signatures. -2. The corpus location does not default to `/testdata/fuzz`, but instead follows the approach outlined [here](https://groups.google.com/d/msg/golang-fuzzing-proposal/WVyRXx7AsO4/CXzvbMT1CgAJ) and more precisely described in [PR #7](https://github.com/thepudds/fzgo/pull/7). -3. Initially, fzgo disallowed multiple fuzz functions to match (per the March 2017 proposal), -but as an experiment fzgo now allows multiple fuzz functions to match in order to -support something like 'go test -fuzz=. ./...' when there are multiple fuzz functions -across multiple packages. Fuzzing happens in round-robin manner if multiple fuzz functions match. -4. The proposal document suggested `GOPATH/pkg/GOOS_GOARCH_fuzz/` for a cache, but the prototype instead -uses `GOPATH/pkg/fuzz/GOOS_GOARCH/`. -5. The initial proposal document suggested generating new mutation-based inputs during `go test` when `-fuzz` was not specified. In order to keep `go test` deterministic, `fzgo` does not do that, but now does use the corpus as a deterministic set of inputs during `go test` when `-fuzz` is not specified. Also, the proposal document suggested `-fuzzinput` as a way of specifying a file from the corpus to execute as a unit test. `fzgo` instead uses the normal `-run` argument to `go test`. For example, `fzgo test -run=TestCorpus/4fa128cf066f2a31 some/pkg` runs the any file in the `some/pkg` corpus with a filename matching `4fa128cf066f2a31`. -6. Some of the commentators at [#19109](https://golang.org/issue/19109) suggested `-fuzztime duration` as a -way of controlling when to stop fuzzing. The proposal document does not include `-fuzztime` and `go-fuzz` -does not support it, but it seems useful in general and `-fuzztime` is in the prototype (and it proved -useful while testing the prototype). This might be removed later. -7. For experimentation, `FZGOFLAGSBUILD` and `FZGOFLAGSFUZZ` environmental variables can optionally contain a space-separated list of arguments to pass to `go-fuzz-build` and `go-fuzz`, respectively. +Here, the `42` seen in the reproducer must be `42`. On the other hand, the exact value of `152,152,152,...` in the key doesn't matter, but what does matter is that the same key value must be used across this sequence of three calls to trigger the bug. -#### Pieces of proposal document not implemented in this prototype +fz.Chain has logic to sometimes re-use input arguments of the same type across different calls (e.g., to make it easier to have a meaningful `Get(key)` following a `Put(key)`), as well logic to feed outputs of one step as the input to a subsequent step, which is helpful in other cases. -* `fuzz.F` or `testing.F` signature for fuzzing function. -* Allowing fuzzing functions to reside in `*_test.go` files. -* Anything to do with deeper integration with the compiler for more robust instrumentation. This -prototype is not focused on that area. -* Any of a much larger set of preexisting build flags like `-ldflags`, `-coverprofile`. -* Areas covered in the March 2017 [proposal document](https://github.com/golang/go/issues/19109#issuecomment-285456008), -outside of the direct user-facing behavior that this prototype focuses on. That said, the majority of user-facing behavior mentioned in the proposal document is either implemented in the prototype or explicitly mentioned in this list as not implemented. +## Example: Finding a Real Concurrency Bug in Real Code -The argument parsing in 'go test' is bespoke, and the argument parsing in `fzgo` is an approximation of that. -That might be OK for an early prototype. The right thing to do might be to extract -[src/cmd/go/internal/test/testflag.go](https://golang.org/src/cmd/go/internal/test/testflag.go), -which includes this comment: +The prior example was a small but challenging example that takes a few minutes of fuzzing to find, but you can see an example of a deadlock found in real code [here](https://github.com/thepudds/fzgen/blob/master/examples/outputs/race-xsync-map-repro/standalone_repro1_test.go) from the xsync.Map from github.com/puzpuzpuz/xsync. -``` -// The flag handling part of go test is large and distracting. -// We can't use the flag package because some of the flags from -// our command line are for us, and some are for 6.out, and -// some are for both. -``` +This deadlock gets reported after a few hours of running the [autogenchain_test.go](https://github.com/thepudds/fzgen/blob/master/examples/outputs/race-xsync-map/autofuzzchain_test.go) generated by fzgen. Interestingly, for any particular found reproducer, the deadlock might only then reproduce about 1 out of 1,000 attempts. + +Fortunately, by pasting the output of `FZDEBUG=repro=1` into a [standalone_repro_test.go](https://github.com/thepudds/fzgen/blob/master/examples/outputs/race-xsync-map-repro/standalone_repro1_test.go) file and using the handy `-count` argument in a normal `go test -count=10000` invocation, we can then reproduce the deadlock cleanly on demand. At that point, the reproducer is completely standalone and does not rely on fzgen any longer. + +## fzgen status + +* fzgen is still a work in progress, but hopefully will soon be approaching beta quality. +* Emitting reproducers for a chain is currently best effort, but the intent is to improve to creating a complete standalone reproducer. +* Corpus encoding in particular will change in the near term. +* Roughly by the time of Go 1.18 graduates from Beta, the current intent is that fzgen will reach a 1.0 status. + * By 1.0, fzgen will have a stable corpus encoding, or an equivalent (such as perhaps the ability to programmatically set an encoding version number to keep using a corpus that is an older fzgen encoding). + +## What next? + +Any and all feedback is welcome! :grinning: + +Please feel free to open a new issue here, or to contact the author on Twitter ([@thepudds1](https://twitter.com/thepudds1)). + +The roadmap issue (TODO) in particular is a reasonable starting place. + +If you made it this far -- thanks! diff --git a/cmd/fzgen/README.md b/cmd/fzgen/README.md new file mode 100644 index 0000000..7c41b98 --- /dev/null +++ b/cmd/fzgen/README.md @@ -0,0 +1,3 @@ +## cmd/fzgen + +See the project [README](https://github.com/thepudds/fzgo/) for more details. \ No newline at end of file diff --git a/cmd/fzgen/main.go b/cmd/fzgen/main.go new file mode 100644 index 0000000..5199ed5 --- /dev/null +++ b/cmd/fzgen/main.go @@ -0,0 +1,11 @@ +package main + +import ( + "os" + + gen "github.com/thepudds/fzgen/gen" +) + +func main() { + os.Exit(gen.FzgenMain()) +} diff --git a/examples/inputs/arg-reuse/argreuse.go b/examples/inputs/arg-reuse/argreuse.go new file mode 100644 index 0000000..b0de3a9 --- /dev/null +++ b/examples/inputs/arg-reuse/argreuse.go @@ -0,0 +1,44 @@ +// Package argexample shows an example that would take longer to +// to panic simply using dvyukov/go-fuzz or 'go test -fuzz=.', +// but which should panic in 20-30 sec or so via fz.Chain +// when fz.Chain is properly reusing input arg values from a step +// as candidate input values to another step (currently +// just a few seconds with GODEBUG=fuzzseed=1, vs. 2-3 minutes for +// cmd/go fuzzing without arg reuse). +// +// To panic, this example has three requirements: +// 1. Step1 must be called. +// 2. Step2 must be subsequently called. +// 3. The two args passed to Step2 must match the first arg of Step1. +// +// fz.Chain usually solves this by wiring the one of the args to Step1 to the inputs to Step2. +// +// The intent is that this example is run sequentially (that is, not with concurrent steps). +// +// (go-fuzz and cmd/go fuzzing can solve this many ways, including by copying +// bytes, or picking same interesting values in the right spots, etc.). +package argexample + +type PanicOnArgReuse struct { + rememberedInt int64 + passedStep1 bool +} + +func New(i int) *PanicOnArgReuse { + return &PanicOnArgReuse{} +} + +func (p *PanicOnArgReuse) Step1(a, b int64) { + // zero value is too easy, as are repeating values. + if a != b && a != 0 && b != 0 { + p.rememberedInt = a + p.passedStep1 = true + } +} + +func (p *PanicOnArgReuse) Step2(a, b int64) { + // TODO: fuzzer should reuse args beyond first arg, and then change this to b == p.rememberedB + if a == p.rememberedInt && b == p.rememberedInt && p.passedStep1 { + panic("bingo - found our desired panic") + } +} diff --git a/examples/inputs/race-xsync-map/go.mod b/examples/inputs/race-xsync-map/go.mod new file mode 100644 index 0000000..7c0e8b9 --- /dev/null +++ b/examples/inputs/race-xsync-map/go.mod @@ -0,0 +1,15 @@ +module github.com/thepudds/fzgen/examples/inputs/race-xsync-map + +go 1.17 + +require ( + github.com/puzpuzpuz/xsync v1.0.1-0.20210823092703-32778049b5f5 + github.com/thepudds/fzgen v0.0.0-00010101000000-000000000000 +) + +require ( + github.com/google/gofuzz v1.2.0 // indirect + github.com/sanity-io/litter v1.5.1 // indirect +) + +replace github.com/thepudds/fzgen => ./../../.. diff --git a/examples/inputs/race-xsync-map/go.sum b/examples/inputs/race-xsync-map/go.sum new file mode 100644 index 0000000..d30d801 --- /dev/null +++ b/examples/inputs/race-xsync-map/go.sum @@ -0,0 +1,49 @@ +github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0 h1:GD+A8+e+wFkqje55/2fOVnZPkoDIu1VooBWfNrnY8Uo= +github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/puzpuzpuz/xsync v0.0.0-20210815074145-06f072a152a8 h1:bAvbvlslJkn5r/JnGkDOoZ5/N1I6zBiDBcCpsZ8an/Q= +github.com/puzpuzpuz/xsync v0.0.0-20210815074145-06f072a152a8/go.mod h1:GF6mnc4fZ1Vit0eWBHPcthv2l1nE81+iZvLsexB/HM4= +github.com/puzpuzpuz/xsync v1.0.1-0.20210823092703-32778049b5f5 h1:ZoUoiy+1+jwi9B2+5F0Mh4HMlI/q01Nqt+qN9T2h5lA= +github.com/puzpuzpuz/xsync v1.0.1-0.20210823092703-32778049b5f5/go.mod h1:GF6mnc4fZ1Vit0eWBHPcthv2l1nE81+iZvLsexB/HM4= +github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +github.com/sanity-io/litter v1.5.1 h1:dwnrSypP6q56o3lFxTU+t2fwQ9A+U5qrXVO4Qg9KwVU= +github.com/sanity-io/litter v1.5.1/go.mod h1:5Z71SvaYy5kcGtyglXOC9rrUi3c1E8CamFWjQsazTh0= +github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312 h1:UsFdQ3ZmlzS0BqZYGxvYaXvFGUbCmPGy8DM7qWJJiIQ= +github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/thepudds/fzgo v0.2.3-0.20211028225222-cdf4f1cc8ee2/go.mod h1:ZgigL1toyKrar3rWdXz7Fuv7bUpKZ4BAYN49TpEFMCI= +github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= diff --git a/examples/inputs/race-xsync-map/xsyncmap.go b/examples/inputs/race-xsync-map/xsyncmap.go new file mode 100644 index 0000000..d76f83f --- /dev/null +++ b/examples/inputs/race-xsync-map/xsyncmap.go @@ -0,0 +1,52 @@ +// xsyncmap is a thin wrapper over github.com/puzpuzpuz/xsync.Map. + +package xsyncmap + +import "github.com/puzpuzpuz/xsync" + +// target https://github.com/puzpuzpuz/xsync at 32778049b + +type XSyncMap struct { + xsyncmap *xsync.Map +} + +func NewXSyncMap() *XSyncMap { + return &XSyncMap{xsync.NewMap()} +} + +func (m *XSyncMap) Delete(key string) { + m.xsyncmap.Delete(key) +} + +func (m *XSyncMap) Load(key string) (value int8, ok bool) { + v, ok := m.xsyncmap.Load(key) + if !ok { + return 0, false + } + return v.(int8), ok +} + +func (m *XSyncMap) LoadAndDelete(key string) (value int8, loaded bool) { + v, loaded := m.xsyncmap.LoadAndDelete(key) + if !loaded { + return 0, false + } + return v.(int8), loaded +} + +func (m *XSyncMap) LoadOrStore(key string, value int8) (actual int8, loaded bool) { + v, loaded := m.xsyncmap.LoadOrStore(key, value) + if !loaded { + return 0, false + } + return v.(int8), loaded +} + +func (m *XSyncMap) Store(key string, value int8) { + m.xsyncmap.Store(key, value) +} + +// Range will be ignored by default by fzgen. +func (m *XSyncMap) Range(f func(key string, value interface{}) bool) { + m.xsyncmap.Range(f) +} diff --git a/examples/inputs/race-xsync-mpmcqueue/go.mod b/examples/inputs/race-xsync-mpmcqueue/go.mod new file mode 100644 index 0000000..8dfaf48 --- /dev/null +++ b/examples/inputs/race-xsync-mpmcqueue/go.mod @@ -0,0 +1,15 @@ +module github.com/thepudds/fzgen/examples/inputs/race-xsyncmap + +go 1.17 + +require ( + github.com/puzpuzpuz/xsync v1.0.1-0.20210823092703-32778049b5f5 + github.com/thepudds/fzgen v0.0.0-00010101000000-000000000000 +) + +require ( + github.com/google/gofuzz v1.2.0 // indirect + github.com/sanity-io/litter v1.5.1 // indirect +) + +replace github.com/thepudds/fzgen => ./../../.. diff --git a/examples/inputs/race-xsync-mpmcqueue/go.sum b/examples/inputs/race-xsync-mpmcqueue/go.sum new file mode 100644 index 0000000..d30d801 --- /dev/null +++ b/examples/inputs/race-xsync-mpmcqueue/go.sum @@ -0,0 +1,49 @@ +github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0 h1:GD+A8+e+wFkqje55/2fOVnZPkoDIu1VooBWfNrnY8Uo= +github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/puzpuzpuz/xsync v0.0.0-20210815074145-06f072a152a8 h1:bAvbvlslJkn5r/JnGkDOoZ5/N1I6zBiDBcCpsZ8an/Q= +github.com/puzpuzpuz/xsync v0.0.0-20210815074145-06f072a152a8/go.mod h1:GF6mnc4fZ1Vit0eWBHPcthv2l1nE81+iZvLsexB/HM4= +github.com/puzpuzpuz/xsync v1.0.1-0.20210823092703-32778049b5f5 h1:ZoUoiy+1+jwi9B2+5F0Mh4HMlI/q01Nqt+qN9T2h5lA= +github.com/puzpuzpuz/xsync v1.0.1-0.20210823092703-32778049b5f5/go.mod h1:GF6mnc4fZ1Vit0eWBHPcthv2l1nE81+iZvLsexB/HM4= +github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +github.com/sanity-io/litter v1.5.1 h1:dwnrSypP6q56o3lFxTU+t2fwQ9A+U5qrXVO4Qg9KwVU= +github.com/sanity-io/litter v1.5.1/go.mod h1:5Z71SvaYy5kcGtyglXOC9rrUi3c1E8CamFWjQsazTh0= +github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312 h1:UsFdQ3ZmlzS0BqZYGxvYaXvFGUbCmPGy8DM7qWJJiIQ= +github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/thepudds/fzgo v0.2.3-0.20211028225222-cdf4f1cc8ee2/go.mod h1:ZgigL1toyKrar3rWdXz7Fuv7bUpKZ4BAYN49TpEFMCI= +github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= diff --git a/examples/inputs/race-xsync-mpmcqueue/xsyncqueue.go b/examples/inputs/race-xsync-mpmcqueue/xsyncqueue.go new file mode 100644 index 0000000..68d5532 --- /dev/null +++ b/examples/inputs/race-xsync-mpmcqueue/xsyncqueue.go @@ -0,0 +1,33 @@ +// xsyncqueue is a thin wrapper over github.com/puzpuzpuz/xsync.MPMCQueue. + +package xsyncqueue + +import "github.com/puzpuzpuz/xsync" + +type XSyncMPMCQueue struct { + xsyncq *xsync.MPMCQueue +} + +func NewMPMCQueue(capacity int) *XSyncMPMCQueue { + return &XSyncMPMCQueue{xsync.NewMPMCQueue(capacity)} +} + +func (q *XSyncMPMCQueue) Dequeue() int8 { + return q.xsyncq.Dequeue().(int8) +} + +func (q *XSyncMPMCQueue) Enqueue(item int8) { + q.xsyncq.Enqueue(item) +} + +func (q *XSyncMPMCQueue) TryDequeue() (item int8, ok bool) { + v, ok := q.xsyncq.TryDequeue() + if !ok { + return 0, false + } + return v.(int8), true +} + +func (q *XSyncMPMCQueue) TryEnqueue(item int8) bool { + return q.xsyncq.TryEnqueue(item) +} diff --git a/examples/inputs/race/race.go b/examples/inputs/race/race.go new file mode 100644 index 0000000..3c23a68 --- /dev/null +++ b/examples/inputs/race/race.go @@ -0,0 +1,46 @@ +// raceexample wraps sync.Map in a type-safe way, but sadly the implementation has a +// a possible data race in handling a counter on the objects stored in the sync.Map. +// +// To observe the data race with the race detector: +// 1. A Store must be followed by two Loads, and all three must use the same key. +// 2. The Store must have certain payload data (Answer: 42). +// 3. The two Loads must happen concurrently. +// 4. Prior to the two Loads, no other Store can update the key to have a non-matching payload. +// +// Using the fzgen/fuzzer.Chain created by default via 'fzgen -chain -parallel -pkg=github.com/thepudds/fzgen/examples/inputs/race', +// this data race is typically caught after a few minutes of fuzzing with '-race' when starting from scratch. +package raceexample + +import ( + "sync" +) + +type MySafeMap struct { + syncMap sync.Map +} + +func NewMySafeMap() *MySafeMap { + return &MySafeMap{} +} + +func (m *MySafeMap) Store(key [16]byte, req *Request) { + m.syncMap.Store(key, req) +} + +func (m *MySafeMap) Load(key [16]byte) *Request { + r, ok := m.syncMap.Load(key) + if ok { + req := r.(*Request) + if req.Answer == 42 { + // DATA RACE (but requires: matching store/load keys, and concurrent matching load keys, and certain payload data) + req.deepQuestion++ + } + return req + } + return nil +} + +type Request struct { + Answer int8 // TODO: change to int when cmd/go has the equivalent of go-fuzz sonar or libfuzzer comparision instrumentation + deepQuestion int +} diff --git a/examples/inputs/return-reuse/returnreuse.go b/examples/inputs/return-reuse/returnreuse.go new file mode 100644 index 0000000..c95ee1a --- /dev/null +++ b/examples/inputs/return-reuse/returnreuse.go @@ -0,0 +1,47 @@ +// Package returnexample shows an example that would be quite +// challenging to panic simply using dvyukov/go-fuzz or 'go test -fuzz=.', +// but which should panic almost immediately via fz.Chain +// when fz.Chain is properly wiring the return values from a step +// as candidate input values to another step. +// +// To panic, this example has three requirements: +// 1. Step1 must be called, where Step1 returns the sha256 of its input uint64. +// 2. Step2 must be subsequently called. +// 3. The sha256 of the uint64 passed to the most recent Step1 must equal the [32]byte passed to Step2. +// +// fz.Chain solves this by wiring the output of Step1 to the input to Step2. +// +// The intent is that this example is run sequentially (that is, not with concurrent steps). +// +// (go-fuzz's sonar won't look at a [32]byte sha256, and +// neither will the current value comparision instrumentation in 'go test -fuzz=.', +// and there are no magic literals to automatically learn for a dictionary). +package returnexample + +import ( + "crypto/sha256" + "encoding/binary" +) + +type PanicOnReturnReuse struct { + rememberedSha [32]byte + calledStep1 bool +} + +func New(i int) *PanicOnReturnReuse { + return &PanicOnReturnReuse{} +} + +func (p *PanicOnReturnReuse) Step1(input uint64) [32]byte { + b := make([]byte, 8) + binary.LittleEndian.PutUint64(b, input) + p.rememberedSha = sha256.Sum256(b) + p.calledStep1 = true + return p.rememberedSha +} + +func (p *PanicOnReturnReuse) Step2(inputSha [32]byte) { + if inputSha == p.rememberedSha && p.calledStep1 { + panic("bingo - found our desired panic") + } +} diff --git a/examples/inputs/test-chain-uuid/inputuuid.go b/examples/inputs/test-chain-uuid/inputuuid.go new file mode 100644 index 0000000..245b7a3 --- /dev/null +++ b/examples/inputs/test-chain-uuid/inputuuid.go @@ -0,0 +1,24 @@ +// This is rougly modeled on a few examples from github.com/google/uuid.UUID. +// This is used for some basic tests. +package uuid + +type MyUUID struct{} + +// NewFromBytes should be picked up as the constructor by default via 'fzgen -chain'. +func NewFromBytes(b []byte) (uuid MyUUID, err error) { return MyUUID{}, nil } + +// SetNodeID is a package-level func, and by default should NOT be included via 'fzgen -chain'. +// Currently fails (is included) +func SetNodeID(id []byte) bool { return false } + +// URN has a non-pointer receiver, and by default SHOULD be included via 'fzgen -chain'. +// Currently fails (is missing) +func (uuid MyUUID) URN() string { return "" } + +// UnmarshalBinary has a pointer receiver, and by default SHOULD be included via 'fzgen -chain'. +// Currently is correctly included. +// UnmarshalBinary implements encoding.BinaryUnmarshaler. +func (uuid *MyUUID) UnmarshalBinary(data []byte) error { return nil } + +// UnmarshalBinary implements encoding.BinaryUnmarshaler. +func (uuid MyUUID) MarshalBinary() ([]byte, error) { return nil, nil } diff --git a/examples/inputs/test-constructor-injection/inputctors.go b/examples/inputs/test-constructor-injection/inputctors.go new file mode 100644 index 0000000..9261f9c --- /dev/null +++ b/examples/inputs/test-constructor-injection/inputctors.go @@ -0,0 +1,89 @@ +package fuzzwrapexamples + +import "bufio" + +// ---- Constructor injection examples/tests ---- + +// When fuzzing a method, by default genfuzzfuncs puts the receiver's +// type into the parameter list of the wrapper function. +// This works reasonably well if the receiver's type has public +// member variables -- we can set and mutate public member variables while fuzzing. +// However, that works less well if there are no public member variables, +// such as for regex.Regexp. +// If asked, we can inject constructors like so: +// * we determine the type of the receiver for the method we want to fuzz. +// * we look for a constructor capable of creating the receiver's type. +// * we "promote" the parameters from the constructor into the params for the wrapper function. +// * we insert a call to the constructor using those params. +// * we use the result of the constructor to call method we want to fuzz. + +type A struct{ c int } + +func NewAPtr(c int) *A { return &A{c} } +func (r *A) PtrMethodWithArg(i int) bool { return r.c == i } +func (r *A) PtrMethodNoArg() bool { return r.c == 0 } +func (r A) ValMethodWithArg(i int) bool { return r.c == i } +func (r A) ValMethodNoArg() bool { return r.c == 0 } + +type B struct{ c int } + +func NewBVal(c int) B { return B{c} } +func (r *B) PtrMethodWithArg(i int) bool { return r.c == i } +func (r *B) PtrMethodNoArg() bool { return r.c == 0 } +func (r B) ValMethodWithArg(i int) bool { return r.c == i } +func (r B) ValMethodNoArg() bool { return r.c == 0 } + +// Package is roughly modeled on go/types.Package, +// which has a collision between a parameter name used for +// for the constructor and a parameter name used in a later function +// under test. (The colliding paramater name happens to be 'name', +// though the actual name doesn't matter -- just that it collides). +type Package struct { + path string + name string +} + +func NewPackage(path, name string) *Package { + return &Package{path: path, name: name} +} + +func (pkg *Package) SetName(name string) { pkg.name = name } + +// Reader is roughly modeled on textproto.Reader +// We want to avoid generating a mismatched object variable name: +// func Fuzz_Reader_ReadLine(r *bufio.Reader) { +// if r == nil { +// return +// } +// r1 := textproto.NewReader(r) +// r2.ReadLine() +// } + +type Z struct{} + +func NewZ(z *bufio.Reader) *Z { + return &Z{} +} + +func (z *Z) ReadLine() (string, error) { + return "", nil +} + +// Reader is roughly modeled on google/uuid NullUUID.UnmarshalBinary +// We want to avoid generating a mismatched object variable name: + +type MyNullUUID struct{} + +func (nu *MyNullUUID) UnmarshalBinary(data []byte) error { return nil } + +// MyRegexp is roughly modeled on regexp.Expand +type MyRegexp struct{} + +func (re *MyRegexp) Expand(dst []byte, template []byte, src []byte, match []int) []byte { + return nil +} + +// Similar to regexp.Compile, let's return an err here +func NewMyRegexp(a int) (*MyRegexp, error) { + return &MyRegexp{}, nil +} diff --git a/examples/inputs/test-exported/inputexported.go b/examples/inputs/test-exported/inputexported.go new file mode 100644 index 0000000..88cbc97 --- /dev/null +++ b/examples/inputs/test-exported/inputexported.go @@ -0,0 +1,43 @@ +package fuzzwrapexamples + +import "io" + +// ---- Export examples/tests ---- + +// FuncExported is a test function to make sure we emit exported functions. +func FuncExported(i int) {} +func funcNotExported(i int) {} + +type typeNotExported int + +func (t *typeNotExported) pointerRcvNotExportedMethod(i int) {} +func (t typeNotExported) nonPointerRcvNotExportedMethod(i int) {} +func (t *typeNotExported) PointerExportedMethod(i int) {} +func (t typeNotExported) NonPointerExportedMethod(i int) {} + +// TypeExported is a test type to make sure we emit exported methods. +type TypeExported int + +func (t *TypeExported) pointerRcvNotExportedMethod(i int) {} +func (t TypeExported) nonPointerRcvNotExportedMethod(i int) {} +func (t *TypeExported) PointerExportedMethod(i int) {} +func (t TypeExported) NonPointerExportedMethod(i int) {} + +var ExportedFuncVar = func(i int) {} +var notExportedFuncVar = func(i int) {} + +// ---- Interface examples/tests ---- + +// ExportedInterface is a test interface to make sure +// we don't emit anything for the declaration of an interface. +type ExportedInterface interface { + ExportedFunc() +} + +// FuncExportedUsesUnsupportedInterface is a test func to make sure +// we don't emit a wrapper for functions that use unsupported interfaces. +func FuncExportedUsesUnsupportedInterface(e ExportedInterface) {} + +// FuncExportedUsesSupportedInterface is a test func to make sure +// we do emit a wrapper for functions that use supported interfaces. +func FuncExportedUsesSupportedInterface(w io.Reader) {} diff --git a/examples/inputs/test-types/intputtypes.go b/examples/inputs/test-types/intputtypes.go new file mode 100644 index 0000000..4aa2104 --- /dev/null +++ b/examples/inputs/test-types/intputtypes.go @@ -0,0 +1,108 @@ +package fuzzwrapexamples + +import ( + "context" + "io" + "net" + "unsafe" +) + +// ---- Various types examples/tests ---- + +// This should not trigger a fz.Fill and should not be skipped. +func Short1(x1 int) {} + +// These should trigger a fz.Fill and should not be skipped. +func Short2(x1 *int) {} +func Short3(x1 **int) {} +func Short4(x1 MyInt) {} +func Short5(x1 complex64) {} +func Short6(x1 complex128) {} +func Short7(x1 uintptr) {} +func Short8(x1 unsafe.Pointer) {} + +// This checks each of the major approaches for interfaces in the randparam.SupportedInterfaces map. +func InterfacesShortList(ctx context.Context, w io.Writer, r io.Reader, sw io.StringWriter, rc io.ReadCloser) { + ctx.Err() + io.Copy(w, r) + sw.WriteString("hello") + rc.Close() +} + +// This is the full list from randparam.SupportedInterfaces. +func InterfacesFullList( + x1 io.Writer, + x2 io.Reader, + x3 io.ReaderAt, + x4 io.WriterTo, + x5 io.Seeker, + x6 io.ByteScanner, + x7 io.RuneScanner, + x8 io.ReadSeeker, + x9 io.ByteReader, + x10 io.RuneReader, + x11 io.ByteWriter, + x12 io.ReadWriter, + x13 io.ReaderFrom, + x14 io.StringWriter, + x15 io.Closer, + x16 io.ReadCloser, + x17 context.Context) { +} + +// This should be skipped due to unsupported interface. +func InterfacesSkip(c net.Conn) {} + +type MyInt int +type MyStruct struct{ A int } + +// This should trigger a fz.Fill and should not be skipped. +func TypesShortListFill( + x1 int, + x2 *int, + x3 **int, + x4 map[string]string, + x5 *map[string]string, + x6 MyInt, + x7 [4]int, + x8 MyStruct, + x9 io.ByteReader, + x10 io.RuneReader, + x11 io.ByteWriter, + x12 io.ReadWriter, + x13 io.ReaderFrom, + x14 io.StringWriter, + x15 io.Closer, + x16 io.ReadCloser, + x17 context.Context) { +} + +// This should not trigger a fz.Fill and should not be skipped. +func TypesShortListNoFill( + x1 int, + x5 string) { +} + +// These should be skipped. +func TypesShortListSkip1(x chan bool) {} +func TypesShortListSkip2(x func(int)) {} + +// This should triggier a fz.Fill with nil checks for non-chained output, +// and fz.Fill without nil checks for chained output. +type TypesNilCheck struct{} + +func NewTypesNilCheck() *TypesNilCheck { return &TypesNilCheck{} } + +func (n *TypesNilCheck) Pointers( + x1 *int, + x2 **int) { +} + +func (n *TypesNilCheck) Interface( + x1 io.Writer, +) { +} + +func (n *TypesNilCheck) WriteTo(stream io.Writer) (int64, error) { + return 0, nil +} diff --git a/examples/outputs/arg-reuse/autofuzzchain_test.go b/examples/outputs/arg-reuse/autofuzzchain_test.go new file mode 100644 index 0000000..45e2ac0 --- /dev/null +++ b/examples/outputs/arg-reuse/autofuzzchain_test.go @@ -0,0 +1,35 @@ +package argexamplefuzz // rename if needed + +// if needed, fill in imports or run 'goimports' +import ( + "testing" + + argexample "github.com/thepudds/fzgen/examples/inputs/arg-reuse" + "github.com/thepudds/fzgen/fuzzer" +) + +func Fuzz_New_Chain(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var i int + fz := fuzzer.NewFuzzer(data) + fz.Fill(&i) + + target := argexample.New(i) + + steps := []fuzzer.Step{ + fuzzer.Step{ + Name: "Fuzz_PanicOnArgReuse_Step1", + Func: func(a int64, b int64) { + target.Step1(a, b) + }}, + fuzzer.Step{ + Name: "Fuzz_PanicOnArgReuse_Step2", + Func: func(a int64, b int64) { + target.Step2(a, b) + }}, + } + + // Execute a specific chain of steps, with the count, sequence and arguments controlled by fz.Chain + fz.Chain(steps, nil) + }) +} diff --git a/examples/outputs/go-fuzz-perf/cmd-go/fuzz_test.go b/examples/outputs/go-fuzz-perf/cmd-go/fuzz_test.go new file mode 100644 index 0000000..58f3c17 --- /dev/null +++ b/examples/outputs/go-fuzz-perf/cmd-go/fuzz_test.go @@ -0,0 +1,24 @@ +package fuzzperf + +import ( + "encoding/binary" + "testing" +) + +func findInt(i int) { + if 1337 == i { + panic("Found int") + } +} + +// FuzzIntegerLittleEndian shows how sonar can discover integers to use as inputs to increase coverage +func FuzzIntegerLittleEndian(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + // interpret data as little-endian encoded + if len(data) < 8 { + return + } + findInt(int(binary.LittleEndian.Uint64(data))) + }) + +} diff --git a/examples/outputs/go-fuzz-perf/go-fuzz/fuzz.go b/examples/outputs/go-fuzz-perf/go-fuzz/fuzz.go new file mode 100644 index 0000000..fe9464a --- /dev/null +++ b/examples/outputs/go-fuzz-perf/go-fuzz/fuzz.go @@ -0,0 +1,52 @@ +package fuzzperf + +import ( + "encoding/binary" + "strconv" +) + +func findInt(i int) { + if 1337 == i { + panic("Found int") + } +} + +// FuzzIntegerBigEndian shows how sonar can discover integers to use as inputs to increase coverage +func FuzzIntegerBigEndian(data []byte) int { + // interpret data as big-endian encoded + if len(data) < 8 { + return 0 + } + findInt(int(binary.BigEndian.Uint64(data))) + return 1 +} + +// FuzzIntegerLittleEndian shows how sonar can discover integers to use as inputs to increase coverage +func FuzzIntegerLittleEndian(data []byte) int { + // interpret data as little-endian encoded + if len(data) < 8 { + return 0 + } + findInt(int(binary.LittleEndian.Uint64(data))) + return 1 +} + +// FuzzIntegerDecimalString interprets input as a decimal string +func FuzzIntegerDecimalString(data []byte) int { + i, err := strconv.Atoi(string(data)) + if err != nil { + return 0 + } + findInt(i) + return 1 +} + +// FuzzIntegerHexString interprets input as a hexadecimal string +func FuzzIntegerHexString(data []byte) int { + i, err := strconv.ParseInt(string(data), 16, 64) + if err != nil { + return 0 + } + findInt(int(i)) + return 1 +} \ No newline at end of file diff --git a/examples/outputs/go.mod b/examples/outputs/go.mod new file mode 100644 index 0000000..7c5912f --- /dev/null +++ b/examples/outputs/go.mod @@ -0,0 +1,32 @@ +module example + +// This is a separate module because it relies on go 1.18, +// such as *testing.F for native cmd/go fuzzing. + +go 1.18 + +replace ( + github.com/thepudds/fzgen => ./../.. + github.com/thepudds/fzgen/examples/inputs/race-xsync-map => ./../../examples/inputs/race-xsync-map + github.com/thepudds/fzgen/examples/inputs/race-xsync-mpmcqueue => ./../../examples/inputs/race-xsync-mpmcqueue +) + +require ( + github.com/google/uuid v1.3.0 + github.com/thepudds/fzgen v0.0.0-00010101000000-000000000000 + github.com/thepudds/fzgen/examples/inputs/race-xsync-map v0.0.0-00010101000000-000000000000 + github.com/thepudds/fzgen/examples/inputs/race-xsync-mpmcqueue v0.0.0-00010101000000-000000000000 + golang.zx2c4.com/go118/netip v0.0.0-20211111135330-a4a02eeacf9d + inet.af/netaddr v0.0.0-20211027220019-c74959edd3b6 +) + +require ( + github.com/RoaringBitmap/roaring v0.9.4 // indirect + github.com/bits-and-blooms/bitset v1.2.0 // indirect + github.com/google/gofuzz v1.2.0 // indirect + github.com/mschoch/smat v0.2.0 // indirect + github.com/puzpuzpuz/xsync v1.0.1-0.20210823092703-32778049b5f5 // indirect + github.com/sanity-io/litter v1.5.1 // indirect + go4.org/intern v0.0.0-20211027215823-ae77deb06f29 // indirect + go4.org/unsafe/assume-no-moving-gc v0.0.0-20211027215541-db492cf91b37 // indirect +) diff --git a/examples/outputs/go.sum b/examples/outputs/go.sum new file mode 100644 index 0000000..6df3c82 --- /dev/null +++ b/examples/outputs/go.sum @@ -0,0 +1,58 @@ +github.com/RoaringBitmap/roaring v0.9.4 h1:ckvZSX5gwCRaJYBNe7syNawCU5oruY9gQmjXlp4riwo= +github.com/RoaringBitmap/roaring v0.9.4/go.mod h1:icnadbWcNyfEHlYdr+tDlOTih1Bf/h+rzPpv4sbomAA= +github.com/bits-and-blooms/bitset v1.2.0 h1:Kn4yilvwNtMACtf1eYDlG8H77R07mZSPbMjLyS07ChA= +github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= +github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw= +github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM= +github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw= +github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0 h1:GD+A8+e+wFkqje55/2fOVnZPkoDIu1VooBWfNrnY8Uo= +github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/puzpuzpuz/xsync v1.0.1-0.20210823092703-32778049b5f5 h1:ZoUoiy+1+jwi9B2+5F0Mh4HMlI/q01Nqt+qN9T2h5lA= +github.com/puzpuzpuz/xsync v1.0.1-0.20210823092703-32778049b5f5/go.mod h1:GF6mnc4fZ1Vit0eWBHPcthv2l1nE81+iZvLsexB/HM4= +github.com/sanity-io/litter v1.5.1 h1:dwnrSypP6q56o3lFxTU+t2fwQ9A+U5qrXVO4Qg9KwVU= +github.com/sanity-io/litter v1.5.1/go.mod h1:5Z71SvaYy5kcGtyglXOC9rrUi3c1E8CamFWjQsazTh0= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312 h1:UsFdQ3ZmlzS0BqZYGxvYaXvFGUbCmPGy8DM7qWJJiIQ= +github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go4.org/intern v0.0.0-20211027215823-ae77deb06f29 h1:UXLjNohABv4S58tHmeuIZDO6e3mHpW2Dx33gaNt03LE= +go4.org/intern v0.0.0-20211027215823-ae77deb06f29/go.mod h1:cS2ma+47FKrLPdXFpr7CuxiTW3eyJbWew4qx0qtQWDA= +go4.org/unsafe/assume-no-moving-gc v0.0.0-20211027215541-db492cf91b37 h1:Tx9kY6yUkLge/pFG7IEMwDZy6CS2ajFc9TvQdPCW0uA= +go4.org/unsafe/assume-no-moving-gc v0.0.0-20211027215541-db492cf91b37/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.zx2c4.com/go118/netip v0.0.0-20211111135330-a4a02eeacf9d h1:9+v0G0naRhLPOJEeJOL6NuXTtAHHwmkyZlgQJ0XcQ8I= +golang.zx2c4.com/go118/netip v0.0.0-20211111135330-a4a02eeacf9d/go.mod h1:5yyfuiqVIJ7t+3MqrpTQ+QqRkMWiESiyDvPNvKYCecg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +inet.af/netaddr v0.0.0-20211027220019-c74959edd3b6 h1:acCzuUSQ79tGsM/O50VRFySfMm19IoMKL+sZztZkCxw= +inet.af/netaddr v0.0.0-20211027220019-c74959edd3b6/go.mod h1:y3MGhcFMlh0KZPMuXXow8mpjxxAk3yoDNsp4cQz54i8= diff --git a/examples/outputs/google-uuid/autofuzz_test.go b/examples/outputs/google-uuid/autofuzz_test.go new file mode 100644 index 0000000..0ba3afd --- /dev/null +++ b/examples/outputs/google-uuid/autofuzz_test.go @@ -0,0 +1,377 @@ +package uuidfuzz // rename if needed + +// if needed, fill in imports or run 'goimports' +import ( + "io" + "testing" + + "github.com/google/uuid" + "github.com/thepudds/fzgen/fuzzer" +) + +// skipping Fuzz_NullUUID_Scan because parameters include unsupported interface: interface{} + +func Fuzz_NullUUID_UnmarshalBinary(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var nu *uuid.NullUUID + var d2 []byte + fz := fuzzer.NewFuzzer(data) + fz.Fill(&nu, &d2) + if nu == nil { + return + } + + nu.UnmarshalBinary(d2) + }) +} + +func Fuzz_NullUUID_UnmarshalJSON(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var nu *uuid.NullUUID + var d2 []byte + fz := fuzzer.NewFuzzer(data) + fz.Fill(&nu, &d2) + if nu == nil { + return + } + + nu.UnmarshalJSON(d2) + }) +} + +func Fuzz_NullUUID_UnmarshalText(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var nu *uuid.NullUUID + var d2 []byte + fz := fuzzer.NewFuzzer(data) + fz.Fill(&nu, &d2) + if nu == nil { + return + } + + nu.UnmarshalText(d2) + }) +} + +// skipping Fuzz_UUID_Scan because parameters include unsupported interface: interface{} + +func Fuzz_UUID_UnmarshalBinary(f *testing.F) { + f.Fuzz(func(t *testing.T, b []byte, d2 []byte) { + u1, err := uuid.FromBytes(b) + if err != nil { + return + } + u1.UnmarshalBinary(d2) + }) +} + +func Fuzz_UUID_UnmarshalText(f *testing.F) { + f.Fuzz(func(t *testing.T, b []byte, d2 []byte) { + u1, err := uuid.FromBytes(b) + if err != nil { + return + } + u1.UnmarshalText(d2) + }) +} + +func Fuzz_Domain_String(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var d uuid.Domain + fz := fuzzer.NewFuzzer(data) + fz.Fill(&d) + + d.String() + }) +} + +func Fuzz_NullUUID_MarshalBinary(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var nu uuid.NullUUID + fz := fuzzer.NewFuzzer(data) + fz.Fill(&nu) + + nu.MarshalBinary() + }) +} + +func Fuzz_NullUUID_MarshalJSON(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var nu uuid.NullUUID + fz := fuzzer.NewFuzzer(data) + fz.Fill(&nu) + + nu.MarshalJSON() + }) +} + +func Fuzz_NullUUID_MarshalText(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var nu uuid.NullUUID + fz := fuzzer.NewFuzzer(data) + fz.Fill(&nu) + + nu.MarshalText() + }) +} + +func Fuzz_NullUUID_Value(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var nu uuid.NullUUID + fz := fuzzer.NewFuzzer(data) + fz.Fill(&nu) + + nu.Value() + }) +} + +func Fuzz_Time_UnixTime(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var t1 uuid.Time + fz := fuzzer.NewFuzzer(data) + fz.Fill(&t1) + + t1.UnixTime() + }) +} + +func Fuzz_UUID_ClockSequence(f *testing.F) { + f.Fuzz(func(t *testing.T, b []byte) { + u1, err := uuid.FromBytes(b) + if err != nil { + return + } + u1.ClockSequence() + }) +} + +func Fuzz_UUID_Domain(f *testing.F) { + f.Fuzz(func(t *testing.T, b []byte) { + u1, err := uuid.FromBytes(b) + if err != nil { + return + } + u1.Domain() + }) +} + +func Fuzz_UUID_ID(f *testing.F) { + f.Fuzz(func(t *testing.T, b []byte) { + u1, err := uuid.FromBytes(b) + if err != nil { + return + } + u1.ID() + }) +} + +func Fuzz_UUID_MarshalBinary(f *testing.F) { + f.Fuzz(func(t *testing.T, b []byte) { + u1, err := uuid.FromBytes(b) + if err != nil { + return + } + u1.MarshalBinary() + }) +} + +func Fuzz_UUID_MarshalText(f *testing.F) { + f.Fuzz(func(t *testing.T, b []byte) { + u1, err := uuid.FromBytes(b) + if err != nil { + return + } + u1.MarshalText() + }) +} + +func Fuzz_UUID_NodeID(f *testing.F) { + f.Fuzz(func(t *testing.T, b []byte) { + u1, err := uuid.FromBytes(b) + if err != nil { + return + } + u1.NodeID() + }) +} + +func Fuzz_UUID_String(f *testing.F) { + f.Fuzz(func(t *testing.T, b []byte) { + u1, err := uuid.FromBytes(b) + if err != nil { + return + } + u1.String() + }) +} + +func Fuzz_UUID_Time(f *testing.F) { + f.Fuzz(func(t *testing.T, b []byte) { + u1, err := uuid.FromBytes(b) + if err != nil { + return + } + u1.Time() + }) +} + +func Fuzz_UUID_URN(f *testing.F) { + f.Fuzz(func(t *testing.T, b []byte) { + u1, err := uuid.FromBytes(b) + if err != nil { + return + } + u1.URN() + }) +} + +func Fuzz_UUID_Value(f *testing.F) { + f.Fuzz(func(t *testing.T, b []byte) { + u1, err := uuid.FromBytes(b) + if err != nil { + return + } + u1.Value() + }) +} + +func Fuzz_UUID_Variant(f *testing.F) { + f.Fuzz(func(t *testing.T, b []byte) { + u1, err := uuid.FromBytes(b) + if err != nil { + return + } + u1.Variant() + }) +} + +func Fuzz_UUID_Version(f *testing.F) { + f.Fuzz(func(t *testing.T, b []byte) { + u1, err := uuid.FromBytes(b) + if err != nil { + return + } + u1.Version() + }) +} + +func Fuzz_Variant_String(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var v uuid.Variant + fz := fuzzer.NewFuzzer(data) + fz.Fill(&v) + + v.String() + }) +} + +func Fuzz_Version_String(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var v uuid.Version + fz := fuzzer.NewFuzzer(data) + fz.Fill(&v) + + v.String() + }) +} + +func Fuzz_FromBytes(f *testing.F) { + f.Fuzz(func(t *testing.T, b []byte) { + uuid.FromBytes(b) + }) +} + +// skipping Fuzz_IsInvalidLengthError because parameters include unsupported interface: error + +// skipping Fuzz_Must because parameters include unsupported interface: error + +func Fuzz_MustParse(f *testing.F) { + f.Fuzz(func(t *testing.T, s string) { + uuid.MustParse(s) + }) +} + +func Fuzz_NewDCESecurity(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var domain uuid.Domain + var id uint32 + fz := fuzzer.NewFuzzer(data) + fz.Fill(&domain, &id) + + uuid.NewDCESecurity(domain, id) + }) +} + +// skipping Fuzz_NewHash because parameters include unsupported interface: hash.Hash + +func Fuzz_NewMD5(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var space uuid.UUID + var d2 []byte + fz := fuzzer.NewFuzzer(data) + fz.Fill(&space, &d2) + + uuid.NewMD5(space, d2) + }) +} + +func Fuzz_NewRandomFromReader(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var r io.Reader + fz := fuzzer.NewFuzzer(data) + fz.Fill(&r) + + uuid.NewRandomFromReader(r) + }) +} + +func Fuzz_NewSHA1(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var space uuid.UUID + var d2 []byte + fz := fuzzer.NewFuzzer(data) + fz.Fill(&space, &d2) + + uuid.NewSHA1(space, d2) + }) +} + +func Fuzz_Parse(f *testing.F) { + f.Fuzz(func(t *testing.T, s string) { + uuid.Parse(s) + }) +} + +func Fuzz_ParseBytes(f *testing.F) { + f.Fuzz(func(t *testing.T, b []byte) { + uuid.ParseBytes(b) + }) +} + +func Fuzz_SetClockSequence(f *testing.F) { + f.Fuzz(func(t *testing.T, seq int) { + uuid.SetClockSequence(seq) + }) +} + +func Fuzz_SetNodeID(f *testing.F) { + f.Fuzz(func(t *testing.T, id []byte) { + uuid.SetNodeID(id) + }) +} + +func Fuzz_SetNodeInterface(f *testing.F) { + f.Fuzz(func(t *testing.T, name string) { + uuid.SetNodeInterface(name) + }) +} + +func Fuzz_SetRand(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var r io.Reader + fz := fuzzer.NewFuzzer(data) + fz.Fill(&r) + + uuid.SetRand(r) + }) +} diff --git a/examples/outputs/google-uuid/autofuzzchain_test.go b/examples/outputs/google-uuid/autofuzzchain_test.go new file mode 100644 index 0000000..2f08997 --- /dev/null +++ b/examples/outputs/google-uuid/autofuzzchain_test.go @@ -0,0 +1,156 @@ +package uuidfuzz + +import ( + "database/sql/driver" + "fmt" + "reflect" + "testing" + + "github.com/google/uuid" + "github.com/thepudds/fzgen/fuzzer" +) + +// Automatically generated via: +// fzgen -chain -ctor=FromBytes github.com/google/uuid + +func Fuzz_FromBytes_Chain(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var b []byte + fz := fuzzer.NewFuzzer(data) + fz.Fill(&b) + + target, err := uuid.FromBytes(b) + if err != nil { + return + } + + steps := []fuzzer.Step{ + // skipping Fuzz_UUID_Scan because parameters include unsupported interface: interface{} + { + Name: "Fuzz_UUID_UnmarshalBinary", + Func: func(d1 []byte) { + target.UnmarshalBinary(d1) + }, + }, + { + Name: "Fuzz_UUID_UnmarshalText", + Func: func(d1 []byte) { + target.UnmarshalText(d1) + }, + }, + { + Name: "Fuzz_UUID_ClockSequence", + Func: func() int { + return target.ClockSequence() + }, + }, + { + Name: "Fuzz_UUID_Domain", + Func: func() uuid.Domain { + return target.Domain() + }, + }, + { + Name: "Fuzz_UUID_ID", + Func: func() uint32 { + return target.ID() + }, + }, + { + Name: "Fuzz_UUID_MarshalBinary", + Func: func() ([]byte, error) { + return target.MarshalBinary() + }, + }, + { + Name: "Fuzz_UUID_MarshalText", + Func: func() ([]byte, error) { + return target.MarshalText() + }, + }, + { + Name: "Fuzz_UUID_NodeID", + Func: func() []byte { + return target.NodeID() + }, + }, + { + Name: "Fuzz_UUID_String", + Func: func() string { + return target.String() + }, + }, + { + Name: "Fuzz_UUID_Time", + Func: func() uuid.Time { + return target.Time() + }, + }, + { + Name: "Fuzz_UUID_URN", + Func: func() string { + return target.URN() + }, + }, + { + Name: "Fuzz_UUID_Value", + Func: func() (driver.Value, error) { + return target.Value() + }, + }, + { + Name: "Fuzz_UUID_Variant", + Func: func() uuid.Variant { + return target.Variant() + }, + }, + { + Name: "Fuzz_UUID_Version", + Func: func() uuid.Version { + return target.Version() + }, + }, + } + + // Execute a specific chain of steps, with the count, sequence and arguments controlled by fz.Chain + fz.Chain(steps) + + // Validate with some roundtrip checks. These can be edited or deleted if not appropriate for your target. + // Check MarshalText. + result1, err := target.MarshalText() + if err != nil { + // Some targets should never return an error here for an object created by a constructor. + // If that is the case for your target, you can change this to a panic(err) or t.Fatal. + return + } + + // Check UnmarshalText. + var tmp1 uuid.UUID + err = tmp1.UnmarshalText(result1) + if err != nil { + panic(fmt.Sprintf("UnmarshalText failed after successful MarshalText. original: %v marshalled: %q error: %v", target, result1, err)) + } + if !reflect.DeepEqual(target, tmp1) { + panic(fmt.Sprintf("MarshalText/UnmarshalText roundtrip equality failed. original: %v marshalled: %q unmarshalled: %v", target, result1, tmp1)) + } + + // Check MarshalBinary. + result2, err := target.MarshalBinary() + if err != nil { + // Some targets should never return an error here for an object created by a constructor. + // If that is the case for your target, you can change this to a panic(err) or t.Fatal. + return + } + + // Check UnmarshalBinary. + var tmp2 uuid.UUID + err = tmp2.UnmarshalBinary(result2) + if err != nil { + panic(fmt.Sprintf("UnmarshalBinary failed after successful MarshalBinary. original: %v %#v marshalled: %q error: %v", target, target, result2, err)) + } + if !reflect.DeepEqual(target, tmp2) { + panic(fmt.Sprintf("MarshalBinary/UnmarshalBinary roundtrip equality failed. original: %v %#v marshalled: %q unmarshalled: %v %#v", + target, target, result2, tmp2, tmp2)) + } + }) +} diff --git a/examples/outputs/goroar/autofuzzchain_test.go b/examples/outputs/goroar/autofuzzchain_test.go new file mode 100644 index 0000000..42b492c --- /dev/null +++ b/examples/outputs/goroar/autofuzzchain_test.go @@ -0,0 +1,101 @@ +package goroarfuzz // rename if needed + +// if needed, fill in imports or run 'goimports' +import ( + "testing" + + "github.com/fzandona/goroar" + "github.com/thepudds/fzgen/fuzzer" +) + +func Fuzz_New_Chain(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + fz := fuzzer.NewFuzzer(data) + + target := goroar.New() + other := goroar.New() + + steps := []fuzzer.Step{ + fuzzer.Step{ + Name: "Fuzz_RoaringBitmap_Add", + Func: func(x uint32) { + target.Add(x) + }}, + fuzzer.Step{ + Name: "Fuzz_RoaringBitmap_Add_Other", + Func: func(x uint32) { + other.Add(x) + }}, + fuzzer.Step{ + Name: "Fuzz_RoaringBitmap_And", + Func: func() { + target.And(other) + }}, + fuzzer.Step{ + Name: "Fuzz_RoaringBitmap_AndNot", + Func: func() { + target.AndNot(other) + }}, + fuzzer.Step{ + Name: "Fuzz_RoaringBitmap_Cardinality", + Func: func() int { + return target.Cardinality() + }}, + fuzzer.Step{ + Name: "Fuzz_RoaringBitmap_Cardinality_Other", + Func: func() int { + return other.Cardinality() + }}, + fuzzer.Step{ + Name: "Fuzz_RoaringBitmap_Clone", + Func: func() *goroar.RoaringBitmap { + return target.Clone() + }}, + fuzzer.Step{ + Name: "Fuzz_RoaringBitmap_Clone_Other", + Func: func() *goroar.RoaringBitmap { + return other.Clone() + }}, + fuzzer.Step{ + Name: "Fuzz_RoaringBitmap_Contains", + Func: func(i uint32) bool { + return target.Contains(i) + }}, +/* + fuzzer.Step{ + Name: "Fuzz_RoaringBitmap_Iterator", + Func: func() <-chan uint32 { + return target.Iterator() + }}, +*/ + fuzzer.Step{ + Name: "Fuzz_RoaringBitmap_Or", + Func: func() { + target.Or(other) + }}, + fuzzer.Step{ + Name: "Fuzz_RoaringBitmap_SizeInBytes", + Func: func() int { + return target.SizeInBytes() + }}, + fuzzer.Step{ + Name: "Fuzz_RoaringBitmap_Stats", + Func: func() { + target.Stats() + }}, + fuzzer.Step{ + Name: "Fuzz_RoaringBitmap_String", + Func: func() string { + return target.String() + }}, + fuzzer.Step{ + Name: "Fuzz_RoaringBitmap_Xor", + Func: func() { + target.Xor(other) + }}, + } + + // Execute a specific chain of steps, with the count, sequence and arguments controlled by fz.Chain + fz.Chain(steps) + }) +} diff --git a/examples/outputs/lz4/autofuzz_test.go b/examples/outputs/lz4/autofuzz_test.go new file mode 100644 index 0000000..3ac8a0d --- /dev/null +++ b/examples/outputs/lz4/autofuzz_test.go @@ -0,0 +1,282 @@ +package lz4fuzz // rename if needed + +// if needed, fill in imports or run 'goimports' +import ( + "io" + "testing" + + "github.com/pierrec/lz4/v4" + "github.com/thepudds/fzgen/fuzzer" +) + +func Fuzz_Compressor_CompressBlock(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var c *lz4.Compressor + var src []byte + var dst []byte + fz := fuzzer.NewFuzzer(data) + fz.Fill(&c, &src, &dst) + if c == nil { + return + } + + c.CompressBlock(src, dst) + }) +} + +func Fuzz_CompressorHC_CompressBlock(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var c *lz4.CompressorHC + var src []byte + var dst []byte + fz := fuzzer.NewFuzzer(data) + fz.Fill(&c, &src, &dst) + if c == nil { + return + } + + c.CompressBlock(src, dst) + }) +} + +// skipping Fuzz_Reader_Apply because parameters include unsupported func or chan: []github.com/pierrec/lz4/v4.Option + +func Fuzz_Reader_Read(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var r io.Reader + var buf []byte + fz := fuzzer.NewFuzzer(data) + fz.Fill(&r, &buf) + + r1 := lz4.NewReader(r) + r1.Read(buf) + }) +} + +func Fuzz_Reader_Reset(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var r io.Reader + var reader io.Reader + fz := fuzzer.NewFuzzer(data) + fz.Fill(&r, &reader) + + r1 := lz4.NewReader(r) + r1.Reset(reader) + }) +} + +func Fuzz_Reader_Size(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var r io.Reader + fz := fuzzer.NewFuzzer(data) + fz.Fill(&r) + + r1 := lz4.NewReader(r) + r1.Size() + }) +} + +func Fuzz_Reader_WriteTo(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var r io.Reader + var w io.Writer + fz := fuzzer.NewFuzzer(data) + fz.Fill(&r, &w) + + r1 := lz4.NewReader(r) + r1.WriteTo(w) + }) +} + +// skipping Fuzz_Writer_Apply because parameters include unsupported func or chan: []github.com/pierrec/lz4/v4.Option + +func Fuzz_Writer_Close(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var w io.Writer + fz := fuzzer.NewFuzzer(data) + fz.Fill(&w) + + w1 := lz4.NewWriter(w) + w1.Close() + }) +} + +func Fuzz_Writer_ReadFrom(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var w io.Writer + var r io.Reader + fz := fuzzer.NewFuzzer(data) + fz.Fill(&w, &r) + + w1 := lz4.NewWriter(w) + w1.ReadFrom(r) + }) +} + +func Fuzz_Writer_Reset(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var w io.Writer + var writer io.Writer + fz := fuzzer.NewFuzzer(data) + fz.Fill(&w, &writer) + + w1 := lz4.NewWriter(w) + w1.Reset(writer) + }) +} + +func Fuzz_Writer_Write(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var w io.Writer + var buf []byte + fz := fuzzer.NewFuzzer(data) + fz.Fill(&w, &buf) + + w1 := lz4.NewWriter(w) + w1.Write(buf) + }) +} + +func Fuzz_BlockSize_String(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var i lz4.BlockSize + fz := fuzzer.NewFuzzer(data) + fz.Fill(&i) + + i.String() + }) +} + +func Fuzz_CompressionLevel_String(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var i lz4.CompressionLevel + fz := fuzzer.NewFuzzer(data) + fz.Fill(&i) + + i.String() + }) +} + +// skipping Fuzz_Option_String because parameters include unsupported func or chan: github.com/pierrec/lz4/v4.Option + +func Fuzz_BlockChecksumOption(f *testing.F) { + f.Fuzz(func(t *testing.T, flag bool) { + lz4.BlockChecksumOption(flag) + }) +} + +func Fuzz_BlockSizeOption(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var size lz4.BlockSize + fz := fuzzer.NewFuzzer(data) + fz.Fill(&size) + + lz4.BlockSizeOption(size) + }) +} + +func Fuzz_ChecksumOption(f *testing.F) { + f.Fuzz(func(t *testing.T, flag bool) { + lz4.ChecksumOption(flag) + }) +} + +func Fuzz_CompressBlock(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var src []byte + var dst []byte + var _x3 []int + fz := fuzzer.NewFuzzer(data) + fz.Fill(&src, &dst, &_x3) + + lz4.CompressBlock(src, dst, _x3) + }) +} + +func Fuzz_CompressBlockBound(f *testing.F) { + f.Fuzz(func(t *testing.T, n int) { + lz4.CompressBlockBound(n) + }) +} + +func Fuzz_CompressBlockHC(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var src []byte + var dst []byte + var depth lz4.CompressionLevel + var _x4 []int + var _x5 []int + fz := fuzzer.NewFuzzer(data) + fz.Fill(&src, &dst, &depth, &_x4, &_x5) + + lz4.CompressBlockHC(src, dst, depth, _x4, _x5) + }) +} + +func Fuzz_CompressionLevelOption(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var level lz4.CompressionLevel + fz := fuzzer.NewFuzzer(data) + fz.Fill(&level) + + lz4.CompressionLevelOption(level) + }) +} + +func Fuzz_ConcurrencyOption(f *testing.F) { + f.Fuzz(func(t *testing.T, n int) { + lz4.ConcurrencyOption(n) + }) +} + +func Fuzz_LegacyOption(f *testing.F) { + f.Fuzz(func(t *testing.T, legacy bool) { + lz4.LegacyOption(legacy) + }) +} + +func Fuzz_NewReader(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var r io.Reader + fz := fuzzer.NewFuzzer(data) + fz.Fill(&r) + + lz4.NewReader(r) + }) +} + +func Fuzz_NewWriter(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var w io.Writer + fz := fuzzer.NewFuzzer(data) + fz.Fill(&w) + + lz4.NewWriter(w) + }) +} + +// skipping Fuzz_OnBlockDoneOption because parameters include unsupported func or chan: func(size int) + +func Fuzz_SizeOption(f *testing.F) { + f.Fuzz(func(t *testing.T, size uint64) { + lz4.SizeOption(size) + }) +} + +func Fuzz_UncompressBlock(f *testing.F) { + f.Fuzz(func(t *testing.T, src []byte, dst []byte) { + lz4.UncompressBlock(src, dst) + }) +} + +func Fuzz_UncompressBlockWithDict(f *testing.F) { + f.Fuzz(func(t *testing.T, src []byte, dst []byte, dict []byte) { + lz4.UncompressBlockWithDict(src, dst, dict) + }) +} + +func Fuzz_ValidFrameHeader(f *testing.F) { + f.Fuzz(func(t *testing.T, in []byte) { + lz4.ValidFrameHeader(in) + }) +} diff --git a/examples/outputs/netlink/autofuzzchain_test.go b/examples/outputs/netlink/autofuzzchain_test.go new file mode 100644 index 0000000..2084f61 --- /dev/null +++ b/examples/outputs/netlink/autofuzzchain_test.go @@ -0,0 +1,112 @@ +package netlinkfuzz // rename if needed + +// if needed, fill in imports or run 'goimports' +import ( + "testing" + + "github.com/mdlayher/netlink" + "github.com/thepudds/fzgen/fuzzer" +) + +func Fuzz_NewAttributeDecoder_Chain(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var b []byte + fz := fuzzer.NewFuzzer(data) + fz.Fill(&b) + + target, err := netlink.NewAttributeDecoder(b) + if err != nil { + return + } + + steps := []fuzzer.Step{ + fuzzer.Step{ + Name: "Fuzz_AttributeDecoder_Bytes", + Func: func() []byte { + return target.Bytes() + }}, + // skipping Fuzz_AttributeDecoder_Do because parameters include unsupported func or chan: func(b []byte) error + + fuzzer.Step{ + Name: "Fuzz_AttributeDecoder_Err", + Func: func() { + target.Err() + }}, + fuzzer.Step{ + Name: "Fuzz_AttributeDecoder_Flag", + Func: func() bool { + return target.Flag() + }}, + fuzzer.Step{ + Name: "Fuzz_AttributeDecoder_Int16", + Func: func() int16 { + return target.Int16() + }}, + fuzzer.Step{ + Name: "Fuzz_AttributeDecoder_Int32", + Func: func() int32 { + return target.Int32() + }}, + fuzzer.Step{ + Name: "Fuzz_AttributeDecoder_Int64", + Func: func() int64 { + return target.Int64() + }}, + fuzzer.Step{ + Name: "Fuzz_AttributeDecoder_Int8", + Func: func() int8 { + return target.Int8() + }}, + fuzzer.Step{ + Name: "Fuzz_AttributeDecoder_Len", + Func: func() int { + return target.Len() + }}, + // skipping Fuzz_AttributeDecoder_Nested because parameters include unsupported func or chan: func(nad *github.com/mdlayher/netlink.AttributeDecoder) error + + fuzzer.Step{ + Name: "Fuzz_AttributeDecoder_Next", + Func: func() bool { + return target.Next() + }}, + fuzzer.Step{ + Name: "Fuzz_AttributeDecoder_String", + Func: func() string { + return target.String() + }}, + fuzzer.Step{ + Name: "Fuzz_AttributeDecoder_Type", + Func: func() uint16 { + return target.Type() + }}, + fuzzer.Step{ + Name: "Fuzz_AttributeDecoder_TypeFlags", + Func: func() uint16 { + return target.TypeFlags() + }}, + fuzzer.Step{ + Name: "Fuzz_AttributeDecoder_Uint16", + Func: func() uint16 { + return target.Uint16() + }}, + fuzzer.Step{ + Name: "Fuzz_AttributeDecoder_Uint32", + Func: func() uint32 { + return target.Uint32() + }}, + fuzzer.Step{ + Name: "Fuzz_AttributeDecoder_Uint64", + Func: func() uint64 { + return target.Uint64() + }}, + fuzzer.Step{ + Name: "Fuzz_AttributeDecoder_Uint8", + Func: func() uint8 { + return target.Uint8() + }}, + } + + // Execute a specific chain of steps, with the count, sequence and arguments controlled by fz.Chain + fz.Chain(steps) + }) +} diff --git a/examples/outputs/race-xsync-map-repro/standalone_repro1_test.go b/examples/outputs/race-xsync-map-repro/standalone_repro1_test.go new file mode 100644 index 0000000..1bf47cd --- /dev/null +++ b/examples/outputs/race-xsync-map-repro/standalone_repro1_test.go @@ -0,0 +1,86 @@ +// This deadlocks in puzpuzpuz/xsync.(*Map).Store -> puzpuzpuz/xsync.(*Map).doStore: +// - reproduces at github.com/puzpuzpuz/xsync@v1.0.1-0.20210823092703-32778049b5f5 +// - fixed in github.com/puzpuzpuz/xsync@latest +// +// Deadlock repro extracted from: +// fzgen -chain -parallel -pkg=github.com/thepudds/fzgen/examples/inputs/race-xsyncmap +// gotip test -fuzz=. -race +// +// With the repro then emitted by: +// export FZDEBUG=repro=1 +// gotip test -run=./170da805c157 +// +// This is now a normal Go test file. Note: need to run this test multiple times, such as: +// go test -count=10000 -timeout=10s +// +// Note: the original bug report was not via fzgen. +// This just looked like an interesting bug to try to reproduce with fzgen +// against a new sync.Map implementation that is being proposed +// to merge into the Go standard library (golang/go#47643). +package xsyncmaprepro + +import ( + "sync" + "testing" + + xsyncmap "github.com/thepudds/fzgen/examples/inputs/race-xsync-map" +) + +func TestRepro_NewXSyncMap_Chain(t *testing.T) { + + target := xsyncmap.NewXSyncMap() + + Fuzz_XSyncMap_LoadOrStore := func(key string, value int8) (int8, bool) { + return target.LoadOrStore(key, value) + } + + Fuzz_XSyncMap_Store := func(key string, value int8) { + target.Store(key, value) + } + + // Execute next steps in parallel. + var wg sync.WaitGroup + wg.Add(2) + go func() { + defer wg.Done() + Fuzz_XSyncMap_LoadOrStore( + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + 44, + ) + }() + go func() { + defer wg.Done() + Fuzz_XSyncMap_LoadOrStore( + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + 44, + ) + }() + wg.Wait() + + // Resume sequential execution. + Fuzz_XSyncMap_LoadOrStore( + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + 44, + ) + Fuzz_XSyncMap_Store( + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + 44, + ) + Fuzz_XSyncMap_Store( + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + 44, + ) + Fuzz_XSyncMap_Store( + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + 44, + ) + Fuzz_XSyncMap_Store( + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + 44, + ) + Fuzz_XSyncMap_Store( + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + 44, + ) + +} diff --git a/examples/outputs/race-xsync-map-repro/standalone_repro2_test.go b/examples/outputs/race-xsync-map-repro/standalone_repro2_test.go new file mode 100644 index 0000000..af5b46c --- /dev/null +++ b/examples/outputs/race-xsync-map-repro/standalone_repro2_test.go @@ -0,0 +1,86 @@ +// repro extracted from: +// fzgen -chain -parallel -pkg=github.com/thepudds/fzgen/examples/inputs/race-xsyncmap +// +// Note: need to run this multiple times, such as: +// go test -count=10000 -timeout=10s +// +// This deadlocks in puzpuzpuz/xsync.(*Map).Store -> puzpuzpuz/xsync.(*Map).doStore +package xsyncmaprepro + +import ( + "sync" + "testing" + + xsyncmap "github.com/thepudds/fzgen/examples/inputs/race-xsync-map" +) + +func TestRepro2_NewXSyncMap_Chain(t *testing.T) { + + // target and steps copied from autofuzzchain_test.go + + target := xsyncmap.NewXSyncMap() + + Fuzz_XSyncMap_LoadOrStore := func(key string, value int8) (int8, bool) { + return target.LoadOrStore(key, value) + } + + Fuzz_XSyncMap_Store := func(key string, value int8) { + target.Store(key, value) + } + + // copied output from FZDEBUG=repro=1 + + Fuzz_XSyncMap_LoadOrStore( + "7210210080120#09ASSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS", + 83, + ) + + // Execute next steps in parallel. + var wg sync.WaitGroup + wg.Add(2) + go func() { + defer wg.Done() + Fuzz_XSyncMap_LoadOrStore( + "SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS", + 83, + ) + }() + go func() { + defer wg.Done() + Fuzz_XSyncMap_Store( + "SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS", + 83, + ) + }() + wg.Wait() + Fuzz_XSyncMap_Store( + "SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS", + 83, + ) + Fuzz_XSyncMap_Store( + "SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS", + 83, + ) + Fuzz_XSyncMap_Store( + "SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS", + 83, + ) + Fuzz_XSyncMap_Store( + "SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS", + 83, + ) + Fuzz_XSyncMap_Store( + "SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS", + 83, + ) + Fuzz_XSyncMap_Store( + "SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS", + 83, + ) + Fuzz_XSyncMap_Store( + "SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS", + 83, + ) + + +} diff --git a/examples/outputs/race-xsync-map/autofuzzchain_test.go b/examples/outputs/race-xsync-map/autofuzzchain_test.go new file mode 100644 index 0000000..3076321 --- /dev/null +++ b/examples/outputs/race-xsync-map/autofuzzchain_test.go @@ -0,0 +1,57 @@ +package xsyncmapfuzz + +// generated via: +// fzgen -chain -parallel github.com/thepudds/fzgen/examples/inputs/race-xsyncmap + +import ( + "testing" + + xsyncmap "github.com/thepudds/fzgen/examples/inputs/race-xsync-map" + "github.com/thepudds/fzgen/fuzzer" +) + +func Fuzz_NewXSyncMap_Chain(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + fz := fuzzer.NewFuzzer(data) + + target := xsyncmap.NewXSyncMap() + + steps := []fuzzer.Step{ + { + Name: "Fuzz_XSyncMap_Delete", + Func: func(key string) { + target.Delete(key) + }, + }, + { + Name: "Fuzz_XSyncMap_Load", + Func: func(key string) (int8, bool) { + return target.Load(key) + }, + }, + { + Name: "Fuzz_XSyncMap_LoadAndDelete", + Func: func(key string) (int8, bool) { + return target.LoadAndDelete(key) + }, + }, + { + Name: "Fuzz_XSyncMap_LoadOrStore", + Func: func(key string, value int8) (int8, bool) { + return target.LoadOrStore(key, value) + }, + }, + // skipping Fuzz_XSyncMap_Range because parameters include unsupported func or chan: func(key string, value interface{}) bool + + { + Name: "Fuzz_XSyncMap_Store", + Func: func(key string, value int8) { + target.Store(key, value) + }, + }, + } + + // Execute a specific chain of steps, with the count, sequence and arguments controlled by fz.Chain + fz.Chain(steps, fuzzer.ChainOptParallel) + }) +} diff --git a/examples/outputs/race-xsync-mpmcqueue-repro/repro_test.go b/examples/outputs/race-xsync-mpmcqueue-repro/repro_test.go new file mode 100644 index 0000000..7a61ec3 --- /dev/null +++ b/examples/outputs/race-xsync-mpmcqueue-repro/repro_test.go @@ -0,0 +1,26 @@ +package xsyncmapfuzz + +// repro extracted from: +// fzgen -chain -parallel -pkg=github.com/thepudds/fzgen/examples/inputs/race-xsyncmap + +import ( + "testing" + + xsyncqueue "github.com/thepudds/fzgen/examples/inputs/race-xsync-mpmcqueue" +) + +func TestRepro_NewMPMCQueue_Chain(t *testing.T) { + + target := xsyncqueue.NewMPMCQueue(1) + + Fuzz_XSyncMPMCQueue_TryEnqueue := func(item int8) bool { + return target.TryEnqueue(item) + } + Fuzz_XSyncMPMCQueue_Enqueue_Dequeue := func(item int8) int8 { + target.Enqueue(item) + return target.Dequeue() + } + + Fuzz_XSyncMPMCQueue_TryEnqueue(0) + Fuzz_XSyncMPMCQueue_Enqueue_Dequeue(0) +} diff --git a/examples/outputs/race-xsync-mpmcqueue/autofuzzchain_test.go b/examples/outputs/race-xsync-mpmcqueue/autofuzzchain_test.go new file mode 100644 index 0000000..4c70c50 --- /dev/null +++ b/examples/outputs/race-xsync-mpmcqueue/autofuzzchain_test.go @@ -0,0 +1,90 @@ +package xsyncqueuefuzz + +// generated by: +// fzgen -chain -parallel -pkg github.com/thepudds/fzgen/examples/inputs/race-xsync-mpmcqueue +// and then modified as follows: +// 1. restricted the capacity to be in the interval [1,4096]. +// 2. enforce a large enough capacity prior to any Enqueue. +// 2. placed an Enqueue prior to every Dequeue and TryDequeue, to avoid possibly blocking on Dequeue. +import ( + "testing" + + xsyncqueue "github.com/thepudds/fzgen/examples/inputs/race-xsync-mpmcqueue" + "github.com/thepudds/fzgen/fuzzer" +) + +func Fuzz_NewMPMCQueue_Chain(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var capacity int + fz := fuzzer.NewFuzzer(data) + fz.Fill(&capacity) + + if capacity < 0 { + capacity = -capacity + } + if capacity%2 == 0 { + capacity = 1 + } else { + capacity = 1 + capacity%4096 + } + + target := xsyncqueue.NewMPMCQueue(capacity) + + var allowDequeue, allowEnqueue bool + if capacity > 256*10 { + // We should have enough capacity to avoid blocking on an Enqueue. + // This check relies on current knowledge of fzgen.Fuzzer implementation. + // Worst case is a loop of 256 with 10 steps being executed in parallel that + // are all Enqueue or TryEnqueue without any Dequeue or TryDequeue, + // which would place 256*10 elements into the queue. + allowEnqueue = true + } + + fz.Fill(&allowDequeue) + if !allowEnqueue { + // For Dequeue, we only allow it if we always proceed every Dequeue and TryDequeue + // with an Enqueue so that it is not possible to have more Dequeues than elements inserted so far, + // so if Enqueue is disallowed, we disallow Dequeue. + allowDequeue = false + } + + steps := []fuzzer.Step{ + fuzzer.Step{ + Name: "Fuzz_XSyncMPMCQueue_Enqueue", + Func: func(item int8) { + if allowEnqueue { + target.Enqueue(item) + } + }}, + fuzzer.Step{ + Name: "Fuzz_XSyncMPMCQueue_Enqueue_Dequeue", + Func: func(item int8) int8 { + if allowEnqueue && allowDequeue { + // We should have enough capacity to avoid blocking. See comment above. + target.Enqueue(item) + return target.Dequeue() + } + return 0 + }}, + fuzzer.Step{ + Name: "Fuzz_XSyncMPMCQueue_TryDequeue", + Func: func() (int8, bool) { + if allowDequeue { + // To avoid a later Dequeue from possibly blocking, TryDequeue here + // must have a paired Enqueue to ensure there is always at least 1 elem + // for a subsequent Dequeue. + target.Enqueue(0) + } + return target.TryDequeue() + }}, + fuzzer.Step{ + Name: "Fuzz_XSyncMPMCQueue_TryEnqueue", + Func: func(item int8) bool { + return target.TryEnqueue(item) + }}, + } + + // Execute a specific chain of steps, with the count, sequence and arguments controlled by fz.Chain + fz.Chain(steps, fuzzer.ChainOptParallel) + }) +} diff --git a/examples/outputs/race/autofuzzchain_test.go b/examples/outputs/race/autofuzzchain_test.go new file mode 100644 index 0000000..0971d94 --- /dev/null +++ b/examples/outputs/race/autofuzzchain_test.go @@ -0,0 +1,40 @@ +package raceexamplefuzz + +// generated by: +// fzgen -chain -parallel github.com/thepudds/fzgen/examples/inputs/race + +import ( + "testing" + + raceexample "github.com/thepudds/fzgen/examples/inputs/race" + "github.com/thepudds/fzgen/fuzzer" +) + +func Fuzz_NewMySafeMap_Chain(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + fz := fuzzer.NewFuzzer(data) + + target := raceexample.NewMySafeMap() + + steps := []fuzzer.Step{ + { + Name: "Fuzz_MySafeMap_Load", + Func: func(key [16]byte) *raceexample.Request { + return target.Load(key) + }, + }, + { + Name: "Fuzz_MySafeMap_Store", + Func: func(key [16]byte, req *raceexample.Request) { + if req == nil { + return + } + target.Store(key, req) + }, + }, + } + + // Execute a specific chain of steps, with the count, sequence and arguments controlled by fz.Chain + fz.Chain(steps, fuzzer.ChainOptParallel) + }) +} diff --git a/examples/outputs/randpanic/autofuzz_test.go b/examples/outputs/randpanic/autofuzz_test.go new file mode 100644 index 0000000..872f346 --- /dev/null +++ b/examples/outputs/randpanic/autofuzz_test.go @@ -0,0 +1,36 @@ +package myrand + +// if needed, fill in imports or run 'goimports' +import ( + "testing" +) + +func Fuzz_PanicOn10(f *testing.F) { + f.Fuzz(func(t *testing.T, a int) { + PanicOn10(a) + }) +} + +func Fuzz_PanicOn10000(f *testing.F) { + f.Fuzz(func(t *testing.T, a int) { + PanicOn10000(a) + }) +} + +func Fuzz_PanicRandomly1000(f *testing.F) { + f.Fuzz(func(t *testing.T, a int) { + PanicRandomly1000(a) + }) +} + +func Fuzz_PanicRandomly10000(f *testing.F) { + f.Fuzz(func(t *testing.T, a int) { + PanicRandomly10000(a) + }) +} + +func Fuzz_PanicRandomly100000(f *testing.F) { + f.Fuzz(func(t *testing.T, a int) { + PanicRandomly100000(a) + }) +} diff --git a/examples/outputs/randpanic/rand.go b/examples/outputs/randpanic/rand.go new file mode 100644 index 0000000..0cc15e8 --- /dev/null +++ b/examples/outputs/randpanic/rand.go @@ -0,0 +1,34 @@ +package myrand + +import "math/rand" + +func PanicRandomly1000(a int) { + if rand.Int31n(1000) == 0 { + panic("1 in a 1000") + } +} + +func PanicRandomly10000(a int) { + if rand.Int31n(10000) == 0 { + panic("1 in a 10000") + } +} + +func PanicRandomly100000(a int) { + if rand.Int31n(100000) == 0 { + panic("1 in a 100000") + } +} + +func PanicOn10(a int) { + if a == 10 { + panic("sent 10") + } +} + +func PanicOn10000(a int) { + if a == 10000 { + panic("sent 10000") + } +} + diff --git a/examples/outputs/roaring/autofuzzchain_test.go b/examples/outputs/roaring/autofuzzchain_test.go new file mode 100644 index 0000000..feece70 --- /dev/null +++ b/examples/outputs/roaring/autofuzzchain_test.go @@ -0,0 +1,293 @@ +package roaringfuzz // rename if needed + +// if needed, fill in imports or run 'goimports' +import ( + "io" + "testing" + + "github.com/RoaringBitmap/roaring" + "github.com/thepudds/fzgen/fuzzer" +) + +func Fuzz_NewBitmap_Chain(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + fz := fuzzer.NewFuzzer(data) + + target := roaring.NewBitmap() + + steps := []fuzzer.Step{ + fuzzer.Step{ + Name: "Fuzz_Bitmap_Add", + Func: func(x uint32) { + target.Add(x) + }}, + fuzzer.Step{ + Name: "Fuzz_Bitmap_AddInt", + Func: func(x int) { + target.AddInt(x) + }}, + fuzzer.Step{ + Name: "Fuzz_Bitmap_AddMany", + Func: func(dat []uint32) { + target.AddMany(dat) + }}, + fuzzer.Step{ + Name: "Fuzz_Bitmap_AddRange", + Func: func(rangeStart uint64, rangeEnd uint64) { + target.AddRange(rangeStart, rangeEnd) + }}, + fuzzer.Step{ + Name: "Fuzz_Bitmap_And", + Func: func(x2 *roaring.Bitmap) { + target.And(x2) + }}, + fuzzer.Step{ + Name: "Fuzz_Bitmap_AndAny", + Func: func(bitmaps []*roaring.Bitmap) { + target.AndAny(bitmaps...) + }}, + fuzzer.Step{ + Name: "Fuzz_Bitmap_AndCardinality", + Func: func(x2 *roaring.Bitmap) uint64 { + return target.AndCardinality(x2) + }}, + fuzzer.Step{ + Name: "Fuzz_Bitmap_AndNot", + Func: func(x2 *roaring.Bitmap) { + target.AndNot(x2) + }}, + fuzzer.Step{ + Name: "Fuzz_Bitmap_CheckedAdd", + Func: func(x uint32) bool { + return target.CheckedAdd(x) + }}, + fuzzer.Step{ + Name: "Fuzz_Bitmap_CheckedRemove", + Func: func(x uint32) bool { + return target.CheckedRemove(x) + }}, + fuzzer.Step{ + Name: "Fuzz_Bitmap_Clear", + Func: func() { + target.Clear() + }}, + fuzzer.Step{ + Name: "Fuzz_Bitmap_Clone", + Func: func() *roaring.Bitmap { + return target.Clone() + }}, + fuzzer.Step{ + Name: "Fuzz_Bitmap_CloneCopyOnWriteContainers", + Func: func() { + target.CloneCopyOnWriteContainers() + }}, + fuzzer.Step{ + Name: "Fuzz_Bitmap_Contains", + Func: func(x uint32) bool { + return target.Contains(x) + }}, + fuzzer.Step{ + Name: "Fuzz_Bitmap_ContainsInt", + Func: func(x int) bool { + return target.ContainsInt(x) + }}, + // skipping Fuzz_Bitmap_Equals because parameters include unsupported interface: interface{} + + fuzzer.Step{ + Name: "Fuzz_Bitmap_Flip", + Func: func(rangeStart uint64, rangeEnd uint64) { + target.Flip(rangeStart, rangeEnd) + }}, + fuzzer.Step{ + Name: "Fuzz_Bitmap_FlipInt", + Func: func(rangeStart int, rangeEnd int) { + target.FlipInt(rangeStart, rangeEnd) + }}, + fuzzer.Step{ + Name: "Fuzz_Bitmap_Freeze", + Func: func() ([]byte, error) { + return target.Freeze() + }}, + fuzzer.Step{ + Name: "Fuzz_Bitmap_FreezeTo", + Func: func(buf []byte) (int, error) { + return target.FreezeTo(buf) + }}, + fuzzer.Step{ + Name: "Fuzz_Bitmap_FromBase64", + Func: func(str string) (int64, error) { + return target.FromBase64(str) + }}, + fuzzer.Step{ + Name: "Fuzz_Bitmap_FromBuffer", + Func: func(buf []byte) (int64, error) { + return target.FromBuffer(buf) + }}, + fuzzer.Step{ + Name: "Fuzz_Bitmap_FrozenView", + Func: func(buf []byte) { + target.FrozenView(buf) + }}, + fuzzer.Step{ + Name: "Fuzz_Bitmap_GetCardinality", + Func: func() uint64 { + return target.GetCardinality() + }}, + fuzzer.Step{ + Name: "Fuzz_Bitmap_GetCopyOnWrite", + Func: func() bool { + return target.GetCopyOnWrite() + }}, + fuzzer.Step{ + Name: "Fuzz_Bitmap_GetFrozenSizeInBytes", + Func: func() uint64 { + return target.GetFrozenSizeInBytes() + }}, + fuzzer.Step{ + Name: "Fuzz_Bitmap_GetSerializedSizeInBytes", + Func: func() uint64 { + return target.GetSerializedSizeInBytes() + }}, + fuzzer.Step{ + Name: "Fuzz_Bitmap_GetSizeInBytes", + Func: func() uint64 { + return target.GetSizeInBytes() + }}, + fuzzer.Step{ + Name: "Fuzz_Bitmap_HasRunCompression", + Func: func() bool { + return target.HasRunCompression() + }}, + fuzzer.Step{ + Name: "Fuzz_Bitmap_Intersects", + Func: func(x2 *roaring.Bitmap) bool { + return target.Intersects(x2) + }}, + fuzzer.Step{ + Name: "Fuzz_Bitmap_IsEmpty", + Func: func() bool { + return target.IsEmpty() + }}, + // skipping Fuzz_Bitmap_Iterate because parameters include unsupported func or chan: func(x uint32) bool + + fuzzer.Step{ + Name: "Fuzz_Bitmap_Iterator", + Func: func() roaring.IntPeekable { + return target.Iterator() + }}, + fuzzer.Step{ + Name: "Fuzz_Bitmap_ManyIterator", + Func: func() roaring.ManyIntIterable { + return target.ManyIterator() + }}, + fuzzer.Step{ + Name: "Fuzz_Bitmap_MarshalBinary", + Func: func() ([]byte, error) { + return target.MarshalBinary() + }}, + fuzzer.Step{ + Name: "Fuzz_Bitmap_Maximum", + Func: func() uint32 { + return target.Maximum() + }}, + fuzzer.Step{ + Name: "Fuzz_Bitmap_Minimum", + Func: func() uint32 { + return target.Minimum() + }}, + fuzzer.Step{ + Name: "Fuzz_Bitmap_Or", + Func: func(x2 *roaring.Bitmap) { + target.Or(x2) + }}, + fuzzer.Step{ + Name: "Fuzz_Bitmap_OrCardinality", + Func: func(x2 *roaring.Bitmap) uint64 { + return target.OrCardinality(x2) + }}, + fuzzer.Step{ + Name: "Fuzz_Bitmap_Rank", + Func: func(x uint32) uint64 { + return target.Rank(x) + }}, + fuzzer.Step{ + Name: "Fuzz_Bitmap_ReadFrom", + Func: func(reader io.Reader, cookieHeader []byte) (int64, error) { + return target.ReadFrom(reader, cookieHeader...) + }}, + fuzzer.Step{ + Name: "Fuzz_Bitmap_Remove", + Func: func(x uint32) { + target.Remove(x) + }}, + fuzzer.Step{ + Name: "Fuzz_Bitmap_RemoveRange", + Func: func(rangeStart uint64, rangeEnd uint64) { + target.RemoveRange(rangeStart, rangeEnd) + }}, + fuzzer.Step{ + Name: "Fuzz_Bitmap_ReverseIterator", + Func: func() roaring.IntIterable { + return target.ReverseIterator() + }}, + fuzzer.Step{ + Name: "Fuzz_Bitmap_RunOptimize", + Func: func() { + target.RunOptimize() + }}, + fuzzer.Step{ + Name: "Fuzz_Bitmap_Select", + Func: func(x uint32) (uint32, error) { + return target.Select(x) + }}, + fuzzer.Step{ + Name: "Fuzz_Bitmap_SetCopyOnWrite", + Func: func(val bool) { + target.SetCopyOnWrite(val) + }}, + fuzzer.Step{ + Name: "Fuzz_Bitmap_Stats", + Func: func() roaring.Statistics { + return target.Stats() + }}, + fuzzer.Step{ + Name: "Fuzz_Bitmap_String", + Func: func() string { + return target.String() + }}, + fuzzer.Step{ + Name: "Fuzz_Bitmap_ToArray", + Func: func() []uint32 { + return target.ToArray() + }}, + fuzzer.Step{ + Name: "Fuzz_Bitmap_ToBase64", + Func: func() (string, error) { + return target.ToBase64() + }}, + fuzzer.Step{ + Name: "Fuzz_Bitmap_ToBytes", + Func: func() ([]byte, error) { + return target.ToBytes() + }}, + fuzzer.Step{ + Name: "Fuzz_Bitmap_UnmarshalBinary", + Func: func(d1 []byte) { + target.UnmarshalBinary(d1) + }}, + fuzzer.Step{ + Name: "Fuzz_Bitmap_WriteTo", + Func: func(stream io.Writer) (int64, error) { + return target.WriteTo(stream) + }}, + fuzzer.Step{ + Name: "Fuzz_Bitmap_Xor", + Func: func(x2 *roaring.Bitmap) { + target.Xor(x2) + }}, + } + + // Execute a specific chain of steps, with the count, sequence and arguments controlled by fz.Chain + fz.Chain(steps) + }) +} diff --git a/examples/outputs/stdlib-encoding-ascii85/autofuzz_test.go b/examples/outputs/stdlib-encoding-ascii85/autofuzz_test.go new file mode 100644 index 0000000..6d9e602 --- /dev/null +++ b/examples/outputs/stdlib-encoding-ascii85/autofuzz_test.go @@ -0,0 +1,58 @@ +package ascii85fuzz // rename if needed + +// if needed, fill in imports or run 'goimports' +import ( + "encoding/ascii85" + "io" + "testing" + + "github.com/thepudds/fzgen/fuzzer" +) + +func Fuzz_CorruptInputError_Error(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var e ascii85.CorruptInputError + fz := fuzzer.NewFuzzer(data) + fz.Fill(&e) + + e.Error() + }) +} + +func Fuzz_Decode(f *testing.F) { + f.Fuzz(func(t *testing.T, dst []byte, src []byte, flush bool) { + ascii85.Decode(dst, src, flush) + }) +} + +func Fuzz_Encode(f *testing.F) { + f.Fuzz(func(t *testing.T, dst []byte, src []byte) { + ascii85.Encode(dst, src) + }) +} + +func Fuzz_MaxEncodedLen(f *testing.F) { + f.Fuzz(func(t *testing.T, n int) { + ascii85.MaxEncodedLen(n) + }) +} + +func Fuzz_NewDecoder(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var r io.Reader + fz := fuzzer.NewFuzzer(data) + fz.Fill(&r) + + ascii85.NewDecoder(r) + }) +} + +func Fuzz_NewEncoder(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var w io.Writer + fz := fuzzer.NewFuzzer(data) + fz.Fill(&w) + + ascii85.NewEncoder(w) + }) +} diff --git a/examples/outputs/stdlib-net-url/autofuzzchain_test.go b/examples/outputs/stdlib-net-url/autofuzzchain_test.go new file mode 100644 index 0000000..42086a8 --- /dev/null +++ b/examples/outputs/stdlib-net-url/autofuzzchain_test.go @@ -0,0 +1,96 @@ +package urlfuzz // rename if needed + +// if needed, fill in imports or run 'goimports' +import ( + "net/url" + "testing" + + "github.com/thepudds/fzgen/fuzzer" +) + +func Fuzz_ParseRequestURI_Chain(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var rawURL string + fz := fuzzer.NewFuzzer(data) + fz.Fill(&rawURL) + + target, err := url.ParseRequestURI(rawURL) + if err != nil { + return + } + + steps := []fuzzer.Step{ + fuzzer.Step{ + Name: "Fuzz_URL_EscapedFragment", + Func: func() string { + return target.EscapedFragment() + }}, + fuzzer.Step{ + Name: "Fuzz_URL_EscapedPath", + Func: func() string { + return target.EscapedPath() + }}, + fuzzer.Step{ + Name: "Fuzz_URL_Hostname", + Func: func() string { + return target.Hostname() + }}, + fuzzer.Step{ + Name: "Fuzz_URL_IsAbs", + Func: func() bool { + return target.IsAbs() + }}, + fuzzer.Step{ + Name: "Fuzz_URL_MarshalBinary", + Func: func() ([]byte, error) { + return target.MarshalBinary() + }}, + fuzzer.Step{ + Name: "Fuzz_URL_Parse", + Func: func(ref string) (*url.URL, error) { + return target.Parse(ref) + }}, + fuzzer.Step{ + Name: "Fuzz_URL_Port", + Func: func() string { + return target.Port() + }}, + fuzzer.Step{ + Name: "Fuzz_URL_Query", + Func: func() url.Values { + return target.Query() + }}, + fuzzer.Step{ + Name: "Fuzz_URL_Redacted", + Func: func() string { + return target.Redacted() + }}, + fuzzer.Step{ + Name: "Fuzz_URL_RequestURI", + Func: func() string { + return target.RequestURI() + }}, + fuzzer.Step{ + Name: "Fuzz_URL_ResolveReference", + Func: func(ref *url.URL) *url.URL { + if ref == nil { + return nil + } + return target.ResolveReference(ref) + }}, + fuzzer.Step{ + Name: "Fuzz_URL_String", + Func: func() string { + return target.String() + }}, + fuzzer.Step{ + Name: "Fuzz_URL_UnmarshalBinary", + Func: func(text []byte) { + target.UnmarshalBinary(text) + }}, + } + + // Execute a specific chain of steps, with the count, sequence and arguments controlled by fz.Chain + fz.Chain(steps) + }) +} diff --git a/examples/outputs/stdlib-netip/autofuzz_test.go b/examples/outputs/stdlib-netip/autofuzz_test.go new file mode 100644 index 0000000..e2b8230 --- /dev/null +++ b/examples/outputs/stdlib-netip/autofuzz_test.go @@ -0,0 +1,656 @@ +package netipfuzz // rename if needed + +// if needed, fill in imports or run 'goimports' +import ( + "testing" + + "github.com/thepudds/fzgen/fuzzer" + "golang.zx2c4.com/go118/netip" +) + +func Fuzz_Addr_UnmarshalBinary(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var ip *netip.Addr + var b []byte + fz := fuzzer.NewFuzzer(data) + fz.Fill(&ip, &b) + if ip == nil { + return + } + + ip.UnmarshalBinary(b) + }) +} + +func Fuzz_Addr_UnmarshalText(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var ip *netip.Addr + var text []byte + fz := fuzzer.NewFuzzer(data) + fz.Fill(&ip, &text) + if ip == nil { + return + } + + ip.UnmarshalText(text) + }) +} + +func Fuzz_AddrPort_UnmarshalBinary(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var p *netip.AddrPort + var b []byte + fz := fuzzer.NewFuzzer(data) + fz.Fill(&p, &b) + if p == nil { + return + } + + p.UnmarshalBinary(b) + }) +} + +func Fuzz_AddrPort_UnmarshalText(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var p *netip.AddrPort + var text []byte + fz := fuzzer.NewFuzzer(data) + fz.Fill(&p, &text) + if p == nil { + return + } + + p.UnmarshalText(text) + }) +} + +func Fuzz_Prefix_UnmarshalBinary(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var p *netip.Prefix + var b []byte + fz := fuzzer.NewFuzzer(data) + fz.Fill(&p, &b) + if p == nil { + return + } + + p.UnmarshalBinary(b) + }) +} + +func Fuzz_Prefix_UnmarshalText(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var p *netip.Prefix + var text []byte + fz := fuzzer.NewFuzzer(data) + fz.Fill(&p, &text) + if p == nil { + return + } + + p.UnmarshalText(text) + }) +} + +func Fuzz_Addr_AppendTo(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var ip netip.Addr + var b []byte + fz := fuzzer.NewFuzzer(data) + fz.Fill(&ip, &b) + + ip.AppendTo(b) + }) +} + +func Fuzz_Addr_As16(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var ip netip.Addr + fz := fuzzer.NewFuzzer(data) + fz.Fill(&ip) + + ip.As16() + }) +} + +func Fuzz_Addr_As4(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var ip netip.Addr + fz := fuzzer.NewFuzzer(data) + fz.Fill(&ip) + + ip.As4() + }) +} + +func Fuzz_Addr_AsSlice(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var ip netip.Addr + fz := fuzzer.NewFuzzer(data) + fz.Fill(&ip) + + ip.AsSlice() + }) +} + +func Fuzz_Addr_BitLen(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var ip netip.Addr + fz := fuzzer.NewFuzzer(data) + fz.Fill(&ip) + + ip.BitLen() + }) +} + +func Fuzz_Addr_Compare(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var ip netip.Addr + var ip2 netip.Addr + fz := fuzzer.NewFuzzer(data) + fz.Fill(&ip, &ip2) + + ip.Compare(ip2) + }) +} + +func Fuzz_Addr_Is4(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var ip netip.Addr + fz := fuzzer.NewFuzzer(data) + fz.Fill(&ip) + + ip.Is4() + }) +} + +func Fuzz_Addr_Is4In6(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var ip netip.Addr + fz := fuzzer.NewFuzzer(data) + fz.Fill(&ip) + + ip.Is4In6() + }) +} + +func Fuzz_Addr_Is6(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var ip netip.Addr + fz := fuzzer.NewFuzzer(data) + fz.Fill(&ip) + + ip.Is6() + }) +} + +func Fuzz_Addr_IsGlobalUnicast(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var ip netip.Addr + fz := fuzzer.NewFuzzer(data) + fz.Fill(&ip) + + ip.IsGlobalUnicast() + }) +} + +func Fuzz_Addr_IsInterfaceLocalMulticast(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var ip netip.Addr + fz := fuzzer.NewFuzzer(data) + fz.Fill(&ip) + + ip.IsInterfaceLocalMulticast() + }) +} + +func Fuzz_Addr_IsLinkLocalMulticast(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var ip netip.Addr + fz := fuzzer.NewFuzzer(data) + fz.Fill(&ip) + + ip.IsLinkLocalMulticast() + }) +} + +func Fuzz_Addr_IsLinkLocalUnicast(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var ip netip.Addr + fz := fuzzer.NewFuzzer(data) + fz.Fill(&ip) + + ip.IsLinkLocalUnicast() + }) +} + +func Fuzz_Addr_IsLoopback(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var ip netip.Addr + fz := fuzzer.NewFuzzer(data) + fz.Fill(&ip) + + ip.IsLoopback() + }) +} + +func Fuzz_Addr_IsMulticast(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var ip netip.Addr + fz := fuzzer.NewFuzzer(data) + fz.Fill(&ip) + + ip.IsMulticast() + }) +} + +func Fuzz_Addr_IsPrivate(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var ip netip.Addr + fz := fuzzer.NewFuzzer(data) + fz.Fill(&ip) + + ip.IsPrivate() + }) +} + +func Fuzz_Addr_IsUnspecified(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var ip netip.Addr + fz := fuzzer.NewFuzzer(data) + fz.Fill(&ip) + + ip.IsUnspecified() + }) +} + +func Fuzz_Addr_IsValid(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var ip netip.Addr + fz := fuzzer.NewFuzzer(data) + fz.Fill(&ip) + + ip.IsValid() + }) +} + +func Fuzz_Addr_Less(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var ip netip.Addr + var ip2 netip.Addr + fz := fuzzer.NewFuzzer(data) + fz.Fill(&ip, &ip2) + + ip.Less(ip2) + }) +} + +func Fuzz_Addr_MarshalBinary(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var ip netip.Addr + fz := fuzzer.NewFuzzer(data) + fz.Fill(&ip) + + ip.MarshalBinary() + }) +} + +func Fuzz_Addr_MarshalText(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var ip netip.Addr + fz := fuzzer.NewFuzzer(data) + fz.Fill(&ip) + + ip.MarshalText() + }) +} + +func Fuzz_Addr_Next(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var ip netip.Addr + fz := fuzzer.NewFuzzer(data) + fz.Fill(&ip) + + ip.Next() + }) +} + +func Fuzz_Addr_Prefix(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var ip netip.Addr + var b int + fz := fuzzer.NewFuzzer(data) + fz.Fill(&ip, &b) + + ip.Prefix(b) + }) +} + +func Fuzz_Addr_Prev(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var ip netip.Addr + fz := fuzzer.NewFuzzer(data) + fz.Fill(&ip) + + ip.Prev() + }) +} + +func Fuzz_Addr_String(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var ip netip.Addr + fz := fuzzer.NewFuzzer(data) + fz.Fill(&ip) + + ip.String() + }) +} + +func Fuzz_Addr_StringExpanded(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var ip netip.Addr + fz := fuzzer.NewFuzzer(data) + fz.Fill(&ip) + + ip.StringExpanded() + }) +} + +func Fuzz_Addr_Unmap(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var ip netip.Addr + fz := fuzzer.NewFuzzer(data) + fz.Fill(&ip) + + ip.Unmap() + }) +} + +func Fuzz_Addr_WithZone(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var ip netip.Addr + var zone string + fz := fuzzer.NewFuzzer(data) + fz.Fill(&ip, &zone) + + ip.WithZone(zone) + }) +} + +func Fuzz_Addr_Zone(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var ip netip.Addr + fz := fuzzer.NewFuzzer(data) + fz.Fill(&ip) + + ip.Zone() + }) +} + +func Fuzz_AddrPort_Addr(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var p netip.AddrPort + fz := fuzzer.NewFuzzer(data) + fz.Fill(&p) + + p.Addr() + }) +} + +func Fuzz_AddrPort_AppendTo(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var p netip.AddrPort + var b []byte + fz := fuzzer.NewFuzzer(data) + fz.Fill(&p, &b) + + p.AppendTo(b) + }) +} + +func Fuzz_AddrPort_IsValid(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var p netip.AddrPort + fz := fuzzer.NewFuzzer(data) + fz.Fill(&p) + + p.IsValid() + }) +} + +func Fuzz_AddrPort_MarshalBinary(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var p netip.AddrPort + fz := fuzzer.NewFuzzer(data) + fz.Fill(&p) + + p.MarshalBinary() + }) +} + +func Fuzz_AddrPort_MarshalText(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var p netip.AddrPort + fz := fuzzer.NewFuzzer(data) + fz.Fill(&p) + + p.MarshalText() + }) +} + +func Fuzz_AddrPort_Port(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var p netip.AddrPort + fz := fuzzer.NewFuzzer(data) + fz.Fill(&p) + + p.Port() + }) +} + +func Fuzz_AddrPort_String(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var p netip.AddrPort + fz := fuzzer.NewFuzzer(data) + fz.Fill(&p) + + p.String() + }) +} + +func Fuzz_Prefix_Addr(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var p netip.Prefix + fz := fuzzer.NewFuzzer(data) + fz.Fill(&p) + + p.Addr() + }) +} + +func Fuzz_Prefix_AppendTo(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var p netip.Prefix + var b []byte + fz := fuzzer.NewFuzzer(data) + fz.Fill(&p, &b) + + p.AppendTo(b) + }) +} + +func Fuzz_Prefix_Bits(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var p netip.Prefix + fz := fuzzer.NewFuzzer(data) + fz.Fill(&p) + + p.Bits() + }) +} + +func Fuzz_Prefix_Contains(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var p netip.Prefix + var ip netip.Addr + fz := fuzzer.NewFuzzer(data) + fz.Fill(&p, &ip) + + p.Contains(ip) + }) +} + +func Fuzz_Prefix_IsSingleIP(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var p netip.Prefix + fz := fuzzer.NewFuzzer(data) + fz.Fill(&p) + + p.IsSingleIP() + }) +} + +func Fuzz_Prefix_IsValid(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var p netip.Prefix + fz := fuzzer.NewFuzzer(data) + fz.Fill(&p) + + p.IsValid() + }) +} + +func Fuzz_Prefix_MarshalBinary(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var p netip.Prefix + fz := fuzzer.NewFuzzer(data) + fz.Fill(&p) + + p.MarshalBinary() + }) +} + +func Fuzz_Prefix_MarshalText(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var p netip.Prefix + fz := fuzzer.NewFuzzer(data) + fz.Fill(&p) + + p.MarshalText() + }) +} + +func Fuzz_Prefix_Masked(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var p netip.Prefix + fz := fuzzer.NewFuzzer(data) + fz.Fill(&p) + + p.Masked() + }) +} + +func Fuzz_Prefix_Overlaps(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var p netip.Prefix + var o netip.Prefix + fz := fuzzer.NewFuzzer(data) + fz.Fill(&p, &o) + + p.Overlaps(o) + }) +} + +func Fuzz_Prefix_String(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var p netip.Prefix + fz := fuzzer.NewFuzzer(data) + fz.Fill(&p) + + p.String() + }) +} + +func Fuzz_AddrFrom16(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var addr [16]byte + fz := fuzzer.NewFuzzer(data) + fz.Fill(&addr) + + netip.AddrFrom16(addr) + }) +} + +func Fuzz_AddrFrom4(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var addr [4]byte + fz := fuzzer.NewFuzzer(data) + fz.Fill(&addr) + + netip.AddrFrom4(addr) + }) +} + +func Fuzz_AddrFromSlice(f *testing.F) { + f.Fuzz(func(t *testing.T, slice []byte) { + netip.AddrFromSlice(slice) + }) +} + +func Fuzz_AddrPortFrom(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var ip netip.Addr + var port uint16 + fz := fuzzer.NewFuzzer(data) + fz.Fill(&ip, &port) + + netip.AddrPortFrom(ip, port) + }) +} + +func Fuzz_MustParseAddr(f *testing.F) { + f.Fuzz(func(t *testing.T, s string) { + netip.MustParseAddr(s) + }) +} + +func Fuzz_MustParseAddrPort(f *testing.F) { + f.Fuzz(func(t *testing.T, s string) { + netip.MustParseAddrPort(s) + }) +} + +func Fuzz_MustParsePrefix(f *testing.F) { + f.Fuzz(func(t *testing.T, s string) { + netip.MustParsePrefix(s) + }) +} + +func Fuzz_ParseAddr(f *testing.F) { + f.Fuzz(func(t *testing.T, s string) { + netip.ParseAddr(s) + }) +} + +func Fuzz_ParseAddrPort(f *testing.F) { + f.Fuzz(func(t *testing.T, s string) { + netip.ParseAddrPort(s) + }) +} + +func Fuzz_ParsePrefix(f *testing.F) { + f.Fuzz(func(t *testing.T, s string) { + netip.ParsePrefix(s) + }) +} + +func Fuzz_PrefixFrom(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var ip netip.Addr + var bits int + fz := fuzzer.NewFuzzer(data) + fz.Fill(&ip, &bits) + + netip.PrefixFrom(ip, bits) + }) +} diff --git a/examples/outputs/stdlib-strconv/autofuzz_test.go b/examples/outputs/stdlib-strconv/autofuzz_test.go new file mode 100644 index 0000000..2f6cb2f --- /dev/null +++ b/examples/outputs/stdlib-strconv/autofuzz_test.go @@ -0,0 +1,246 @@ +package strconvfuzz // rename if needed + +// if needed, fill in imports or run 'goimports' +import ( + "strconv" + "testing" + + "github.com/thepudds/fzgen/fuzzer" +) + +func Fuzz_NumError_Error(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var e *strconv.NumError + fz := fuzzer.NewFuzzer(data) + fz.Fill(&e) + if e == nil { + return + } + + e.Error() + }) +} + +func Fuzz_NumError_Unwrap(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var e *strconv.NumError + fz := fuzzer.NewFuzzer(data) + fz.Fill(&e) + if e == nil { + return + } + + e.Unwrap() + }) +} + +func Fuzz_AppendBool(f *testing.F) { + f.Fuzz(func(t *testing.T, dst []byte, b bool) { + strconv.AppendBool(dst, b) + }) +} + +func Fuzz_AppendFloat(f *testing.F) { + f.Fuzz(func(t *testing.T, dst []byte, f2 float64, fmt byte, prec int, bitSize int) { + strconv.AppendFloat(dst, f2, fmt, prec, bitSize) + }) +} + +func Fuzz_AppendInt(f *testing.F) { + f.Fuzz(func(t *testing.T, dst []byte, i int64, base int) { + strconv.AppendInt(dst, i, base) + }) +} + +func Fuzz_AppendQuote(f *testing.F) { + f.Fuzz(func(t *testing.T, dst []byte, s string) { + strconv.AppendQuote(dst, s) + }) +} + +func Fuzz_AppendQuoteRune(f *testing.F) { + f.Fuzz(func(t *testing.T, dst []byte, r rune) { + strconv.AppendQuoteRune(dst, r) + }) +} + +func Fuzz_AppendQuoteRuneToASCII(f *testing.F) { + f.Fuzz(func(t *testing.T, dst []byte, r rune) { + strconv.AppendQuoteRuneToASCII(dst, r) + }) +} + +func Fuzz_AppendQuoteRuneToGraphic(f *testing.F) { + f.Fuzz(func(t *testing.T, dst []byte, r rune) { + strconv.AppendQuoteRuneToGraphic(dst, r) + }) +} + +func Fuzz_AppendQuoteToASCII(f *testing.F) { + f.Fuzz(func(t *testing.T, dst []byte, s string) { + strconv.AppendQuoteToASCII(dst, s) + }) +} + +func Fuzz_AppendQuoteToGraphic(f *testing.F) { + f.Fuzz(func(t *testing.T, dst []byte, s string) { + strconv.AppendQuoteToGraphic(dst, s) + }) +} + +func Fuzz_AppendUint(f *testing.F) { + f.Fuzz(func(t *testing.T, dst []byte, i uint64, base int) { + strconv.AppendUint(dst, i, base) + }) +} + +func Fuzz_Atoi(f *testing.F) { + f.Fuzz(func(t *testing.T, s string) { + strconv.Atoi(s) + }) +} + +func Fuzz_CanBackquote(f *testing.F) { + f.Fuzz(func(t *testing.T, s string) { + strconv.CanBackquote(s) + }) +} + +func Fuzz_FormatBool(f *testing.F) { + f.Fuzz(func(t *testing.T, b bool) { + strconv.FormatBool(b) + }) +} + +func Fuzz_FormatComplex(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var c complex128 + var fmt byte + var prec int + var bitSize int + fz := fuzzer.NewFuzzer(data) + fz.Fill(&c, &fmt, &prec, &bitSize) + + strconv.FormatComplex(c, fmt, prec, bitSize) + }) +} + +func Fuzz_FormatFloat(f *testing.F) { + f.Fuzz(func(t *testing.T, f1 float64, fmt byte, prec int, bitSize int) { + strconv.FormatFloat(f1, fmt, prec, bitSize) + }) +} + +func Fuzz_FormatInt(f *testing.F) { + f.Fuzz(func(t *testing.T, i int64, base int) { + strconv.FormatInt(i, base) + }) +} + +func Fuzz_FormatUint(f *testing.F) { + f.Fuzz(func(t *testing.T, i uint64, base int) { + strconv.FormatUint(i, base) + }) +} + +func Fuzz_IsGraphic(f *testing.F) { + f.Fuzz(func(t *testing.T, r rune) { + strconv.IsGraphic(r) + }) +} + +func Fuzz_IsPrint(f *testing.F) { + f.Fuzz(func(t *testing.T, r rune) { + strconv.IsPrint(r) + }) +} + +func Fuzz_Itoa(f *testing.F) { + f.Fuzz(func(t *testing.T, i int) { + strconv.Itoa(i) + }) +} + +func Fuzz_ParseBool(f *testing.F) { + f.Fuzz(func(t *testing.T, str string) { + strconv.ParseBool(str) + }) +} + +func Fuzz_ParseComplex(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, bitSize int) { + strconv.ParseComplex(s, bitSize) + }) +} + +func Fuzz_ParseFloat(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, bitSize int) { + strconv.ParseFloat(s, bitSize) + }) +} + +func Fuzz_ParseInt(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, base int, bitSize int) { + strconv.ParseInt(s, base, bitSize) + }) +} + +func Fuzz_ParseUint(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, base int, bitSize int) { + strconv.ParseUint(s, base, bitSize) + }) +} + +func Fuzz_Quote(f *testing.F) { + f.Fuzz(func(t *testing.T, s string) { + strconv.Quote(s) + }) +} + +func Fuzz_QuoteRune(f *testing.F) { + f.Fuzz(func(t *testing.T, r rune) { + strconv.QuoteRune(r) + }) +} + +func Fuzz_QuoteRuneToASCII(f *testing.F) { + f.Fuzz(func(t *testing.T, r rune) { + strconv.QuoteRuneToASCII(r) + }) +} + +func Fuzz_QuoteRuneToGraphic(f *testing.F) { + f.Fuzz(func(t *testing.T, r rune) { + strconv.QuoteRuneToGraphic(r) + }) +} + +func Fuzz_QuoteToASCII(f *testing.F) { + f.Fuzz(func(t *testing.T, s string) { + strconv.QuoteToASCII(s) + }) +} + +func Fuzz_QuoteToGraphic(f *testing.F) { + f.Fuzz(func(t *testing.T, s string) { + strconv.QuoteToGraphic(s) + }) +} + +func Fuzz_QuotedPrefix(f *testing.F) { + f.Fuzz(func(t *testing.T, s string) { + strconv.QuotedPrefix(s) + }) +} + +func Fuzz_Unquote(f *testing.F) { + f.Fuzz(func(t *testing.T, s string) { + strconv.Unquote(s) + }) +} + +func Fuzz_UnquoteChar(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, quote byte) { + strconv.UnquoteChar(s, quote) + }) +} diff --git a/fuzzer/fuzzer.go b/fuzzer/fuzzer.go new file mode 100644 index 0000000..40f58a4 --- /dev/null +++ b/fuzzer/fuzzer.go @@ -0,0 +1,821 @@ +package fuzzer + +import ( + "bytes" + "fmt" + "os" + "reflect" + "runtime" + "strconv" + "strings" + "sync" + + "github.com/sanity-io/litter" + "github.com/thepudds/fzgen/fuzzer/internal/plan" + "github.com/thepudds/fzgen/fuzzer/internal/randparam" +) + +// SupportedInterfaces enumerates interfaces that can be filled by Fill(&obj). +var SupportedInterfaces = randparam.SupportedInterfaces + +// Step describes an operation to step the system forward. +// Func can take any number of arguments and return any number of values. +// The Name string conventionally should be an acceptable func identifier. +// See Chain for more details on usage. +type Step struct { + Name string + Func interface{} +} + +// Fuzzer is a utility object that can fill in many types +// such as io.Reader, structs, maps, and so on, as well as supports chaining over a set of +// functions in sequence, including connecting output to inputs +// and re-using inputs (e.g., to help excericse APIs like a Store followed +// by a Load). +// Conventially called 'fz'. +type Fuzzer struct { + data []byte + randparamFuzzer *randparam.Fuzzer + execState *execState + + chainOpts chainOpts +} + +type FuzzerOpt func(*Fuzzer) error + +// NewFuzzer returns a Fuzzer, which relies on the input data []byte +// to control its subsequent operations. +// In the future, NewFuzzer may take options, though currently does not. +func NewFuzzer(data []byte, options ...FuzzerOpt) (fz *Fuzzer) { + fill := randparam.NewFuzzer(data) + state := &execState{ + reusableInputs: make(map[reflect.Type][]*reflect.Value), + outputSlots: make(map[reflect.Type][]*outputSlot), + // TODO: not needed? + // reusableOutputs: make(map[reflect.Type][]reflect.Value), + } + return &Fuzzer{ + data: data, + randparamFuzzer: fill, + execState: state, + } +} + +// Fill fills in most simple types, maps, slices, arrays, and recursively fills any public members of x. +// It supports about 20 or so common interfaces, such as io.Reader, io.Writer, or io.ReadWriter. +// See SupportedInterfaces for current list of suppored interfaces. +// Callers pass in a pointer to the object to fill, such as: +// var i int +// Fill(&i) +// var r io.Reader +// Fill(&r) +// var s1, s2 string +// Fill(&s1, &s2) +// Fill ignores channels, func pointers, complex64, complex128, and uintptr, +// For number, string, and []byte types, it tries to populate the obj value with literals found in the initial input []byte. +// +// In order to maximize deterministic behavior, help guide the fuzzing engine, and allow for generation of reproducers, +// Fill must not be called from within the Steps used with Chain. If you need additional values within a Step, add +// them as parameters to the Step, and Chain will fill those parameters. +func (fz *Fuzzer) Fill(x ...interface{}) { + // TODO: probably panic if called from within Chain. + for _, arg := range x { + before := fz.randparamFuzzer.Remaining() + + fz.randparamFuzzer.Fill(arg) + + if debugPrintPlan { + fmt.Printf("fzgen: filled object of type \"%T\" using %d bytes. %d bytes remaining.\n", + arg, before-fz.randparamFuzzer.Remaining(), fz.randparamFuzzer.Remaining()) + } + } +} + +type execState struct { + // reusableInputs is a map from type to list of all new args of that type from all steps, + // ordered by the sequence of calls defined the Plan and the order within the args of a + // given Call from the plan. + // Each entry in the map is a slice of the filled-in reflect.Values for that reflect.Type + // for each argument in the plan that is defined to be a new value (and not a reused input or output). + // For example, if the plan is effectively: + // call1(a, b string) + // call2(c int, d string) + // then the map entry for key of reflect.Type string would be {a, b, d} as long as a, b, and d are defined + // by the plan to be new values. On the other hand, if the plan defines d to reuse a's input value, + // then the map entry for key of reflect.Type string would be {a, b}, without d. + reusableInputs map[reflect.Type][]*reflect.Value + + // outputSlots is map from type to return value slots, covering the complete set of return types in all calls in the plan, + // and ordered by the sequence of calls defined the Plan and the order of the return values of a + // given Call from the plan. + // It is used as an intermediate step prior to actually invoking any calls + // to determine which if any return values should be saved when it is time to invoke a specific call. + // TODO: we probably could collapse outputSlots and reusableOutputs. + outputSlots map[reflect.Type][]*outputSlot + + // reusableOutputs is a map of from type to list of all return values that will be used as later inputs. + // It is similar in spirit to reusableInputs, but whereas reusableInputs contains all new input values, + // reusableOutputs only contains returns values that are planned to be reused by a subsequent call. + // For example, if the plan is effectively: + // call1() int + // call2(a int) + // and the plan defines that call2 will attempt to reuse an int return value as its input arg, + // then the map entry for key of reflect.Type int would be effectively be {{call1RetVal0}}, + // where call2 will use the zeroth return value from call1 as the input to call2. + // After we invoke call1, we fill in the reflect.Value in the right slot of the slice. + // When we later invoke call2, we read that value. + // Note that we set up the slice (with invalid reflect.Values) before any concurrent goroutines run, + // and then take care to read the reflect.Value (e.g., to invoke call2) only after it has been + // filled in (e.g., after the invocation of call1). + // TODO: not needed? + // reusableOutputs map[reflect.Type][]reflect.Value +} + +// execCall represents a function call we intend to make, based on which +// fuzzer.Step func was selected in our Plan. +type execCall struct { + planCall plan.Call // raw plan.Call filled in by fz.Fill + + name string + index int // zero-based index of this call. currently only used for emitting variable name for repro. + fv reflect.Value // func we will call. + args []argument // arguments for this call, some of which might initially be placeholder invalid reflect.Value. + outputSlots []*outputSlot // pointers to the output slots for this call's return values. +} + +type argument struct { + useReturnVal bool // indicates if this argument will come from another call's return value. + typ reflect.Type // type of input argument. + val *reflect.Value // argument value to use. + slot *outputSlot // slot of the return value. +} + +type outputSlot struct { + // Indicates if this return value will be used by a subsequent call. If false, + // we don't store the value after the corresponding call completes. + needed bool + // Type of return value. + typ reflect.Type + // Return value to use. Initially set to invalid reflect.Value{}, which is filled + // in after the corresponding call completes. + val reflect.Value + // Channel to broadcast via close(ch) that the return value val is ready to be read. + ch chan struct{} + // zero-indexed call that the return value will come from. + returnValCall int + // zero-indexed arg from that call that the return value will come from. + returnValArg int +} + +type ChainOpt func(*Fuzzer) error + +type chainOpts struct { + parallel bool +} + +// ChainParallel indicates the Fuzzer is allowed to run the +// defined set of Steps in parallel. The Fuzzer can choose to run +// all selected Steps in parallel, though most often prefers +// to run only a portion of Steps in parallel in a single +// Chain execution in order to increase deterministic behavior +// and help the underlying fuzzing engine evolve interesting intputs. +// Care is taken so that a given corpus will result in the same +// Steps executing with the same arguments regardless of +// whether or not ChainParallel is set. +// +// ChainParallel is most often useful with the race detector, such as +// 'go test -fuzz=. -race', though because the race detector +// can have 10x-20x performance impact, one approach is to +// run for a period of time with ChainParallel set but +// without the race detector to build up a larger corpus faster, +// and then later run with the race detector enabled. +func ChainParallel(fz *Fuzzer) error { + fz.chainOpts.parallel = true + return nil +} + +// Chain invokes a set of Steps, looking for problematic sequences and input arguments. +// The Fuzzer chooses which Steps to calls and how often to call them, +// then creates any needed arguments, and calls the Steps in a sequence selected by the fuzzer. +// The only current option is ChainOptParallel. +// If the last return value of a Step is of type error and a non-nil value is returned, +// this indicates a sequence of Steps should stop execution, +// The only current option is ChainOptParallel. +func (fz *Fuzzer) Chain(steps []Step, options ...ChainOpt) { + // Start by filling in our plan, which will let us know the sequence of steps along + // with sources for input args (which might be re-using input args, + // or using return values, or new values from fz.Fill). + pl := plan.Plan{} + before := fz.randparamFuzzer.Remaining() + switch debugPlanVersion { + case 2: + // Current approach. + // Get any remaining bytes from randparamFuzzer. + data := fz.randparamFuzzer.Data() + buf := bytes.NewReader(data) + + // Convert those bytes into a Plan. + pl = unmarshalPlan(buf, steps) + + // Drain from randparamFuzzer any bytes we used building the Plan. + used := len(data) - buf.Len() + fz.randparamFuzzer.Drain(used) + default: + panic("unexpected debugPlanVersion") + } + + if debugPrintPlan { + emitPlan(pl) + fmt.Printf("fzgen: filled Plan using %d bytes. %d bytes remaining.\n", + before-fz.randparamFuzzer.Remaining(), fz.randparamFuzzer.Remaining()) + } + + // Using functional options. + // (Side note: Rob Pike's blog introducing functional options is a great read: + // https://commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html) + for _, opt := range options { + // For a minor bit of improved call location backwards compat, skip any nil opts in case we have older generated code with a nil as + // second argument. + if opt == nil { + continue + } + err := opt(fz) + if err != nil { + // TODO: currently we have no errors. panic is probably the right way to communicate from inside a fuzz func. + panic(err) + } + } + + fz.chain(steps, pl) +} + +func (fz *Fuzzer) chain(steps []Step, pl plan.Plan) { + parallelAllowed := fz.chainOpts.parallel + + // First, determine if we will spin and loop for any parallel calls. + allowSpin, loopCount := fz.calcParallelControl() + + // Second, create our list of execCalls based on the list of plan.Calls. + // We do not yet fully populate the arguments for an execCall, + // which we will do on a subsequent pass. + var execCalls []execCall + for i := range pl.Calls { + // Based on the plan, compute index into the user's Step list. + s := int(pl.Calls[i].StepIndex) % len(steps) + + ec := execCall{ + planCall: pl.Calls[i], + name: steps[s].Name, + index: i, + fv: mustFunc(steps[s].Func), + args: []argument{}, // empty to start, we will fill in below. + } + + execCalls = append(execCalls, ec) + } + + // Third, create arguments as needed for each execCall, + // or record that we will obtain an argument from the + // return value of an earlier execCall. + for i := range execCalls { + allowReturnValReuse := loopCount == 1 + // Build arguments for this call, and also get its reflect.Value function. + // This can update the execCall to track outputSlots. + args := fz.prepareStep(&execCalls[i], allowReturnValReuse, fz.Fill) + + // Track what we need to execute this call later. + execCalls[i].args = args + } + + // TODO: consider reintroducing shuffle of plan or execCalls, though might have less benefit after interweaving filling args + // with filling the plan, which then gives the fuzzing engine a better chance of reordering. + // (We've tried a few different variations of rand-based shuffling of plan or execCalls, and not clear how helpful. + // If we shuffle, care must be taken around re-using input args (for example, could shuffle after that), + // as well as around re-using return values (for example, could shuffle before that is set up to avoid shuffling + // our way into a deadlock on the arg ready channel). + + sequential := true + var startParallelIndex, stopParallelIndex int // inclusive range + var parallelPlan byte + // This is purposefully the last byte drawn (after the Plan and after our args have all been filled), + // including so that tail-trim minimization will elimanate this from our input data []byte + // If the byte was uniformaly random: + // 1/8 of time - serial + // 3/4 of time - parallel pair + // 1/8 of time - parallel for up to N from end + // However, if the byte in missing (e.g., tail trim minimization) it will be drawn + // as the zero value, which purposefully means serial here, + // and which means serial will be favored if the observed coverage or crash still happens serially. + // Also, we try to take advantage of ASCII '0' minimization behavior of cmd/go + // to mean serial, and then as cmd/go minimization steps to ASCII '1', '2', '3', ..., + // we interpret those to mean pair parallel, stepping from the end. + fz.Fill(¶llelPlan) + if parallelAllowed && len(execCalls) > 1 { + switch { + case parallelPlan == '0' || parallelPlan < 32: + sequential = true + case parallelPlan < 224: + startParallelIndex, stopParallelIndex = calcParallelPair(parallelPlan, len(execCalls)) + sequential = false + default: + startParallelIndex, stopParallelIndex = calcParallelN(parallelPlan, len(execCalls)) + sequential = false + } + } + + if sequential { + // This only matters for debug output, but might as well make it clear. + allowSpin, loopCount = false, 1 + } + + if debugPrintPlan { + fmt.Printf("fzgen: parallelPlan byte: %v startParallelIndex: %d stopParallelIndex: %d sequential: %v\n", + parallelPlan, startParallelIndex, stopParallelIndex, sequential) + } + if debugPrintRepro { + if sequential { + fmt.Printf("PLANNED STEPS: (sequential: %v)\n\n", sequential) + } else { + fmt.Printf("PLANNED STEPS: (sequential: %v, loop count: %d, spin: %v)\n\n", sequential, loopCount, allowSpin) + } + emitBasicRepro(execCalls, sequential, startParallelIndex, stopParallelIndex) + } + + // Invoke our chained calls! + if sequential { + for _, ec := range execCalls { + fz.callStep(ec) + } + } else { + var wg sync.WaitGroup + for i := range execCalls { + // Spin between parallel calls. + if allowSpin && i > startParallelIndex && i <= stopParallelIndex { + runtime.Gosched() + spin() + } + + if i >= startParallelIndex && i <= stopParallelIndex { + // This is a call we will run in parallel. + wg.Add(1) + go func(i int) { + defer wg.Done() + for j := 0; j < loopCount; j++ { + fz.callStep(execCalls[i]) + } + }(i) + + if i == stopParallelIndex { + // Return to sequential execution, waiting on our in-flight goroutines + // we just started above. + wg.Wait() + } + } else { + // Everything outside of start/StopParallelIndex runs sequentially. + fz.callStep(execCalls[i]) + } + } + } +} + +// calcParallelControl draws and interprets bytes to control our spinning and looping. +func (fz *Fuzzer) calcParallelControl() (allowSpin bool, loopCount int) { + // TODO: probably move drawing the bytes to marshal.go. + // TODO: orderPlan is not currently implemented, but we reserve a byte. + // (Previously, we had a couple different flavors of randomized goroutine ordering + // via a seed byte, but that is disabled). + var spinPlan, loopPlan, orderPlan byte + fz.Fill(&spinPlan, &loopPlan, &orderPlan) + + // We prefer to spin (mostly to aid with reproducibility), including if '0' or 0x0 appear during minimization. + // (And yes, '0' is less than 192, but being explicit here as reminder when the numbers get juggled). + allowSpin = spinPlan == '0' || loopPlan < 192 + + // We prefer to not to loop much (mostly for perf & mem usage), including if '0' or 0x0 appear during minimization. + switch { + case loopPlan == '0' || loopPlan < 128: + loopCount = 1 + case loopPlan < 224: + loopCount = 4 + case loopPlan < 250: + loopCount = 16 + case loopPlan < 254: + loopCount = 64 + default: + loopCount = 256 + } + + if loopCount >= 16 { + // Disable spin for larger loop counts. + // This is partly to help with performance, and more debatable, + // the types of concurrency bugs that benefit from a large loop count + // might tend to benefit from starting parallel loops at the same time without + // an artificial delay between starts. + allowSpin = false + } + return allowSpin, loopCount +} + +// calcParallelPair interprets the bytes from our Plan to indicate when we +// should start and stop parallel execution for a pair of calls. +func calcParallelPair(parallelPlan byte, execCallLen int) (startParallelIndex, stopParallelIndex int) { + // startParallelIndex is index of the first exeCall of the pair to run in parallel. + // In general, we want to favor (1) sequential and (2) putting the parallelism as far towards the + // end of a call sequence as we can, including so that new coverage which might have + // been first *observed* with a logical race (e.g., two racing stores that are not a data race) + // is minimized down to sequential behavior when possible. + // We offset from '0' to take advantage of current cmd/go minimizing behavior + // of trying '0', then '1', then '2', ... for each byte when minimizing: + // '0' is handled above, which we want to mean serial + // '1' we handle here, which we want to mean the last and second-to-last are in parallel. + // '2' means second-to-last and third-to-last in parallel, and so on. + // We subtract 1 from the right-side mod operand because the last execCall would be a no-op as startParallelIndex, + // and hence there are only len(execCalls)-1 interesting values for startParallelIndex here. + offset := int(parallelPlan-'1') % (execCallLen - 1) + + stopParallelIndex = execCallLen - 1 - offset + startParallelIndex = stopParallelIndex - 1 + + if startParallelIndex < 0 { + panic("bug computing startParallelIndex") + } + if stopParallelIndex >= execCallLen { + panic("bug computing stopParallelIndex") + } + return startParallelIndex, stopParallelIndex +} + +// calcParallelN interprets the bytes from our Plan to indicate when we +// should start and stop parallel execution, which will be up to N calls in parallel from the end +// of the plan. +func calcParallelN(parallelPlan byte, execCallLen int) (startParallelIndex, stopParallelIndex int) { + offset := int(parallelPlan) % (execCallLen - 1) + + startParallelIndex = execCallLen - 2 - offset + stopParallelIndex = execCallLen - 1 + + if startParallelIndex < 0 { + panic("bug computing startParallelIndex") + } + if stopParallelIndex >= execCallLen { + panic("bug computing stopParallelIndex") + } + return startParallelIndex, stopParallelIndex +} + +func (fz *Fuzzer) callStep(ec execCall) []reflect.Value { + // TODO: don't need all these args eventually + + for _, arg := range ec.args { + if arg.useReturnVal { + // Wait until the return value is ready to be read. + <-arg.slot.ch + } + } + + // Prepare the reflect.Value arg list we will use to call the func. + // This contains the input values we previously created. + reflectArgs := []reflect.Value{} + for i := range ec.args { + reflectArgs = append(reflectArgs, *ec.args[i].val) + } + + // Call the user's func. + ret := ec.fv.Call(reflectArgs) + + if len(ret) != ec.fv.Type().NumOut() { + panic("fzgen: mismatch on return value count") + } + if len(ret) != len(ec.outputSlots) { + panic(fmt.Sprintf("fzgen: for execCall %v, mismatch on return value count vs. execCall.outputSlots count: %+v, %+v", ec.name, ret, ec.outputSlots)) + } + + // Check to see if any of these return results are needed by an subsequent call. + if fz.execState != nil { + for i := 0; i < ec.fv.Type().NumOut(); i++ { + if ec.outputSlots[i].needed { + // at least one subsequent call will use this return value. + outV := ret[i] + // sanity check types match + outT := ec.fv.Type().Out(i) + if outT != outV.Type() || outT != ec.outputSlots[i].typ { + panic("fzgen: mismatch on return value types") + } + + // store this return value in the right outputSlot for later use by a subsequent call. + slot := ec.outputSlots[i] + slot.val = outV + + // Broadcast that the slot.val is ready to be read. + close(slot.ch) + } + } + } + + // fmt.Println(ret) + // fmt.Printf("ret: %T %v\n", ret[0], ret[0]) + return ret +} + +func (fz *Fuzzer) prepareStep(ec *execCall, allowReturnValReuse bool, fillFunc func(...interface{})) []argument { + // TODO: additional sanity checking on types? + fv := ec.fv + ft := fv.Type() + + // Build up a list of arguments that are filled in with fresh values, or via reusing prior values. + args := []argument{} + for i := 0; i < ft.NumIn(); i++ { + // Create or find an input value + var arg argument + inT := ft.In(i) + + // Track if we will need to create a new arg via reflect.New (vs. reusing an input or output). + createNew := true + + // Check if our plan indicates we should try to reuse an input or output. + if fz.execState != nil && len(ec.planCall.ArgSource) > i { + switch ec.planCall.ArgSource[i].SourceType % 3 { + case 0: + // Reuse an argument, if one can be found. + inputs, ok := fz.execState.reusableInputs[inT] + if ok && len(inputs) > 0 { + // TODO: take index from plan eventually; for simple tests, fine to take first + inV := inputs[0] + // We want the Elem() for use below in Call, because + // inV represents a pointer to the type we want (e.g. from reflect.New), + // so we do the indirect via inV.Elem() to get our original type. + inElem := inV.Elem() + arg = argument{ + useReturnVal: false, + typ: inV.Type(), + val: &inElem, + } + createNew = false + } + case 1: + if allowReturnValReuse { + // Mark that we will use a return value from an earlier step, if one can be found. + outputSlots, ok := fz.execState.outputSlots[inT] + if ok && len(outputSlots) > 0 { + // We found a return value. + // TODO: BUG: Note that it could be from any step, including one which happens + // after us. + // TODO: take index from plan eventually; for simple tests, fine to take first + outputSlot := outputSlots[0] + outputSlot.needed = true + arg = argument{ + useReturnVal: true, + typ: inT, + val: &outputSlot.val, + slot: outputSlot, + } + createNew = false + } + } + } + } + + if createNew { + // Create a new instance. + // Note: NOT doing anything special if inT represent a pointer type (including not calling Elem here) + inV := reflect.New(inT) + + // inInf is an interface with a pointer as its value, for example, *string if inT.Kind() is string + inIntf := inV.Interface() + + // Do the work of filling in this value + fillFunc(inIntf) + + inElem := inV.Elem() + arg = argument{ + useReturnVal: false, + typ: inV.Type(), + val: &inElem, + } + + if fz.execState != nil { + // This is a new arg, store for later. + // (A reused input arg would have already beeen stored for later use). + fz.execState.reusableInputs[arg.typ] = append(fz.execState.reusableInputs[arg.typ], arg.val) + // TODO: simple pop for now + if len(fz.execState.reusableInputs[arg.typ]) > 10 { + fz.execState.reusableInputs[arg.typ] = fz.execState.reusableInputs[arg.typ][1:] + } + } + } + + // Add this now useful value to our list of input args for this call. + args = append(args, arg) + } + + // Finally, add all of the return types for this call to our + // set of all known return value types for all of our steps seen so far. + // A later call might might use one of our return values as an input arg. + if fz.execState != nil { + for i := 0; i < fv.Type().NumOut(); i++ { + outT := fv.Type().Out(i) + slot := &outputSlot{ + needed: false, + typ: outT, + val: reflect.Value{}, + ch: make(chan struct{}), + returnValCall: ec.index, + returnValArg: i, + } + fz.execState.outputSlots[outT] = append(fz.execState.outputSlots[outT], slot) + // execCall.outputSlots is a slice containing slots for all return values for + // the call, with slice elements ordered by the return value order of the call. + ec.outputSlots = append(ec.outputSlots, slot) + // panic(fmt.Sprintf("for type %v, appending to ec.outputSlots: %#v", outT, ec.outputSlots)) + } + } + + return args +} + +func mustFunc(obj interface{}) reflect.Value { + fv := reflect.ValueOf(obj) + if fv.Kind() != reflect.Func { + panic(fmt.Sprintf("fzgen: Step.Func is not of type func. [kind: %v %%T: %T value: %v]", fv.Kind(), fv, fv)) + } + return fv +} + +var spinCount int + +func spin() { + // TODO: tune duration of spin down? + // It's going to depend on funcs under test and HW and so on, but on one test with logical race that set up a data: + // 1<<16 vs. no spin moved reproducibility from ~80% to ~95% + // 1<<20 moved reproducibility to ~100% + var i int + for i < 1<<18 { + i++ + } + spinCount += i +} + +var ( + debugPrintRepro bool + debugPrintPlan bool + debugPlanVersion int = 2 +) + +func emitPlan(pl plan.Plan) { + litter.Config.Compact = false + // TODO: Probably use litter.Options object + fmt.Println("PLAN:") + litter.Dump(pl) + fmt.Println() +} + +// emitBasicRepro is the start of a more complete standalone reproducer +// creation, which ultimately could be a standalone _test.go file +// that does not have any dependency on fzgen/fuzzer or testing.F. +// +// Example current output, showing: +// - literals for new args filled in by fz.Fill +// - literals for reused args +// - temporary variables when an output val is wired to a later input arg. +// +// Output: +// +// Fuzz_MySafeMap_Load( +// [4]uint8{0,0,0,0}, +// ) +// __fzCall2Retval1 := Fuzz_MySafeMap_Load( +// [4]uint8{0,0,0,0}, +// ) +// Fuzz_MySafeMap_Store( +// [4]uint8{0,0,0,0}, +// __fzCall2Retval1, +// ) +func emitBasicRepro(calls []execCall, sequential bool, startParallelIndex int, stopParallelIndex int) { + litter.Config.Compact = true + // TODO: Probably use litter.Options object + // TODO: litter.Config.HomePackage = "" + + for i, ec := range calls { + parallelCall := false + if !sequential && i >= startParallelIndex && i <= stopParallelIndex { + parallelCall = true + } + + // TODO: consider emitting spin? + // if i > startParallelIndex && i <= stopParallelIndex { + // fmt.Print("\n\tspin()\n") + // } + + if parallelCall && i == startParallelIndex { + if i != 0 { + fmt.Println() + } + fmt.Print("\tvar wg sync.WaitGroup\n") + fmt.Printf("\twg.Add(%d)\n\n", stopParallelIndex-startParallelIndex+1) + fmt.Print("\t// Execute next steps in parallel.\n") + } + + if parallelCall { + fmt.Print("\tgo func() {\n") + fmt.Print("\t\tdefer wg.Done()\n") + } + + // start emititng the actual call invocation. + if parallelCall { + fmt.Print("\t\t") + } else { + fmt.Print("\t") + } + + // check if we are reusing any of return values from this call. + showReturn := false + for _, slot := range ec.outputSlots { + if slot.needed { + showReturn = true + break + } + } + + if showReturn { + // emit assignement to return values, which can look like: + // __fzCall2Retval1, _, _ := + for i, slot := range ec.outputSlots { + if i > 0 { + fmt.Print(", ") + } + if !ec.outputSlots[i].needed { + fmt.Print("_") + } else { + // one-based temp variable names for friendlier output. + fmt.Printf("__fzCall%dRetval%d", slot.returnValCall+1, slot.returnValArg+1) + } + } + fmt.Print(" := ") + } + + // emit the args, which might just be literals, or + // might include one or more temp variables for a return value. + fmt.Printf("%s(\n", ec.name) + for _, arg := range ec.args { + if parallelCall { + fmt.Print("\t") + } + if !arg.useReturnVal { + fmt.Printf("\t\t%s,\n", litter.Sdump(arg.val.Interface())) + } else { + // one-based temp variable names for friendlier output. + fmt.Printf("\t\t__fzCall%dRetval%d,\n", arg.slot.returnValCall+1, arg.slot.returnValArg+1) + } + } + + // close out the invocation of this call. + if parallelCall { + fmt.Print("\t\t)\n") + fmt.Print("\t}()\n") + } else { + fmt.Print("\t)\n") + } + + if parallelCall && i == stopParallelIndex { + fmt.Print("\twg.Wait()\n") + if i < len(calls)-1 { + fmt.Printf("\n\t// Resume sequential execution.\n") + } + } + } + fmt.Println() +} + +func init() { + fzgenDebugParse() +} + +func fzgenDebugParse() { + debug := strings.Split(os.Getenv("FZDEBUG"), ",") + for _, f := range debug { + if strings.HasPrefix(f, "repro=") { + debugReproVal, err := strconv.Atoi(strings.TrimPrefix(f, "repro=")) + if err != nil || debugReproVal > 1 { + panic("unexpected repro value in FZDEBUG env var") + } + if debugReproVal == 1 { + debugPrintRepro = true + } + } + if strings.HasPrefix(f, "plan=") { + debugPlanVal, err := strconv.Atoi(strings.TrimPrefix(f, "plan=")) + if err != nil || debugPlanVal > 1 { + panic("unexpected repro value in FZDEBUG env var") + } + if debugPlanVal == 1 { + debugPrintPlan = true + } + } + if strings.HasPrefix(f, "planversion=") { + debugPlanVersion, err := strconv.Atoi(strings.TrimPrefix(f, "planversion=")) + if err != nil || debugPlanVersion > 2 { + panic("unexpected planversion value in FZDEBUG env var") + } + } + } +} diff --git a/fuzzer/fuzzer_race_test.go b/fuzzer/fuzzer_race_test.go new file mode 100644 index 0000000..4a571e8 --- /dev/null +++ b/fuzzer/fuzzer_race_test.go @@ -0,0 +1,191 @@ +//go:build race + +package fuzzer + +import ( + "testing" + + raceexample "github.com/thepudds/fzgen/examples/inputs/race" + "github.com/thepudds/fzgen/fuzzer/internal/plan" +) + +// TODO: need to update this for new encoding -- all zero draw now ==> sequential, so no race + +// These go test invocations are expected to crash with a race detector violation: +// go test -run=SimpleRace/parallel -v -race +// go test -run=SimpleRace -v -race +// This should not crash: +// go test -run=SimpleRace/sequential -v -race +func TestFuzzerChainWithSimpleRace(t *testing.T) { + tests := []struct { + name string + opt *ChainOptions + pl plan.Plan + }{ + { + name: "sequential increment shared int", + opt: &ChainOptions{Parallel: false}, + pl: plan.Plan{ + GoroutineOrdering: 0, + Calls: []plan.Call{ + { + StepIndex: 0, // will be first call above + ArgSource: []plan.ArgSource{{ + SourceType: 2, // corresponds to new arg, which for this test will be zero value + ArgIndex: 0, + }}, + }, + { + StepIndex: 1, // will be second call above + ArgSource: []plan.ArgSource{{ + SourceType: 2, // corresponds to new arg, which for this test will be zero value + ArgIndex: 0, + }}, + }, + }, + }, + }, + { + name: "parallel increment shared int", + opt: &ChainOptions{Parallel: true}, + pl: plan.Plan{ + GoroutineOrdering: 0, + Calls: []plan.Call{ + { + StepIndex: 0, // will be first call above + ArgSource: []plan.ArgSource{{ + SourceType: 2, // corresponds to new arg, which for this test will be zero value + ArgIndex: 0, + }}, + }, + { + StepIndex: 1, // will be second call above + ArgSource: []plan.ArgSource{{ + SourceType: 2, // corresponds to new arg, which for this test will be zero value + ArgIndex: 0, + }}, + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // We create an Fuzzer, but we don't rely on the data []byte in this test + // because we force a Plan, so we use an empty data []byte here. + fz := NewFuzzer([]byte{}) + + var shared int + + steps := []Step{ + { + Name: "step 1: increment a shared variable", + Func: func(a int) { shared++ }, // DATA RACE if called in parallel + }, + { + Name: "step 2: increment a shared variable", + Func: func(a int) { shared++ }, // DATA RACE if called in parallel + }, + } + + // Success is being flagged (or not) by race detector. + // See comments at top of this test func. + fz.chain(steps, tt.opt, tt.pl) + }) + } +} + +// These are expected to crash when run with the race detector: +// go test -run=ComplexRace/parallel -v -race +// go test -run=ComplexRace -v -race +// This should not crash: +// go test -run=ComplexRace/sequential -v -race +func TestFuzzerChainWithComplexRace(t *testing.T) { + tests := []struct { + name string + opt *ChainOptions + pl plan.Plan + }{ + { + name: "sequential increment shared int", + opt: &ChainOptions{Parallel: false}, + pl: plan.Plan{ + GoroutineOrdering: 0, + Calls: []plan.Call{ + { + StepIndex: 0, // will be first call above + ArgSource: []plan.ArgSource{{ + SourceType: 2, // corresponds to new arg, which for this test will be zero value + ArgIndex: 0, + }}, + }, + { + StepIndex: 1, // will be second call above + ArgSource: []plan.ArgSource{{ + SourceType: 2, // corresponds to new arg, which for this test will be zero value + ArgIndex: 0, + }}, + }, + }, + }, + }, + { + name: "parallel increment shared int", + opt: &ChainOptions{Parallel: true}, + pl: plan.Plan{ + GoroutineOrdering: 0, + Calls: []plan.Call{ + { + StepIndex: 0, // will be first call above + ArgSource: []plan.ArgSource{{ + SourceType: 2, // corresponds to new arg, which for this test will be zero value + ArgIndex: 0, + }}, + }, + { + StepIndex: 1, // will be second call above + ArgSource: []plan.ArgSource{{ + SourceType: 2, // corresponds to new arg, which for this test will be zero value + ArgIndex: 0, + }}, + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // We create an Fuzzer, but we don't rely on the data []byte in this test + // because we force a Plan, so we use an empty data []byte here. + fz := NewFuzzer([]byte{}) + + // target and steps here taken from: + // fzgen -chain -pkg=github.com/thepudds/fzgen/examples/inputs/race + target := raceexample.NewMySafeMap() + + steps := []Step{ + { + Name: "Fuzz_MySafeMap_Load", + Func: func(key [16]byte) *raceexample.Request { + return target.Load(key) + }, + }, + { + Name: "Fuzz_MySafeMap_Store", + Func: func(key [16]byte, req *raceexample.Request) { + if req == nil { + return + } + target.Store(key, req) + }, + }, + } + + // Success is being flagged (or not) by race detector. + // See comments at top of this test func. + fz.chain(steps, tt.opt, tt.pl) + }) + } +} diff --git a/fuzzer/fuzzer_test.go b/fuzzer/fuzzer_test.go new file mode 100644 index 0000000..3a11d51 --- /dev/null +++ b/fuzzer/fuzzer_test.go @@ -0,0 +1,309 @@ +package fuzzer + +import ( + "bytes" + "fmt" + "io" + "io/ioutil" + "strconv" + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/thepudds/fzgen/fuzzer/internal/plan" +) + +func TestCallStep(t *testing.T) { + tests := []struct { + step Step // use Step.Name for subtest name + want interface{} + }{ + { + Step{ + Name: "input int", + Func: func(a int) int { return a }, + }, + int(42), + }, + { + Step{ + Name: "input int pointer", + Func: func(a *int) int { return *a }, + }, + int(42), + }, + { + Step{ + Name: "input io.Reader", + Func: func(r io.Reader) []byte { + b, err := ioutil.ReadAll(r) + if err != nil { + panic(err) + } + return b + }, + }, + []byte{42}, + }, + } + for _, tt := range tests { + t.Run(tt.step.Name, func(t *testing.T) { + // We create an Fuzzer, but we don't rely on the data []byte in this test + // because we use a fake fill function, so we use an empty data []byte here. + fz := NewFuzzer([]byte{}) + + // The zero value of plan.Call{} is not generally useful for real clients, + // but it works for this test. + ec := execCall{ + planCall: plan.Call{}, + name: tt.step.Name, + fv: mustFunc(tt.step.Func), + args: []argument{}, // empty list to start, will be populated by prepareStep + } + + allowReturnValReuse := true + args := fz.prepareStep(&ec, allowReturnValReuse, fakeFill) + ec.args = args + + ret := fz.callStep(ec) + + if len(ret) != 1 { + t.Fatalf("callStep() = %v, len %v, test function expects single element returned", ret, len(ret)) + } + v := ret[0] + gotIntf := v.Interface() + + if diff := cmp.Diff(tt.want, gotIntf); diff != "" { + t.Errorf("callStep() mismatch (-want +got):\n%s", diff) + } + }) + } +} + +func TestFuzzerChain(t *testing.T) { + tests := []struct { + name string + wantBingo bool // do we expect to find a "bingo" panic + pl plan.Plan + }{ + { + name: "all new args", + wantBingo: false, + pl: plan.Plan{ + GoroutineOrdering: 0, + Calls: []plan.Call{ + { + StepIndex: 0, // will be first call above + ArgSource: []plan.ArgSource{{ + SourceType: 2, // corresponds to new arg, which for this test will be zero value + ArgIndex: 0, + }}, + }, + { + StepIndex: 1, // will be second call above + ArgSource: []plan.ArgSource{{ + SourceType: 2, // corresponds to new arg, which for this test will be zero value + ArgIndex: 0, + }}, + }, + }, + }, + }, + { + name: "reuse arg for step1 in step2", + wantBingo: false, + pl: plan.Plan{ + GoroutineOrdering: 0, + Calls: []plan.Call{ + { + StepIndex: 0, // will be first call above + ArgSource: []plan.ArgSource{{ + SourceType: 2, // corresponds to new arg, which for this test will be zero value + ArgIndex: 0, + }}, + }, + { + StepIndex: 1, // will be second call above + ArgSource: []plan.ArgSource{{ + SourceType: 0, // corresponds to reusing an input arg + ArgIndex: 0, + }}, + }, + }, + }, + }, + // Setup a sequential plan by hand that is effectively: + // ret := step1(0) // 0 because with an empty data []byte, any filled in value will be the type's zero value. + // step2(ret) // reuse return value from step1 based on how we set up plan below. + { + name: "reuse return value from step 1 in step 2", + wantBingo: true, + + pl: plan.Plan{ + GoroutineOrdering: 0, + Calls: []plan.Call{ + { + StepIndex: 0, // will be first call above + ArgSource: []plan.ArgSource{{ + SourceType: 2, // corresponds to new arg, which for this test will be zero value + ArgIndex: 0, + }}, + }, + { + StepIndex: 1, // will be second call above + ArgSource: []plan.ArgSource{{ + SourceType: 1, // corresponds to using a return value + ArgIndex: 0, + }}, + }, + }, + }, + }, + } + + steps := []Step{ + { + Name: "step 1: input int and return int", + Func: func(a int) int { return a + 42 }, + }, + { + Name: "step 2: input int and return string", + Func: func(a int) string { + if a == 42 { + panic("bingo - found desired panic after finding 42") + } + return strconv.Itoa(a) + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // We create an Fuzzer, but we don't rely on the data []byte in this test + // because we force a Plan, so we use an empty data []byte here. + fz := NewFuzzer([]byte{}) + + // Success is currently not panicking in reflect or similar, + // while panicking with an expected "bingo" panic if we set up the + // test case to do that via a properly working chain. + // TODO: could get the return value back and validate, though would + // also probably want to force the input arg not to be zero (e.g., construct a data []byte, or ...) + if tt.wantBingo { + defer func() { + err := recover() + s, ok := err.(string) + if ok && strings.Contains(s, "bingo") { + t.Logf("expected panic occurred: %v", err) + } else { + t.Error("did not get expected panic") + } + }() + } + + fz.chain(steps, tt.pl) + }) + } +} + +// fakeFill is a simple test standin for fzgen/fuzzer.Fuzzer.Fill. +// must take pointer to value of interest. For example, to fill an int: +// var a int +// fakeFill(&a) +func fakeFill(args ...interface{}) { + for _, obj := range args { + answer := 42 + switch v := obj.(type) { + case *int: // handles an int parameter in a Step.Func + *v = answer + case **int: // handles an *int parameter in a Step.Func + *v = new(int) + **v = answer + case *io.Reader: // handles an io.Reader parameter in a Step.Func + var b []byte + b = append(b, byte(answer)) + // to actually fill: fz.Fill(&b) + *v = bytes.NewReader(b) + default: + panic(fmt.Sprintf("unsupported type in fakeFill: %T %v", obj, obj)) + } + } +} + +func TestFuzzerFill(t *testing.T) { + t.Run("int32 - 4 byte input", func(t *testing.T) { + input := []byte{0x0, 0x42, 0x0, 0x0, 0x0} + want := int32(0x42) + + fz := NewFuzzer(input) + var got int32 + fz.Fill(&got) + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("fuzzer.Fill() mismatch (-want +got):\n%s", diff) + } + }) + + t.Run("int32 - 1 byte input", func(t *testing.T) { + input := []byte{0x0, 0x42} + want := int32(0x0) + + fz := NewFuzzer(input) + var got int32 + fz.Fill(&got) + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("fuzzer.Fill() mismatch (-want +got):\n%s", diff) + } + }) +} + +func TestCalcParallelPair(t *testing.T) { + tests := []struct { + name string + parallelPlan byte + execCallLen int + wantStartParallelIndex int + wantStopParallelIndex int + }{ + {"1", '1', 10, 8, 9}, + {"2", '2', 10, 7, 8}, + {"9", '9', 10, 0, 1}, + {"9+1", '9' + 1, 10, 8, 9}, + {"wrap", 0, 10, 8, 9}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotStartParallelIndex, gotStopParallelIndex := calcParallelPair(tt.parallelPlan, tt.execCallLen) + if gotStartParallelIndex != tt.wantStartParallelIndex { + t.Errorf("calcParallelIndex() startParallelIndex = %v, want %v", gotStartParallelIndex, tt.wantStartParallelIndex) + } + if gotStopParallelIndex != tt.wantStopParallelIndex { + t.Errorf("calcParallelIndex() stopParallelIndex = %v, want %v", gotStopParallelIndex, tt.wantStopParallelIndex) + } + }) + } +} + +func TestCalcParallelN(t *testing.T) { + tests := []struct { + name string + parallelPlan byte + execCallLen int + wantStartParallelIndex int + wantStopParallelIndex int + }{ + {"0", 0, 10, 8, 9}, + {"1", 1, 10, 7, 9}, + {"8", 8, 10, 0, 9}, + {"9", 9, 10, 8, 9}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotStartParallelIndex, gotStopParallelIndex := calcParallelN(tt.parallelPlan, tt.execCallLen) + if gotStartParallelIndex != tt.wantStartParallelIndex { + t.Errorf("calcParallelIndex() startParallelIndex = %v, want %v", gotStartParallelIndex, tt.wantStartParallelIndex) + } + if gotStopParallelIndex != tt.wantStopParallelIndex { + t.Errorf("calcParallelIndex() stopParallelIndex = %v, want %v", gotStopParallelIndex, tt.wantStopParallelIndex) + } + }) + } +} diff --git a/fuzzer/internal/plan/plan.go b/fuzzer/internal/plan/plan.go new file mode 100644 index 0000000..d695fcb --- /dev/null +++ b/fuzzer/internal/plan/plan.go @@ -0,0 +1,32 @@ +package plan + +type Plan struct { + GoroutineOrdering byte // TODO: currently unused. delete or restore. was used as seed value. + Calls []Call // sequential list of steps to execute +} + +// Call represents a function call we intend to make, based on the +// fuzzer.Step funcs were selected in our Plan. +type Call struct { + // These will be filled in from the data []byte from the core fuzzing engine (go-fuzz or cmd/go) + // via fz.Fill. + StepIndex uint8 // which Step in steps does this represent, mod len(steps) (or if > len(steps ==> stop) + ArgSource []ArgSource // list of how to create arguments for this Step. Zero len ==> all new args. If len is > args, ignore extra. +} + +// ArgSource represents how to obtain values for one agument to an ExecStep. +// Values can come from: +// reusing inputs to other ExecSteps +// reusing outputs from other Exect steps +// a new value +// When attempting to reuse an input or output to another ExecStep, if there is a mismatch on type, +// or if asked to use non-existent return values for source step, +// we first try flipping from new args to outputs or vice versa, then fallback to new value. +// In future, could consider increment through arg/output slots, and increment through ExecSteps prior to ultimately falling back to new value. +// In other words, the ArgSource would indicate where to start our search for a matching type, and +// then could exaustively & deterministcally look for match. +// ASCII '0' is dec 48. 48 mod 3 = 0. Given go-fuzz and probably cmd/go like 0x0 as well as ASCII '0', we pick reusing source for SourceType % 3==0. +type ArgSource struct { + SourceType uint8 // mod 3: 0 means reuse arg from source ExecStep, 1 is reuse output from source ExecStep, 2 is new arg. + ArgIndex uint8 // which source element, such as all prior input args of a given type, mod len(source). +} diff --git a/fuzzer/internal/randparam/bytes2rand.go b/fuzzer/internal/randparam/bytes2rand.go index c8a48cd..f6538eb 100644 --- a/fuzzer/internal/randparam/bytes2rand.go +++ b/fuzzer/internal/randparam/bytes2rand.go @@ -16,9 +16,21 @@ func (s *randSource) Remaining() int { return len(s.data) } -// Drain removes all remaining bytes in the input []byte. -func (s *randSource) Drain() { - s.data = nil +// Drain removes the next n bytes from the input []byte. +// If n is greater than Remaining, it drains all remaining bytes. +func (s *randSource) Drain(n int) { + if len(s.data) >= n { + s.data = s.data[n:] + } else { + s.data = nil + } +} + +// Data returns a []byte covering the remaining bytes from +// the original input []byte. Any bytes that are considered +// consumed should be indicated via Drain. +func (s *randSource) Data() []byte { + return s.data } // PeekByte looks at the next byte without consuming it, diff --git a/fuzzer/internal/randparam/bytes2rand_test.go b/fuzzer/internal/randparam/bytes2rand_test.go index 01cf46c..c377697 100644 --- a/fuzzer/internal/randparam/bytes2rand_test.go +++ b/fuzzer/internal/randparam/bytes2rand_test.go @@ -31,7 +31,6 @@ func TestRandSource_Uint64(t *testing.T) { if gotValue2 := src.Uint64(); gotValue2 != tt.wantDraw2 { t.Errorf("second RandSource.Uint64() = 0x%x, want 0x%x", gotValue2, tt.wantDraw2) } - }) } } diff --git a/fuzzer/internal/randparam/randparam.go b/fuzzer/internal/randparam/randparam.go index f41e96e..22679fc 100644 --- a/fuzzer/internal/randparam/randparam.go +++ b/fuzzer/internal/randparam/randparam.go @@ -1,94 +1,74 @@ // Package randparam allows a []byte to be used as a source of random parameter values. // -// The primary use case is to allow fzgo to use dvyukov/go-fuzz to fuzz rich signatures such as: -// FuzzFunc(re string, input string, posix bool) -// google/gofuzz is used to walk the structure of parameters, but randparam uses custom random generators, -// including in the hopes of allowing dvyukov/go-fuzz literal injection to work, -// as well as to better exploit the genetic mutations of dvyukov/go-fuzz, etc. +// The primary use case is to allow thepudds/fzgen to automatically generate fuzzing functions +// for rich signatures such as: +// regexp.MatchReader(pattern string, r io.RuneReader) +// +// randparam fills in common top-level interfaces such as io.Reader, io.Writer, io.ReadWriter, and so on. +// See SupportedInterfaces for current list. +// +// This package predates builtin cmd/go fuzzing support, and originally +// was targeted at use by thepudds/fzgo, which was a working prototype of an earlier "first class fuzzing in cmd/go" proposal, +// (There is still some baggage left over from that earlier world). package randparam import ( + "bytes" + "context" "fmt" - "math/rand" - - gofuzz "github.com/google/gofuzz" + "io" + "io/ioutil" + "math" + "reflect" ) -// Fuzzer generates random values for public members. -// It wires together dvyukov/go-fuzz (for randomness, instrumentation, managing corpus, etc.) -// with google/gofuzz (for walking a structure recursively), though it uses functions from -// this package to actually fill in string, []byte, and number values. -type Fuzzer struct { - gofuzzFuzzer *gofuzz.Fuzzer +// SupportedInterfaces enumerates interfaces that can be filled by Fill(&obj). +var SupportedInterfaces = map[string]bool{ + "io.Writer": true, + "io.Reader": true, + "io.ReaderAt": true, + "io.WriterTo": true, + "io.Seeker": true, + "io.ByteScanner": true, + "io.RuneScanner": true, + "io.ReadSeeker": true, + "io.ByteReader": true, + "io.RuneReader": true, + "io.ByteWriter": true, + "io.ReadWriter": true, + "io.ReaderFrom": true, + "io.StringWriter": true, + "io.Closer": true, + "io.ReadCloser": true, + "context.Context": true, } -// randFuncs is a list of our custom variable generation functions -// that tap into our custom random number generator to pull values from -// the initial input []byte. -var randFuncs = []interface{}{ - randInt, - randInt8, - randInt16, - randInt32, - randInt64, - randUint, - randUint8, - randUint16, - randUint32, - randUint64, - randFloat32, - randFloat64, - randByte, - randRune, +// Fuzzer generates random values for public members. +// It allows wiring together cmd/go fuzzing or dvyukov/go-fuzz (for randomness, instrumentation, managing corpus, etc.) +// with the ability to fill in common interfaces, as well as string, []byte, and number values. +type Fuzzer struct { + fzgoSrc *randSource } // NewFuzzer returns a *Fuzzer, initialized with the []byte as an input stream for drawing values via rand.Rand. func NewFuzzer(data []byte) *Fuzzer { // create our random data stream that fill use data []byte for results. fzgoSrc := &randSource{data} - randSrc := rand.New(fzgoSrc) - - // create some closures for custom fuzzing (so that we have direct access to fzgoSrc). - randFuncsWithFzgoSrc := []interface{}{ - func(ptr *[]byte, c gofuzz.Continue) { - randBytes(ptr, c, fzgoSrc) - }, - func(ptr *string, c gofuzz.Continue) { - randString(ptr, c, fzgoSrc) - }, - func(ptr *[]string, c gofuzz.Continue) { - randStringSlice(ptr, c, fzgoSrc) - }, - } - - // combine our two custom fuzz function lists. - funcs := append(randFuncs, randFuncsWithFzgoSrc...) - // create the google/gofuzz fuzzer - gofuzzFuzzer := gofuzz.New().RandSource(randSrc).Funcs(funcs...) + f := &Fuzzer{ + fzgoSrc: fzgoSrc, + } - // gofuzzFuzzer.NilChance(0).NumElements(2, 2) - // TODO: pick parameters for NilChance, NumElements, e.g.: - // gofuzzFuzzer.NilChance(0.1).NumElements(0, 10) + // TODO: probably have parameters for number of elements.NilChance, NumElements, e.g.: // Initially allowing too much variability with NumElements seemed // to be a problem, but more likely that was an early indication of // the need to better tune the exact string/[]byte encoding to work // better with sonar. - // TODO: consider if we want to use the first byte for meta parameters. - firstByte := fzgoSrc.Byte() - switch { - case firstByte < 32: - gofuzzFuzzer.NilChance(0).NumElements(2, 2) - case firstByte < 64: - gofuzzFuzzer.NilChance(0).NumElements(1, 1) - case firstByte < 96: - gofuzzFuzzer.NilChance(0).NumElements(3, 3) - case firstByte < 128: - gofuzzFuzzer.NilChance(0).NumElements(4, 4) - case firstByte <= 255: - gofuzzFuzzer.NilChance(0.1).NumElements(0, 10) - } + // TODO: consider if we want to use the first byte for meta parameters like + // forcing count of slices like we used to do in fzgo.and so. + // We still draw the first byte here to reserve it. + fzgoSrc.Byte() // TODO: probably delete the alternative string encoding code. // Probably DON'T have different string encodings. @@ -98,29 +78,124 @@ func NewFuzzer(data []byte) *Fuzzer { // fzgoSrc.lengthEncodedStrings = false // } - f := &Fuzzer{gofuzzFuzzer: gofuzzFuzzer} return f } -// Fuzz fills in public members of obj. For numbers, strings, []bytes, it tries to populate the -// obj value with literals found in the initial input []byte. -func (f *Fuzzer) Fuzz(obj interface{}) { - f.gofuzzFuzzer.Fuzz(obj) +// Remaining reports how many bytes remain in our original input []byte. +func (f *Fuzzer) Remaining() int { + return f.fzgoSrc.Remaining() } -// Fill fills in public members of obj. For numbers, strings, []bytes, it tries to populate the -// obj value with literals found in the initial input []byte. -// TODO: decide to call this Fill or Fuzz or something else. We support both Fill and Fuzz for now. -func (f *Fuzzer) Fill(obj interface{}) { - f.gofuzzFuzzer.Fuzz(obj) +// Drain removes the next n bytes from the input []byte. +// If n is greater than Remaining, it drains all remaining bytes. +func (f *Fuzzer) Drain(n int) { + f.fzgoSrc.Drain(n) +} + +// Data returns a []byte covering the remaining bytes from +// the original input []byte. Any bytes that are considered +// consumed should be indicated via Drain. +func (f *Fuzzer) Data() []byte { + return f.fzgoSrc.Data() } -// Override google/gofuzz fuzzing approach for strings, []byte, and numbers +// fillInterface reports if it has filled an interface pointer. +// +// Note: keep in sync with SupportedInterfaces (TODO: consider making dynamic). +// +// Rough counts of most common interfaces in public funcs/methods For stdlib +// (based on output from early version of fzgo that skipped all interfaces): +// $ grep -r 'skipping' | awk '{print $10}' | grep -v 'func' | sort | uniq -c | sort -rn | head -20 +// 146 io.Writer +// 122 io.Reader +// 75 reflect.Type +// 64 go/types.Type +// 55 interface{} +// 44 context.Context +// 41 []interface{} +// 22 go/constant.Value +// 17 net.Conn +// 17 math/rand.Source +// 16 net/http.ResponseWriter +// 16 net/http.Handler +// 16 image/color.Color +// 13 io.ReadWriteCloser +// 13 error +// 12 image/color.Palette +// 11 io.ReaderAt +// 9 crypto/cipher.Block +// 8 net.Listener +// 6 go/ast.Node +// +func (f *Fuzzer) fillInterface(obj interface{}) bool { + var b []byte + switch v := obj.(type) { + // Cases using bytes.NewReader + case *io.Reader: + f.Fill(&b) + *v = bytes.NewReader(b) + case *io.ReaderAt: + f.Fill(&b) + *v = bytes.NewReader(b) + case *io.WriterTo: + f.Fill(&b) + *v = bytes.NewReader(b) + case *io.Seeker: + f.Fill(&b) + *v = bytes.NewReader(b) + case *io.ByteScanner: + f.Fill(&b) + *v = bytes.NewReader(b) + case *io.RuneScanner: + f.Fill(&b) + *v = bytes.NewReader(b) + case *io.ReadSeeker: + f.Fill(&b) + *v = bytes.NewReader(b) + case *io.ByteReader: + f.Fill(&b) + *v = bytes.NewReader(b) + case *io.RuneReader: + f.Fill(&b) + *v = bytes.NewReader(b) + + // Cases using bytes.NewBuffer + case *io.ByteWriter: + f.Fill(&b) + *v = bytes.NewBuffer(b) + case *io.ReadWriter: // TODO: consider a bytes.Reader + ioutil.Discard? + f.Fill(&b) + *v = bytes.NewBuffer(b) + case *io.ReaderFrom: + f.Fill(&b) + *v = bytes.NewBuffer(b) + case *io.StringWriter: + f.Fill(&b) + *v = bytes.NewBuffer(b) + + // Cases using ioutil.NopCloser(bytes.NewReader) + case *io.Closer: + f.Fill(&b) + *v = ioutil.NopCloser(bytes.NewReader(b)) + case *io.ReadCloser: + f.Fill(&b) + *v = ioutil.NopCloser(bytes.NewReader(b)) + + // Cases using context.Background + case *context.Context: + *v = context.Background() + + // No match + default: + return false + } + return true +} -// randBytes is a custom fill function so that we have exact control over how +// fillByteSlice is a custom fill function so that we have exact control over how // strings and []byte are encoded. // -// randBytes generates a byte slice using the input []byte stream. +// fillByteSlice generates a byte slice using the input []byte stream. // []byte are deserialized as length encoded, where a leading byte // encodes the length in range [0-255], but the exact interpretation is a little subtle. // There is surely room for improvement here, but this current approach is the result of some @@ -177,7 +252,7 @@ func (f *Fuzzer) Fill(obj interface{}) { // * that non-zero byte is the actual length used, unless that non-zero byte // is 0xFF, in which case that signals a zero-length string/[]byte, and // * the length value used must be able to draw enough real random bytes from the input []byte. -func randBytes(ptr *[]byte, c gofuzz.Continue, fzgoSrc *randSource) { +func (f *Fuzzer) fillByteSlice(ptr *[]byte) { verbose := false // TODO: probably remove eventually. if verbose { fmt.Println("randBytes verbose:", verbose) @@ -191,18 +266,17 @@ func randBytes(ptr *[]byte, c gofuzz.Continue, fzgoSrc *randSource) { // mainly in order to better work with go-fuzz sonar. // see long comment above. for { - if fzgoSrc.Remaining() == 0 { + if f.Remaining() == 0 { if verbose { fmt.Println("ran out of bytes, 0 remaining") } // return nil slice (which will be empty string for string) *ptr = nil return - } // draw a size in [0, 255] from our input byte[] stream - sizeField := int(fzgoSrc.Byte()) + sizeField := int(f.fzgoSrc.Byte()) if verbose { fmt.Println("sizeField:", sizeField) } @@ -210,14 +284,14 @@ func randBytes(ptr *[]byte, c gofuzz.Continue, fzgoSrc *randSource) { // If we don't have enough data, we want to // *not* use the size field or the data after sizeField, // in order to work better with sonar. - if sizeField > fzgoSrc.Remaining() { + if sizeField > f.Remaining() { if verbose { fmt.Printf("%d bytes requested via size field, %d remaining, drain rest\n", - sizeField, fzgoSrc.Remaining()) + sizeField, f.Remaining()) } // return nil slice (which will be empty string for string). // however, before we return, we consume all of our remaining bytes. - fzgoSrc.Drain() + f.Drain(f.Remaining()) *ptr = nil return @@ -244,23 +318,23 @@ func randBytes(ptr *[]byte, c gofuzz.Continue, fzgoSrc *randSource) { bs = make([]byte, size) for i := range bs { - bs[i] = fzgoSrc.Byte() + bs[i] = f.fzgoSrc.Byte() } *ptr = bs } -// randString is a custom fill function so that we have exact control over how +// fillString is a custom fill function so that we have exact control over how // strings are encoded. It is a thin wrapper over randBytes. -func randString(s *string, c gofuzz.Continue, fzgoSrc *randSource) { +func (f *Fuzzer) fillString(s *string) { var bs []byte - randBytes(&bs, c, fzgoSrc) + f.fillByteSlice(&bs) *s = string(bs) } // TODO: this might be temporary. Here we handle slices of strings as a preview of -// improvements we might get by dropping google/gofuzz for walking some of the data structures. -func randStringSlice(s *[]string, c gofuzz.Continue, fzgoSrc *randSource) { - size, ok := calcSize(fzgoSrc) +// some possible performance improvements. +func (f *Fuzzer) fillStringSlice(s *[]string) { + size, ok := f.calcSize(f.fzgoSrc) if !ok { *s = nil return @@ -268,14 +342,14 @@ func randStringSlice(s *[]string, c gofuzz.Continue, fzgoSrc *randSource) { ss := make([]string, size) for i := range ss { var str string - randString(&str, c, fzgoSrc) + f.fillString(&str) ss[i] = str } *s = ss } // TODO: temporarily extracted this from randBytes. Decide to drop vs. keep/unify. -func calcSize(fzgoSrc *randSource) (size int, ok bool) { +func (f *Fuzzer) calcSize(fzgoSrc *randSource) (size int, ok bool) { verbose := false // TODO: probably remove eventually. // try to find a size field. @@ -283,7 +357,7 @@ func calcSize(fzgoSrc *randSource) (size int, ok bool) { // mainly in order to better work with go-fuzz sonar. // see long comment above. for { - if fzgoSrc.Remaining() == 0 { + if f.Remaining() == 0 { if verbose { fmt.Println("ran out of bytes, 0 remaining") } @@ -293,7 +367,7 @@ func calcSize(fzgoSrc *randSource) (size int, ok bool) { } // draw a size in [0, 255] from our input byte[] stream - sizeField := int(fzgoSrc.Byte()) + sizeField := int(f.fzgoSrc.Byte()) if verbose { fmt.Println("sizeField:", sizeField) } @@ -301,14 +375,14 @@ func calcSize(fzgoSrc *randSource) (size int, ok bool) { // If we don't have enough data, we want to // *not* use the size field or the data after sizeField, // in order to work better with sonar. - if sizeField > fzgoSrc.Remaining() { + if sizeField > f.fzgoSrc.Remaining() { if verbose { fmt.Printf("%d bytes requested via size field, %d remaining, drain rest\n", sizeField, fzgoSrc.Remaining()) } // return nil slice (which will be empty string for string). // however, before we return, we consume all of our remaining bytes. - fzgoSrc.Drain() + fzgoSrc.Drain(fzgoSrc.Remaining()) return 0, false } @@ -334,81 +408,328 @@ func calcSize(fzgoSrc *randSource) (size int, ok bool) { return size, true } -// A set of custom numeric value filling funcs follows. -// These are currently simple implementations that only use gofuzz.Continue -// as a source for data, which means obtaining 64-bits of the input stream -// at a time. For sizes < 64 bits, this could be tighted up to waste less of the input stream -// by getting access to fzgo/randparam.randSource. -// -// Once the end of the input []byte is reached, zeros are drawn, including -// if in the middle of obtaining bytes for a >1 bye number. -// Tt is probably ok to draw zeros past the end -// for numbers because we use a little endian interpretation -// for numbers (which means if we find byte 0x1 then that's the end -// and we draw zeros for say a uint32, the result is 1; sonar -// seems to guess the length of numeric values, so it likely -// works end to end even if we draw zeros. -// TODO: The next bytes appended (via some mutation) after a number can change -// the result (e.g., if a 0x2 is appended in example above, result is no longer 1), -// so maybe better to also not draw zeros for numeric values? +// =================== TEMP =================== + +// TODO: delete old implementation +// // bytesToConsume calculates the bytes that should be +// // used for a given numeric reflect.Value, and panics otherwise. +// // reflect.Int always uses 8 bytes for consistency across platforms. +// func kindNumericSize(k reflect.Kind) int { +// // recall, byte and rune are type aliases, and hence are convered. +// switch k { +// case reflect.Int, reflect.Int64, reflect.Uint64, reflect.Float64: +// return 8 +// case reflect.Int32, reflect.Uint32, reflect.Float32: +// return 4 +// case reflect.Int16, reflect.Uint16: +// return 2 +// case reflect.Int8, reflect.Uint8: +// return 1 +// default: +// panic(fmt.Sprintf("fzgen: kindBytes: unexpected kind %v", k)) +// } +// } + +// TODO: delete old implementations +// func (f *Fuzzer) fillNumeric(v reflect.Value) { +// bits := f.numericDraw(v.Kind()) + +// // recall, byte and rune are type aliases, and hence are convered. +// switch v.Kind() { +// case reflect.Int, reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8: +// v.SetInt(int64(bits)) +// case reflect.Uint, reflect.Uint64, reflect.Uint32, reflect.Uint16, reflect.Uint8: +// v.SetUint(bits) +// case reflect.Float64: +// v.SetFloat(math.Float64frombits(bits)) +// case reflect.Float32: +// v.SetFloat(float64(math.Float32frombits(uint32(bits)))) +// case reflect.Complex128, reflect.Complex64: +// // TODO: handle complex? +// panic("fzgen: fillNumeric: complex not yet implemented") +// default: +// panic(fmt.Sprintf("fzgen: fillNumeric: unexpected kind %v for value %v of type %v", v.Kind(), v, v.Type())) +// } +// } + +// TODO: delete old implementation +// func (f *Fuzzer) fillInt64(v reflect.Value) { +// consume := kindNumericSize(v.Kind()) +// if f.Remaining() < consume { +// v.SetInt(0) +// return +// } +// var i int64 +// b := f.Data()[:consume] +// f.Drain(consume) +// buf := bytes.NewReader(b) +// binary.Read(buf, binary.LittleEndian, &i) +// // i := int64(binary.LittleEndian.Uint64(b)) +// v.SetInt(i) +// } -func randInt(val *int, c gofuzz.Continue) { - *val = int(c.Rand.Uint64()) +func (f *Fuzzer) Fill(obj interface{}) { + f.Fill2(obj) } -func randInt8(val *int8, c gofuzz.Continue) { - *val = int8(c.Rand.Uint64()) +func (f *Fuzzer) Fill2(obj interface{}) { + v := reflect.ValueOf(obj) + if v.Kind() != reflect.Ptr { + panic("fzgen: Fill requires pointers") + } + // indirect through pointer, and rescursively fill + v = v.Elem() + f.fill(v, 0, fillOpts{}) } -func randInt16(val *int16, c gofuzz.Continue) { - *val = int16(c.Rand.Uint64()) +type fillOpts struct { + panicOnUnsupported bool } -func randInt32(val *int32, c gofuzz.Continue) { - *val = int32(c.Rand.Uint64()) -} +func (f *Fuzzer) fill(v reflect.Value, depth int, opts fillOpts) { + depth++ + if depth > 10 { + return + } -func randInt64(val *int64, c gofuzz.Continue) { - *val = int64(c.Rand.Uint64()) + switch v.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + // recall, rune is type alias of int32. + bits := f.numericDraw(v.Kind()) + v.SetInt(int64(bits)) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + // recall, byte is type alias of uint8. + bits := f.numericDraw(v.Kind()) + v.SetUint(bits) + case reflect.Float32: + bits := f.numericDraw(v.Kind()) + v.SetFloat(float64(math.Float32frombits(uint32(bits)))) + case reflect.Float64: + bits := f.numericDraw(v.Kind()) + v.SetFloat(math.Float64frombits(bits)) + case reflect.Complex64, reflect.Complex128: + var a, b float64 + f.Fill(&a) + f.Fill(&b) + v.SetComplex(complex(a, b)) + case reflect.String: + var s string + f.fillString(&s) + v.SetString(s) + case reflect.Bool: + var b byte + f.Fill(&b) + if b < 128 { + v.SetBool(false) + } else { + v.SetBool(true) + } + case reflect.Array: + if v.Type().Elem().String() == "uint8" { + // TODO: does this help? This is not consistent with other types, but we don't know size of elements for most other + // types (e.g., could be array of structs that have strings). + // At least for now, make it behave like older byte array fill, which only filled if there was enough + // remaining in the input data []byte. + if f.Remaining() < v.Len() { + break + } + } + for i := 0; i < v.Len(); i++ { + f.fill(v.Index(i), depth, opts) + } + case reflect.Slice: + if v.Type().Elem().String() == "uint8" { + var b []byte + f.fillByteSlice(&b) + v.Set(reflect.MakeSlice(v.Type(), len(b), len(b))) + for i := 0; i < v.Len(); i++ { + v.Index(i).SetUint(uint64(b[i])) + } + } else { + // TODO: favor smaller slice sizes? + // TODO: make slice size for non-byte slices more controllable via config. max is 10 for now. + var size byte + f.Fill(&size) + size %= 10 + v.Set(reflect.MakeSlice(v.Type(), int(size), int(size))) + for i := 0; i < v.Len(); i++ { + f.fill(v.Index(i), depth, opts) + } + } + case reflect.Map: + // TODO: similar to slice - favor smaller, more configurable + var size byte + f.Fill(&size) + size %= 10 + v.Set(reflect.MakeMapWithSize(v.Type(), int(size))) + for i := 0; i < int(size); i++ { + key := reflect.New(v.Type().Key()).Elem() + value := reflect.New(v.Type().Elem()).Elem() + f.fill(key, depth, opts) + f.fill(value, depth, opts) + v.SetMapIndex(key, value) + } + case reflect.Struct: + for i := 0; i < v.NumField(); i++ { + if v.Field(i).CanSet() { + // TODO: could consider option for unexported fields + f.fill(v.Field(i), depth, opts) + } + } + case reflect.Interface: + // get back the &interface{}. + iface := v.Addr().Interface() + // see if we can fill it. + success := f.fillInterface(iface) + if !success && opts.panicOnUnsupported { + panic(fmt.Sprintf("fzgen: fill: unsupported interface kind %v for value %v of type %v", v.Kind(), v, v.Type())) + } + case reflect.Ptr: + // create a zero value elem, then recursively fill that + v.Set(reflect.New(v.Type().Elem())) + f.fill(v.Elem(), depth, opts) + case reflect.Uintptr, reflect.Chan, reflect.Func, reflect.UnsafePointer: + if opts.panicOnUnsupported { + panic(fmt.Sprintf("fzgen: fill: unsupported kind %v for value %v of type %v", v.Kind(), v, v.Type())) + } + case reflect.Invalid: + panic("fzgen: fill: reflect.Invalid object") + default: + panic(fmt.Sprintf("fzgen: fill: unexpected kind %v for value %v of type %v", v.Kind(), v, v.Type())) + } } -func randUint(val *uint, c gofuzz.Continue) { - *val = uint(c.Rand.Uint64()) +// numericDraw calculates the bytes that should be +// used for a given numeric reflect.Value. If there are not enough bytes +// remaining in our data []byte, returns 0. Otherwise, returns +// the bits corresponding to the proper size. +// reflect.Int always uses 8 bytes for consistency across platforms. +// This panics if not a numeric kind, or if called for a complex kind. +// For complex kinds, instead draw two floats. +func (f *Fuzzer) numericDraw(k reflect.Kind) (bits uint64) { + switch k { + case reflect.Int, reflect.Int64, reflect.Uint64, reflect.Float64: + // reflect.Int always uses 8 bytes for consistency across platforms. + if f.Remaining() < 8 { + return 0 + } + bits = uint64(f.fzgoSrc.Byte()) | + uint64(f.fzgoSrc.Byte())<<8 | + uint64(f.fzgoSrc.Byte())<<16 | + uint64(f.fzgoSrc.Byte())<<24 | + uint64(f.fzgoSrc.Byte())<<32 | + uint64(f.fzgoSrc.Byte())<<40 | + uint64(f.fzgoSrc.Byte())<<48 | + uint64(f.fzgoSrc.Byte())<<56 + case reflect.Int32, reflect.Uint32, reflect.Float32: + if f.Remaining() < 4 { + return 0 + } + bits = uint64(f.fzgoSrc.Byte()) | + uint64(f.fzgoSrc.Byte())<<8 | + uint64(f.fzgoSrc.Byte())<<16 | + uint64(f.fzgoSrc.Byte())<<24 + case reflect.Int16, reflect.Uint16: + if f.Remaining() < 2 { + return 0 + } + bits = uint64(f.fzgoSrc.Byte()) | + uint64(f.fzgoSrc.Byte())<<8 + case reflect.Int8, reflect.Uint8: + if f.Remaining() < 1 { + return 0 + } + bits = uint64(f.fzgoSrc.Byte()) + default: + panic(fmt.Sprintf("fzgen: numericDraw: unexpected kind %v", k)) + } + return bits } -func randUint8(val *uint8, c gofuzz.Continue) { - *val = uint8(c.Rand.Uint64()) -} +// ---- TODO: TEMP compound types ---- -func randUint16(val *uint16, c gofuzz.Continue) { - *val = uint16(c.Rand.Uint64()) -} +func (f *Fuzzer) randByteArray4(val *[4]byte) { + if f.fzgoSrc.Remaining() < len(val) { + for _, i := range val { + val[i] = 0 + return + } + } -func randUint32(val *uint32, c gofuzz.Continue) { - *val = uint32(c.Rand.Uint64()) + for i := 0; i < len(val); i++ { + val[i] = f.fzgoSrc.Byte() + } } -func randUint64(val *uint64, c gofuzz.Continue) { - *val = uint64(c.Rand.Uint64()) -} +func (f *Fuzzer) randByteArray8(val *[8]byte) { + if f.fzgoSrc.Remaining() < len(val) { + for _, i := range val { + val[i] = 0 + return + } + } -func randFloat32(val *float32, c gofuzz.Continue) { - *val = float32(c.Rand.Uint64()) + for i := 0; i < len(val); i++ { + val[i] = f.fzgoSrc.Byte() + } } -func randFloat64(val *float64, c gofuzz.Continue) { - *val = float64(c.Rand.Uint64()) +func (f *Fuzzer) randByteArray16(val *[16]byte) { + if f.fzgoSrc.Remaining() < len(val) { + for _, i := range val { + val[i] = 0 + return + } + } + + for i := 0; i < len(val); i++ { + val[i] = f.fzgoSrc.Byte() + } } -func randByte(val *byte, c gofuzz.Continue) { - *val = byte(c.Rand.Uint64()) +func (f *Fuzzer) randByteArray20(val *[20]byte) { + if f.fzgoSrc.Remaining() < len(val) { + for _, i := range val { + val[i] = 0 + return + } + } + + for i := 0; i < len(val); i++ { + val[i] = f.fzgoSrc.Byte() + } } -func randRune(val *rune, c gofuzz.Continue) { - *val = rune(c.Rand.Uint64()) +func (f *Fuzzer) randByteArray32(val *[32]byte) { + if f.fzgoSrc.Remaining() < len(val) { + for _, i := range val { + val[i] = 0 + return + } + } + + for i := 0; i < len(val); i++ { + val[i] = f.fzgoSrc.Byte() + } } -// Note: complex64, complex128, uintptr are not supported by google/gofuzz, I think. -// TODO: Interfaces are also not currently supported by google/gofuzz, or at least not -// easily as far as I am aware. That said, currently have most of the pieces elsewhere -// for us to handle common interfaces like io.Writer, io.Reader, etc. +// TODO: delete older comments here. +// +// ---- Basic types ---- +// A set of custom numeric value filling funcs follows. +// These are currently simple implementations. When they used gofuzz.Continue +// as a source for data, it meant obtaining 64-bits of the input stream +// at a time. For sizes < 64 bits, this could be tighted up to waste less of the input stream +// by getting access to fzgo/randparam.randSource. +// +// Once the end of the input []byte is reached, zeros are drawn, including +// if in the middle of obtaining bytes for a >1 bye number. +// Tt is probably ok to draw zeros past the end +// for numbers because we use a little endian interpretation +// for numbers (which means if we find byte 0x1 then that's the end +// and we draw zeros for say a uint32, the result is 1; sonar +// seems to guess the length of numeric values, so it likely +// works end to end even if we draw zeros. +// TODO: The next bytes appended (via some mutation) after a number can change +// the result (e.g., if a 0x2 is appended in example above, result is no longer 1), +// so maybe better to also not draw zeros for numeric values? diff --git a/fuzzer/internal/randparam/randparam_test.go b/fuzzer/internal/randparam/randparam_test.go index 305bc09..e95d127 100644 --- a/fuzzer/internal/randparam/randparam_test.go +++ b/fuzzer/internal/randparam/randparam_test.go @@ -2,22 +2,23 @@ package randparam import ( "encoding/binary" + "io" + "io/ioutil" "testing" "github.com/google/go-cmp/cmp" ) -func TestFuzzingParams(t *testing.T) { - +func TestFuzzingBasicTypes(t *testing.T) { t.Run("string - 8 byte length, 8 bytes of string input", func(t *testing.T) { input := append([]byte{0x0, 0x8}, []byte("12345678")...) want := "12345678" fuzzer := NewFuzzer(input) var got string - fuzzer.Fuzz(&got) + fuzzer.Fill2(&got) if diff := cmp.Diff(want, got); diff != "" { - t.Errorf("fuzzer.Fuzz() mismatch (-want +got):\n%s", diff) + t.Errorf("fuzzer.Fill() mismatch (-want +got):\n%s", diff) } }) @@ -27,9 +28,9 @@ func TestFuzzingParams(t *testing.T) { fuzzer := NewFuzzer(input) var got string - fuzzer.Fuzz(&got) + fuzzer.Fill2(&got) if diff := cmp.Diff(want, got); diff != "" { - t.Errorf("fuzzer.Fuzz() mismatch (-want +got):\n%s", diff) + t.Errorf("fuzzer.Fill() mismatch (-want +got):\n%s", diff) } }) @@ -39,34 +40,34 @@ func TestFuzzingParams(t *testing.T) { fuzzer := NewFuzzer(input) var got string - fuzzer.Fuzz(&got) + fuzzer.Fill2(&got) if diff := cmp.Diff(want, got); diff != "" { - t.Errorf("fuzzer.Fuzz() mismatch (-want +got):\n%s", diff) + t.Errorf("fuzzer.Fill() mismatch (-want +got):\n%s", diff) } }) t.Run("string - 9 byte length, 2 bytes of string input", func(t *testing.T) { - input := append([]byte{0x9}, []byte("12")...) + input := append([]byte{0x0, 0x9}, []byte("12")...) want := "" fuzzer := NewFuzzer(input) var got string - fuzzer.Fuzz(&got) + fuzzer.Fill2(&got) if diff := cmp.Diff(want, got); diff != "" { - t.Errorf("fuzzer.Fuzz() mismatch (-want +got):\n%s", diff) + t.Errorf("fuzzer.Fill() mismatch (-want +got):\n%s", diff) } }) t.Run("string - zero length string explicitly encoded", func(t *testing.T) { longByteSlice := make([]byte, 1000) - input := append([]byte{0xFF}, longByteSlice...) + input := append([]byte{0x0, 0xFF}, longByteSlice...) want := "" fuzzer := NewFuzzer(input) var got string - fuzzer.Fuzz(&got) + fuzzer.Fill2(&got) if diff := cmp.Diff(want, got); diff != "" { - t.Errorf("fuzzer.Fuzz() mismatch (-want +got):\n%s", diff) + t.Errorf("fuzzer.Fill() mismatch (-want +got):\n%s", diff) } }) @@ -76,9 +77,9 @@ func TestFuzzingParams(t *testing.T) { fuzzer := NewFuzzer(input) var got string - fuzzer.Fuzz(&got) + fuzzer.Fill2(&got) if diff := cmp.Diff(want, got); diff != "" { - t.Errorf("fuzzer.Fuzz() mismatch (-want +got):\n%s", diff) + t.Errorf("fuzzer.Fill() mismatch (-want +got):\n%s", diff) } }) @@ -89,14 +90,14 @@ func TestFuzzingParams(t *testing.T) { fuzzer := NewFuzzer(input) var got1, got2 string - fuzzer.Fuzz(&got1) - fuzzer.Fuzz(&got2) + fuzzer.Fill2(&got1) + fuzzer.Fill2(&got2) if diff := cmp.Diff(want1, got1); diff != "" { - t.Errorf("fuzzer.Fuzz() mismatch (-want1 +got1):\n%s", diff) + t.Errorf("fuzzer.Fill() mismatch (-want1 +got1):\n%s", diff) } if diff := cmp.Diff(want2, got2); diff != "" { - t.Errorf("fuzzer.Fuzz() mismatch (-want2 +got2):\n%s", diff) + t.Errorf("fuzzer.Fill() mismatch (-want2 +got2):\n%s", diff) } }) @@ -107,14 +108,14 @@ func TestFuzzingParams(t *testing.T) { fuzzer := NewFuzzer(input) var got1, got2 string - fuzzer.Fuzz(&got1) - fuzzer.Fuzz(&got2) + fuzzer.Fill2(&got1) + fuzzer.Fill2(&got2) if diff := cmp.Diff(want1, got1); diff != "" { - t.Errorf("fuzzer.Fuzz() mismatch (-want1 +got1):\n%s", diff) + t.Errorf("fuzzer.Fill() mismatch (-want1 +got1):\n%s", diff) } if diff := cmp.Diff(want2, got2); diff != "" { - t.Errorf("fuzzer.Fuzz() mismatch (-want2 +got2):\n%s", diff) + t.Errorf("fuzzer.Fill() mismatch (-want2 +got2):\n%s", diff) } }) @@ -124,9 +125,9 @@ func TestFuzzingParams(t *testing.T) { fuzzer := NewFuzzer(input) var got []byte - fuzzer.Fuzz(&got) + fuzzer.Fill2(&got) if diff := cmp.Diff(want, got); diff != "" { - t.Errorf("fuzzer.Fuzz() mismatch (-want +got):\n%s", diff) + t.Errorf("fuzzer.Fill() mismatch (-want +got):\n%s", diff) } }) @@ -136,9 +137,21 @@ func TestFuzzingParams(t *testing.T) { fuzzer := NewFuzzer(input) var got []byte - fuzzer.Fuzz(&got) + fuzzer.Fill2(&got) + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("fuzzer.Fill() mismatch (-want +got):\n%s", diff) + } + }) + + t.Run("bytes slice - 9 byte length, 2 bytes of string input", func(t *testing.T) { + input := append([]byte{0x0, 0x9}, []byte("12")...) + want := []byte{} + + fuzzer := NewFuzzer(input) + var got []byte + fuzzer.Fill2(&got) if diff := cmp.Diff(want, got); diff != "" { - t.Errorf("fuzzer.Fuzz() mismatch (-want +got):\n%s", diff) + t.Errorf("fuzzer.Fill() mismatch (-want +got):\n%s", diff) } }) @@ -150,21 +163,21 @@ func TestFuzzingParams(t *testing.T) { fuzzer := NewFuzzer(input) var got uint64 - fuzzer.Fuzz(&got) + fuzzer.Fill2(&got) if diff := cmp.Diff(want, got); diff != "" { - t.Errorf("fuzzer.Fuzz() mismatch (-want +got):\n%s", diff) + t.Errorf("fuzzer.Fill() mismatch (-want +got):\n%s", diff) } }) t.Run("uint64 - 4 bytes input", func(t *testing.T) { input := []byte{0x0, 0xef, 0xbe, 0xad, 0xde} - want := uint64(0xdeadbeef) + want := uint64(0x0) fuzzer := NewFuzzer(input) var got uint64 - fuzzer.Fuzz(&got) + fuzzer.Fill2(&got) if diff := cmp.Diff(want, got); diff != "" { - t.Errorf("fuzzer.Fuzz() mismatch (-want +got):\n%s", diff) + t.Errorf("fuzzer.Fill() mismatch (-want +got):\n%s", diff) } }) @@ -174,21 +187,157 @@ func TestFuzzingParams(t *testing.T) { fuzzer := NewFuzzer(input) var got int32 - fuzzer.Fuzz(&got) + fuzzer.Fill2(&got) if diff := cmp.Diff(want, got); diff != "" { - t.Errorf("fuzzer.Fuzz() mismatch (-want +got):\n%s", diff) + t.Errorf("fuzzer.Fill() mismatch (-want +got):\n%s", diff) } }) t.Run("int32 - 1 byte input", func(t *testing.T) { input := []byte{0x0, 0x42} - want := int32(0x42) + want := int32(0x0) fuzzer := NewFuzzer(input) var got int32 - fuzzer.Fuzz(&got) + fuzzer.Fill2(&got) + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("fuzzer.Fill() mismatch (-want +got):\n%s", diff) + } + }) +} + +func TestFuzzingInterfaces(t *testing.T) { + t.Run("io.Reader - 8 byte length, 8 bytes of string input", func(t *testing.T) { + input := append([]byte{0x0, 0x8}, []byte("12345678")...) + want := "12345678" + + fuzzer := NewFuzzer(input) + var r io.Reader + fuzzer.Fill2(&r) + b, err := ioutil.ReadAll(r) + if err != nil { + t.Errorf("fuzzer.Fill() returned err: %v", err) + } + got := string(b) + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("fuzzer.Fill() mismatch (-want +got):\n%s", diff) + } + }) + + t.Run("io.ReadWriter - 8 byte length, 8 bytes of string input", func(t *testing.T) { + input := append([]byte{0x0, 0x8}, []byte("12345678")...) + want := "12345678" + + fuzzer := NewFuzzer(input) + var rw io.ReadWriter + fuzzer.Fill2(&rw) + b, err := ioutil.ReadAll(rw) + if err != nil { + t.Errorf("fuzzer.Fill() returned err: %v", err) + } + got := string(b) + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("fuzzer.Fill() mismatch (-want +got):\n%s", diff) + } + }) + + t.Run("io.ReadCloser - 8 byte length, 8 bytes of string input", func(t *testing.T) { + input := append([]byte{0x0, 0x8}, []byte("12345678")...) + want := "12345678" + + fuzzer := NewFuzzer(input) + var rc io.ReadCloser + fuzzer.Fill2(&rc) + b, err := ioutil.ReadAll(rc) + if err != nil { + t.Errorf("fuzzer.Fill() returned err: %v", err) + } + got := string(b) + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("fuzzer.Fill() mismatch (-want +got):\n%s", diff) + } + }) +} + +func TestFuzzingStructs(t *testing.T) { + t.Run("uint64 in struct - 8 bytes input", func(t *testing.T) { + type Foo struct { + U *uint64 + } + input := append([]byte{0x0}, make([]byte, 8)...) + i := uint64(0xfeedfacedeadbeef) + binary.LittleEndian.PutUint64(input[1:], i) + + u := uint64(0xfeedfacedeadbeef) + want := Foo{&u} + + fuzzer := NewFuzzer(input) + var got Foo + fuzzer.Fill2(&got) + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("fuzzer.Fill() mismatch (-want +got):\n%s", diff) + } + }) + + t.Run("uint32 slice in struct - 8 bytes input", func(t *testing.T) { + input := append([]byte{0x0, 0x2}, make([]byte, 8)...) + i1 := uint32(0xfeedface) + i2 := uint32(0xdeadbeef) + binary.LittleEndian.PutUint32(input[2:], i1) + binary.LittleEndian.PutUint32(input[6:], i2) + + type Foo struct { + U []uint32 + } + want := Foo{[]uint32{0xfeedface, 0xdeadbeef}} + + fuzzer := NewFuzzer(input) + var got Foo + fuzzer.Fill2(&got) + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("fuzzer.Fill() mismatch (-want +got):\n%s", diff) + } + }) + + t.Run("map in struct - 8 bytes input", func(t *testing.T) { + input := append([]byte{0x0, 0x1}, make([]byte, 8)...) + i1 := uint32(0xfeedface) + i2 := uint32(0xdeadbeef) + binary.LittleEndian.PutUint32(input[2:], i1) + binary.LittleEndian.PutUint32(input[6:], i2) + + type Foo struct { + M map[uint32]uint32 + } + want := Foo{ + M: map[uint32]uint32{0xfeedface: 0xdeadbeef}, + } + + fuzzer := NewFuzzer(input) + var got Foo + fuzzer.Fill2(&got) + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("fuzzer.Fill() mismatch (-want +got):\n%s", diff) + } + }) + + t.Run("uint32 array in struct - 8 bytes input", func(t *testing.T) { + input := append([]byte{0x0}, make([]byte, 8)...) + i1 := uint32(0xfeedface) + i2 := uint32(0xdeadbeef) + binary.LittleEndian.PutUint32(input[1:], i1) + binary.LittleEndian.PutUint32(input[5:], i2) + + type Foo struct { + U [2]uint32 + } + want := Foo{[2]uint32{0xfeedface, 0xdeadbeef}} + + fuzzer := NewFuzzer(input) + var got Foo + fuzzer.Fill2(&got) if diff := cmp.Diff(want, got); diff != "" { - t.Errorf("fuzzer.Fuzz() mismatch (-want +got):\n%s", diff) + t.Errorf("fuzzer.Fill() mismatch (-want +got):\n%s", diff) } }) } diff --git a/fuzzer/marshall.go b/fuzzer/marshall.go new file mode 100644 index 0000000..9b0ddc9 --- /dev/null +++ b/fuzzer/marshall.go @@ -0,0 +1,89 @@ +package fuzzer + +import ( + "encoding/binary" + "io" + + "github.com/thepudds/fzgen/fuzzer/internal/plan" +) + +// unmarshalPlan consumes bytes to construct a Plan describing +// which calls we will fuzz, in what order, and how their +// arguments will be obtained (e.g., new value, re-used arg, re-used return val). +// It attempts to return plan.Calls that are fully covered by the input bytes +// in an effort to be friendlier to corpus evolution. +// If a call is included in the returned Plan, its argument sources +// are fully described by the bytes, though if an argument source in a plan.Call +// is a new argument, there might not be enough subsequent bytes to fully fill the argument +// value, which currently means it might be filled with the zero value for a basic type +// or have zero values for some elements if it is a composite type. +// This is hopefully reasonably friendly to the underlying fuzzing engine, +// including the coverage-guided evolution and tail trim minimization, which is currently +// the first minimization technique for both cmd/go and dvyukov/go-fuzz. +// +// It is the caller's responsibility to track how many bytes are consumed. +// E.g., caller could do: +// buf := bytes.NewReader(data) +// pl := unmarshalPlan(buf, ...) +// used := len(data) - buf.Len() +func unmarshalPlan(r io.Reader, steps []Step) plan.Plan { + pl := plan.Plan{} + + var callCountByte uint8 + var callCount int + err := binary.Read(r, binary.LittleEndian, &callCountByte) + if err != nil { + return pl + } + // Favor a few calls over, say, 2 or 8. + switch { + case callCountByte < 64: + callCount = 3 + case callCountByte < 128: + callCount = 4 + case callCountByte < 192: + callCount = 5 + default: + // TODO: Current max calls in our plan is 10. Probably make this configurable. + // Note that a loop means we might execute more than 10 calls total (e.g., loop of 256 with + // callCount of 10 would mean 2560 total calls executed). + callCount = int(callCountByte%10) + 1 + } + + // skip GoroutineOrdering + for i := 0; i < callCount; i++ { + // Try to read a new Call. + // If we can't fully populate call.StepIndex and the ArgSource slice with right count of args, + // we will not use this call (which is probably friendlier to mutator/evoluation than completing a partial + // using zeros or similar). + call := plan.Call{} + + // Read call.StepIndex. + err := binary.Read(r, binary.LittleEndian, &call.StepIndex) + // Recall, for binary.Read: + // err is io.EOF only if no bytes were read. + // If an io.EOF happens after reading some but not all the bytes, binary.Read returns io.ErrUnexpectedEOF. + if err != nil { + // We don't use a partially read Call. + break + } + + // Based on the StepIndex we just read, compute the actual index into the user's Step list. + s := int(call.StepIndex) % len(steps) + fv := mustFunc(steps[s].Func) + ft := fv.Type() + + argSources := make([]plan.ArgSource, ft.NumIn()) + err = binary.Read(r, binary.LittleEndian, &argSources) + if err != nil { + // We don't use a partially read Call, including if we haven't filled in + // all of the needed ArgSource for this step's signature. + break + } + // Success, add to the plan and continue. + // If we exactly finished the bytes, we break out of the loop next iteration with io.EOF. + call.ArgSource = append(call.ArgSource, argSources...) + pl.Calls = append(pl.Calls, call) + } + return pl +} diff --git a/fuzzer/marshall_test.go b/fuzzer/marshall_test.go new file mode 100644 index 0000000..38a8e15 --- /dev/null +++ b/fuzzer/marshall_test.go @@ -0,0 +1,153 @@ +package fuzzer + +import ( + "bytes" + "testing" + + "github.com/google/go-cmp/cmp" + raceexample "github.com/thepudds/fzgen/examples/inputs/race" + "github.com/thepudds/fzgen/fuzzer/internal/plan" +) + +func TestUnmarshalPlan(t *testing.T) { + tests := []struct { + name string + data []byte + wantConsumedBytes int + wantPlan plan.Plan + }{ + { + name: "read exactly", + // this attempts to read 2 calls + data: []byte{201, 0x1, 0x2, 0x6, 0x1, 0x7, 0x0, 0x2, 0x8}, + wantConsumedBytes: 9, + wantPlan: plan.Plan{ + GoroutineOrdering: 0, + Calls: []plan.Call{ + { + StepIndex: 1, // Store + ArgSource: []plan.ArgSource{ + { + SourceType: 2, // corresponds to new arg + ArgIndex: 6, + }, + { + SourceType: 1, // corresponds to re-use return + ArgIndex: 7, + }, + }, + }, + { + StepIndex: 0, // Load + ArgSource: []plan.ArgSource{{ + SourceType: 2, // corresponds to new arg + ArgIndex: 8, + }}, + }, + }, + }, + }, + { + name: "read with 1 extra byte left over", + // this attempts to read 2 calls, with a final byte left over + data: []byte{201, 0x1, 0x2, 0x6, 0x1, 0x7, 0x0, 0x2, 0x8, 0x1}, + wantConsumedBytes: 9, + wantPlan: plan.Plan{ + GoroutineOrdering: 0, + Calls: []plan.Call{ + { + StepIndex: 1, // Store + ArgSource: []plan.ArgSource{ + { + SourceType: 2, // corresponds to new arg + ArgIndex: 6, + }, + { + SourceType: 1, // corresponds to re-use return + ArgIndex: 7, + }, + }, + }, + { + StepIndex: 0, // Load + ArgSource: []plan.ArgSource{{ + SourceType: 2, // corresponds to new arg + ArgIndex: 8, + }}, + }, + }, + }, + }, + { + name: "short read with one complete call found", + // this attempts to read 2 calls, but only finds one complete call + data: []byte{201, 0x1, 0x2, 0x6, 0x1, 0x7, 0x0, 0x2}, + wantConsumedBytes: 8, + wantPlan: plan.Plan{ + GoroutineOrdering: 0, + Calls: []plan.Call{ + { + StepIndex: 1, // Store + ArgSource: []plan.ArgSource{ + { + SourceType: 2, // corresponds to new arg + ArgIndex: 6, + }, + { + SourceType: 1, // corresponds to re-use return + ArgIndex: 7, + }, + }, + }, + }, + }, + }, + { + name: "short read with no complete call found", + // this attempts to read 2 calls, but does not find any complete call + data: []byte{201, 0x1, 0x2}, + wantConsumedBytes: 3, + wantPlan: plan.Plan{ + GoroutineOrdering: 0, + Calls: nil, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // target and steps here taken from: + // fzgen -chain -pkg=github.com/thepudds/fzgen/examples/inputs/race + target := raceexample.NewMySafeMap() + + steps := []Step{ + { + Name: "Fuzz_MySafeMap_Load", + Func: func(key [16]byte) *raceexample.Request { + return target.Load(key) + }, + }, + { + Name: "Fuzz_MySafeMap_Store", + Func: func(key [16]byte, req *raceexample.Request) { + if req == nil { + return + } + target.Store(key, req) + }, + }, + } + + buf := bytes.NewBuffer(tt.data) + gotPlan := unmarshalPlan(buf, steps) + if diff := cmp.Diff(tt.wantPlan, gotPlan); diff != "" { + t.Errorf("unmarshalPlan() mismatch (-want +got):\n%s", diff) + } + + gotConsumedBytes := len(tt.data) - buf.Len() + if tt.wantConsumedBytes != gotConsumedBytes { + t.Errorf("unmarshalPlan() expected consumed bytes: %v, got: %v", tt.wantConsumedBytes, gotConsumedBytes) + } + }) + } +} diff --git a/gen/fzgen.go b/gen/fzgen.go index e4b445d..ed92767 100644 --- a/gen/fzgen.go +++ b/gen/fzgen.go @@ -1,7 +1,6 @@ -// genfuzzfuncs is an early stage prototype for automatically generating -// fuzz functions, similar in spirit to cweill/gotests. +// fzgen automatically generates fuzz functions, similar in spirit to cweill/gotests. // -// For example, if you run genfuzzfuncs against github.com/google/uuid, it generates +// For example, if you run fzgen against github.com/google/uuid, it generates // a uuid_fuzz.go file with 30 or so functions like: // // func Fuzz_UUID_MarshalText(u1 uuid.UUID) { @@ -19,7 +18,7 @@ // using the rich signature fuzzing support in thepudds/fzgo, such as: // // fzgo test -fuzz=. ./... -package main +package fzgen import ( "flag" @@ -28,58 +27,74 @@ import ( "os" ) -// one way to test this on the stdlib: -// for x in $(go list std | egrep -v 'internal|runtime|unsafe|vendor|image/color/palette'); do start=$(pwd); echo $x; mkdir -p $x; cd $x ; genfuzzfuncs -pkg=$x -o=fuzz.go && go build || echo "--- FAILED $x ---"; cd $start; done +// one way to test this is against the stdlib (here, this just tests that fzgen generates and the result compiles successfully): +// for x in $(go list std | egrep -v 'internal|runtime|unsafe|vendor|image/color/palette'); do start=$(pwd); echo $x; mkdir -p $x; cd $x ; fzgen $x && gotip test . || echo "--- FAILED $x ---"; cd $start; done &> out.txt // current stats: // grep -r '^func Fuzz' | wc -l // 2775 // grep -r 'skipping' | wc -l // 603 -// to also test that fzgo can build the resulting rich signatures -// mkdir ~/go/src/fzgo.test -// cd ~/go/src/fzgo.test -// for x in $(go list std | egrep -v 'internal|runtime|unsafe|vendor|image/color/palette'); do start=$(pwd); echo $x; mkdir -p $x; cd $x ; genfuzzfuncs -pkg=$x -o=fuzz.go && go build && fzgo test -fuzz=. -c || echo "--- FAILED $x ---"; cd $start; done // Usage contains short usage information. var Usage = ` -usage: - genfuzzfuncs [-pkg=pkgPattern] [-func=regexp] [-unexported] [-qualifyall] [-ctors=false] [-ctorspattern=regexp] +Usage: + fzgen [-chain] [-parallel] [-ctor=] [-unexported] [pkg] -Running genfuzzfuncs without any arguments targets the package in the current directory. +Running fzgen without any arguments targets the package in the current directory. -genfuzzfuncs outputs a set of wrapper functions for all functions -matching the func regex in the target package, which defaults to current directory. -Any function that already starts with 'Fuzz' is skipped, and so are any functions -with zero parameters or that have interface parameters. +fzgen outputs a set of wrapper fuzz functions for all functions matching +the -func regexp, which defaults to matching all functions. The target package +defaults to the current directory. The target package should be in the current +module or listed as dependency of the current module (e.g., via 'go get example.com/foo'). The resulting wrapper functions will all start with 'Fuzz', and are candidates -for use with fuzzing via thepudds/fzgo. +for use with fuzzing via Go 1.18 cmd/go (e.g., 'gotip test -fuzz=.'). -genfuzzfuncs does not attempt to populate imports, but 'goimports -w ' -should usaully be able to do so. +A package pattern is allowed, but should only match one package. + +Test functions and any function that already starts with 'Fuzz' are skipped, +as are functions that have unsupported parameters such as a channel. ` -func main() { +func FzgenMain() int { // handle flags flag.Usage = func() { fmt.Fprint(os.Stderr, Usage) flag.PrintDefaults() } - pkgFlag := flag.String("pkg", ".", "package pattern, defaults to current package") - funcFlag := flag.String("func", ".", "function regex, defaults to matching all") + + // Most commonly used: + chainFlag := flag.Bool("chain", false, "loop over the methods of an object, which requires finding a suitable constructor in the same package and which is controllable via the -ctor flag.") + parallelFlag := flag.Bool("parallel", false, "indicates an emitted chain can be run in parallel. requires -chain") + outFileFlag := flag.String("o", "autofuzz_test.go", "output file name. defaults to autofuzz_test.go or autofuzzchain_test.go") + constructorPatternFlag := flag.String("ctor", "^New", "regexp to use if searching for constructors to automatically use.") + + // Less commonly used: + funcPatternFlag := flag.String("func", ".", "function regex, defaults to matching all candidate functions") unexportedFlag := flag.Bool("unexported", false, "emit wrappers for unexported functions in addition to exported functions") qualifyAllFlag := flag.Bool("qualifyall", true, "all identifiers are qualified with package, including identifiers from the target package. "+ "If the package is '.' or not set, this defaults to false. Else, it defaults to true.") - constructorFlag := flag.Bool("ctors", true, "automatically insert constructors when wrapping a method call "+ + constructorFlag := flag.Bool("ctorinject", true, "automatically insert constructors when wrapping a method call "+ "if a suitable constructor can be found in the same package.") - constructorPatternFlag := flag.String("ctorspattern", "^New", "regexp to use if searching for constructors to automatically use.") - outFileFlag := flag.String("o", "autogeneratedfuzz.go", "output file name.") flag.Parse() - if len(flag.Args()) != 0 { + + var pkgPattern string + switch { + case flag.NArg() > 1: + fmt.Println("fzgen: only one package pattern argument is allowed, and it must match only one package") flag.Usage() - os.Exit(2) + return 2 + case flag.NArg() == 1: + pkgPattern = flag.Arg(0) + default: + pkgPattern = "." + } + + if *parallelFlag && !*chainFlag { + fmt.Fprintf(os.Stderr, "fzgen: error: -parallel flag requires -chain") + return 2 } // search for functions in the requested package that @@ -89,7 +104,7 @@ func main() { options |= flagRequireExported } var qualifyAll bool - if *pkgFlag == "." { + if pkgPattern == "." { qualifyAll = false } else { // qualifyAllFlag defaults to true, which is what we want @@ -97,8 +112,9 @@ func main() { qualifyAll = *qualifyAllFlag } - functions, err := FindFunc(*pkgFlag, *funcFlag, nil, options) + functions, err := findFunc(pkgPattern, *funcPatternFlag, nil, options) if err != nil { + // TODO: probably return 1 instead in order to work better with testscripts? fail(err) } @@ -106,20 +122,32 @@ func main() { qualifyAll: qualifyAll, insertConstructors: *constructorFlag, constructorPattern: *constructorPatternFlag, + parallel: *parallelFlag, } - out, err := createWrappers(*pkgFlag, functions, wrapperOpts) + var out []byte + if !*chainFlag { + out, err = emitIndependentWrappers(pkgPattern, functions, wrapperOpts) + } else { + out, err = emitChainWrappers(pkgPattern, functions, wrapperOpts) + } if err != nil { fail(err) } - err = ioutil.WriteFile(*outFileFlag, out, 0644) + + if *chainFlag && *outFileFlag == "autofuzz_test.go" { + *outFileFlag = "autofuzzchain_test.go" + } + err = ioutil.WriteFile(*outFileFlag, out, 0o644) if err != nil { fail(err) } + fmt.Println("fzgen: created", *outFileFlag) + return 0 } func fail(err error) { - fmt.Fprintf(os.Stderr, "genfuzzfuncs: error: %v\n", err) + fmt.Fprintf(os.Stderr, "fzgen: error: %v\n", err) os.Exit(1) } diff --git a/gen/genfuncs.go b/gen/genfuncs.go index 00d1d19..1d9ee0a 100644 --- a/gen/genfuncs.go +++ b/gen/genfuncs.go @@ -1,18 +1,17 @@ -package main +package fzgen import ( "bytes" + "errors" "fmt" "go/types" "io" "os" - "os/exec" - "regexp" + "path/filepath" "sort" - "strings" - "github.com/thepudds/fzgo/fuzz" - "golang.org/x/tools/go/packages" + "github.com/thepudds/fzgen/fuzzer" + "github.com/thepudds/fzgen/gen/internal/mod" "golang.org/x/tools/imports" ) @@ -20,26 +19,31 @@ type wrapperOptions struct { qualifyAll bool // qualify all variables with package name insertConstructors bool // attempt to insert suitable constructors when wrapping methods constructorPattern string // regexp for searching for candidate constructors + parallel bool // set the Parallel flag in the emitted code, which allows steps of a chain to run in parallel } -// createWrappers emits fuzzing wrappers where possible for the list of functions passed in. +type emitFunc func(format string, args ...interface{}) + +var errSilentSkip = errors.New("silently skipping wrapper generation") + +// emitIndependentWrappers emits fuzzing wrappers where possible for the list of functions passed in. // It might skip a function if it has no input parameters, or if it has a non-fuzzable parameter // type such as interface{}. // See package comment in main.go for more details. -func createWrappers(pkgPattern string, functions []fuzz.Func, options wrapperOptions) ([]byte, error) { +func emitIndependentWrappers(pkgPattern string, functions []mod.Func, options wrapperOptions) ([]byte, error) { if len(functions) == 0 { return nil, fmt.Errorf("no matching functions found") } // start by hunting for possible constructors in the same package if requested. - var possibleConstructors []fuzz.Func + var possibleConstructors []mod.Func if options.insertConstructors { // We default to the pattern ^New, but allow user-specified patterns. // We don't check the err here because it can be expected to not find anything if there // are no functions that start with New (and this is our second call to FindFunc, so // other problems should have been reported earlier). // TODO: consider related tweak to error reporting in FindFunc? - possibleConstructors, _ = FindFunc(pkgPattern, options.constructorPattern, nil, + possibleConstructors, _ = findFunc(pkgPattern, options.constructorPattern, nil, flagExcludeFuzzPrefix|flagAllowMultiFuzz|flagRequireExported) // put possibleConstructors into a semi-deterministic order. // TODO: for now, we'll prefer simpler constructors as approximated by length (so 'New' before 'NewSomething'). @@ -48,16 +52,25 @@ func createWrappers(pkgPattern string, functions []fuzz.Func, options wrapperOpt }) } - // emit the intro material + // prepare the output buf := new(bytes.Buffer) var w io.Writer = buf + emit := func(format string, args ...interface{}) { + fmt.Fprintf(w, format, args...) + } + + // emit the intro material var pkgSuffix string if options.qualifyAll { pkgSuffix = "fuzz // rename if needed" } - fmt.Fprintf(w, "package %s%s\n\n", functions[0].TypesFunc.Pkg().Name(), pkgSuffix) - fmt.Fprint(w, "// if needed, fill in imports or run 'goimports'\n") - fmt.Fprint(w, "import (\n)\n\n") + emit("package %s%s\n\n", functions[0].TypesFunc.Pkg().Name(), pkgSuffix) + emit("// if needed, fill in imports or run 'goimports'\n") + emit("import (\n") + emit("\t\"testing\"\n") + emit("\t\"%s\"\n", functions[0].PkgPath) + emit("\t\"github.com/thepudds/fzgen/fuzzer\"\n") + emit(")\n\n") // put our functions we want to wrap into a deterministic order sort.Slice(functions, func(i, j int) bool { @@ -67,29 +80,53 @@ func createWrappers(pkgPattern string, functions []fuzz.Func, options wrapperOpt // could strip '*' or sort another way, but probably ok, at least for now. return functions[i].TypesFunc.String() < functions[j].TypesFunc.String() }) + // loop over our the functions we are wrapping, emitting a wrapper where possible. for _, function := range functions { - err := createWrapper(w, function, possibleConstructors, options.qualifyAll) + err := emitIndependentWrapper(emit, function, possibleConstructors, options.qualifyAll) + if errors.Is(err, errSilentSkip) { + continue + } if err != nil { return nil, fmt.Errorf("error processing %s: %v", function.FuncName, err) } } // fix up any needed imports. - out, err := imports.Process("autogeneratedfuzz.go", buf.Bytes(), nil) - if err != nil { + // TODO: perf: this seems slower than expected. Check what style of path should be used for filename? + // imports.Process has this comment: + // Note that filename's directory influences which imports can be chosen, + // so it is important that filename be accurate. + filename, err := filepath.Abs(("autofuzz_test.go")) + warn := func(err error) { fmt.Fprintln(os.Stderr, "genfuzzfuncs: warning: continuing after failing to automatically adjust imports:", err) + } + if err != nil { + warn(err) + return buf.Bytes(), nil + } + out, err := imports.Process(filename, buf.Bytes(), nil) + if err != nil { + warn(err) return buf.Bytes(), nil } return out, nil } -// createWrapper emits one fuzzing wrapper if possible. +// paramRepr contains string representations of inputParams to the wrapper function that we are +// creating. It includes params for the function under test, as well as in some cases +// args for a related constructor. +type paramRepr struct { + paramName string + typ string + v *types.Var +} + +// emitIndependentWrapper emits one fuzzing wrapper if possible. // It takes a list of possible constructors to insert into the wrapper body if the // constructor is suitable for creating the receiver of a wrapped method. // qualifyAll indicates if all variables should be qualified with their package. -func createWrapper(w io.Writer, function fuzz.Func, possibleConstructors []fuzz.Func, qualifyAll bool) error { - var err error +func emitIndependentWrapper(emit emitFunc, function mod.Func, possibleConstructors []mod.Func, qualifyAll bool) error { f := function.TypesFunc wrappedSig, ok := f.Type().(*types.Signature) if !ok { @@ -97,102 +134,147 @@ func createWrapper(w io.Writer, function fuzz.Func, possibleConstructors []fuzz. } localPkg := f.Pkg() - // set up types.Qualifier funcs we can use with the types package + // Set up types.Qualifier funcs we can use with the types package // to scope variables by a package or not. defaultQualifier, localQualifier := qualifiers(localPkg, qualifyAll) - // TODO: rename allParams to namespace? or possibleCollisions - // start building up our list of parameters we will use in input + // Get our receiver, which might be nil if we don't have a receiver + recv := wrappedSig.Recv() + + // Determine our wrapper name, which includes the receiver's type if we are wrapping a method. + var wrapperName string + var err error + if recv == nil { + wrapperName = fmt.Sprintf("Fuzz_%s", f.Name()) + } else { + n, err := findReceiverNamedType(recv) + if err != nil { + // output to stderr, but don't treat as fatal error. + fmt.Fprintf(os.Stderr, "genfuzzfuncs: warning: createWrapper: failed to determine receiver type: %v: %v\n", recv, err) + return nil + } + recvNamedTypeLocalName := types.TypeString(n.Obj().Type(), localQualifier) + wrapperName = fmt.Sprintf("Fuzz_%s_%s", recvNamedTypeLocalName, f.Name()) + } + + // Start building up our list of parameters we will use in input // parameters to the new wrapper func we are about to emit. - var allParams []*types.Var + var inputParams []*types.Var - // check if we have a receiver for the function under test, (i.e., testing a method) + // Check if we have a receiver for the function under test (that is, testing a method) // and then see if we can replace the receiver by finding // a suitable constructor and "promoting" the constructor's arguments up into the wrapper's parameter list. // - // The end result is rather than emitting a wrapper like so for strings.Replacer.Replace: - // func Fuzz_Replacer_Replace(r strings.Replacer, s string) { - // r.Replace(s) - // } + // The end result is rather than emitting a wrapper like so for strings.Reader.Read: + // f.Fuzz(func(t *testing.T, r *strings.Reader, b []byte) { + // r.Read(b) + // }) // // Instead of that, if we find a suitable constructor for the wrapped method's receiver 'r', - // we can instead insert a call to the constructor, + // we (optionally) instead insert a call to the constructor, // and "promote" up the constructor's args into the fuzz wrapper's parameters: - // func Fuzz_Replacer_Replace(oldnew []string, s string) { - // r := strings.NewReplacer(oldnew...) - // r.Replace(s) - // } - recv := wrappedSig.Recv() - var ctorReplace ctorReplacement + // f.Fuzz(func(t *testing.T, s string, b []byte) { + // r := strings.NewReader(s) + // r.Read(b) + // }) + var ctorReplace ctorMatch if recv != nil { if recv.Name() == "" { // this can be an interface method. skip, nothing to do here. - return nil + return errSilentSkip } var paramsToAdd []*types.Var ctorReplace, paramsToAdd, err = constructorReplace(recv, possibleConstructors) if err != nil { return err } - allParams = append(allParams, paramsToAdd...) + inputParams = append(inputParams, paramsToAdd...) } - // add in the parameters for the function under test. + // Also add in the parameters for the function under test. for i := 0; i < wrappedSig.Params().Len(); i++ { v := wrappedSig.Params().At(i) - allParams = append(allParams, v) - } - - // determine our wrapper name, which includes the receiver's type if we are wrapping a method. - var wrapperName string - if recv == nil { - wrapperName = fmt.Sprintf("Fuzz_%s", f.Name()) - } else { - n, err := findReceiverNamedType(recv) - if err != nil { - // output to stderr, but don't treat as fatal error. - fmt.Fprintf(os.Stderr, "genfuzzfuncs: warning: createWrapper: failed to determine receiver type: %v: %v\n", recv, err) - return nil - } - recvNamedTypeLocalName := types.TypeString(n.Obj().Type(), localQualifier) - wrapperName = fmt.Sprintf("Fuzz_%s_%s", recvNamedTypeLocalName, f.Name()) - } - - // check if we have an interface or function pointer in our desired parameters, - // we can't fill in with values during fuzzing. - if disallowedParams(w, allParams, wrapperName) { - // skip this wrapper, disallowedParams emitted a comment with more details. - return nil + inputParams = append(inputParams, v) } - if len(allParams) == 0 { + if len(inputParams) == 0 { // skip this wrapper, not useful for fuzzing if no inputs (no receiver, no parameters). - return nil + return errSilentSkip } - // start emitting the wrapper function! - // start the func declartion - fmt.Fprintf(w, "func %s(", wrapperName) - - // iterate over the our input parameters and emit. - // If we are a method, this includes either an object that is wrapped receiver's type, - // or it includes the parameters for a constructor if we found a suitable one. - for i, v := range allParams { - // want: foo string, bar int - if i > 0 { - // need a comma if something has already been emitted - fmt.Fprint(w, ", ") - } - paramName := avoidCollision(v, i, localPkg, allParams) + var paramReprs []paramRepr + for i, v := range inputParams { typeStringWithSelector := types.TypeString(v.Type(), defaultQualifier) - fmt.Fprintf(w, "%s %s", paramName, typeStringWithSelector) + paramName := avoidCollision(v, i, localPkg, inputParams) + paramReprs = append(paramReprs, paramRepr{paramName: paramName, typ: typeStringWithSelector, v: v}) + } + + // Check if we have an interface or function pointer in our desired parameters, + // which we can't fill with values during fuzzing. + support := checkParamSupport(emit, inputParams, wrapperName) + if support == noSupport { + // skip this wrapper. disallowedParams emitted a comment with more details. + return errSilentSkip + } + + // Start emitting the wrapper function! + // Start with the func declaration and the start of f.Fuzz. + emit("func %s(f *testing.F) {\n", wrapperName) + emit("\tf.Fuzz(func(t *testing.T, ") + + switch support { + case nativeSupport: + // The result for this line will end up similar to: + // f.Fuzz(func(t *testing.T, s string, i int) { + // Iterate over the our input parameters and emit. + // If we are a method, this includes either an object that is wrapped receiver's type, + // or it includes the parameters for a constructor if we found a suitable one. + for i, p := range paramReprs { + // want: foo string, bar int + if i > 0 { + // need a comma if something has already been emitted + emit(", ") + } + emit("%s %s", p.paramName, p.typ) + } + emit(") {\n") + + // Always crashing on a nil receiver is not particularly interesting, so emit the code to avoid. + // Also avoid nil crash if we have any other pointer parameters. + // A user can easliy delete all or part this boilerplate if they don't want particular nil checks. + emitNilChecks(emit, inputParams, localPkg) + case fillRequired: + // This is something not yet supported by cmd/go, but we can shim it via fzgen. + // The result will up similar to: + // f.Fuzz(func(t *testing.T, data []byte) { + // var m map[string]int + // fz := fuzzer.NewFuzzer(data) + // fz.Fill(&map) + // First, finish the line we are on. + emit("data []byte) {\n") + // Second, declare the variables we need to fill. + for _, p := range paramReprs { + emit("\t\tvar %s %s\n", p.paramName, p.typ) + } + // Third, create a fzgen.Fuzzer + emit("\t\tfz := fuzzer.NewFuzzer(data)\n") + // Fourth, emit a potentially wide Fill call for all the variables we declared. + emit("\t\tfz.Fill(") + for i, p := range paramReprs { + if i > 0 { + // need a comma if something has already been emitted + emit(", ") + } + emit("&%s", p.paramName) + } + emit(")\n") + // Avoid nil crash if we have pointer parameters. + emitNilChecks(emit, inputParams, localPkg) + emit("\n") + default: + panic(fmt.Sprintf("unexpected result from checkParamSupport: %v", support)) } - fmt.Fprint(w, ") {\n") - // Always crashing on a nil receiver is not particularly interesting, so emit the code to avoid. - // Also check if we have any other pointer parameters. - emitNilChecks(w, allParams, localPkg) - - // emit a constructor if we have one. + // Emit a constructor if we have one. // collisionOffset tracks how far we are into the parameters of the final fuzz function signature. // (For a constructor call, it will be zero because for the final fuzz function, // the signature starts with any constructor parameters. For the function under test, @@ -203,180 +285,217 @@ func createWrapper(w io.Writer, function fuzz.Func, possibleConstructors []fuzz. // (original name, provenance) -> new name mapping, or perhaps simplify the logic // so that we never use original names. collisionOffset := 0 - if ctorReplace.Sig != nil && recv != nil { - // insert our constructor! - fmt.Fprintf(w, "\t%s := ", avoidCollision(recv, 0, localPkg, allParams)) - if qualifyAll { - fmt.Fprintf(w, "%s.%s(", localPkg.Name(), ctorReplace.Func.Name()) + if recv != nil { + if ctorReplace.sig != nil { + // insert our constructor! + emit("\t%s", avoidCollision(recv, 0, localPkg, inputParams)) + if ctorReplace.secondResultIsErr { + emit(", err") + } + emit(" := ") + if qualifyAll { + emit("%s.%s(", localPkg.Name(), ctorReplace.f.Name()) + } else { + emit("%s(", ctorReplace.f.Name()) + } + emitArgs(emit, ctorReplace.sig, 0, localPkg, inputParams) + emit(")\n") + if ctorReplace.secondResultIsErr { + emit("\tif err != nil {\n") + emit("\t\treturn\n") + emit("\t}\n") + } + collisionOffset = ctorReplace.sig.Params().Len() } else { - fmt.Fprintf(w, "%s(", ctorReplace.Func.Name()) + // We have a receiver, but we are not injecting a constructor (perhaps because + // the option was disabled, or perhaps because we did not find a suitable constructor) + // Reserve space in our naming space for the receiver. + // TODO: we test this case, but add comment here describing which test would fail. + collisionOffset = 1 } - emitArgs(w, ctorReplace.Sig, 0, localPkg, allParams) - fmt.Fprintf(w, ")\n") - collisionOffset = ctorReplace.Sig.Params().Len() } - // emit the call to the wrapped function. - emitWrappedFunc(w, f, wrappedSig, collisionOffset, qualifyAll, allParams, localPkg) - - fmt.Fprint(w, "}\n\n") + // Emit the call to the wrapped function. + emitWrappedFunc(emit, f, wrappedSig, "", collisionOffset, qualifyAll, inputParams, localPkg) + emit("\t})\n") + emit("}\n\n") return nil } -// qualifiers sets up a types.Qualifier func we can use with the types package, -// paying attention to whether we are qualifying everything or not. -func qualifiers(localPkg *types.Package, qualifyAll bool) (defaultQualifier, localQualifier types.Qualifier) { - - localQualifier = func(pkg *types.Package) string { - if pkg == localPkg { - return "" - } - return pkg.Name() - } - if qualifyAll { - defaultQualifier = externalQualifier - } else { - defaultQualifier = localQualifier - } - return defaultQualifier, localQualifier -} - -// externalQualifier can be used as types.Qualifier in calls to types.TypeString and similar. -func externalQualifier(p *types.Package) string { - // always return the package name, which - // should give us things like pkgname.SomeType - return p.Name() -} - -// avoidCollision takes a variable (which might correpsond to a parameter or argument), -// and returns a non-colliding name, or the original name, based on -// whether or not it collided with package name or other with parameters. -func avoidCollision(v *types.Var, i int, localPkg *types.Package, allWrapperParams []*types.Var) string { - // handle corner case of using the package name as a parameter name (e.g., flag.UnquoteUsage(flag *Flag)), - // or two parameters of the same name (e.g., if one was from a constructor and the other from the func under test). - paramName := v.Name() - - if paramName == "_" { - // treat all underscore identifiers as colliding, and use something like "x1" or "x2" in their place. - // this avoids 'cannot use _ as value' errors for things like 'NotNilFilter(_ string, v reflect.Value)' stdlib ast package. - // an alternative would be to elide them when possible, but easier to retain, at least for now. - return fmt.Sprintf("x%d", i+1) - } - - collision := false - if paramName == localPkg.Name() { - collision = true - } - for _, p := range allWrapperParams { - if v != p && paramName == p.Name() { - collision = true - } - } - if collision { - paramName = fmt.Sprintf("%s%d", string([]rune(paramName)[0]), i+1) - } - return paramName -} - // emitNilChecks emits checks for nil for our input parameters. // Always crashing on a nil receiver is not particularly interesting, so emit the code to avoid. // Also check if we have any other pointer parameters. // A user can decide to delete if they want to test nil recivers or nil parameters. // Also, could have a flag to disable. -func emitNilChecks(w io.Writer, allParams []*types.Var, localPkg *types.Package) { +func emitNilChecks(emit emitFunc, allParams []*types.Var, localPkg *types.Package) { + foundPointer := false for i, v := range allParams { _, ok := v.Type().(*types.Pointer) if ok { + if !foundPointer { // first + foundPointer = true + emit("\tif ") + } else { // second or later + emit("|| ") + } paramName := avoidCollision(v, i, localPkg, allParams) - fmt.Fprintf(w, "\tif %s == nil {\n", paramName) - fmt.Fprint(w, "\t\treturn\n") - fmt.Fprint(w, "\t}\n") + emit("%s == nil", paramName) } } + if foundPointer { + emit(" {\n") + emit("\t\treturn\n") + emit("\t}\n") + } } // emitWrappedFunc emits the call to the function under test. -func emitWrappedFunc(w io.Writer, f *types.Func, wrappedSig *types.Signature, collisionOffset int, qualifyAll bool, allParams []*types.Var, localPkg *types.Package) { +// A target that is not "" indicates the caller wants to use a +// specific target name in place of any receiver name. +// For example, a target set to "target" would result in "target.Load(key)". +func emitWrappedFunc(emit emitFunc, f *types.Func, wrappedSig *types.Signature, target string, collisionOffset int, qualifyAll bool, allParams []*types.Var, localPkg *types.Package) { recv := wrappedSig.Recv() - if recv != nil { + switch { + case recv != nil && target != "": + // Use target in place of the existing receiver, only doing this when we have a receiver. + // (If there is no receiver, target isn't useful). + emit("\t%s.%s(", target, f.Name()) + case recv != nil: recvName := avoidCollision(recv, 0, localPkg, allParams) - fmt.Fprintf(w, "\t%s.%s(", recvName, f.Name()) - } else { - if qualifyAll { - fmt.Fprintf(w, "\t%s.%s(", localPkg.Name(), f.Name()) - } else { - fmt.Fprintf(w, "\t%s(", f.Name()) - } + emit("\t%s.%s(", recvName, f.Name()) + case qualifyAll: + emit("\t%s.%s(", localPkg.Name(), f.Name()) + default: + emit("\t%s(", f.Name()) } // emit the arguments to the wrapped function. - emitArgs(w, wrappedSig, collisionOffset, localPkg, allParams) - fmt.Fprint(w, ")\n") + emitArgs(emit, wrappedSig, collisionOffset, localPkg, allParams) + emit(")\n") } // emitArgs emits the arguments needed to call a signature, including handling renaming arguments // based on collisions with package name or other parameters. -func emitArgs(w io.Writer, sig *types.Signature, collisionOffset int, localPkg *types.Package, allWrapperParams []*types.Var) { +func emitArgs(emit emitFunc, sig *types.Signature, collisionOffset int, localPkg *types.Package, allWrapperParams []*types.Var) { for i := 0; i < sig.Params().Len(); i++ { v := sig.Params().At(i) paramName := avoidCollision(v, i+collisionOffset, localPkg, allWrapperParams) if i > 0 { - fmt.Fprint(w, ", ") + emit(", ") } - fmt.Fprint(w, paramName) + emit(paramName) } if sig.Variadic() { // last argument needs an elipsis - fmt.Fprint(w, "...") + emit("...") } } -// disallowedParams reports if the parameters include interfaces or funcs, and emits -// a comment saying we are skipping if found. -// We could try to handle certain interfaces like io.Reader, but right now google/gofuzz -// I think will panic if asked to fuzz an interface with "panic: Can't handle ". -// Could translate at least io.Reader/io.Writer to []byte or *bytes.Buffer or similar. -func disallowedParams(w io.Writer, allWrapperParams []*types.Var, wrapperName string) bool { +type paramSupport uint + +const ( + noSupport paramSupport = iota // we don't yet support it at all + fillRequired // not supported by underlying fuzzing engine, but we support it via fz.Fill + nativeSupport // supported natively by underlying fuzzing engine + unknown +) + +// checkParamSupport reports the level of support across the input parameters. +// It stops checking if it finds a param that is noSupport. +// TODO: this is currently focuses on excluding the most common problems, and defaults to trying nativeSupport (which might cause cmd/go to complain). +func checkParamSupport(emit emitFunc, allWrapperParams []*types.Var, wrapperName string) paramSupport { + res := unknown + if len(allWrapperParams) == 0 { + // An easy case that is handled by cmd/go is no params at all. + // This doesn't currently happen with independent wrappers, but does happen with chain wrappers + // that are targeting a method with no params. + return nativeSupport + } + min := func(a, b paramSupport) paramSupport { + // TODO: use generics in 1.18 ;-) + if a < b { + return a + } + return b + } for _, v := range allWrapperParams { // basic checking for interfaces, funcs, or pointers or slices of interfaces or funcs. - var t types.Type - switch u := v.Type().Underlying().(type) { - case *types.Pointer: - t = u.Elem() + // TODO: should do a more comprehensive check, perhaps recursive, including handling cycles, but keep it simple for now. + // TODO: alt, invert this to check for things we believe cmd/go supports and disallow things we know we can't fill? + // TODO: I think cmd/go does support not pointers like *int or ***int? And not yet maps or slices outside of []byte? + t := v.Type() + t = stripPointers(t, 0) + if t != v.Type() { + // Stripped at least one pointer. Mark that we will need to fill *if* the other checks also pass, + // but we know our best case is to fill (thoug we might also mark noSupport down below after the other checks). + res = min(fillRequired, res) + } + + // TODO: I thought cmd/go supported named types like MyInt, but seemingly not. Add quick check here, which should + // give correct result, but leave rest of logic as is for now (even though some is redundant w/ this check). + if t != t.Underlying() { + res = min(fillRequired, res) + } + + // Switch to check if we might be able to fill this type. + switch u := t.Underlying().(type) { case *types.Slice: t = u.Elem() - default: - t = v.Type() - } + tt, ok := t.(*types.Basic) + if ok && tt.Kind() == types.Byte { + res = min(nativeSupport, res) // TODO: does cmd/go support more slices than []byte? + } else { + res = min(fillRequired, res) + } + case *types.Array: + t = u.Elem() + res = min(fillRequired, res) + case *types.Map: + t = u.Elem() // basic attempt to check for something like map[string]io.Reader below. + res = min(fillRequired, res) + case *types.Struct: + res = min(fillRequired, res) + case *types.Basic: + switch u.Kind() { + case types.Uintptr, types.UnsafePointer, types.Complex64, types.Complex128: + // fz.Fill handles complex, and fills Uintptr and UnsafePointer with nil, which is hopefully reasonable choice. + res = min(fillRequired, res) + } + } + // We might have updated t above. Switch to check if t is unsupported + // (which might have been an Elem of a slice or map, etc..) switch t.Underlying().(type) { case *types.Interface: - _, ok := fuzz.InterfaceImpl[v.Type().String()] - if !ok { - // TODO: leaving old output for now. - fmt.Fprintf(w, "// skipping %s because parameters include interfaces or funcs: %v\n\n", - wrapperName, v.Type()) - // fmt.Fprintf(w, "// skipping %s because parameters include unsupported interface: %v\n\n", - // wrapperName, v.Type()) - return true + if !fuzzer.SupportedInterfaces[t.String()] { + emit("// skipping %s because parameters include unsupported interface: %v\n\n", wrapperName, v.Type()) + res = min(noSupport, res) + return res } - case *types.Signature: - fmt.Fprintf(w, "// skipping %s because parameters include interfaces or funcs: %v\n\n", - wrapperName, v.Type()) - return true + res = min(fillRequired, res) + case *types.Signature, *types.Chan: + emit("// skipping %s because parameters include unsupported func or chan: %v\n\n", wrapperName, v.Type()) + res = min(noSupport, res) + return res } + + // If we didn't easily find a problematic type above, we'll guess that cmd/go supports it, + // and let cmd/go complain if it needs to for more complex cases not handled above. + res = min(nativeSupport, res) } - return false + return res } -// ctorReplacement holds the signature of a suitable constructor if we found one. +// ctorMatch holds the signature of a suitable constructor if we found one. // We use the signature to "promote" the needed arguments from the constructor // parameter list up to the wrapper function parameter list. // Sig is nil if a suitable constructor was not found. -type ctorReplacement struct { - Sig *types.Signature - Func *types.Func +type ctorMatch struct { + sig *types.Signature + f *types.Func + ctorResultN *types.Named // TODO: no longer need this, probably + secondResultIsErr bool } // constructorReplace determines if there is a constructor we can replace, @@ -384,115 +503,173 @@ type ctorReplacement struct { // add to the main wrapper method. They will either be the parameters // needed to pass into the constructor, or it will be a single parameter // corresponding to the wrapped method receiver if we didn't find a usable constructor. -func constructorReplace(recv *types.Var, possibleConstructors []fuzz.Func) (ctorReplacement, []*types.Var, error) { - var ctorReplace ctorReplacement +func constructorReplace(recv *types.Var, possibleCtors []mod.Func) (ctorMatch, []*types.Var, error) { + var match ctorMatch + var err error var paramsToAdd []*types.Var - for _, possibleConstructor := range possibleConstructors { - ctorSig, ok := possibleConstructor.TypesFunc.Type().(*types.Signature) - if !ok { - return ctorReplace, paramsToAdd, fmt.Errorf("function %s is not *types.Signature (%+v)", - possibleConstructor, possibleConstructor.TypesFunc) + for _, possibleCtor := range possibleCtors { + match, err = constructorMatch(recv, possibleCtor) + if err != nil { + return ctorMatch{}, nil, err } - - if ctorSig.Params().Len() == 0 { - // constructors are mainly useful for fuzzing if they take at least one argument. - // if not, better off keep looking for another constructor (and if later no constructor can be found at all, - // than it is better to not use a constructor if the struct has public members; if there is no constructor found - // that has at least one arg, and there are no public members on the struct, then not much we can do). - continue + if match.sig != nil { + // stop our search, and insert our constructor's arguments. + for i := 0; i < match.sig.Params().Len(); i++ { + v := match.sig.Params().At(i) + paramsToAdd = append(paramsToAdd, v) + } + return match, paramsToAdd, nil } + } - ctorResults := ctorSig.Results() - if ctorResults.Len() != 1 { - // only handle single result constructors. could try to handle error as well, or more. - continue - } - ctorResult := ctorResults.At(0) + // we didn't find a matching constructor, + // so the method receiver will be added to the wrapper function's parameters. + paramsToAdd = append(paramsToAdd, recv) + return match, paramsToAdd, nil +} - recvN, err := findReceiverNamedType(recv) - if err != nil { - // output to stderr, but don't treat as fatal error. - fmt.Fprintf(os.Stderr, "genfuzzfuncs: warning: constructorReplace: failed to determine receiver type when looking for constructors: %v: %v\n", recv, err) - continue - } +// constructorMatch determines if a receiver for a possible method +// under test has a matching type with a constructor. +// It compares the named types, and allows a match if the constructor +// has a single return value, or two return values with an error type as the second. +func constructorMatch(recv *types.Var, possibleCtor mod.Func) (ctorMatch, error) { + ctorSig, ok := possibleCtor.TypesFunc.Type().(*types.Signature) + if !ok { + return ctorMatch{}, fmt.Errorf("function %s is not *types.Signature (%+v)", + possibleCtor, possibleCtor.TypesFunc) + } - ctorResultN, err := findReceiverNamedType(ctorResult) - if err != nil { - // findReceiverNamedType returns a types.Named if the passed in - // types.Var is a types.Pointer or already types.Named. - // This candidate constructor is neither of those, which means we can't - // use it to give us the type we need for the receiver for this method we are trying to fuzz. - // This is not an error. It just means it didn't match. - continue - } + // TODO: we used to disallow ctors here with ctorSig.Params().Len() == 0, but probably OK to allow? - // TODO: types.Identical wasn't working as expected. Imperfect fall back for now. - // types.TypeString(recvN, nil) returns a fullly exanded string that includes the import path, e.g.,: - // github.com/thepudds/fzgo/genfuzzfuncs/examples/test-constructor-injection.A - if types.TypeString(recvN, nil) == types.TypeString(ctorResultN, nil) { - // we found a match between this constructor's return type and the receiver type - // we need for the method we are trying to fuzz! (ignoring a pointer, which we stripped off above). - ctorReplace.Sig = ctorSig - ctorReplace.Func = possibleConstructor.TypesFunc - break + ctorResults := ctorSig.Results() + if ctorResults.Len() > 2 || ctorResults.Len() == 0 { + return ctorMatch{}, nil + } + secondResultIsErr := false + if ctorResults.Len() == 2 { + // We allow error type as second return value + secondResult := ctorResults.At(1) + _, ok = secondResult.Type().Underlying().(*types.Interface) + if ok && secondResult.Type().String() == "error" { + secondResultIsErr = true + } else { + return ctorMatch{}, nil } } - if ctorReplace.Sig == nil { - // we didn't find a matching constructor, - // so the method receiver will be added to the wrapper function's parameters. - paramsToAdd = append(paramsToAdd, recv) - } else { - // insert our constructor's arguments. - for i := 0; i < ctorReplace.Sig.Params().Len(); i++ { - v := ctorReplace.Sig.Params().At(i) - paramsToAdd = append(paramsToAdd, v) + + ctorResult := ctorResults.At(0) + + recvN, err := findReceiverNamedType(recv) + if err != nil { + // output to stderr, but don't treat as fatal error. + fmt.Fprintf(os.Stderr, "genfuzzfuncs: warning: constructorReplace: failed to determine receiver type when looking for constructors: %v: %v\n", recv, err) + return ctorMatch{}, nil + } + + // TODO: ctorResult here is not a receiver. probably rename findReceiverNamedType to be more general. + ctorResultN, err := findReceiverNamedType(ctorResult) + if err != nil { + // findReceiverNamedType returns a types.Named if the passed in + // types.Var is a types.Pointer or already types.Named. + // This candidate constructor is neither of those, which means we can't + // use it to give us the type we need for the receiver for this method we are trying to fuzz. + // This is not an error for matching purposes. It just means it didn't match. + return ctorMatch{}, nil + } + + // TODO: this is old & very early code... is there some reason we can't compare types.Var.Type() more directly? + // TODO (old): types.Identical wasn't working as expected. Imperfect fall back for now. + // types.TypeString(recvN, nil) returns a fully exanded string that includes the import path, e.g.,: + // github.com/thepudds/fzgo/genfuzzfuncs/examples/test-constructor-injection.A + if types.TypeString(recvN, nil) == types.TypeString(ctorResultN, nil) { + // we found a match between this constructor's return type and the receiver type + // we need for the method we are trying to fuzz! (ignoring a pointer, which we stripped off above). + match := ctorMatch{ + sig: ctorSig, + f: possibleCtor.TypesFunc, + ctorResultN: ctorResultN, + secondResultIsErr: secondResultIsErr, } + return match, nil } - return ctorReplace, paramsToAdd, nil + + // We didn't find a match + return ctorMatch{}, nil } -// TODO: would be good to find some canonical documentation or example of this. -func isExportedFunc(f *types.Func) bool { - if !f.Exported() { - return false +// avoidCollision takes a variable (which might correpsond to a parameter or argument), +// and returns a non-colliding name, or the original name, based on +// whether or not it collided with package name or other with parameters. +func avoidCollision(v *types.Var, i int, localPkg *types.Package, allWrapperParams []*types.Var) string { + // handle corner case of using the package name as a parameter name (e.g., flag.UnquoteUsage(flag *Flag)), + // or two parameters of the same name (e.g., if one was from a constructor and the other from the func under test). + paramName := v.Name() + + if paramName == "_" { + // treat all underscore identifiers as colliding, and use something like "_x1" or "_x2" in their place. + // this avoids 'cannot use _ as value' errors for things like 'NotNilFilter(_ string, v reflect.Value)' stdlib ast package. + // an alternative would be to elide them when possible, but easier to retain, at least for now. + return fmt.Sprintf("_x%d", i+1) } - // the function itself is exported, but it might be a method on an unexported type. - sig, ok := f.Type().(*types.Signature) - if !ok { - return false + + collision := false + switch paramName { + case localPkg.Name(), "t", "f", "fz", "data", "target", "steps", "result1", "result2", "tmp1", "tmp2": + // avoid the common variable names for testing.T, testing.F, fzgen.Fuzzer, + // as well as variables we might emit (preferring an aesthetically pleasing + // name for something like "steps" in the common case over preserving + // a rare use of "steps" by a wrapped func). + collision = true + default: + for _, p := range allWrapperParams { + if v != p && paramName == p.Name() { + collision = true + break + } + } } - recv := sig.Recv() - if recv == nil { - // not a method, and the func itself is exported. - return true + if collision { + // TODO: could check again to see if this also collisde, + // but maybe find an example where it matters first (no examples across 3K+ stdlib funcs). + paramName = fmt.Sprintf("%s%d", string([]rune(paramName)[0]), i+1) } + return paramName +} - n, err := findReceiverNamedType(recv) - if err != nil { - // don't treat as fatal error. - fmt.Fprintf(os.Stderr, "genfuzzfuncs: warning: failed to determine if exported for receiver %v for func %v: %v\n", - recv, f, err) - return false +// qualifiers sets up a types.Qualifier func we can use with the types package, +// paying attention to whether we are qualifying everything or not. +func qualifiers(localPkg *types.Package, qualifyAll bool) (defaultQualifier, localQualifier types.Qualifier) { + localQualifier = func(pkg *types.Package) string { + if pkg == localPkg { + return "" + } + return pkg.Name() } + if qualifyAll { + defaultQualifier = externalQualifier + } else { + defaultQualifier = localQualifier + } + return defaultQualifier, localQualifier +} - return n.Obj().Exported() +// externalQualifier can be used as types.Qualifier in calls to types.TypeString and similar. +func externalQualifier(p *types.Package) string { + // always return the package name, which + // should give us things like pkgname.SomeType + return p.Name() } -// isInterfaceRecv helps filter out interface receivers such as 'func (interface).Is(error) bool' -// from errors.Is: -// x, ok := err.(interface{ Is(error) bool }); ok && x.Is(target) -func isInterfaceRecv(f *types.Func) bool { - sig, ok := f.Type().(*types.Signature) - if !ok { - return false +func stripPointers(t types.Type, depth int) types.Type { + if depth > 10 { + return t // TODO: not sure we need depth, but we'll play it safe for now. } - recv := sig.Recv() - if recv == nil { - // not a method - return false + depth++ + u, ok := t.Underlying().(*types.Pointer) + if !ok { + return t } - _, ok = recv.Type().(*types.Interface) - return ok + return stripPointers(u.Elem(), depth) } // findReceiverNamedType returns a types.Named if the passed in @@ -516,125 +693,3 @@ func findReceiverNamedType(recv *types.Var) (*types.Named, error) { } return reportErr() } - -// TODO: currently this is a temporary fork from fuzz.FindFunc - -// FindFuncFlag describes bitwise flags for FindFunc -type FindFuncFlag uint - -const ( - flagAllowMultiFuzz FindFuncFlag = 1 << iota - flagRequireFuzzPrefix - flagExcludeFuzzPrefix - flagRequireExported - flagVerbose -) - -// FindFunc searches for requested functions matching a package pattern and func pattern. -// TODO: this is a temporary fork from fzgo/fuzz.FindFunc. -// TODO: maybe change flags to a predicate function? -func FindFunc(pkgPattern, funcPattern string, env []string, flags FindFuncFlag) ([]fuzz.Func, error) { - report := func(err error) error { - return fmt.Errorf("error while loading packages for pattern %v: %v", pkgPattern, err) - } - var result []fuzz.Func - - // load packages based on our package pattern - // build tags example: https://groups.google.com/d/msg/golang-tools/Adwr7jEyDmw/wQZ5qi8ZGAAJ - cfg := &packages.Config{ - Mode: packages.LoadSyntax, - // TODO: BuildFlags: []string{buildTagsArg}, retain? probably doesn't matter. - } - if len(env) > 0 { - cfg.Env = env - } - pkgs, err := packages.Load(cfg, pkgPattern) - if err != nil { - return nil, report(err) - } - if packages.PrintErrors(pkgs) > 0 { - return nil, fmt.Errorf("package load error for package pattern %v", pkgPattern) - } - - // look for a func that starts with 'Fuzz' and matches our regexp. - // loop over the packages we found and loop over the Defs for each package. - for _, pkg := range pkgs { - // TODO: consider alternative: "from a Package, look at Syntax.Scope.Objects and filter with ast.IsExported." - for id, obj := range pkg.TypesInfo.Defs { - // check if we have a func - f, ok := obj.(*types.Func) - if ok { - // TODO: merge back to fuzz.FindFunc? - if isInterfaceRecv(f) { - continue - } - if flags&flagExcludeFuzzPrefix != 0 && strings.HasPrefix(id.Name, "Fuzz") { - // skip any function that already starts with Fuzz - continue - } - if flags&flagRequireFuzzPrefix != 0 && !strings.HasPrefix(id.Name, "Fuzz") { - // skip any function that does not start with Fuzz - continue - } - if flags&flagRequireExported != 0 { - if !isExportedFunc(f) { - continue - } - } - - matchedPattern, err := regexp.MatchString(funcPattern, id.Name) - if err != nil { - return nil, report(err) - } - if matchedPattern { - // found a match. - // check if we already found a match in a prior iteration our of loops. - if len(result) > 0 && flags&flagAllowMultiFuzz == 0 { - return nil, fmt.Errorf("multiple matches not allowed. multiple matches for pattern %v and func %v: %v.%v and %v.%v", - pkgPattern, funcPattern, pkg.PkgPath, id.Name, result[0].PkgPath, result[0].FuncName) - } - pkgDir, err := goListDir(pkg.PkgPath, env) - if err != nil { - return nil, report(err) - } - - function := fuzz.Func{ - FuncName: id.Name, PkgName: pkg.Name, PkgPath: pkg.PkgPath, PkgDir: pkgDir, - TypesFunc: f, - } - result = append(result, function) - - // keep looping to see if we find another match - } - } - } - } - // done looking - if len(result) == 0 { - return nil, fmt.Errorf("failed to find fuzz function for pattern %v and func %v", pkgPattern, funcPattern) - } - return result, nil -} - -// goListDir returns the dir for a package import path -// TODO: this is a temporary fork from fzgo/fuzz -func goListDir(pkgPath string, env []string) (string, error) { - if len(env) == 0 { - env = os.Environ() - } - - // TODO: use build tags, or not? - // cmd := exec.Command("go", "list", "-f", "{{.Dir}}", buildTagsArg, pkgPath) - cmd := exec.Command("go", "list", "-f", "{{.Dir}}", pkgPath) - cmd.Env = env - - out, err := cmd.Output() - if err != nil { - return "", fmt.Errorf("failed to find directory of %v: %v", pkgPath, err) - } - result := strings.TrimSpace(string(out)) - if strings.Contains(result, "\n") { - return "", fmt.Errorf("multiple directory results for package %v", pkgPath) - } - return result, nil -} diff --git a/gen/genfuncs_stdlib_test.go b/gen/genfuncs_stdlib_test.go new file mode 100644 index 0000000..0526902 --- /dev/null +++ b/gen/genfuncs_stdlib_test.go @@ -0,0 +1,88 @@ +//go:build go1.17 +// +build go1.17 + +package fzgen + +import ( + "io/ioutil" + "path/filepath" + "testing" + + "github.com/google/go-cmp/cmp" +) + +func TestStrings(t *testing.T) { + if testing.Short() { + // TODO: probably remove this test at some point? + // It is long, and sensitive to changes in stdlib strings pkg. + t.Skip("skipping test in short mode. also, currently relies on strings package from Go 1.17") + } + tests := []struct { + name string // Note: we use the test name also as the golden filename + onlyExported bool + qualifyAll bool + insertConstructors bool + }{ + { + name: "strings_inject_ctor_true_exported_not_local_pkg.go", + onlyExported: true, + qualifyAll: true, + insertConstructors: true, + }, + { + name: "strings_inject_ctor_false_exported_local_pkg.go", + onlyExported: true, + qualifyAll: false, + insertConstructors: true, + }, + { + name: "strings_inject_ctor_false_exported_not_local_pkg.go", + onlyExported: true, + qualifyAll: true, + insertConstructors: false, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + pkgPattern := "strings" + options := flagExcludeFuzzPrefix | flagAllowMultiFuzz + if tt.onlyExported { + options |= flagRequireExported + } + functions, err := findFunc(pkgPattern, ".", nil, options) + if err != nil { + t.Fatalf("FindFuncfail() failed: %v", err) + } + + wrapperOpts := wrapperOptions{ + qualifyAll: tt.qualifyAll, + insertConstructors: tt.insertConstructors, + constructorPattern: "^New", + } + out, err := emitIndependentWrappers(pkgPattern, functions, wrapperOpts) + if err != nil { + t.Fatalf("createWrappers() failed: %v", err) + } + + got := string(out) + golden := filepath.Join("..", "testdata", tt.name) + if *updateFlag { + err = ioutil.WriteFile(golden, []byte(got), 0o644) + if err != nil { + t.Fatalf("failed to update golden file: %v", err) + } + } + b, err := ioutil.ReadFile(golden) + if err != nil { + t.Fatalf("failed to read golden file: %v", err) + } + want := string(b) + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("createWrappers() mismatch (-want +got):\n%s", diff) + } + }) + } +} diff --git a/gen/genfuncs_test.go b/gen/genfuncs_test.go new file mode 100644 index 0000000..a7bfe7a --- /dev/null +++ b/gen/genfuncs_test.go @@ -0,0 +1,245 @@ +package fzgen + +import ( + "flag" + "io/ioutil" + "path/filepath" + "testing" + + "github.com/google/go-cmp/cmp" +) + +// to update golden files in ./testdata: +// go test -update +var updateFlag = flag.Bool("update", false, "update golden files") + +// The first subtest here is the simplest & fatest test in the file. To run just that: +// go test -run=Tyes/types_exported_not_local_pkg + +func TestTypes(t *testing.T) { + tests := []struct { + name string // Note: we use the test name also as the golden filename + onlyExported bool + qualifyAll bool + }{ + { + name: "types_exported_not_local_pkg.go", + onlyExported: true, + qualifyAll: true, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + pkgPattern := "github.com/thepudds/fzgen/examples/inputs/test-types" + options := flagExcludeFuzzPrefix | flagAllowMultiFuzz + if tt.onlyExported { + options |= flagRequireExported + } + functions, err := findFunc(pkgPattern, ".", nil, options) + if err != nil { + t.Fatalf("FindFuncfail() failed: %v", err) + } + + wrapperOpts := wrapperOptions{ + qualifyAll: tt.qualifyAll, + insertConstructors: true, + constructorPattern: "^New", + } + + out, err := emitIndependentWrappers(pkgPattern, functions, wrapperOpts) + if err != nil { + t.Fatalf("createWrappers() failed: %v", err) + } + + got := string(out) + golden := filepath.Join("..", "testdata", tt.name) + if *updateFlag { + // Note: using Fatalf above including so that we don't update if there was an earlier failure. + err = ioutil.WriteFile(golden, []byte(got), 0o644) + if err != nil { + t.Fatalf("failed to update golden file: %v", err) + } + } + b, err := ioutil.ReadFile(golden) + if err != nil { + t.Fatalf("failed to read golden file: %v", err) + } + want := string(b) + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("createWrappers() mismatch (-want +got):\n%s", diff) + } + }) + } +} + +// the simplest to run is: +// go test -run=ConstructorInjection/constructor_injection:_exported,_not_local_pkg + +func TestConstructorInjection(t *testing.T) { + tests := []struct { + name string // Note: we use the test name also as the golden filename + onlyExported bool + qualifyAll bool + injectConstructors bool + }{ + { + // this corresponds roughly to: + // fzgen -ctors -pkg=github.com/thepudds/fzgo/genfuzzfuncs/examples/test-constructor-injection + name: "inject_ctor_true_exported_not_local_pkg.go", + onlyExported: true, + qualifyAll: true, + injectConstructors: true, + }, + { + // this corresponds roughly to: + // fzgen -ctors=false -pkg=github.com/thepudds/fzgo/genfuzzfuncs/examples/test-constructor-injection + name: "inject_ctor_false_exported_not_local_pkg.go", + onlyExported: true, + qualifyAll: true, + injectConstructors: false, + }, + { + // this corresponds roughly to: + // genfuzzfuncs -ctors -qualifyall=false -pkg=github.com/thepudds/fzgo/genfuzzfuncs/examples/test-constructor-injection + name: "inject_ctor_true_exported_local_pkg.go", + onlyExported: true, + qualifyAll: false, + injectConstructors: true, + }, + { + // this corresponds roughly to: + // genfuzzfuncs -ctors=false -qualifyall=false -pkg=github.com/thepudds/fzgo/genfuzzfuncs/examples/test-constructor-injection + name: "inject_ctor_false_exported_local_pkg.go", + onlyExported: true, + qualifyAll: false, + injectConstructors: false, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + pkgPattern := "github.com/thepudds/fzgen/examples/inputs/test-constructor-injection" + options := flagExcludeFuzzPrefix | flagAllowMultiFuzz + if tt.onlyExported { + options |= flagRequireExported + } + functions, err := findFunc(pkgPattern, ".", nil, options) + if err != nil { + t.Fatalf("FindFuncfail() failed: %v", err) + } + + wrapperOpts := wrapperOptions{ + qualifyAll: tt.qualifyAll, + insertConstructors: tt.injectConstructors, + constructorPattern: "^New", + } + out, err := emitIndependentWrappers(pkgPattern, functions, wrapperOpts) + if err != nil { + t.Fatalf("createWrappers() failed: %v", err) + } + + got := string(out) + golden := filepath.Join("..", "testdata", tt.name) + if *updateFlag { + // Note: using Fatalf above including so that we don't update if there was an earlier failure. + err = ioutil.WriteFile(golden, []byte(got), 0o644) + if err != nil { + t.Fatalf("failed to update golden file: %v", err) + } + } + b, err := ioutil.ReadFile(golden) + if err != nil { + t.Fatalf("failed to read golden file: %v", err) + } + want := string(b) + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("createWrappers() mismatch (-want +got):\n%s", diff) + } + }) + } +} + +// the simplest one to run is: +// go test -run=TestExported/exported_not_local_pkg +// to update golden files in ./testdata: +// go test -update -run=TestExported/exported_not_local_pkg +// or to update all the TestExported golden files: +// go test -update -run=TestExported + +func TestExported(t *testing.T) { + tests := []struct { + name string // Note: we use the test name also as the golden filename + onlyExported bool + qualifyAll bool + }{ + { + name: "exported_not_local_pkg.go", + onlyExported: true, + qualifyAll: true, + }, + { + name: "exported_local_pkg.go", + onlyExported: true, + qualifyAll: false, + }, + { + name: "exported_and_private_not_local_pkg.go", + onlyExported: false, + qualifyAll: true, + }, + { + name: "exported_and_private_local_pkg.go", + onlyExported: false, + qualifyAll: false, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + pkgPattern := "github.com/thepudds/fzgen/examples/inputs/test-exported" + options := flagExcludeFuzzPrefix | flagAllowMultiFuzz + if tt.onlyExported { + options |= flagRequireExported + } + functions, err := findFunc(pkgPattern, ".", nil, options) + if err != nil { + t.Fatalf("FindFuncfail() failed: %v", err) + } + + wrapperOpts := wrapperOptions{ + qualifyAll: tt.qualifyAll, + insertConstructors: true, + constructorPattern: "^New", + } + + out, err := emitIndependentWrappers(pkgPattern, functions, wrapperOpts) + if err != nil { + t.Fatalf("createWrappers() failed: %v", err) + } + + got := string(out) + golden := filepath.Join("..", "testdata", tt.name) + if *updateFlag { + // Note: using Fatalf above including so that we don't update if there was an earlier failure. + err = ioutil.WriteFile(golden, []byte(got), 0o644) + if err != nil { + t.Fatalf("failed to update golden file: %v", err) + } + } + b, err := ioutil.ReadFile(golden) + if err != nil { + t.Fatalf("failed to read golden file: %v", err) + } + want := string(b) + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("createWrappers() mismatch (-want +got):\n%s", diff) + } + }) + } +} diff --git a/gen/genfuncsloop.go b/gen/genfuncsloop.go new file mode 100644 index 0000000..f89a4f4 --- /dev/null +++ b/gen/genfuncsloop.go @@ -0,0 +1,641 @@ +package fzgen + +import ( + "bytes" + "errors" + "fmt" + "go/types" + "io" + "os" + "path/filepath" + "sort" + "strings" + + "github.com/thepudds/fzgen/gen/internal/mod" + "golang.org/x/tools/imports" +) + +// emitChainWrappers emits fuzzing wrappers where possible for the list of functions passed in. +// It might skip a function if it has no input parameters, or if it has a non-fuzzable parameter +// type such as interface{}. +// See package comment in main.go for more details. +func emitChainWrappers(pkgPattern string, functions []mod.Func, options wrapperOptions) ([]byte, error) { + if len(functions) == 0 { + return nil, fmt.Errorf("no matching functions found") + } + + // start by hunting for possible constructors in the same package if requested. + // TODO: consider extracting this to helper func + var possibleConstructors []mod.Func + if options.insertConstructors { + // We default to the pattern ^New, but allow user-specified patterns. + // We don't check the err here because it can be expected to not find anything if there + // are no functions that start with New (and this is our second call to FindFunc, so + // other problems should have been reported earlier). + // TODO: consider related tweak to error reporting in FindFunc? + possibleConstructors, _ = findFunc(pkgPattern, options.constructorPattern, nil, + flagExcludeFuzzPrefix|flagAllowMultiFuzz|flagRequireExported) + // put possibleConstructors into a semi-deterministic order. + // TODO: for now, we'll prefer simpler constructors as approximated by length (so 'New' before 'NewSomething'). + sort.Slice(possibleConstructors, func(i, j int) bool { + return len(possibleConstructors[i].FuncName) < len(possibleConstructors[j].FuncName) + }) + } + + // TODO: for now, require an exact match on constructor. Could relax this (e.g., perhaps use first that has return type + // matching >1 method that matched the func pattern, or shortest match, or ...) + if len(possibleConstructors) == 0 { + return nil, fmt.Errorf("constructor pattern does not match any constructors") + } + if len(possibleConstructors) > 1 { + var s []string + for _, c := range possibleConstructors { + s = append(s, c.FuncName) + } + return nil, fmt.Errorf("constructor pattern must match exactly one constructor. matches: " + strings.Join(s, ", ")) + } + + // prepare the output + buf := new(bytes.Buffer) + var w io.Writer = buf + emit := func(format string, args ...interface{}) { + fmt.Fprintf(w, format, args...) + } + + // emit the intro material + var pkgSuffix string + if options.qualifyAll { + // TODO: remove this trailing comment, probably + pkgSuffix = "fuzz // rename if needed" + } + emit("package %s%s\n\n", functions[0].TypesFunc.Pkg().Name(), pkgSuffix) + // TODO: also remove this trailing comment, probably + emit("// if needed, fill in imports or run 'goimports'\n") + emit("import (\n") + emit("\t\"testing\"\n") + emit("\t\"%s\"\n", functions[0].PkgPath) + emit("\t\"github.com/thepudds/fzgen/fuzzer\"\n") + emit(")\n\n") + + // use the first constructor + ctor := possibleConstructors[0] + err := emitChainPreamble(emit, ctor, options.qualifyAll) + if errors.Is(err, errSilentSkip) { + // TODO: more specific error? + return nil, errors.New("failed to create chain preamble for constructor: " + ctor.FuncName) + } + if err != nil { + return nil, err + } + + // put our functions we want to wrap into a deterministic order + sort.Slice(functions, func(i, j int) bool { + // types.Func.String outputs strings like: + // func (github.com/thepudds/fzgo/genfuzzfuncs/examples/test-constructor-injection.A).ValMethodWithArg(i int) bool + // works ok for clustering results, though pointer receiver and non-pointer receiver methods don't cluster. + // could strip '*' or sort another way, but probably ok, at least for now. + return functions[i].TypesFunc.String() < functions[j].TypesFunc.String() + }) + + emit("\tsteps := []fuzzer.Step{\n") + + // loop over our the functions we are wrapping, emitting a wrapper where possible. + for _, function := range functions { + err := emitChainWrapper(emit, function, ctor, options.qualifyAll) + if errors.Is(err, errSilentSkip) { + continue + } + if err != nil { + return nil, fmt.Errorf("error processing %s: %v", function.FuncName, err) + } + } + // close out steps slice + emit("\t}\n\n") + + // emit the chain func + emit("\t// Execute a specific chain of steps, with the count, sequence and arguments controlled by fz.Chain\n") + if !options.parallel { + emit("\tfz.Chain(steps)\n") + } else { + emit("\tfz.Chain(steps, fuzzer.ChainParallel)\n") + } + + // possibly emit some roundtrip validation checks. + // TODO: move out to separate func. + // TODO: make this table-driven, nicer, and handle additional common roundtrip patterns. + var haveMarshalBinary, haveUnmarshalBinary, haveMarshalText, haveUnmarshalText bool + for _, flavor := range []string{"Binary", "Text"} { + for _, function := range functions { + implements, err := implementsEncodingMarshaler(function.TypesFunc, "Marshal"+flavor) + if err != nil { + fmt.Fprintln(os.Stderr, "fzgen: warning: continuing after failing to check encoding.Marshal capabilities:", err) + continue + } + if implements && flavor == "Binary" { + haveMarshalBinary = true + } + if implements && flavor == "Text" { + haveMarshalText = true + } + implements, err = implementsEncodingUnmarshaler(function.TypesFunc, "Unmarshal"+flavor) + if err != nil { + fmt.Fprintln(os.Stderr, "fzgen: warning: continuing after failing to check encoding.Unmarshal capabilities:", err) + continue + } + if implements && flavor == "Binary" { + haveUnmarshalBinary = true + } + if implements && flavor == "Text" { + haveUnmarshalText = true + } + } + } + var doBinaryRoundtrip, doTextRoundtrip bool + if haveMarshalText && haveUnmarshalText { + doTextRoundtrip = true + } + if haveMarshalBinary && haveUnmarshalBinary { + doBinaryRoundtrip = true + } + + var ctorTypeStringWithSelector string + if doBinaryRoundtrip || doTextRoundtrip { + emit("\n// Validate with some roundtrip checks. These can be edited or deleted if not appropriate for your target.") + + // Set up a qualifier so that we handle a local package vs. not for the temp variables + // for our target. + // TODO: make utility func, probably. + localPkg := ctor.TypesFunc.Pkg() + // Set up types.Qualifier funcs we can use with the types package + // to scope variables by a package or not. + defaultQualifier, _ := qualifiers(localPkg, options.qualifyAll) + ctorType := ctor.TypesFunc.Type() + ctorSig, ok := ctorType.(*types.Signature) + if !ok { + fmt.Fprintln(os.Stderr, "fzgen: warning: continuing after failing to determine type of target for roundtrip checks") + doBinaryRoundtrip = false + doTextRoundtrip = false + } + ctorResults := ctorSig.Results() + if ctorResults.Len() < 1 { + fmt.Fprintln(os.Stderr, "fzgen: warning: skipping roundtrip checks") + doBinaryRoundtrip = false + doTextRoundtrip = false + } + ctorResult := ctorResults.At(0) + ctorTypeStringWithSelector = types.TypeString(ctorResult.Type(), defaultQualifier) + } + if doTextRoundtrip { + emit(encodingTextMarshalerRoundtripTmpl, ctorTypeStringWithSelector) + } + if doBinaryRoundtrip { + emit(encodingBinaryMarshalerRoundtripTmpl, ctorTypeStringWithSelector) + } + + // close out the f.Fuzz func + emit("\t})\n") + + // close out test func + emit("}\n\n") + + // fix up any needed imports. + // TODO: perf: this seems slower than expected. Check what style of path should be used for filename? + // imports.Process has this comment: + // Note that filename's directory influences which imports can be chosen, + // so it is important that filename be accurate. + filename, err := filepath.Abs(("autofuzz_test.go")) + warn := func(err error) { + fmt.Fprintln(os.Stderr, "fzgen: warning: continuing after failing to automatically adjust imports:", err) + } + if err != nil { + warn(err) + return buf.Bytes(), nil + } + out, err := imports.Process(filename, buf.Bytes(), nil) + if err != nil { + warn(err) + return buf.Bytes(), nil + } + return out, nil +} + +func emitChainPreamble(emit emitFunc, function mod.Func, qualifyAll bool) error { + f := function.TypesFunc + wrappedSig, ok := f.Type().(*types.Signature) + if !ok { + return fmt.Errorf("function %s is not *types.Signature (%+v)", function, f) + } + localPkg := f.Pkg() + + // Set up types.Qualifier funcs we can use with the types package + // to scope variables by a package or not. + defaultQualifier, localQualifier := qualifiers(localPkg, qualifyAll) + + // Get our receiver, which might be nil if we don't have a receiver + recv := wrappedSig.Recv() + + // Determine our wrapper name, which includes the receiver's type if we are wrapping a method. + var wrapperName string + if recv == nil { + wrapperName = fmt.Sprintf("Fuzz_%s_Chain", f.Name()) + } else { + n, err := findReceiverNamedType(recv) + if err != nil { + // output to stderr, but don't treat as fatal error. + fmt.Fprintf(os.Stderr, "genfuzzfuncs: warning: createWrapper: failed to determine receiver type: %v: %v\n", recv, err) + return nil + } + recvNamedTypeLocalName := types.TypeString(n.Obj().Type(), localQualifier) + wrapperName = fmt.Sprintf("Fuzz_%s_%s", recvNamedTypeLocalName, f.Name()) + } + + // Start building up our list of parameters we will use in input + // parameters to the new wrapper func we are about to emit. + var inputParams []*types.Var + + // Also add in the parameters for the function under test. + for i := 0; i < wrappedSig.Params().Len(); i++ { + v := wrappedSig.Params().At(i) + inputParams = append(inputParams, v) + } + + var paramReprs []paramRepr + for i, v := range inputParams { + typeStringWithSelector := types.TypeString(v.Type(), defaultQualifier) + paramName := avoidCollision(v, i, localPkg, inputParams) + paramReprs = append(paramReprs, paramRepr{paramName: paramName, typ: typeStringWithSelector, v: v}) + } + + // Check if we have an interface or function pointer in our desired parameters, + // which we can't fill with values during fuzzing. + support := checkParamSupport(emit, inputParams, wrapperName) + if support == noSupport { + // skip this wrapper. disallowedParams emitted a comment with more details. + return errSilentSkip + } + + // Start emitting the wrapper function! + // Start with the func declaration and the start of f.Fuzz. + emit("func %s(f *testing.F) {\n", wrapperName) + emit("\tf.Fuzz(func(t *testing.T, ") + + switch support { + case nativeSupport, fillRequired: + // We always want fz := fuzzer.NewFuzzer(data) so that + // we can call fz.Chain at the bottom of the function we are emitting, + // so we do the same thing here regardless of whether or not cmd/go + // natively supports the input args to our constructor. + // If we also need to do fz.Fill for types that are not natively supported + // by cmd/go, the result will be similar to: + // f.Fuzz(func(t *testing.T, data []byte) { + // var m map[string]int + // fz := fuzzer.NewFuzzer(data) + // fz.Fill(&map) + // First, finish the line we are on. + emit("data []byte) {\n") + // Second, declare the variables we need to fill. + for _, p := range paramReprs { + emit("\t\tvar %s %s\n", p.paramName, p.typ) + } + // Third, create a fzgen.Fuzzer + emit("\t\tfz := fuzzer.NewFuzzer(data)\n") + + // Fourth, emit a potentially wide Fill call for any input params for the constructor. + if len(inputParams) > 0 { + emit("\t\tfz.Fill(") + for i, p := range paramReprs { + if i > 0 { + // need a comma if something has already been emitted + emit(", ") + } + emit("&%s", p.paramName) + } + emit(")\n") + } + + // Avoid nil crash if we have pointer parameters. + emitNilChecks(emit, inputParams, localPkg) + emit("\n") + default: + panic(fmt.Sprintf("unexpected result from checkParamSupport: %v", support)) + } + + // Emit the call to the wrapped function, which is the constructor whose result + // we will reuse in our steps. + returnsErr, err := chainTargetReturnsErr(f) + if err != nil { + return err + } + if returnsErr { + // TODO: instead of "target", would be nicer to reuse receiver variable name here (e.g., from first sample method). + // If we do that, remove "target" from reserved strings in avoidCollision. + emit("\ttarget, err := ") + } else { + emit("\ttarget := ") + } + emitWrappedFunc(emit, f, wrappedSig, "", 0, qualifyAll, inputParams, localPkg) + if returnsErr { + emit("\tif err != nil {\n") + emit("\t\treturn\n") + emit("\t}\n") + } + emit("\n") + return nil +} + +// chainTargetReturnsErr checks that we can handle this constructor as our target for our chain, +// and also reports if the target constuctor returns an error as the second of two return values. +func chainTargetReturnsErr(f *types.Func) (bool, error) { + ctorSig, ok := f.Type().(*types.Signature) + if !ok { + return false, fmt.Errorf("constructor %s is not *types.Signature (%+v)", f.Name(), f) + } + + ctorResults := ctorSig.Results() + if ctorResults.Len() > 2 || ctorResults.Len() == 0 { + return false, fmt.Errorf("constructor %s must return 1 value, or 2 values with second value of type error", f.Name()) + } + if ctorResults.Len() == 2 { + // handle case of error type as second return value + secondResult := ctorResults.At(1) + _, ok = secondResult.Type().Underlying().(*types.Interface) + if ok && secondResult.Type().String() == "error" { + return true, nil + } else { + return false, fmt.Errorf("constructor %s with 2 return values does not have second of type error", f.Name()) + } + } + return false, nil +} + +// emitChainWrapper emits one fuzzing wrapper if possible. +// It takes a list of possible constructors to insert into the wrapper body if the +// constructor is suitable for creating the receiver of a wrapped method. +// qualifyAll indicates if all variables should be qualified with their package. +func emitChainWrapper(emit emitFunc, function mod.Func, constructor mod.Func, qualifyAll bool) error { + f := function.TypesFunc + wrappedSig, ok := f.Type().(*types.Signature) + if !ok { + return fmt.Errorf("function %s is not *types.Signature (%+v)", function, f) + } + localPkg := f.Pkg() + + // Set up types.Qualifier funcs we can use with the types package + // to scope variables by a package or not. + defaultQualifier, localQualifier := qualifiers(localPkg, qualifyAll) + + // Get our receiver, which might be nil if we don't have a receiver + recv := wrappedSig.Recv() + + // Determine our wrapper name, which includes the receiver's type if we are wrapping a method, + // as well as see if the receiver matches the type of our constructor. + var wrapperName string + if recv == nil { + // TODO: could optionally include even if no receiver. For example, uuid.SetNodeID() changes global state, I think. + // TODO: could include constructors or other funcs that return the chain's targeted type. + // With return value chaining, this would support creating the ip2 in Addr.Less(ip2 Addr) in netip. + // If we restore this branch, set wrapperName = fmt.Sprintf("Fuzz_%s", f.Name()) + return errSilentSkip + } else { + n, err := findReceiverNamedType(recv) + if err != nil { + // output to stderr, but don't treat as fatal error. + fmt.Fprintf(os.Stderr, "fzgen: warning: createWrapper: failed to determine receiver type: %v: %v\n", recv, err) + return nil + } + // Check if we have a constructor that works. + ctorMatch, err := constructorMatch(recv, constructor) + if err != nil { + return fmt.Errorf("genfuncsloop: error when looking for matching constructor: %v", err) + } + if ctorMatch.sig == nil { + return errSilentSkip + } + + recvNamedTypeLocalName := types.TypeString(n.Obj().Type(), localQualifier) + wrapperName = fmt.Sprintf("Fuzz_%s_%s", recvNamedTypeLocalName, f.Name()) + } + + // Start building up our list of parameters we will use in input + // parameters to the new wrapper func we are about to emit. + var inputParams []*types.Var + + // Also add in the parameters for the function under test. + // Note that we allow zero len Params because we only get this far + // if we have a match on the receiver with the target object. + for i := 0; i < wrappedSig.Params().Len(); i++ { + v := wrappedSig.Params().At(i) + inputParams = append(inputParams, v) + } + + var paramReprs []paramRepr + for i, v := range inputParams { + typeStringWithSelector := types.TypeString(v.Type(), defaultQualifier) + paramName := avoidCollision(v, i, localPkg, inputParams) + paramReprs = append(paramReprs, paramRepr{paramName: paramName, typ: typeStringWithSelector, v: v}) + } + + // Check if we have an interface or function pointer in our desired parameters, + // which we can't fill with values during fuzzing. + support := checkParamSupport(emit, inputParams, wrapperName) + if support == noSupport { + // skip this wrapper. disallowedParams emitted a comment with more details. + return errSilentSkip + } + + // Start emitting the wrapper function, inside of a fzgen/fuzzer.Step. Will be similar to: + // Step{ + // Name: "input int", + // Func: func(a int) int { + // return a + // } + // }, + emit("\t{\n") + emit("\t\tName: \"%s\",\n", wrapperName) + emit("\t\tFunc: func(") + + switch support { + case nativeSupport, fillRequired: + // For independent wrappers, in some cases we need to emit fz.Fill to handle + // creating rich args that are beyond what cmd/go can fuzz (including because + // the standard go test infrastructure will be calling the wrappers we created). + // In contrast, for the chain steps we are creating here, we emit the same + // code for both nativeSupport and fillRequired, and handle the difference at run time. + // This is because for chain steps, we never emit fz.Fill calls because at run time + // we are the ones to call the function literal, and hence we create those rich args + // at run time, and hence here we just create function literals with the arguments + // we want. + // + // The result for this line will end up similar to: + // Func: func(s string, i *int) { + // Iterate over the our input parameters and emit. + for i, p := range paramReprs { + // want: foo string, bar int + if i > 0 { + // need a comma if something has already been emitted + emit(", ") + } + emit("%s %s", p.paramName, p.typ) + } + emit(") ") + + // TODO: centralize error check logic + results := wrappedSig.Results() + if results.Len() > 0 && !(results.Len() == 1 && results.At(0).Type().String() == "error") { + emit("(") // goimports should clean up paren if it is not needed + for i := 0; i < results.Len(); i++ { + if i > 0 { + emit(", ") + } + returnTypeStringWithSelector := types.TypeString(results.At(i).Type(), defaultQualifier) + emit(returnTypeStringWithSelector) + } + emit(")") + } + emit(" {\n") + + // For independent wrappers, we do emitNilChecks for parameters to avoid boring crashes, + // but chained wrappers we always call new if we find a pointer at run time. + // TODO: consider uintptr, unsafe.Pointer, ... + default: + panic(fmt.Sprintf("unexpected result from checkParamSupport: %v", support)) + } + + // Emit the call to the wrapped function. + // collisionOffset is 0 because we do not have a constructor within this function + // literal we are creating and hence we don't need to worry about calculating + // a collisionOffset. + // Include a 'return' if we have a non-error return value for our wrapped func. + results := wrappedSig.Results() + if results.Len() > 0 && !(results.Len() == 1 && results.At(0).Type().String() == "error") { + emit("\treturn ") + } + emitWrappedFunc(emit, f, wrappedSig, "target", 0, qualifyAll, inputParams, localPkg) + + // close out the func as well as the Step struct + emit("\t\t},\n") + emit("\t},\n") + return nil +} + +// implementsEncodingMarshaler reports if f implements encoding.BinaryMarshaler or encoding.TextMarshaler. +// TODO: this is a bit quick. replace with table driven check that handles other common roundrip interfaces & patterns. +func implementsEncodingMarshaler(f *types.Func, desiredName string) (bool, error) { + sig, ok := f.Type().(*types.Signature) + if !ok { + return false, fmt.Errorf("func %s is not *types.Signature (%+v)", f.Name(), f) + } + + if f.Name() != desiredName { + return false, nil + } + + // check params + params := sig.Params() + if params.Len() != 0 { + return false, nil + } + + // check return values + ctorResults := sig.Results() + if ctorResults.Len() != 2 { + return false, nil + } + + // check if first return type is []byte. + firstResult := ctorResults.At(0) + if firstResult.Type().String() != "[]byte" && firstResult.Type().String() != "[]uint8" { + return false, nil + } + + // check if second return type is error type. + secondResult := ctorResults.At(1) + _, ok = secondResult.Type().Underlying().(*types.Interface) + if !ok || secondResult.Type().String() != "error" { + return false, nil + } + + return true, nil +} + +// implementsEncodingUnmarshaler reports if f implements encoding.BinaryUnmarshaler or encoding.TextUnmarshaler. +// TODO: this is a bit quick. replace with table driven check that handles other common roundrip interfaces & patterns. +func implementsEncodingUnmarshaler(f *types.Func, desiredName string) (bool, error) { + sig, ok := f.Type().(*types.Signature) + if !ok { + return false, fmt.Errorf("func %s is not *types.Signature (%+v)", f.Name(), f) + } + + if f.Name() != desiredName { + return false, nil + } + + // check params + params := sig.Params() + if params.Len() != 1 { + return false, nil + } + + // check if only param is []byte. + param := params.At(0) + if param.Type().String() != "[]byte" && param.Type().String() != "[]uint8" { + return false, nil + } + + // check return values + ctorResults := sig.Results() + if ctorResults.Len() != 1 { + return false, nil + } + + // check if only return type is error type. + secondResult := ctorResults.At(0) + _, ok = secondResult.Type().Underlying().(*types.Interface) + if !ok || secondResult.Type().String() != "error" { + return false, nil + } + + return true, nil +} + +// TODO: make template, and make table driven to handle additional common patterns. +var encodingTextMarshalerRoundtripTmpl string = ` + // Check MarshalText. + result1, err := target.MarshalText() + if err != nil { + // Some targets should never return an error here for an object created by a constructor. + // If that is the case for your target, you can change this to a panic(err) or t.Fatal. + return + } + + // Check UnmarshalText. + var tmp1 %s + err = tmp1.UnmarshalText(result1) + if err != nil { + panic(fmt.Sprintf("UnmarshalText failed after successful MarshalText. original: %%v marshalled: %%q error: %%v", target, result1, err)) + } + if !reflect.DeepEqual(target, tmp1) { + panic(fmt.Sprintf("MarshalText/UnmarshalText roundtrip equality failed. original: %%v marshalled: %%q unmarshalled: %%v", target, result1, tmp1)) + } +` + +var encodingBinaryMarshalerRoundtripTmpl string = ` + // Check MarshalBinary. + result2, err := target.MarshalBinary() + if err != nil { + // Some targets should never return an error here for an object created by a constructor. + // If that is the case for your target, you can change this to a panic(err) or t.Fatal. + return + } + + // Check UnmarshalBinary. + var tmp2 %s + err = tmp2.UnmarshalBinary(result2) + if err != nil { + panic(fmt.Sprintf("UnmarshalBinary failed after successful MarshalBinary. original: %%v %%#v marshalled: %%q error: %%v", target, target, result2, err)) + } + if !reflect.DeepEqual(target, tmp2) { + panic(fmt.Sprintf("MarshalBinary/UnmarshalBinary roundtrip equality failed. original: %%v %%#v marshalled: %%q unmarshalled: %%v %%#v", + target, target, result2, tmp2, tmp2)) + } +` diff --git a/gen/genfuncsloop_test.go b/gen/genfuncsloop_test.go new file mode 100644 index 0000000..8f8a17f --- /dev/null +++ b/gen/genfuncsloop_test.go @@ -0,0 +1,213 @@ +package fzgen + +import ( + "io/ioutil" + "path/filepath" + "testing" + + "github.com/google/go-cmp/cmp" +) + +// to update golden files in ./testdata: +// go test -update + +// The first subtest here is the simplest & most useful single test in the file. To run just that: +// go test -run=Race/race_exported_not_local_pkg + +func TestChainRace(t *testing.T) { + tests := []struct { + name string // Note: we use the test name also as the golden filename + onlyExported bool + qualifyAll bool + parallel bool + }{ + { + name: "race_exported_not_local_pkg.go", + onlyExported: true, + qualifyAll: true, + parallel: true, + }, + { + name: "race_exported_local_pkg.go", + onlyExported: true, + qualifyAll: false, + parallel: false, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + pkgPattern := "github.com/thepudds/fzgen/examples/inputs/race" + options := flagExcludeFuzzPrefix | flagAllowMultiFuzz + if tt.onlyExported { + options |= flagRequireExported + } + functions, err := findFunc(pkgPattern, ".", nil, options) + if err != nil { + t.Fatalf("FindFuncfail() failed: %v", err) + } + + wrapperOpts := wrapperOptions{ + qualifyAll: tt.qualifyAll, + insertConstructors: true, + constructorPattern: "^New", + parallel: tt.parallel, + } + + out, err := emitChainWrappers(pkgPattern, functions, wrapperOpts) + if err != nil { + t.Fatalf("createWrappers() failed: %v", err) + } + + got := string(out) + golden := filepath.Join("..", "testdata", tt.name) + if *updateFlag { + // Note: using Fatalf above including so that we don't update if there was an earlier failure. + err = ioutil.WriteFile(golden, []byte(got), 0o644) + if err != nil { + t.Fatalf("failed to update golden file: %v", err) + } + } + b, err := ioutil.ReadFile(golden) + if err != nil { + t.Fatalf("failed to read golden file: %v", err) + } + want := string(b) + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("emitChainWrappers() mismatch (-want +got):\n%s", diff) + } + }) + } +} + +func TestChainUUID(t *testing.T) { + tests := []struct { + name string // Note: we use the test name also as the golden filename + onlyExported bool + parallel bool + qualifyAll bool + }{ + { + name: "uuid_exported_local_pkg.go", + onlyExported: true, + parallel: true, + qualifyAll: false, + }, + { + name: "uuid_exported_not_local_pkg.go", + onlyExported: true, + parallel: true, + qualifyAll: true, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + pkgPattern := "github.com/thepudds/fzgen/examples/inputs/test-chain-uuid" + options := flagExcludeFuzzPrefix | flagAllowMultiFuzz + if tt.onlyExported { + options |= flagRequireExported + } + functions, err := findFunc(pkgPattern, ".", nil, options) + if err != nil { + t.Fatalf("FindFuncfail() failed: %v", err) + } + + wrapperOpts := wrapperOptions{ + qualifyAll: tt.qualifyAll, + insertConstructors: true, + constructorPattern: "^New", + parallel: tt.parallel, + } + + out, err := emitChainWrappers(pkgPattern, functions, wrapperOpts) + if err != nil { + t.Fatalf("createWrappers() failed: %v", err) + } + + got := string(out) + golden := filepath.Join("..", "testdata", tt.name) + if *updateFlag { + // Note: using Fatalf above including so that we don't update if there was an earlier failure. + err = ioutil.WriteFile(golden, []byte(got), 0o644) + if err != nil { + t.Fatalf("failed to update golden file: %v", err) + } + } + b, err := ioutil.ReadFile(golden) + if err != nil { + t.Fatalf("failed to read golden file: %v", err) + } + want := string(b) + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("emitChainWrappers() mismatch (-want +got):\n%s", diff) + } + }) + } +} + +func TestChainNilChecks(t *testing.T) { + tests := []struct { + name string // Note: we use the test name also as the golden filename + onlyExported bool + qualifyAll bool + parallel bool + }{ + { + name: "nil_checks_exported_not_local_pkg.go", + onlyExported: true, + qualifyAll: true, + parallel: false, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + pkgPattern := "github.com/thepudds/fzgen/examples/inputs/test-types" + options := flagExcludeFuzzPrefix | flagAllowMultiFuzz + if tt.onlyExported { + options |= flagRequireExported + } + functions, err := findFunc(pkgPattern, ".", nil, options) + if err != nil { + t.Fatalf("FindFuncfail() failed: %v", err) + } + + wrapperOpts := wrapperOptions{ + qualifyAll: tt.qualifyAll, + insertConstructors: true, + constructorPattern: "^New", + parallel: tt.parallel, + } + + out, err := emitChainWrappers(pkgPattern, functions, wrapperOpts) + if err != nil { + t.Fatalf("createWrappers() failed: %v", err) + } + + got := string(out) + golden := filepath.Join("..", "testdata", tt.name) + if *updateFlag { + // Note: using Fatalf above including so that we don't update if there was an earlier failure. + err = ioutil.WriteFile(golden, []byte(got), 0o644) + if err != nil { + t.Fatalf("failed to update golden file: %v", err) + } + } + b, err := ioutil.ReadFile(golden) + if err != nil { + t.Fatalf("failed to read golden file: %v", err) + } + want := string(b) + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("emitChainWrappers() mismatch (-want +got):\n%s", diff) + } + }) + } +} diff --git a/gen/internal/mod/packages.go b/gen/internal/mod/packages.go index c7a8b12..f356a0c 100644 --- a/gen/internal/mod/packages.go +++ b/gen/internal/mod/packages.go @@ -1,19 +1,10 @@ -package fuzz +package mod import ( "fmt" "go/types" - "os" - "os/exec" - "regexp" - "sort" - "strings" - - "golang.org/x/tools/go/packages" ) -const buildTagsArg = "-tags=gofuzz fuzz" - // Func represents a discovered function that will be fuzzed. type Func struct { FuncName string @@ -24,8 +15,8 @@ type Func struct { } // FuzzName returns the '.' string. -// For example, it might be 'fmt.FuzzFmt'. This is used -// in messages, as well it is part of the path when creating +// For example, it might be 'fmt.FuzzFmt'. In fzgo, +// this was used in messages, and as part of the path when creating // the corpus location under testdata. func (f *Func) FuzzName() string { return fmt.Sprintf("%s.%s", f.PkgName, f.FuncName) @@ -34,109 +25,3 @@ func (f *Func) FuzzName() string { func (f *Func) String() string { return f.FuzzName() } - -// FindFunc searches for a requested function to fuzz. -// It is not an error to not find any -- in that case, it returns a nil list and nil error. -// The March 2017 proposal document https://github.com/golang/go/issues/19109#issuecomment-285456008 -// suggests not allowing something like 'go test -fuzz=. ./...' to match multiple fuzz functions. -// As an experiment, allowMultiFuzz flag allows that. -// FindFunc also allows for multiple packages in pkgPattern separated by whitespace. -func FindFunc(pkgPattern, funcPattern string, env []string, allowMultiFuzz bool) ([]Func, error) { - report := func(err error) error { - return fmt.Errorf("error while loading packages for pattern %v: %v", pkgPattern, err) - } - var result []Func - - // load packages based on our package pattern - // build tags example: https://groups.google.com/d/msg/golang-tools/Adwr7jEyDmw/wQZ5qi8ZGAAJ - cfg := &packages.Config{ - Mode: packages.LoadSyntax, - BuildFlags: []string{buildTagsArg}, - } - if len(env) > 0 { - cfg.Env = env - } - pkgs, err := packages.Load(cfg, strings.Fields(pkgPattern)...) - if err != nil { - return nil, report(err) - } - if packages.PrintErrors(pkgs) > 0 { - return nil, fmt.Errorf("package load error for package pattern %v", pkgPattern) - } - - // look for a func that starts with 'Fuzz' and matches our regexp. - // loop over the packages we found and loop over the Defs for each package. - for _, pkg := range pkgs { - for id, obj := range pkg.TypesInfo.Defs { - // check if we have a func - f, ok := obj.(*types.Func) - if ok { - - // check if it starts with "Fuzz" and matches our fuzz function regular expression - if !strings.HasPrefix(id.Name, "Fuzz") { - continue - } - - matchedPattern, err := regexp.MatchString(funcPattern, id.Name) - if err != nil { - return nil, report(err) - } - if matchedPattern { - // found a match. - // check if we already found a match in a prior iteration our of loops. - if len(result) > 0 && !allowMultiFuzz { - return nil, fmt.Errorf("multiple matches not allowed. multiple matches for pattern %v and func %v: %v.%v and %v.%v", - pkgPattern, funcPattern, pkg.PkgPath, id.Name, result[0].PkgPath, result[0].FuncName) - } - pkgDir, err := goListDir(pkg.PkgPath, env) - if err != nil { - return nil, report(err) - } - - function := Func{ - FuncName: id.Name, PkgName: pkg.Name, PkgPath: pkg.PkgPath, PkgDir: pkgDir, - TypesFunc: f, - } - result = append(result, function) - - // keep looping to see if we find another match - } - } - } - } - // done looking - if len(result) == 0 { - return nil, fmt.Errorf("failed to find fuzz function for pattern %v and func %v", pkgPattern, funcPattern) - } - // put our found functions into a deterministic order - sort.Slice(result, func(i, j int) bool { - // types.Func.String outputs strings like: - // func (github.com/thepudds/fzgo/genfuzzfuncs/examples/test-constructor-injection.A).ValMethodWithArg(i int) bool - // works ok for clustering results, though pointer receiver and non-pointer receiver methods don't cluster. - // for direct fuzzing, we only support functions, not methods, though for wrapping (outside of fzgo itself) we support methods. - // could strip '*' or sort another way, but probably ok, at least for now. - return result[i].TypesFunc.String() < result[j].TypesFunc.String() - }) - - return result, nil -} - -// goListDir returns the dir for a package import path -func goListDir(pkgPath string, env []string) (string, error) { - if len(env) == 0 { - env = os.Environ() - } - - cmd := exec.Command("go", "list", "-f", "{{.Dir}}", buildTagsArg, pkgPath) - cmd.Env = env - - out, err := cmd.Output() - if err != nil { - return "", fmt.Errorf("failed to find directory of %v: %v", pkgPath, err) - } - result := strings.TrimSpace(string(out)) - if strings.Contains(result, "\n") { - return "", fmt.Errorf("multiple directory results for package %v", pkgPath) - } - return result, nil -} diff --git a/gen/packages.go b/gen/packages.go new file mode 100644 index 0000000..e0be933 --- /dev/null +++ b/gen/packages.go @@ -0,0 +1,189 @@ +package fzgen + +import ( + "fmt" + "go/types" + "os" + "os/exec" + "regexp" + "strings" + + "github.com/thepudds/fzgen/gen/internal/mod" + "golang.org/x/tools/go/packages" +) + +// FindFuncFlag describes bitwise flags for FindFunc +// TODO: this is a temporary fork from fzgo/fuzz.FindFunc. +type FindFuncFlag uint + +const ( + flagAllowMultiFuzz FindFuncFlag = 1 << iota + flagRequireFuzzPrefix + flagExcludeFuzzPrefix + flagRequireExported +) + +// findFunc searches for requested functions matching a package pattern and func pattern. +// TODO: this is a temporary fork from fzgo/fuzz.findFunc. +// TODO: maybe change flags to a predicate function? +func findFunc(pkgPattern, funcPattern string, env []string, flags FindFuncFlag) ([]mod.Func, error) { + report := func(err error) error { + return fmt.Errorf("error while loading packages for pattern %v: %v", pkgPattern, err) + } + var result []mod.Func + + // load packages based on our package pattern + // TODO: set build tags? Previously: BuildFlags: []string{buildTagsArg}, retain? probably not needed. + // build tags example: https://groups.google.com/d/msg/golang-tools/Adwr7jEyDmw/wQZ5qi8ZGAAJ + cfg := &packages.Config{ + Mode: packages.LoadSyntax, + // TODO: packages.LoadSyntax is deprecated, so consider something similar to: + // Mode: packages.NeedSyntax | packages.NeedTypes | packages.NeedTypesInfo, + // However, that specific change is not correct. + // With that change, 'fzgen -pkg=github.com/google/uuid' from an empty directory in + // a module with correct uuid 'require' fails with error: + // error while loading packages for pattern github.com/google/uuid: failed to find directory for package "": exit status 1 + // Note empty string for what should be the package path at "...directory for package %q"? + // Maybe revist only after restoring end-to-end testing via testscripts. + } + if len(env) > 0 { + cfg.Env = env + } + pkgs, err := packages.Load(cfg, pkgPattern) + if err != nil { + return nil, report(err) + } + if packages.PrintErrors(pkgs) > 0 { + return nil, fmt.Errorf("package load error for package pattern %v", pkgPattern) + } + + // look for a func that starts with 'Fuzz' and matches our regexp. + // loop over the packages we found and loop over the Defs for each package. + for _, pkg := range pkgs { + // TODO: consider alternative: "from a Package, look at Syntax.Scope.Objects and filter with ast.IsExported." + for id, obj := range pkg.TypesInfo.Defs { + // check if we have a func + f, ok := obj.(*types.Func) + if ok { + if isInterfaceRecv(f) { + // TODO: control via flag? + // TODO: merge back to fzgo/fuzz.FindFunc? + continue + } + if flags&flagExcludeFuzzPrefix != 0 && strings.HasPrefix(id.Name, "Fuzz") { + // skip any function that already starts with Fuzz + continue + } + if flags&flagRequireFuzzPrefix != 0 && !strings.HasPrefix(id.Name, "Fuzz") { + // skip any function that does not start with Fuzz + continue + } + if flags&flagRequireExported != 0 { + if !isExportedFunc(f) { + continue + } + } + + matchedPattern, err := regexp.MatchString(funcPattern, id.Name) + if err != nil { + return nil, report(err) + } + if matchedPattern { + // found a match. + // check if we already found a match in a prior iteration our of chains. + if len(result) > 0 && flags&flagAllowMultiFuzz == 0 { + return nil, fmt.Errorf("multiple matches not allowed. multiple matches for pattern %v and func %v: %v.%v and %v.%v", + pkgPattern, funcPattern, pkg.PkgPath, id.Name, result[0].PkgPath, result[0].FuncName) + } + pkgDir, err := goListDir(pkg.PkgPath, env) + if err != nil { + return nil, report(err) + } + + function := mod.Func{ + FuncName: id.Name, PkgName: pkg.Name, PkgPath: pkg.PkgPath, PkgDir: pkgDir, + TypesFunc: f, + } + result = append(result, function) + + // keep chaining to see if we find another match + } + } + } + } + // done looking + if len(result) == 0 { + return nil, fmt.Errorf("failed to find any functions for package pattern %q and func pattern %q", pkgPattern, funcPattern) + } + return result, nil +} + +// goListDir returns the dir for a package import path. +func goListDir(pkgPath string, env []string) (string, error) { + if len(env) == 0 { + env = os.Environ() + } + + // TODO: use build tags, or not? + // cmd := exec.Command("go", "list", "-f", "{{.Dir}}", buildTagsArg, pkgPath) + cmd := exec.Command("go", "list", "-f", "{{.Dir}}", pkgPath) + cmd.Env = env + cmd.Stderr = os.Stderr + + out, err := cmd.Output() + if err != nil { + // If this fails with a pkgPath as empty string, check packages.Config.Mode + fmt.Fprintf(os.Stderr, "fzgen: 'go list -f {{.Dir}} %v' failed for pkgPath %q\n%v\n", pkgPath, pkgPath, string(out)) + return "", fmt.Errorf("failed to find directory for package %q: %v", pkgPath, err) + } + result := strings.TrimSpace(string(out)) + if strings.Contains(result, "\n") { + return "", fmt.Errorf("multiple directory results for package %v", pkgPath) + } + return result, nil +} + +// TODO: would be good to find some canonical documentation or example of this. +func isExportedFunc(f *types.Func) bool { + if !f.Exported() { + return false + } + // the function itself is exported, but it might be a method on an unexported type. + sig, ok := f.Type().(*types.Signature) + if !ok { + return false + } + recv := sig.Recv() + if recv == nil { + // not a method, and the func itself is exported. + return true + } + + n, err := findReceiverNamedType(recv) + if err != nil { + // don't treat as fatal error. + fmt.Fprintf(os.Stderr, "genfuzzfuncs: warning: failed to determine if exported for receiver %v for func %v: %v\n", + recv, f, err) + return false + } + + return n.Obj().Exported() +} + +// isInterfaceRecv helps filter out interface receivers such as 'func (interface).Is(error) bool' +// Previously would have issues from errors.Is: +// x, ok := err.(interface{ Is(error) bool }); ok && x.Is(target) +func isInterfaceRecv(f *types.Func) bool { + sig, ok := f.Type().(*types.Signature) + if !ok { + return false + } + recv := sig.Recv() + if recv == nil { + // not a method + return false + } + // TODO: should this be Type().Underlying()? + _, ok = recv.Type().(*types.Interface) + return ok +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..a4ca79a --- /dev/null +++ b/go.mod @@ -0,0 +1,18 @@ +module github.com/thepudds/fzgen + +go 1.17 + +require ( + github.com/google/go-cmp v0.5.6 + github.com/rogpeppe/go-internal v1.8.1 + github.com/sanity-io/litter v1.5.1 + golang.org/x/tools v0.1.8 +) + +require ( + github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e // indirect + golang.org/x/mod v0.5.1 // indirect + golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 // indirect + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect + gopkg.in/errgo.v2 v2.1.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..2d606ab --- /dev/null +++ b/go.sum @@ -0,0 +1,52 @@ +github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b h1:XxMZvQZtTXpWMNWK82vdjCLCe7uGMFXdTsJH0v3Hkvw= +github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e h1:aoZm08cpOy4WuID//EZDgcC4zIxODThtZNPirFr42+A= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0 h1:GD+A8+e+wFkqje55/2fOVnZPkoDIu1VooBWfNrnY8Uo= +github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= +github.com/sanity-io/litter v1.5.1 h1:dwnrSypP6q56o3lFxTU+t2fwQ9A+U5qrXVO4Qg9KwVU= +github.com/sanity-io/litter v1.5.1/go.mod h1:5Z71SvaYy5kcGtyglXOC9rrUi3c1E8CamFWjQsazTh0= +github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312 h1:UsFdQ3ZmlzS0BqZYGxvYaXvFGUbCmPGy8DM7qWJJiIQ= +github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38= +golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 h1:id054HUawV2/6IGm2IV8KZQjqtwAOo2CYlOToYqa0d0= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.8 h1:P1HhGGuLW4aAclzjtmJdf0mJOjVUZUzOTqkAkWL+l6w= +golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= diff --git a/script_test.go b/script_test.go index b7d2818..4e5f943 100644 --- a/script_test.go +++ b/script_test.go @@ -1,32 +1,70 @@ -package main +package fzgen import ( + "flag" + "log" "os" + "runtime" "testing" "github.com/rogpeppe/go-internal/gotooltest" "github.com/rogpeppe/go-internal/testscript" + gen "github.com/thepudds/fzgen/gen" ) +var end2EndFlag = flag.Bool("end2end", false, "run longer end-to-end tests that assume a 1.18 gotip in PATH") + func TestMain(m *testing.M) { - os.Exit(testscript.RunMain(fzgoTestingMain{m}, map[string]func() int{ - "fzgo": fzgoMain, + os.Exit(testscript.RunMain(fzgenTestingMain{m}, map[string]func() int{ + "fzgen": gen.FzgenMain, })) } -type fzgoTestingMain struct { +func TestScripts(t *testing.T) { + if !*end2EndFlag { + t.Skip("skipping longer end-to-end tests that assume a 1.18 gotip in PATH. use -end2end flag to run.") + } + p := testscript.Params{ + Dir: "testscripts", + // For gotip from our path to work without re-downloading, + // we need a valid HOME env var. Testscripts default to HOME=/no-home + // If we don't do this here, get failure: + // gotip: not downloaded. Run 'gotip download' to install to /no-home/sdk/gotip + Setup: func(e *testscript.Env) error { + home, err := os.UserHomeDir() + if err != nil { + log.Fatal(err) + } + wd, err := os.Getwd() + if err != nil { + log.Fatal(err) + } + e.Vars = append(e.Vars, + homeEnvVar()+"="+home, + "FZLOCALDIR="+wd, + ) + return nil + }, + // TestWork: true, // setting -testwork should work, so hopefully no need to manually set here + } + if err := gotooltest.Setup(&p); err != nil { + t.Fatal(err) + } + testscript.Run(t, p) +} + +type fzgenTestingMain struct { m *testing.M } -func (m fzgoTestingMain) Run() int { +func (m fzgenTestingMain) Run() int { // could do additional setup here if needed (e.g., check or set env vars, start a Go proxy server, etc.) return m.m.Run() } -func TestScripts(t *testing.T) { - p := testscript.Params{Dir: "testscripts"} - if err := gotooltest.Setup(&p); err != nil { - t.Fatal(err) +func homeEnvVar() string { + if runtime.GOOS == "windows" { + return "USERPROFILE" } - testscript.Run(t, p) + return "HOME" } diff --git a/test.sh b/test.sh new file mode 100644 index 0000000..f6508f3 --- /dev/null +++ b/test.sh @@ -0,0 +1,21 @@ +#!/bin/bash +set -x + +export CGO_ENABLED=0 + +# fast. +go test ./fuzzer/... + +# fast-ish. +go test ./gen -vet=off -run=ChainRace/race_exported_not_local_pkg +go test ./gen -vet=off -run=Types + +# -end2end faster than ./gen. +go test . -vet=off -run=TestScript/return_reuse -end2end +go test . -vet=off -end2end + +# slowest. +go test ./gen -vet=off + +# wrap up with go vet. -vet=off seemed to help above, but should confirm. +go vet ./... diff --git a/testdata/exported_and_private_local_pkg.go b/testdata/exported_and_private_local_pkg.go new file mode 100644 index 0000000..9f4ec6f --- /dev/null +++ b/testdata/exported_and_private_local_pkg.go @@ -0,0 +1,133 @@ +package fuzzwrapexamples + +// if needed, fill in imports or run 'goimports' +import ( + "io" + "testing" + + "github.com/thepudds/fzgen/fuzzer" +) + +func Fuzz_TypeExported_PointerExportedMethod(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var t1 *TypeExported + var i int + fz := fuzzer.NewFuzzer(data) + fz.Fill(&t1, &i) + if t1 == nil { + return + } + + t1.PointerExportedMethod(i) + }) +} + +func Fuzz_TypeExported_pointerRcvNotExportedMethod(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var t1 *TypeExported + var i int + fz := fuzzer.NewFuzzer(data) + fz.Fill(&t1, &i) + if t1 == nil { + return + } + + t1.pointerRcvNotExportedMethod(i) + }) +} + +func Fuzz_typeNotExported_PointerExportedMethod(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var t1 *typeNotExported + var i int + fz := fuzzer.NewFuzzer(data) + fz.Fill(&t1, &i) + if t1 == nil { + return + } + + t1.PointerExportedMethod(i) + }) +} + +func Fuzz_typeNotExported_pointerRcvNotExportedMethod(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var t1 *typeNotExported + var i int + fz := fuzzer.NewFuzzer(data) + fz.Fill(&t1, &i) + if t1 == nil { + return + } + + t1.pointerRcvNotExportedMethod(i) + }) +} + +func Fuzz_TypeExported_NonPointerExportedMethod(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var t1 TypeExported + var i int + fz := fuzzer.NewFuzzer(data) + fz.Fill(&t1, &i) + + t1.NonPointerExportedMethod(i) + }) +} + +func Fuzz_TypeExported_nonPointerRcvNotExportedMethod(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var t1 TypeExported + var i int + fz := fuzzer.NewFuzzer(data) + fz.Fill(&t1, &i) + + t1.nonPointerRcvNotExportedMethod(i) + }) +} + +func Fuzz_typeNotExported_NonPointerExportedMethod(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var t1 typeNotExported + var i int + fz := fuzzer.NewFuzzer(data) + fz.Fill(&t1, &i) + + t1.NonPointerExportedMethod(i) + }) +} + +func Fuzz_typeNotExported_nonPointerRcvNotExportedMethod(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var t1 typeNotExported + var i int + fz := fuzzer.NewFuzzer(data) + fz.Fill(&t1, &i) + + t1.nonPointerRcvNotExportedMethod(i) + }) +} + +func Fuzz_FuncExported(f *testing.F) { + f.Fuzz(func(t *testing.T, i int) { + FuncExported(i) + }) +} + +func Fuzz_FuncExportedUsesSupportedInterface(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var w io.Reader + fz := fuzzer.NewFuzzer(data) + fz.Fill(&w) + + FuncExportedUsesSupportedInterface(w) + }) +} + +// skipping Fuzz_FuncExportedUsesUnsupportedInterface because parameters include unsupported interface: github.com/thepudds/fzgen/examples/inputs/test-exported.ExportedInterface + +func Fuzz_funcNotExported(f *testing.F) { + f.Fuzz(func(t *testing.T, i int) { + funcNotExported(i) + }) +} diff --git a/testdata/exported_and_private_not_local_pkg.go b/testdata/exported_and_private_not_local_pkg.go new file mode 100644 index 0000000..f26fc9d --- /dev/null +++ b/testdata/exported_and_private_not_local_pkg.go @@ -0,0 +1,134 @@ +package fuzzwrapexamplesfuzz // rename if needed + +// if needed, fill in imports or run 'goimports' +import ( + "io" + "testing" + + fuzzwrapexamples "github.com/thepudds/fzgen/examples/inputs/test-exported" + "github.com/thepudds/fzgen/fuzzer" +) + +func Fuzz_TypeExported_PointerExportedMethod(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var t1 *fuzzwrapexamples.TypeExported + var i int + fz := fuzzer.NewFuzzer(data) + fz.Fill(&t1, &i) + if t1 == nil { + return + } + + t1.PointerExportedMethod(i) + }) +} + +func Fuzz_TypeExported_pointerRcvNotExportedMethod(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var t1 *fuzzwrapexamples.TypeExported + var i int + fz := fuzzer.NewFuzzer(data) + fz.Fill(&t1, &i) + if t1 == nil { + return + } + + t1.pointerRcvNotExportedMethod(i) + }) +} + +func Fuzz_typeNotExported_PointerExportedMethod(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var t1 *fuzzwrapexamples.typeNotExported + var i int + fz := fuzzer.NewFuzzer(data) + fz.Fill(&t1, &i) + if t1 == nil { + return + } + + t1.PointerExportedMethod(i) + }) +} + +func Fuzz_typeNotExported_pointerRcvNotExportedMethod(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var t1 *fuzzwrapexamples.typeNotExported + var i int + fz := fuzzer.NewFuzzer(data) + fz.Fill(&t1, &i) + if t1 == nil { + return + } + + t1.pointerRcvNotExportedMethod(i) + }) +} + +func Fuzz_TypeExported_NonPointerExportedMethod(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var t1 fuzzwrapexamples.TypeExported + var i int + fz := fuzzer.NewFuzzer(data) + fz.Fill(&t1, &i) + + t1.NonPointerExportedMethod(i) + }) +} + +func Fuzz_TypeExported_nonPointerRcvNotExportedMethod(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var t1 fuzzwrapexamples.TypeExported + var i int + fz := fuzzer.NewFuzzer(data) + fz.Fill(&t1, &i) + + t1.nonPointerRcvNotExportedMethod(i) + }) +} + +func Fuzz_typeNotExported_NonPointerExportedMethod(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var t1 fuzzwrapexamples.typeNotExported + var i int + fz := fuzzer.NewFuzzer(data) + fz.Fill(&t1, &i) + + t1.NonPointerExportedMethod(i) + }) +} + +func Fuzz_typeNotExported_nonPointerRcvNotExportedMethod(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var t1 fuzzwrapexamples.typeNotExported + var i int + fz := fuzzer.NewFuzzer(data) + fz.Fill(&t1, &i) + + t1.nonPointerRcvNotExportedMethod(i) + }) +} + +func Fuzz_FuncExported(f *testing.F) { + f.Fuzz(func(t *testing.T, i int) { + fuzzwrapexamples.FuncExported(i) + }) +} + +func Fuzz_FuncExportedUsesSupportedInterface(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var w io.Reader + fz := fuzzer.NewFuzzer(data) + fz.Fill(&w) + + fuzzwrapexamples.FuncExportedUsesSupportedInterface(w) + }) +} + +// skipping Fuzz_FuncExportedUsesUnsupportedInterface because parameters include unsupported interface: github.com/thepudds/fzgen/examples/inputs/test-exported.ExportedInterface + +func Fuzz_funcNotExported(f *testing.F) { + f.Fuzz(func(t *testing.T, i int) { + fuzzwrapexamples.funcNotExported(i) + }) +} diff --git a/testdata/exported_local_pkg.go b/testdata/exported_local_pkg.go new file mode 100644 index 0000000..5d4d6be --- /dev/null +++ b/testdata/exported_local_pkg.go @@ -0,0 +1,52 @@ +package fuzzwrapexamples + +// if needed, fill in imports or run 'goimports' +import ( + "io" + "testing" + + "github.com/thepudds/fzgen/fuzzer" +) + +func Fuzz_TypeExported_PointerExportedMethod(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var t1 *TypeExported + var i int + fz := fuzzer.NewFuzzer(data) + fz.Fill(&t1, &i) + if t1 == nil { + return + } + + t1.PointerExportedMethod(i) + }) +} + +func Fuzz_TypeExported_NonPointerExportedMethod(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var t1 TypeExported + var i int + fz := fuzzer.NewFuzzer(data) + fz.Fill(&t1, &i) + + t1.NonPointerExportedMethod(i) + }) +} + +func Fuzz_FuncExported(f *testing.F) { + f.Fuzz(func(t *testing.T, i int) { + FuncExported(i) + }) +} + +func Fuzz_FuncExportedUsesSupportedInterface(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var w io.Reader + fz := fuzzer.NewFuzzer(data) + fz.Fill(&w) + + FuncExportedUsesSupportedInterface(w) + }) +} + +// skipping Fuzz_FuncExportedUsesUnsupportedInterface because parameters include unsupported interface: github.com/thepudds/fzgen/examples/inputs/test-exported.ExportedInterface diff --git a/testdata/exported_not_local_pkg.go b/testdata/exported_not_local_pkg.go new file mode 100644 index 0000000..54c51c8 --- /dev/null +++ b/testdata/exported_not_local_pkg.go @@ -0,0 +1,53 @@ +package fuzzwrapexamplesfuzz // rename if needed + +// if needed, fill in imports or run 'goimports' +import ( + "io" + "testing" + + fuzzwrapexamples "github.com/thepudds/fzgen/examples/inputs/test-exported" + "github.com/thepudds/fzgen/fuzzer" +) + +func Fuzz_TypeExported_PointerExportedMethod(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var t1 *fuzzwrapexamples.TypeExported + var i int + fz := fuzzer.NewFuzzer(data) + fz.Fill(&t1, &i) + if t1 == nil { + return + } + + t1.PointerExportedMethod(i) + }) +} + +func Fuzz_TypeExported_NonPointerExportedMethod(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var t1 fuzzwrapexamples.TypeExported + var i int + fz := fuzzer.NewFuzzer(data) + fz.Fill(&t1, &i) + + t1.NonPointerExportedMethod(i) + }) +} + +func Fuzz_FuncExported(f *testing.F) { + f.Fuzz(func(t *testing.T, i int) { + fuzzwrapexamples.FuncExported(i) + }) +} + +func Fuzz_FuncExportedUsesSupportedInterface(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var w io.Reader + fz := fuzzer.NewFuzzer(data) + fz.Fill(&w) + + fuzzwrapexamples.FuncExportedUsesSupportedInterface(w) + }) +} + +// skipping Fuzz_FuncExportedUsesUnsupportedInterface because parameters include unsupported interface: github.com/thepudds/fzgen/examples/inputs/test-exported.ExportedInterface diff --git a/testdata/inject_ctor_false_exported_local_pkg.go b/testdata/inject_ctor_false_exported_local_pkg.go new file mode 100644 index 0000000..2d3d30c --- /dev/null +++ b/testdata/inject_ctor_false_exported_local_pkg.go @@ -0,0 +1,200 @@ +package fuzzwrapexamples + +// if needed, fill in imports or run 'goimports' +import ( + "bufio" + "testing" + + "github.com/thepudds/fzgen/fuzzer" +) + +func Fuzz_A_PtrMethodNoArg(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var r *A + fz := fuzzer.NewFuzzer(data) + fz.Fill(&r) + if r == nil { + return + } + + r.PtrMethodNoArg() + }) +} + +func Fuzz_A_PtrMethodWithArg(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var r *A + var i int + fz := fuzzer.NewFuzzer(data) + fz.Fill(&r, &i) + if r == nil { + return + } + + r.PtrMethodWithArg(i) + }) +} + +func Fuzz_B_PtrMethodNoArg(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var r *B + fz := fuzzer.NewFuzzer(data) + fz.Fill(&r) + if r == nil { + return + } + + r.PtrMethodNoArg() + }) +} + +func Fuzz_B_PtrMethodWithArg(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var r *B + var i int + fz := fuzzer.NewFuzzer(data) + fz.Fill(&r, &i) + if r == nil { + return + } + + r.PtrMethodWithArg(i) + }) +} + +func Fuzz_MyNullUUID_UnmarshalBinary(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var nu *MyNullUUID + var d2 []byte + fz := fuzzer.NewFuzzer(data) + fz.Fill(&nu, &d2) + if nu == nil { + return + } + + nu.UnmarshalBinary(d2) + }) +} + +func Fuzz_MyRegexp_Expand(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var re *MyRegexp + var dst []byte + var template []byte + var src []byte + var match []int + fz := fuzzer.NewFuzzer(data) + fz.Fill(&re, &dst, &template, &src, &match) + if re == nil { + return + } + + re.Expand(dst, template, src, match) + }) +} + +func Fuzz_Package_SetName(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var pkg *Package + var name string + fz := fuzzer.NewFuzzer(data) + fz.Fill(&pkg, &name) + if pkg == nil { + return + } + + pkg.SetName(name) + }) +} + +func Fuzz_Z_ReadLine(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var z *Z + fz := fuzzer.NewFuzzer(data) + fz.Fill(&z) + if z == nil { + return + } + + z.ReadLine() + }) +} + +func Fuzz_A_ValMethodNoArg(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var r A + fz := fuzzer.NewFuzzer(data) + fz.Fill(&r) + + r.ValMethodNoArg() + }) +} + +func Fuzz_A_ValMethodWithArg(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var r A + var i int + fz := fuzzer.NewFuzzer(data) + fz.Fill(&r, &i) + + r.ValMethodWithArg(i) + }) +} + +func Fuzz_B_ValMethodNoArg(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var r B + fz := fuzzer.NewFuzzer(data) + fz.Fill(&r) + + r.ValMethodNoArg() + }) +} + +func Fuzz_B_ValMethodWithArg(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var r B + var i int + fz := fuzzer.NewFuzzer(data) + fz.Fill(&r, &i) + + r.ValMethodWithArg(i) + }) +} + +func Fuzz_NewAPtr(f *testing.F) { + f.Fuzz(func(t *testing.T, c int) { + NewAPtr(c) + }) +} + +func Fuzz_NewBVal(f *testing.F) { + f.Fuzz(func(t *testing.T, c int) { + NewBVal(c) + }) +} + +func Fuzz_NewMyRegexp(f *testing.F) { + f.Fuzz(func(t *testing.T, a int) { + NewMyRegexp(a) + }) +} + +func Fuzz_NewPackage(f *testing.F) { + f.Fuzz(func(t *testing.T, path string, name string) { + NewPackage(path, name) + }) +} + +func Fuzz_NewZ(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var z *bufio.Reader + fz := fuzzer.NewFuzzer(data) + fz.Fill(&z) + if z == nil { + return + } + + NewZ(z) + }) +} diff --git a/testdata/inject_ctor_false_exported_not_local_pkg.go b/testdata/inject_ctor_false_exported_not_local_pkg.go new file mode 100644 index 0000000..152b3b7 --- /dev/null +++ b/testdata/inject_ctor_false_exported_not_local_pkg.go @@ -0,0 +1,201 @@ +package fuzzwrapexamplesfuzz // rename if needed + +// if needed, fill in imports or run 'goimports' +import ( + "bufio" + "testing" + + fuzzwrapexamples "github.com/thepudds/fzgen/examples/inputs/test-constructor-injection" + "github.com/thepudds/fzgen/fuzzer" +) + +func Fuzz_A_PtrMethodNoArg(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var r *fuzzwrapexamples.A + fz := fuzzer.NewFuzzer(data) + fz.Fill(&r) + if r == nil { + return + } + + r.PtrMethodNoArg() + }) +} + +func Fuzz_A_PtrMethodWithArg(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var r *fuzzwrapexamples.A + var i int + fz := fuzzer.NewFuzzer(data) + fz.Fill(&r, &i) + if r == nil { + return + } + + r.PtrMethodWithArg(i) + }) +} + +func Fuzz_B_PtrMethodNoArg(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var r *fuzzwrapexamples.B + fz := fuzzer.NewFuzzer(data) + fz.Fill(&r) + if r == nil { + return + } + + r.PtrMethodNoArg() + }) +} + +func Fuzz_B_PtrMethodWithArg(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var r *fuzzwrapexamples.B + var i int + fz := fuzzer.NewFuzzer(data) + fz.Fill(&r, &i) + if r == nil { + return + } + + r.PtrMethodWithArg(i) + }) +} + +func Fuzz_MyNullUUID_UnmarshalBinary(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var nu *fuzzwrapexamples.MyNullUUID + var d2 []byte + fz := fuzzer.NewFuzzer(data) + fz.Fill(&nu, &d2) + if nu == nil { + return + } + + nu.UnmarshalBinary(d2) + }) +} + +func Fuzz_MyRegexp_Expand(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var re *fuzzwrapexamples.MyRegexp + var dst []byte + var template []byte + var src []byte + var match []int + fz := fuzzer.NewFuzzer(data) + fz.Fill(&re, &dst, &template, &src, &match) + if re == nil { + return + } + + re.Expand(dst, template, src, match) + }) +} + +func Fuzz_Package_SetName(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var pkg *fuzzwrapexamples.Package + var name string + fz := fuzzer.NewFuzzer(data) + fz.Fill(&pkg, &name) + if pkg == nil { + return + } + + pkg.SetName(name) + }) +} + +func Fuzz_Z_ReadLine(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var z *fuzzwrapexamples.Z + fz := fuzzer.NewFuzzer(data) + fz.Fill(&z) + if z == nil { + return + } + + z.ReadLine() + }) +} + +func Fuzz_A_ValMethodNoArg(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var r fuzzwrapexamples.A + fz := fuzzer.NewFuzzer(data) + fz.Fill(&r) + + r.ValMethodNoArg() + }) +} + +func Fuzz_A_ValMethodWithArg(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var r fuzzwrapexamples.A + var i int + fz := fuzzer.NewFuzzer(data) + fz.Fill(&r, &i) + + r.ValMethodWithArg(i) + }) +} + +func Fuzz_B_ValMethodNoArg(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var r fuzzwrapexamples.B + fz := fuzzer.NewFuzzer(data) + fz.Fill(&r) + + r.ValMethodNoArg() + }) +} + +func Fuzz_B_ValMethodWithArg(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var r fuzzwrapexamples.B + var i int + fz := fuzzer.NewFuzzer(data) + fz.Fill(&r, &i) + + r.ValMethodWithArg(i) + }) +} + +func Fuzz_NewAPtr(f *testing.F) { + f.Fuzz(func(t *testing.T, c int) { + fuzzwrapexamples.NewAPtr(c) + }) +} + +func Fuzz_NewBVal(f *testing.F) { + f.Fuzz(func(t *testing.T, c int) { + fuzzwrapexamples.NewBVal(c) + }) +} + +func Fuzz_NewMyRegexp(f *testing.F) { + f.Fuzz(func(t *testing.T, a int) { + fuzzwrapexamples.NewMyRegexp(a) + }) +} + +func Fuzz_NewPackage(f *testing.F) { + f.Fuzz(func(t *testing.T, path string, name string) { + fuzzwrapexamples.NewPackage(path, name) + }) +} + +func Fuzz_NewZ(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var z *bufio.Reader + fz := fuzzer.NewFuzzer(data) + fz.Fill(&z) + if z == nil { + return + } + + fuzzwrapexamples.NewZ(z) + }) +} diff --git a/testdata/inject_ctor_true_exported_local_pkg.go b/testdata/inject_ctor_true_exported_local_pkg.go new file mode 100644 index 0000000..91bc401 --- /dev/null +++ b/testdata/inject_ctor_true_exported_local_pkg.go @@ -0,0 +1,155 @@ +package fuzzwrapexamples + +// if needed, fill in imports or run 'goimports' +import ( + "bufio" + "testing" + + "github.com/thepudds/fzgen/fuzzer" +) + +func Fuzz_A_PtrMethodNoArg(f *testing.F) { + f.Fuzz(func(t *testing.T, c int) { + r := NewAPtr(c) + r.PtrMethodNoArg() + }) +} + +func Fuzz_A_PtrMethodWithArg(f *testing.F) { + f.Fuzz(func(t *testing.T, c int, i int) { + r := NewAPtr(c) + r.PtrMethodWithArg(i) + }) +} + +func Fuzz_B_PtrMethodNoArg(f *testing.F) { + f.Fuzz(func(t *testing.T, c int) { + r := NewBVal(c) + r.PtrMethodNoArg() + }) +} + +func Fuzz_B_PtrMethodWithArg(f *testing.F) { + f.Fuzz(func(t *testing.T, c int, i int) { + r := NewBVal(c) + r.PtrMethodWithArg(i) + }) +} + +func Fuzz_MyNullUUID_UnmarshalBinary(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var nu *MyNullUUID + var d2 []byte + fz := fuzzer.NewFuzzer(data) + fz.Fill(&nu, &d2) + if nu == nil { + return + } + + nu.UnmarshalBinary(d2) + }) +} + +func Fuzz_MyRegexp_Expand(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var a int + var dst []byte + var template []byte + var src []byte + var match []int + fz := fuzzer.NewFuzzer(data) + fz.Fill(&a, &dst, &template, &src, &match) + + re, err := NewMyRegexp(a) + if err != nil { + return + } + re.Expand(dst, template, src, match) + }) +} + +func Fuzz_Package_SetName(f *testing.F) { + f.Fuzz(func(t *testing.T, path string, n2 string, n3 string) { + pkg := NewPackage(path, n2) + pkg.SetName(n3) + }) +} + +func Fuzz_Z_ReadLine(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var z *bufio.Reader + fz := fuzzer.NewFuzzer(data) + fz.Fill(&z) + if z == nil { + return + } + + z1 := NewZ(z) + z1.ReadLine() + }) +} + +func Fuzz_A_ValMethodNoArg(f *testing.F) { + f.Fuzz(func(t *testing.T, c int) { + r := NewAPtr(c) + r.ValMethodNoArg() + }) +} + +func Fuzz_A_ValMethodWithArg(f *testing.F) { + f.Fuzz(func(t *testing.T, c int, i int) { + r := NewAPtr(c) + r.ValMethodWithArg(i) + }) +} + +func Fuzz_B_ValMethodNoArg(f *testing.F) { + f.Fuzz(func(t *testing.T, c int) { + r := NewBVal(c) + r.ValMethodNoArg() + }) +} + +func Fuzz_B_ValMethodWithArg(f *testing.F) { + f.Fuzz(func(t *testing.T, c int, i int) { + r := NewBVal(c) + r.ValMethodWithArg(i) + }) +} + +func Fuzz_NewAPtr(f *testing.F) { + f.Fuzz(func(t *testing.T, c int) { + NewAPtr(c) + }) +} + +func Fuzz_NewBVal(f *testing.F) { + f.Fuzz(func(t *testing.T, c int) { + NewBVal(c) + }) +} + +func Fuzz_NewMyRegexp(f *testing.F) { + f.Fuzz(func(t *testing.T, a int) { + NewMyRegexp(a) + }) +} + +func Fuzz_NewPackage(f *testing.F) { + f.Fuzz(func(t *testing.T, path string, name string) { + NewPackage(path, name) + }) +} + +func Fuzz_NewZ(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var z *bufio.Reader + fz := fuzzer.NewFuzzer(data) + fz.Fill(&z) + if z == nil { + return + } + + NewZ(z) + }) +} diff --git a/testdata/inject_ctor_true_exported_not_local_pkg.go b/testdata/inject_ctor_true_exported_not_local_pkg.go new file mode 100644 index 0000000..0a8c6fa --- /dev/null +++ b/testdata/inject_ctor_true_exported_not_local_pkg.go @@ -0,0 +1,156 @@ +package fuzzwrapexamplesfuzz // rename if needed + +// if needed, fill in imports or run 'goimports' +import ( + "bufio" + "testing" + + fuzzwrapexamples "github.com/thepudds/fzgen/examples/inputs/test-constructor-injection" + "github.com/thepudds/fzgen/fuzzer" +) + +func Fuzz_A_PtrMethodNoArg(f *testing.F) { + f.Fuzz(func(t *testing.T, c int) { + r := fuzzwrapexamples.NewAPtr(c) + r.PtrMethodNoArg() + }) +} + +func Fuzz_A_PtrMethodWithArg(f *testing.F) { + f.Fuzz(func(t *testing.T, c int, i int) { + r := fuzzwrapexamples.NewAPtr(c) + r.PtrMethodWithArg(i) + }) +} + +func Fuzz_B_PtrMethodNoArg(f *testing.F) { + f.Fuzz(func(t *testing.T, c int) { + r := fuzzwrapexamples.NewBVal(c) + r.PtrMethodNoArg() + }) +} + +func Fuzz_B_PtrMethodWithArg(f *testing.F) { + f.Fuzz(func(t *testing.T, c int, i int) { + r := fuzzwrapexamples.NewBVal(c) + r.PtrMethodWithArg(i) + }) +} + +func Fuzz_MyNullUUID_UnmarshalBinary(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var nu *fuzzwrapexamples.MyNullUUID + var d2 []byte + fz := fuzzer.NewFuzzer(data) + fz.Fill(&nu, &d2) + if nu == nil { + return + } + + nu.UnmarshalBinary(d2) + }) +} + +func Fuzz_MyRegexp_Expand(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var a int + var dst []byte + var template []byte + var src []byte + var match []int + fz := fuzzer.NewFuzzer(data) + fz.Fill(&a, &dst, &template, &src, &match) + + re, err := fuzzwrapexamples.NewMyRegexp(a) + if err != nil { + return + } + re.Expand(dst, template, src, match) + }) +} + +func Fuzz_Package_SetName(f *testing.F) { + f.Fuzz(func(t *testing.T, path string, n2 string, n3 string) { + pkg := fuzzwrapexamples.NewPackage(path, n2) + pkg.SetName(n3) + }) +} + +func Fuzz_Z_ReadLine(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var z *bufio.Reader + fz := fuzzer.NewFuzzer(data) + fz.Fill(&z) + if z == nil { + return + } + + z1 := fuzzwrapexamples.NewZ(z) + z1.ReadLine() + }) +} + +func Fuzz_A_ValMethodNoArg(f *testing.F) { + f.Fuzz(func(t *testing.T, c int) { + r := fuzzwrapexamples.NewAPtr(c) + r.ValMethodNoArg() + }) +} + +func Fuzz_A_ValMethodWithArg(f *testing.F) { + f.Fuzz(func(t *testing.T, c int, i int) { + r := fuzzwrapexamples.NewAPtr(c) + r.ValMethodWithArg(i) + }) +} + +func Fuzz_B_ValMethodNoArg(f *testing.F) { + f.Fuzz(func(t *testing.T, c int) { + r := fuzzwrapexamples.NewBVal(c) + r.ValMethodNoArg() + }) +} + +func Fuzz_B_ValMethodWithArg(f *testing.F) { + f.Fuzz(func(t *testing.T, c int, i int) { + r := fuzzwrapexamples.NewBVal(c) + r.ValMethodWithArg(i) + }) +} + +func Fuzz_NewAPtr(f *testing.F) { + f.Fuzz(func(t *testing.T, c int) { + fuzzwrapexamples.NewAPtr(c) + }) +} + +func Fuzz_NewBVal(f *testing.F) { + f.Fuzz(func(t *testing.T, c int) { + fuzzwrapexamples.NewBVal(c) + }) +} + +func Fuzz_NewMyRegexp(f *testing.F) { + f.Fuzz(func(t *testing.T, a int) { + fuzzwrapexamples.NewMyRegexp(a) + }) +} + +func Fuzz_NewPackage(f *testing.F) { + f.Fuzz(func(t *testing.T, path string, name string) { + fuzzwrapexamples.NewPackage(path, name) + }) +} + +func Fuzz_NewZ(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var z *bufio.Reader + fz := fuzzer.NewFuzzer(data) + fz.Fill(&z) + if z == nil { + return + } + + fuzzwrapexamples.NewZ(z) + }) +} diff --git a/testdata/nil_checks_exported_not_local_pkg.go b/testdata/nil_checks_exported_not_local_pkg.go new file mode 100644 index 0000000..41c09f8 --- /dev/null +++ b/testdata/nil_checks_exported_not_local_pkg.go @@ -0,0 +1,42 @@ +package fuzzwrapexamplesfuzz // rename if needed + +// if needed, fill in imports or run 'goimports' +import ( + "io" + "testing" + + fuzzwrapexamples "github.com/thepudds/fzgen/examples/inputs/test-types" + "github.com/thepudds/fzgen/fuzzer" +) + +func Fuzz_NewTypesNilCheck_Chain(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + fz := fuzzer.NewFuzzer(data) + + target := fuzzwrapexamples.NewTypesNilCheck() + + steps := []fuzzer.Step{ + { + Name: "Fuzz_TypesNilCheck_Interface", + Func: func(x1 io.Writer) { + target.Interface(x1) + }, + }, + { + Name: "Fuzz_TypesNilCheck_Pointers", + Func: func(x1 *int, x2 **int) { + target.Pointers(x1, x2) + }, + }, + { + Name: "Fuzz_TypesNilCheck_WriteTo", + Func: func(stream io.Writer) (int64, error) { + return target.WriteTo(stream) + }, + }, + } + + // Execute a specific chain of steps, with the count, sequence and arguments controlled by fz.Chain + fz.Chain(steps) + }) +} diff --git a/testdata/race_exported_local_pkg.go b/testdata/race_exported_local_pkg.go new file mode 100644 index 0000000..4756986 --- /dev/null +++ b/testdata/race_exported_local_pkg.go @@ -0,0 +1,34 @@ +package raceexample + +// if needed, fill in imports or run 'goimports' +import ( + "testing" + + "github.com/thepudds/fzgen/fuzzer" +) + +func Fuzz_NewMySafeMap_Chain(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + fz := fuzzer.NewFuzzer(data) + + target := NewMySafeMap() + + steps := []fuzzer.Step{ + { + Name: "Fuzz_MySafeMap_Load", + Func: func(key [16]byte) *Request { + return target.Load(key) + }, + }, + { + Name: "Fuzz_MySafeMap_Store", + Func: func(key [16]byte, req *Request) { + target.Store(key, req) + }, + }, + } + + // Execute a specific chain of steps, with the count, sequence and arguments controlled by fz.Chain + fz.Chain(steps) + }) +} diff --git a/testdata/race_exported_not_local_pkg.go b/testdata/race_exported_not_local_pkg.go new file mode 100644 index 0000000..fcc414f --- /dev/null +++ b/testdata/race_exported_not_local_pkg.go @@ -0,0 +1,35 @@ +package raceexamplefuzz // rename if needed + +// if needed, fill in imports or run 'goimports' +import ( + "testing" + + raceexample "github.com/thepudds/fzgen/examples/inputs/race" + "github.com/thepudds/fzgen/fuzzer" +) + +func Fuzz_NewMySafeMap_Chain(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + fz := fuzzer.NewFuzzer(data) + + target := raceexample.NewMySafeMap() + + steps := []fuzzer.Step{ + { + Name: "Fuzz_MySafeMap_Load", + Func: func(key [16]byte) *raceexample.Request { + return target.Load(key) + }, + }, + { + Name: "Fuzz_MySafeMap_Store", + Func: func(key [16]byte, req *raceexample.Request) { + target.Store(key, req) + }, + }, + } + + // Execute a specific chain of steps, with the count, sequence and arguments controlled by fz.Chain + fz.Chain(steps, fuzzer.ChainParallel) + }) +} diff --git a/testdata/strings_inject_ctor_false_exported_local_pkg.go b/testdata/strings_inject_ctor_false_exported_local_pkg.go new file mode 100644 index 0000000..d8e3397 --- /dev/null +++ b/testdata/strings_inject_ctor_false_exported_local_pkg.go @@ -0,0 +1,517 @@ +package strings + +// if needed, fill in imports or run 'goimports' +import ( + "io" + "testing" + "unicode" + + "github.com/thepudds/fzgen/fuzzer" +) + +func Fuzz_Builder_Cap(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var b *Builder + fz := fuzzer.NewFuzzer(data) + fz.Fill(&b) + if b == nil { + return + } + + b.Cap() + }) +} + +func Fuzz_Builder_Grow(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var b *Builder + var n int + fz := fuzzer.NewFuzzer(data) + fz.Fill(&b, &n) + if b == nil { + return + } + + b.Grow(n) + }) +} + +func Fuzz_Builder_Len(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var b *Builder + fz := fuzzer.NewFuzzer(data) + fz.Fill(&b) + if b == nil { + return + } + + b.Len() + }) +} + +func Fuzz_Builder_Reset(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var b *Builder + fz := fuzzer.NewFuzzer(data) + fz.Fill(&b) + if b == nil { + return + } + + b.Reset() + }) +} + +func Fuzz_Builder_String(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var b *Builder + fz := fuzzer.NewFuzzer(data) + fz.Fill(&b) + if b == nil { + return + } + + b.String() + }) +} + +func Fuzz_Builder_Write(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var b *Builder + var p []byte + fz := fuzzer.NewFuzzer(data) + fz.Fill(&b, &p) + if b == nil { + return + } + + b.Write(p) + }) +} + +func Fuzz_Builder_WriteByte(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var b *Builder + var c byte + fz := fuzzer.NewFuzzer(data) + fz.Fill(&b, &c) + if b == nil { + return + } + + b.WriteByte(c) + }) +} + +func Fuzz_Builder_WriteRune(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var b *Builder + var r rune + fz := fuzzer.NewFuzzer(data) + fz.Fill(&b, &r) + if b == nil { + return + } + + b.WriteRune(r) + }) +} + +func Fuzz_Builder_WriteString(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var b *Builder + var s string + fz := fuzzer.NewFuzzer(data) + fz.Fill(&b, &s) + if b == nil { + return + } + + b.WriteString(s) + }) +} + +func Fuzz_Reader_Len(f *testing.F) { + f.Fuzz(func(t *testing.T, s string) { + r := NewReader(s) + r.Len() + }) +} + +func Fuzz_Reader_Read(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, b []byte) { + r := NewReader(s) + r.Read(b) + }) +} + +func Fuzz_Reader_ReadAt(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, b []byte, off int64) { + r := NewReader(s) + r.ReadAt(b, off) + }) +} + +func Fuzz_Reader_ReadByte(f *testing.F) { + f.Fuzz(func(t *testing.T, s string) { + r := NewReader(s) + r.ReadByte() + }) +} + +func Fuzz_Reader_ReadRune(f *testing.F) { + f.Fuzz(func(t *testing.T, s string) { + r := NewReader(s) + r.ReadRune() + }) +} + +func Fuzz_Reader_Reset(f *testing.F) { + f.Fuzz(func(t *testing.T, s1 string, s2 string) { + r := NewReader(s1) + r.Reset(s2) + }) +} + +func Fuzz_Reader_Seek(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, offset int64, whence int) { + r := NewReader(s) + r.Seek(offset, whence) + }) +} + +func Fuzz_Reader_Size(f *testing.F) { + f.Fuzz(func(t *testing.T, s string) { + r := NewReader(s) + r.Size() + }) +} + +func Fuzz_Reader_UnreadByte(f *testing.F) { + f.Fuzz(func(t *testing.T, s string) { + r := NewReader(s) + r.UnreadByte() + }) +} + +func Fuzz_Reader_UnreadRune(f *testing.F) { + f.Fuzz(func(t *testing.T, s string) { + r := NewReader(s) + r.UnreadRune() + }) +} + +func Fuzz_Reader_WriteTo(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var s string + var w io.Writer + fz := fuzzer.NewFuzzer(data) + fz.Fill(&s, &w) + + r := NewReader(s) + r.WriteTo(w) + }) +} + +func Fuzz_Replacer_Replace(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var oldnew []string + var s string + fz := fuzzer.NewFuzzer(data) + fz.Fill(&oldnew, &s) + + r := NewReplacer(oldnew...) + r.Replace(s) + }) +} + +func Fuzz_Replacer_WriteString(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var oldnew []string + var w io.Writer + var s string + fz := fuzzer.NewFuzzer(data) + fz.Fill(&oldnew, &w, &s) + + r := NewReplacer(oldnew...) + r.WriteString(w, s) + }) +} + +func Fuzz_Compare(f *testing.F) { + f.Fuzz(func(t *testing.T, a string, b string) { + Compare(a, b) + }) +} + +func Fuzz_Contains(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, substr string) { + Contains(s, substr) + }) +} + +func Fuzz_ContainsAny(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, chars string) { + ContainsAny(s, chars) + }) +} + +func Fuzz_ContainsRune(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, r rune) { + ContainsRune(s, r) + }) +} + +func Fuzz_Count(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, substr string) { + Count(s, substr) + }) +} + +func Fuzz_EqualFold(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, t2 string) { + EqualFold(s, t2) + }) +} + +func Fuzz_Fields(f *testing.F) { + f.Fuzz(func(t *testing.T, s string) { + Fields(s) + }) +} + +// skipping Fuzz_FieldsFunc because parameters include unsupported func or chan: func(rune) bool + +func Fuzz_HasPrefix(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, prefix string) { + HasPrefix(s, prefix) + }) +} + +func Fuzz_HasSuffix(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, suffix string) { + HasSuffix(s, suffix) + }) +} + +func Fuzz_Index(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, substr string) { + Index(s, substr) + }) +} + +func Fuzz_IndexAny(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, chars string) { + IndexAny(s, chars) + }) +} + +func Fuzz_IndexByte(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, c byte) { + IndexByte(s, c) + }) +} + +// skipping Fuzz_IndexFunc because parameters include unsupported func or chan: func(rune) bool + +func Fuzz_IndexRune(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, r rune) { + IndexRune(s, r) + }) +} + +func Fuzz_Join(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var elems []string + var sep string + fz := fuzzer.NewFuzzer(data) + fz.Fill(&elems, &sep) + + Join(elems, sep) + }) +} + +func Fuzz_LastIndex(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, substr string) { + LastIndex(s, substr) + }) +} + +func Fuzz_LastIndexAny(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, chars string) { + LastIndexAny(s, chars) + }) +} + +func Fuzz_LastIndexByte(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, c byte) { + LastIndexByte(s, c) + }) +} + +// skipping Fuzz_LastIndexFunc because parameters include unsupported func or chan: func(rune) bool + +// skipping Fuzz_Map because parameters include unsupported func or chan: func(rune) rune + +func Fuzz_NewReader(f *testing.F) { + f.Fuzz(func(t *testing.T, s string) { + NewReader(s) + }) +} + +func Fuzz_NewReplacer(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var oldnew []string + fz := fuzzer.NewFuzzer(data) + fz.Fill(&oldnew) + + NewReplacer(oldnew...) + }) +} + +func Fuzz_Repeat(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, count int) { + Repeat(s, count) + }) +} + +func Fuzz_Replace(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, old string, new string, n int) { + Replace(s, old, new, n) + }) +} + +func Fuzz_ReplaceAll(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, old string, new string) { + ReplaceAll(s, old, new) + }) +} + +func Fuzz_Split(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, sep string) { + Split(s, sep) + }) +} + +func Fuzz_SplitAfter(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, sep string) { + SplitAfter(s, sep) + }) +} + +func Fuzz_SplitAfterN(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, sep string, n int) { + SplitAfterN(s, sep, n) + }) +} + +func Fuzz_SplitN(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, sep string, n int) { + SplitN(s, sep, n) + }) +} + +func Fuzz_Title(f *testing.F) { + f.Fuzz(func(t *testing.T, s string) { + Title(s) + }) +} + +func Fuzz_ToLower(f *testing.F) { + f.Fuzz(func(t *testing.T, s string) { + ToLower(s) + }) +} + +func Fuzz_ToLowerSpecial(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var c unicode.SpecialCase + var s string + fz := fuzzer.NewFuzzer(data) + fz.Fill(&c, &s) + + ToLowerSpecial(c, s) + }) +} + +func Fuzz_ToTitle(f *testing.F) { + f.Fuzz(func(t *testing.T, s string) { + ToTitle(s) + }) +} + +func Fuzz_ToTitleSpecial(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var c unicode.SpecialCase + var s string + fz := fuzzer.NewFuzzer(data) + fz.Fill(&c, &s) + + ToTitleSpecial(c, s) + }) +} + +func Fuzz_ToUpper(f *testing.F) { + f.Fuzz(func(t *testing.T, s string) { + ToUpper(s) + }) +} + +func Fuzz_ToUpperSpecial(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var c unicode.SpecialCase + var s string + fz := fuzzer.NewFuzzer(data) + fz.Fill(&c, &s) + + ToUpperSpecial(c, s) + }) +} + +func Fuzz_ToValidUTF8(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, replacement string) { + ToValidUTF8(s, replacement) + }) +} + +func Fuzz_Trim(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, cutset string) { + Trim(s, cutset) + }) +} + +// skipping Fuzz_TrimFunc because parameters include unsupported func or chan: func(rune) bool + +func Fuzz_TrimLeft(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, cutset string) { + TrimLeft(s, cutset) + }) +} + +// skipping Fuzz_TrimLeftFunc because parameters include unsupported func or chan: func(rune) bool + +func Fuzz_TrimPrefix(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, prefix string) { + TrimPrefix(s, prefix) + }) +} + +func Fuzz_TrimRight(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, cutset string) { + TrimRight(s, cutset) + }) +} + +// skipping Fuzz_TrimRightFunc because parameters include unsupported func or chan: func(rune) bool + +func Fuzz_TrimSpace(f *testing.F) { + f.Fuzz(func(t *testing.T, s string) { + TrimSpace(s) + }) +} + +func Fuzz_TrimSuffix(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, suffix string) { + TrimSuffix(s, suffix) + }) +} diff --git a/testdata/strings_inject_ctor_false_exported_not_local_pkg.go b/testdata/strings_inject_ctor_false_exported_not_local_pkg.go new file mode 100644 index 0000000..17da19a --- /dev/null +++ b/testdata/strings_inject_ctor_false_exported_not_local_pkg.go @@ -0,0 +1,590 @@ +package stringsfuzz // rename if needed + +// if needed, fill in imports or run 'goimports' +import ( + "io" + "strings" + "testing" + "unicode" + + "github.com/thepudds/fzgen/fuzzer" +) + +func Fuzz_Builder_Cap(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var b *strings.Builder + fz := fuzzer.NewFuzzer(data) + fz.Fill(&b) + if b == nil { + return + } + + b.Cap() + }) +} + +func Fuzz_Builder_Grow(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var b *strings.Builder + var n int + fz := fuzzer.NewFuzzer(data) + fz.Fill(&b, &n) + if b == nil { + return + } + + b.Grow(n) + }) +} + +func Fuzz_Builder_Len(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var b *strings.Builder + fz := fuzzer.NewFuzzer(data) + fz.Fill(&b) + if b == nil { + return + } + + b.Len() + }) +} + +func Fuzz_Builder_Reset(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var b *strings.Builder + fz := fuzzer.NewFuzzer(data) + fz.Fill(&b) + if b == nil { + return + } + + b.Reset() + }) +} + +func Fuzz_Builder_String(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var b *strings.Builder + fz := fuzzer.NewFuzzer(data) + fz.Fill(&b) + if b == nil { + return + } + + b.String() + }) +} + +func Fuzz_Builder_Write(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var b *strings.Builder + var p []byte + fz := fuzzer.NewFuzzer(data) + fz.Fill(&b, &p) + if b == nil { + return + } + + b.Write(p) + }) +} + +func Fuzz_Builder_WriteByte(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var b *strings.Builder + var c byte + fz := fuzzer.NewFuzzer(data) + fz.Fill(&b, &c) + if b == nil { + return + } + + b.WriteByte(c) + }) +} + +func Fuzz_Builder_WriteRune(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var b *strings.Builder + var r rune + fz := fuzzer.NewFuzzer(data) + fz.Fill(&b, &r) + if b == nil { + return + } + + b.WriteRune(r) + }) +} + +func Fuzz_Builder_WriteString(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var b *strings.Builder + var s string + fz := fuzzer.NewFuzzer(data) + fz.Fill(&b, &s) + if b == nil { + return + } + + b.WriteString(s) + }) +} + +func Fuzz_Reader_Len(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var r *strings.Reader + fz := fuzzer.NewFuzzer(data) + fz.Fill(&r) + if r == nil { + return + } + + r.Len() + }) +} + +func Fuzz_Reader_Read(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var r *strings.Reader + var b []byte + fz := fuzzer.NewFuzzer(data) + fz.Fill(&r, &b) + if r == nil { + return + } + + r.Read(b) + }) +} + +func Fuzz_Reader_ReadAt(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var r *strings.Reader + var b []byte + var off int64 + fz := fuzzer.NewFuzzer(data) + fz.Fill(&r, &b, &off) + if r == nil { + return + } + + r.ReadAt(b, off) + }) +} + +func Fuzz_Reader_ReadByte(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var r *strings.Reader + fz := fuzzer.NewFuzzer(data) + fz.Fill(&r) + if r == nil { + return + } + + r.ReadByte() + }) +} + +func Fuzz_Reader_ReadRune(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var r *strings.Reader + fz := fuzzer.NewFuzzer(data) + fz.Fill(&r) + if r == nil { + return + } + + r.ReadRune() + }) +} + +func Fuzz_Reader_Reset(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var r *strings.Reader + var s string + fz := fuzzer.NewFuzzer(data) + fz.Fill(&r, &s) + if r == nil { + return + } + + r.Reset(s) + }) +} + +func Fuzz_Reader_Seek(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var r *strings.Reader + var offset int64 + var whence int + fz := fuzzer.NewFuzzer(data) + fz.Fill(&r, &offset, &whence) + if r == nil { + return + } + + r.Seek(offset, whence) + }) +} + +func Fuzz_Reader_Size(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var r *strings.Reader + fz := fuzzer.NewFuzzer(data) + fz.Fill(&r) + if r == nil { + return + } + + r.Size() + }) +} + +func Fuzz_Reader_UnreadByte(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var r *strings.Reader + fz := fuzzer.NewFuzzer(data) + fz.Fill(&r) + if r == nil { + return + } + + r.UnreadByte() + }) +} + +func Fuzz_Reader_UnreadRune(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var r *strings.Reader + fz := fuzzer.NewFuzzer(data) + fz.Fill(&r) + if r == nil { + return + } + + r.UnreadRune() + }) +} + +func Fuzz_Reader_WriteTo(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var r *strings.Reader + var w io.Writer + fz := fuzzer.NewFuzzer(data) + fz.Fill(&r, &w) + if r == nil { + return + } + + r.WriteTo(w) + }) +} + +func Fuzz_Replacer_Replace(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var r *strings.Replacer + var s string + fz := fuzzer.NewFuzzer(data) + fz.Fill(&r, &s) + if r == nil { + return + } + + r.Replace(s) + }) +} + +func Fuzz_Replacer_WriteString(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var r *strings.Replacer + var w io.Writer + var s string + fz := fuzzer.NewFuzzer(data) + fz.Fill(&r, &w, &s) + if r == nil { + return + } + + r.WriteString(w, s) + }) +} + +func Fuzz_Compare(f *testing.F) { + f.Fuzz(func(t *testing.T, a string, b string) { + strings.Compare(a, b) + }) +} + +func Fuzz_Contains(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, substr string) { + strings.Contains(s, substr) + }) +} + +func Fuzz_ContainsAny(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, chars string) { + strings.ContainsAny(s, chars) + }) +} + +func Fuzz_ContainsRune(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, r rune) { + strings.ContainsRune(s, r) + }) +} + +func Fuzz_Count(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, substr string) { + strings.Count(s, substr) + }) +} + +func Fuzz_EqualFold(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, t2 string) { + strings.EqualFold(s, t2) + }) +} + +func Fuzz_Fields(f *testing.F) { + f.Fuzz(func(t *testing.T, s string) { + strings.Fields(s) + }) +} + +// skipping Fuzz_FieldsFunc because parameters include unsupported func or chan: func(rune) bool + +func Fuzz_HasPrefix(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, prefix string) { + strings.HasPrefix(s, prefix) + }) +} + +func Fuzz_HasSuffix(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, suffix string) { + strings.HasSuffix(s, suffix) + }) +} + +func Fuzz_Index(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, substr string) { + strings.Index(s, substr) + }) +} + +func Fuzz_IndexAny(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, chars string) { + strings.IndexAny(s, chars) + }) +} + +func Fuzz_IndexByte(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, c byte) { + strings.IndexByte(s, c) + }) +} + +// skipping Fuzz_IndexFunc because parameters include unsupported func or chan: func(rune) bool + +func Fuzz_IndexRune(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, r rune) { + strings.IndexRune(s, r) + }) +} + +func Fuzz_Join(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var elems []string + var sep string + fz := fuzzer.NewFuzzer(data) + fz.Fill(&elems, &sep) + + strings.Join(elems, sep) + }) +} + +func Fuzz_LastIndex(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, substr string) { + strings.LastIndex(s, substr) + }) +} + +func Fuzz_LastIndexAny(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, chars string) { + strings.LastIndexAny(s, chars) + }) +} + +func Fuzz_LastIndexByte(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, c byte) { + strings.LastIndexByte(s, c) + }) +} + +// skipping Fuzz_LastIndexFunc because parameters include unsupported func or chan: func(rune) bool + +// skipping Fuzz_Map because parameters include unsupported func or chan: func(rune) rune + +func Fuzz_NewReader(f *testing.F) { + f.Fuzz(func(t *testing.T, s string) { + strings.NewReader(s) + }) +} + +func Fuzz_NewReplacer(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var oldnew []string + fz := fuzzer.NewFuzzer(data) + fz.Fill(&oldnew) + + strings.NewReplacer(oldnew...) + }) +} + +func Fuzz_Repeat(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, count int) { + strings.Repeat(s, count) + }) +} + +func Fuzz_Replace(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, old string, new string, n int) { + strings.Replace(s, old, new, n) + }) +} + +func Fuzz_ReplaceAll(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, old string, new string) { + strings.ReplaceAll(s, old, new) + }) +} + +func Fuzz_Split(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, sep string) { + strings.Split(s, sep) + }) +} + +func Fuzz_SplitAfter(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, sep string) { + strings.SplitAfter(s, sep) + }) +} + +func Fuzz_SplitAfterN(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, sep string, n int) { + strings.SplitAfterN(s, sep, n) + }) +} + +func Fuzz_SplitN(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, sep string, n int) { + strings.SplitN(s, sep, n) + }) +} + +func Fuzz_Title(f *testing.F) { + f.Fuzz(func(t *testing.T, s string) { + strings.Title(s) + }) +} + +func Fuzz_ToLower(f *testing.F) { + f.Fuzz(func(t *testing.T, s string) { + strings.ToLower(s) + }) +} + +func Fuzz_ToLowerSpecial(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var c unicode.SpecialCase + var s string + fz := fuzzer.NewFuzzer(data) + fz.Fill(&c, &s) + + strings.ToLowerSpecial(c, s) + }) +} + +func Fuzz_ToTitle(f *testing.F) { + f.Fuzz(func(t *testing.T, s string) { + strings.ToTitle(s) + }) +} + +func Fuzz_ToTitleSpecial(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var c unicode.SpecialCase + var s string + fz := fuzzer.NewFuzzer(data) + fz.Fill(&c, &s) + + strings.ToTitleSpecial(c, s) + }) +} + +func Fuzz_ToUpper(f *testing.F) { + f.Fuzz(func(t *testing.T, s string) { + strings.ToUpper(s) + }) +} + +func Fuzz_ToUpperSpecial(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var c unicode.SpecialCase + var s string + fz := fuzzer.NewFuzzer(data) + fz.Fill(&c, &s) + + strings.ToUpperSpecial(c, s) + }) +} + +func Fuzz_ToValidUTF8(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, replacement string) { + strings.ToValidUTF8(s, replacement) + }) +} + +func Fuzz_Trim(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, cutset string) { + strings.Trim(s, cutset) + }) +} + +// skipping Fuzz_TrimFunc because parameters include unsupported func or chan: func(rune) bool + +func Fuzz_TrimLeft(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, cutset string) { + strings.TrimLeft(s, cutset) + }) +} + +// skipping Fuzz_TrimLeftFunc because parameters include unsupported func or chan: func(rune) bool + +func Fuzz_TrimPrefix(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, prefix string) { + strings.TrimPrefix(s, prefix) + }) +} + +func Fuzz_TrimRight(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, cutset string) { + strings.TrimRight(s, cutset) + }) +} + +// skipping Fuzz_TrimRightFunc because parameters include unsupported func or chan: func(rune) bool + +func Fuzz_TrimSpace(f *testing.F) { + f.Fuzz(func(t *testing.T, s string) { + strings.TrimSpace(s) + }) +} + +func Fuzz_TrimSuffix(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, suffix string) { + strings.TrimSuffix(s, suffix) + }) +} diff --git a/testdata/strings_inject_ctor_true_exported_not_local_pkg.go b/testdata/strings_inject_ctor_true_exported_not_local_pkg.go new file mode 100644 index 0000000..d9ad2a5 --- /dev/null +++ b/testdata/strings_inject_ctor_true_exported_not_local_pkg.go @@ -0,0 +1,518 @@ +package stringsfuzz // rename if needed + +// if needed, fill in imports or run 'goimports' +import ( + "io" + "strings" + "testing" + "unicode" + + "github.com/thepudds/fzgen/fuzzer" +) + +func Fuzz_Builder_Cap(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var b *strings.Builder + fz := fuzzer.NewFuzzer(data) + fz.Fill(&b) + if b == nil { + return + } + + b.Cap() + }) +} + +func Fuzz_Builder_Grow(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var b *strings.Builder + var n int + fz := fuzzer.NewFuzzer(data) + fz.Fill(&b, &n) + if b == nil { + return + } + + b.Grow(n) + }) +} + +func Fuzz_Builder_Len(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var b *strings.Builder + fz := fuzzer.NewFuzzer(data) + fz.Fill(&b) + if b == nil { + return + } + + b.Len() + }) +} + +func Fuzz_Builder_Reset(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var b *strings.Builder + fz := fuzzer.NewFuzzer(data) + fz.Fill(&b) + if b == nil { + return + } + + b.Reset() + }) +} + +func Fuzz_Builder_String(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var b *strings.Builder + fz := fuzzer.NewFuzzer(data) + fz.Fill(&b) + if b == nil { + return + } + + b.String() + }) +} + +func Fuzz_Builder_Write(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var b *strings.Builder + var p []byte + fz := fuzzer.NewFuzzer(data) + fz.Fill(&b, &p) + if b == nil { + return + } + + b.Write(p) + }) +} + +func Fuzz_Builder_WriteByte(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var b *strings.Builder + var c byte + fz := fuzzer.NewFuzzer(data) + fz.Fill(&b, &c) + if b == nil { + return + } + + b.WriteByte(c) + }) +} + +func Fuzz_Builder_WriteRune(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var b *strings.Builder + var r rune + fz := fuzzer.NewFuzzer(data) + fz.Fill(&b, &r) + if b == nil { + return + } + + b.WriteRune(r) + }) +} + +func Fuzz_Builder_WriteString(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var b *strings.Builder + var s string + fz := fuzzer.NewFuzzer(data) + fz.Fill(&b, &s) + if b == nil { + return + } + + b.WriteString(s) + }) +} + +func Fuzz_Reader_Len(f *testing.F) { + f.Fuzz(func(t *testing.T, s string) { + r := strings.NewReader(s) + r.Len() + }) +} + +func Fuzz_Reader_Read(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, b []byte) { + r := strings.NewReader(s) + r.Read(b) + }) +} + +func Fuzz_Reader_ReadAt(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, b []byte, off int64) { + r := strings.NewReader(s) + r.ReadAt(b, off) + }) +} + +func Fuzz_Reader_ReadByte(f *testing.F) { + f.Fuzz(func(t *testing.T, s string) { + r := strings.NewReader(s) + r.ReadByte() + }) +} + +func Fuzz_Reader_ReadRune(f *testing.F) { + f.Fuzz(func(t *testing.T, s string) { + r := strings.NewReader(s) + r.ReadRune() + }) +} + +func Fuzz_Reader_Reset(f *testing.F) { + f.Fuzz(func(t *testing.T, s1 string, s2 string) { + r := strings.NewReader(s1) + r.Reset(s2) + }) +} + +func Fuzz_Reader_Seek(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, offset int64, whence int) { + r := strings.NewReader(s) + r.Seek(offset, whence) + }) +} + +func Fuzz_Reader_Size(f *testing.F) { + f.Fuzz(func(t *testing.T, s string) { + r := strings.NewReader(s) + r.Size() + }) +} + +func Fuzz_Reader_UnreadByte(f *testing.F) { + f.Fuzz(func(t *testing.T, s string) { + r := strings.NewReader(s) + r.UnreadByte() + }) +} + +func Fuzz_Reader_UnreadRune(f *testing.F) { + f.Fuzz(func(t *testing.T, s string) { + r := strings.NewReader(s) + r.UnreadRune() + }) +} + +func Fuzz_Reader_WriteTo(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var s string + var w io.Writer + fz := fuzzer.NewFuzzer(data) + fz.Fill(&s, &w) + + r := strings.NewReader(s) + r.WriteTo(w) + }) +} + +func Fuzz_Replacer_Replace(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var oldnew []string + var s string + fz := fuzzer.NewFuzzer(data) + fz.Fill(&oldnew, &s) + + r := strings.NewReplacer(oldnew...) + r.Replace(s) + }) +} + +func Fuzz_Replacer_WriteString(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var oldnew []string + var w io.Writer + var s string + fz := fuzzer.NewFuzzer(data) + fz.Fill(&oldnew, &w, &s) + + r := strings.NewReplacer(oldnew...) + r.WriteString(w, s) + }) +} + +func Fuzz_Compare(f *testing.F) { + f.Fuzz(func(t *testing.T, a string, b string) { + strings.Compare(a, b) + }) +} + +func Fuzz_Contains(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, substr string) { + strings.Contains(s, substr) + }) +} + +func Fuzz_ContainsAny(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, chars string) { + strings.ContainsAny(s, chars) + }) +} + +func Fuzz_ContainsRune(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, r rune) { + strings.ContainsRune(s, r) + }) +} + +func Fuzz_Count(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, substr string) { + strings.Count(s, substr) + }) +} + +func Fuzz_EqualFold(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, t2 string) { + strings.EqualFold(s, t2) + }) +} + +func Fuzz_Fields(f *testing.F) { + f.Fuzz(func(t *testing.T, s string) { + strings.Fields(s) + }) +} + +// skipping Fuzz_FieldsFunc because parameters include unsupported func or chan: func(rune) bool + +func Fuzz_HasPrefix(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, prefix string) { + strings.HasPrefix(s, prefix) + }) +} + +func Fuzz_HasSuffix(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, suffix string) { + strings.HasSuffix(s, suffix) + }) +} + +func Fuzz_Index(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, substr string) { + strings.Index(s, substr) + }) +} + +func Fuzz_IndexAny(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, chars string) { + strings.IndexAny(s, chars) + }) +} + +func Fuzz_IndexByte(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, c byte) { + strings.IndexByte(s, c) + }) +} + +// skipping Fuzz_IndexFunc because parameters include unsupported func or chan: func(rune) bool + +func Fuzz_IndexRune(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, r rune) { + strings.IndexRune(s, r) + }) +} + +func Fuzz_Join(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var elems []string + var sep string + fz := fuzzer.NewFuzzer(data) + fz.Fill(&elems, &sep) + + strings.Join(elems, sep) + }) +} + +func Fuzz_LastIndex(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, substr string) { + strings.LastIndex(s, substr) + }) +} + +func Fuzz_LastIndexAny(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, chars string) { + strings.LastIndexAny(s, chars) + }) +} + +func Fuzz_LastIndexByte(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, c byte) { + strings.LastIndexByte(s, c) + }) +} + +// skipping Fuzz_LastIndexFunc because parameters include unsupported func or chan: func(rune) bool + +// skipping Fuzz_Map because parameters include unsupported func or chan: func(rune) rune + +func Fuzz_NewReader(f *testing.F) { + f.Fuzz(func(t *testing.T, s string) { + strings.NewReader(s) + }) +} + +func Fuzz_NewReplacer(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var oldnew []string + fz := fuzzer.NewFuzzer(data) + fz.Fill(&oldnew) + + strings.NewReplacer(oldnew...) + }) +} + +func Fuzz_Repeat(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, count int) { + strings.Repeat(s, count) + }) +} + +func Fuzz_Replace(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, old string, new string, n int) { + strings.Replace(s, old, new, n) + }) +} + +func Fuzz_ReplaceAll(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, old string, new string) { + strings.ReplaceAll(s, old, new) + }) +} + +func Fuzz_Split(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, sep string) { + strings.Split(s, sep) + }) +} + +func Fuzz_SplitAfter(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, sep string) { + strings.SplitAfter(s, sep) + }) +} + +func Fuzz_SplitAfterN(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, sep string, n int) { + strings.SplitAfterN(s, sep, n) + }) +} + +func Fuzz_SplitN(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, sep string, n int) { + strings.SplitN(s, sep, n) + }) +} + +func Fuzz_Title(f *testing.F) { + f.Fuzz(func(t *testing.T, s string) { + strings.Title(s) + }) +} + +func Fuzz_ToLower(f *testing.F) { + f.Fuzz(func(t *testing.T, s string) { + strings.ToLower(s) + }) +} + +func Fuzz_ToLowerSpecial(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var c unicode.SpecialCase + var s string + fz := fuzzer.NewFuzzer(data) + fz.Fill(&c, &s) + + strings.ToLowerSpecial(c, s) + }) +} + +func Fuzz_ToTitle(f *testing.F) { + f.Fuzz(func(t *testing.T, s string) { + strings.ToTitle(s) + }) +} + +func Fuzz_ToTitleSpecial(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var c unicode.SpecialCase + var s string + fz := fuzzer.NewFuzzer(data) + fz.Fill(&c, &s) + + strings.ToTitleSpecial(c, s) + }) +} + +func Fuzz_ToUpper(f *testing.F) { + f.Fuzz(func(t *testing.T, s string) { + strings.ToUpper(s) + }) +} + +func Fuzz_ToUpperSpecial(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var c unicode.SpecialCase + var s string + fz := fuzzer.NewFuzzer(data) + fz.Fill(&c, &s) + + strings.ToUpperSpecial(c, s) + }) +} + +func Fuzz_ToValidUTF8(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, replacement string) { + strings.ToValidUTF8(s, replacement) + }) +} + +func Fuzz_Trim(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, cutset string) { + strings.Trim(s, cutset) + }) +} + +// skipping Fuzz_TrimFunc because parameters include unsupported func or chan: func(rune) bool + +func Fuzz_TrimLeft(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, cutset string) { + strings.TrimLeft(s, cutset) + }) +} + +// skipping Fuzz_TrimLeftFunc because parameters include unsupported func or chan: func(rune) bool + +func Fuzz_TrimPrefix(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, prefix string) { + strings.TrimPrefix(s, prefix) + }) +} + +func Fuzz_TrimRight(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, cutset string) { + strings.TrimRight(s, cutset) + }) +} + +// skipping Fuzz_TrimRightFunc because parameters include unsupported func or chan: func(rune) bool + +func Fuzz_TrimSpace(f *testing.F) { + f.Fuzz(func(t *testing.T, s string) { + strings.TrimSpace(s) + }) +} + +func Fuzz_TrimSuffix(f *testing.F) { + f.Fuzz(func(t *testing.T, s string, suffix string) { + strings.TrimSuffix(s, suffix) + }) +} diff --git a/testdata/types_exported_not_local_pkg.go b/testdata/types_exported_not_local_pkg.go new file mode 100644 index 0000000..39f051c --- /dev/null +++ b/testdata/types_exported_not_local_pkg.go @@ -0,0 +1,212 @@ +package fuzzwrapexamplesfuzz // rename if needed + +// if needed, fill in imports or run 'goimports' +import ( + "context" + "io" + "testing" + "unsafe" + + fuzzwrapexamples "github.com/thepudds/fzgen/examples/inputs/test-types" + "github.com/thepudds/fzgen/fuzzer" +) + +func Fuzz_TypesNilCheck_Interface(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var x1 io.Writer + fz := fuzzer.NewFuzzer(data) + fz.Fill(&x1) + + n := fuzzwrapexamples.NewTypesNilCheck() + n.Interface(x1) + }) +} + +func Fuzz_TypesNilCheck_Pointers(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var x1 *int + var x2 **int + fz := fuzzer.NewFuzzer(data) + fz.Fill(&x1, &x2) + if x1 == nil || x2 == nil { + return + } + + n := fuzzwrapexamples.NewTypesNilCheck() + n.Pointers(x1, x2) + }) +} + +func Fuzz_TypesNilCheck_WriteTo(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var stream io.Writer + fz := fuzzer.NewFuzzer(data) + fz.Fill(&stream) + + n := fuzzwrapexamples.NewTypesNilCheck() + n.WriteTo(stream) + }) +} + +func Fuzz_InterfacesFullList(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var x1 io.Writer + var x2 io.Reader + var x3 io.ReaderAt + var x4 io.WriterTo + var x5 io.Seeker + var x6 io.ByteScanner + var x7 io.RuneScanner + var x8 io.ReadSeeker + var x9 io.ByteReader + var x10 io.RuneReader + var x11 io.ByteWriter + var x12 io.ReadWriter + var x13 io.ReaderFrom + var x14 io.StringWriter + var x15 io.Closer + var x16 io.ReadCloser + var x17 context.Context + fz := fuzzer.NewFuzzer(data) + fz.Fill(&x1, &x2, &x3, &x4, &x5, &x6, &x7, &x8, &x9, &x10, &x11, &x12, &x13, &x14, &x15, &x16, &x17) + + fuzzwrapexamples.InterfacesFullList(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17) + }) +} + +func Fuzz_InterfacesShortList(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var ctx context.Context + var w io.Writer + var r io.Reader + var sw io.StringWriter + var rc io.ReadCloser + fz := fuzzer.NewFuzzer(data) + fz.Fill(&ctx, &w, &r, &sw, &rc) + + fuzzwrapexamples.InterfacesShortList(ctx, w, r, sw, rc) + }) +} + +// skipping Fuzz_InterfacesSkip because parameters include unsupported interface: net.Conn + +func Fuzz_Short1(f *testing.F) { + f.Fuzz(func(t *testing.T, x1 int) { + fuzzwrapexamples.Short1(x1) + }) +} + +func Fuzz_Short2(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var x1 *int + fz := fuzzer.NewFuzzer(data) + fz.Fill(&x1) + if x1 == nil { + return + } + + fuzzwrapexamples.Short2(x1) + }) +} + +func Fuzz_Short3(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var x1 **int + fz := fuzzer.NewFuzzer(data) + fz.Fill(&x1) + if x1 == nil { + return + } + + fuzzwrapexamples.Short3(x1) + }) +} + +func Fuzz_Short4(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var x1 fuzzwrapexamples.MyInt + fz := fuzzer.NewFuzzer(data) + fz.Fill(&x1) + + fuzzwrapexamples.Short4(x1) + }) +} + +func Fuzz_Short5(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var x1 complex64 + fz := fuzzer.NewFuzzer(data) + fz.Fill(&x1) + + fuzzwrapexamples.Short5(x1) + }) +} + +func Fuzz_Short6(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var x1 complex128 + fz := fuzzer.NewFuzzer(data) + fz.Fill(&x1) + + fuzzwrapexamples.Short6(x1) + }) +} + +func Fuzz_Short7(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var x1 uintptr + fz := fuzzer.NewFuzzer(data) + fz.Fill(&x1) + + fuzzwrapexamples.Short7(x1) + }) +} + +func Fuzz_Short8(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var x1 unsafe.Pointer + fz := fuzzer.NewFuzzer(data) + fz.Fill(&x1) + + fuzzwrapexamples.Short8(x1) + }) +} + +func Fuzz_TypesShortListFill(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var x1 int + var x2 *int + var x3 **int + var x4 map[string]string + var x5 *map[string]string + var x6 fuzzwrapexamples.MyInt + var x7 [4]int + var x8 fuzzwrapexamples.MyStruct + var x9 io.ByteReader + var x10 io.RuneReader + var x11 io.ByteWriter + var x12 io.ReadWriter + var x13 io.ReaderFrom + var x14 io.StringWriter + var x15 io.Closer + var x16 io.ReadCloser + var x17 context.Context + fz := fuzzer.NewFuzzer(data) + fz.Fill(&x1, &x2, &x3, &x4, &x5, &x6, &x7, &x8, &x9, &x10, &x11, &x12, &x13, &x14, &x15, &x16, &x17) + if x2 == nil || x3 == nil || x5 == nil { + return + } + + fuzzwrapexamples.TypesShortListFill(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17) + }) +} + +func Fuzz_TypesShortListNoFill(f *testing.F) { + f.Fuzz(func(t *testing.T, x1 int, x5 string) { + fuzzwrapexamples.TypesShortListNoFill(x1, x5) + }) +} + +// skipping Fuzz_TypesShortListSkip1 because parameters include unsupported func or chan: chan bool + +// skipping Fuzz_TypesShortListSkip2 because parameters include unsupported func or chan: func(int) diff --git a/testdata/uuid_exported_local_pkg.go b/testdata/uuid_exported_local_pkg.go new file mode 100644 index 0000000..5a358df --- /dev/null +++ b/testdata/uuid_exported_local_pkg.go @@ -0,0 +1,67 @@ +package uuid + +// if needed, fill in imports or run 'goimports' +import ( + "fmt" + "reflect" + "testing" + + "github.com/thepudds/fzgen/fuzzer" +) + +func Fuzz_NewFromBytes_Chain(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var b []byte + fz := fuzzer.NewFuzzer(data) + fz.Fill(&b) + + target, err := NewFromBytes(b) + if err != nil { + return + } + + steps := []fuzzer.Step{ + { + Name: "Fuzz_MyUUID_UnmarshalBinary", + Func: func(d1 []byte) { + target.UnmarshalBinary(d1) + }, + }, + { + Name: "Fuzz_MyUUID_MarshalBinary", + Func: func() ([]byte, error) { + return target.MarshalBinary() + }, + }, + { + Name: "Fuzz_MyUUID_URN", + Func: func() string { + return target.URN() + }, + }, + } + + // Execute a specific chain of steps, with the count, sequence and arguments controlled by fz.Chain + fz.Chain(steps, fuzzer.ChainParallel) + + // Validate with some roundtrip checks. These can be edited or deleted if not appropriate for your target. + // Check MarshalBinary. + result2, err := target.MarshalBinary() + if err != nil { + // Some targets should never return an error here for an object created by a constructor. + // If that is the case for your target, you can change this to a panic(err) or t.Fatal. + return + } + + // Check UnmarshalBinary. + var tmp2 MyUUID + err = tmp2.UnmarshalBinary(result2) + if err != nil { + panic(fmt.Sprintf("UnmarshalBinary failed after successful MarshalBinary. original: %v %#v marshalled: %q error: %v", target, target, result2, err)) + } + if !reflect.DeepEqual(target, tmp2) { + panic(fmt.Sprintf("MarshalBinary/UnmarshalBinary roundtrip equality failed. original: %v %#v marshalled: %q unmarshalled: %v %#v", + target, target, result2, tmp2, tmp2)) + } + }) +} diff --git a/testdata/uuid_exported_not_local_pkg.go b/testdata/uuid_exported_not_local_pkg.go new file mode 100644 index 0000000..36c8010 --- /dev/null +++ b/testdata/uuid_exported_not_local_pkg.go @@ -0,0 +1,68 @@ +package uuidfuzz // rename if needed + +// if needed, fill in imports or run 'goimports' +import ( + "fmt" + "reflect" + "testing" + + uuid "github.com/thepudds/fzgen/examples/inputs/test-chain-uuid" + "github.com/thepudds/fzgen/fuzzer" +) + +func Fuzz_NewFromBytes_Chain(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var b []byte + fz := fuzzer.NewFuzzer(data) + fz.Fill(&b) + + target, err := uuid.NewFromBytes(b) + if err != nil { + return + } + + steps := []fuzzer.Step{ + { + Name: "Fuzz_MyUUID_UnmarshalBinary", + Func: func(d1 []byte) { + target.UnmarshalBinary(d1) + }, + }, + { + Name: "Fuzz_MyUUID_MarshalBinary", + Func: func() ([]byte, error) { + return target.MarshalBinary() + }, + }, + { + Name: "Fuzz_MyUUID_URN", + Func: func() string { + return target.URN() + }, + }, + } + + // Execute a specific chain of steps, with the count, sequence and arguments controlled by fz.Chain + fz.Chain(steps, fuzzer.ChainParallel) + + // Validate with some roundtrip checks. These can be edited or deleted if not appropriate for your target. + // Check MarshalBinary. + result2, err := target.MarshalBinary() + if err != nil { + // Some targets should never return an error here for an object created by a constructor. + // If that is the case for your target, you can change this to a panic(err) or t.Fatal. + return + } + + // Check UnmarshalBinary. + var tmp2 uuid.MyUUID + err = tmp2.UnmarshalBinary(result2) + if err != nil { + panic(fmt.Sprintf("UnmarshalBinary failed after successful MarshalBinary. original: %v %#v marshalled: %q error: %v", target, target, result2, err)) + } + if !reflect.DeepEqual(target, tmp2) { + panic(fmt.Sprintf("MarshalBinary/UnmarshalBinary roundtrip equality failed. original: %v %#v marshalled: %q unmarshalled: %v %#v", + target, target, result2, tmp2, tmp2)) + } + }) +} diff --git a/testscripts/arg_reuse_fuzzing.txt b/testscripts/arg_reuse_fuzzing.txt new file mode 100644 index 0000000..8ca6da2 --- /dev/null +++ b/testscripts/arg_reuse_fuzzing.txt @@ -0,0 +1,65 @@ +# Test end-to-end fuzzing. This test focuses on arg reuse in fz.Chain. +# See github.com/thepudds/fzgen/examples/inputs/arg-reuse for comments about target. +# Requires -end2end flag for 'go test'. +# +# Some test details: +# 1. We assume a 1.18 gotip is in the path. (We exit early if no gotip is in path, +# or if it appears to be the wrong version). +# 2. To just run this script, execute from the fzgen directory: +# go test -run=TestScripts/arg_reuse_fuzzing -end2end +# 3. Adding '-v -testwork' shows more detail, including the temp dir location, +# which will be left behind for inspection or manual debugging. + +[!exec:gotip$exe] skip 'skipping because no gotip found in path' + +# Validate result is Go 1.18+. +exec gotip version +# We do not envision a Go 2. +stdout 'go version.*go1\.(1[8-9]|[2-9][0-9]|[1-9][0-9][0-9])' + +# Set up a usable module, including the run time dependency for fzgen/fuzzer +# Note: we force use of our local copy of fzgen via a 'replace' directive. +go mod init temp +go mod edit -replace github.com/thepudds/fzgen=$FZLOCALDIR +go get github.com/thepudds/fzgen/fuzzer +go list -m all + +# Create our set of wrappers, which will be chainable. +fzgen -chain github.com/thepudds/fzgen/examples/inputs/arg-reuse +exists autofuzzchain_test.go + +# TODO: consider making the cmd/go fuzzing deterministic with -parallel=1, +# but based on current GODEBUG=fuzzseed=N implementation, not sure if that is useful here. +# env GODEBUG=fuzzseed=1 + +# TODO: ideally we would also start with an empty cached corpus, but 'env GOCACHE=$WORK' also slows down build/test currently. +# Could manually clean cache globally or surgically, or ... + +# Let's go! Start fuzzing our autogenerated loop. +# This can take 2-3 min to hit the desired panic from scatch with an empty corpus, +# but we include a current crasher below, which just takes a few seconds. +# TODO: when we have better fuzzing effeciency, we might remove the crasher from here. +! exec gotip test -fuzz=. -fuzztime=60s +stdout 'panic: bingo' + +# This crasher is currently: +# gotip test -run=Fuzz_New_Chain/d433d5c99 +# +# PLANNED STEPS: (sequential: true) +# +# Fuzz_PanicOnArgReuse_Step1( +# 3472328296227680304, +# 3472328296227745840, +# ) +# Fuzz_PanicOnArgReuse_Step2( +# 3472328296227680304, +# 3472328296227680304, +# ) +# Fuzz_PanicOnArgReuse_Step1( +# 0, +# 0, +# ) + +-- testdata/fuzz/Fuzz_New_Chain/crasher -- +go test fuzz v1 +[]byte("000000000000000100000000000000000000001000000000000000000000") \ No newline at end of file diff --git a/testscripts/help.txt b/testscripts/help.txt index 1c2dbbd..ee12b01 100644 --- a/testscripts/help.txt +++ b/testscripts/help.txt @@ -1,27 +1,8 @@ -# 'fzgo help' shows fuzzing help +# 'fzgen -h' shows fuzzing help. If there are multiple testscript failures including +# this one, this is a good one to investiage first to see if there is a problem +# with the testscript setup in general. -# Exit early if -short was specified. -[short] skip 'skipping help tests because -short was specified' - -! fzgo help -stdout '^fzgo is a simple prototype' -stdout '^[ ]*-fuzz regexp' -stdout '^[ ]*fuzz at most one function matching regexp' -! stderr .+ - -# 'fzgo' also shows fuzzing help -! fzgo -stdout '^fzgo is a simple prototype' - -# 'fzgo -h' and 'fzgo --help' show fuzzing help -! fzgo -h -stdout '^fzgo is a simple prototype' -! fzgo --help -stdout '^fzgo is a simple prototype' - -# '-h' and '--help' with 'fzgo test -fuzz' show fuzzing help -! fzgo test -fuzz . --help -stdout '^fzgo is a simple prototype' -! fzgo test -fuzz . -h -stdout '^fzgo is a simple prototype' +fzgen -h +stderr 'Usage' +! stdout .+ diff --git a/testscripts/race_fuzzing.txt b/testscripts/race_fuzzing.txt new file mode 100644 index 0000000..e3f7fca --- /dev/null +++ b/testscripts/race_fuzzing.txt @@ -0,0 +1,85 @@ +# Test end-to-end fuzzing. This test focuses on data race detection via return reuse in fz.Chain. +# See github.com/thepudds/fzgen/examples/inputs/race for comments about target. +# Requires -end2end flag for 'go test'. +# +# Some test details: +# 1. We assume a 1.18 gotip is in the path. (We exit early if no gotip is in path, +# or if it appears to be the wrong version). +# 2. To just run this script, execute from the fzgen directory: +# go test -run=TestScripts/race_fuzzing -end2end +# 3. Adding '-v -testwork' shows more detail, including the temp dir location, +# which will be left behind for inspection or manual debugging. + +[!exec:gotip$exe] skip 'skipping because no gotip found in path' + +# Validate result is Go 1.18+. +exec gotip version +# We do not envision a Go 2. +stdout 'go version.*go1\.(1[8-9]|[2-9][0-9]|[1-9][0-9][0-9])' + +# Set up a usable module, including the run time dependency for fzgen/fuzzer +# Note: we force use of our local copy of fzgen via a 'replace' directive. +go mod init temp +go mod edit -replace github.com/thepudds/fzgen=$FZLOCALDIR +go get github.com/thepudds/fzgen/fuzzer +go list -m all + +# Create our set of wrappers, which will be chainable. +fzgen -chain -parallel github.com/thepudds/fzgen/examples/inputs/race +exists autofuzzchain_test.go + +# TODO: consider making the cmd/go fuzzing deterministic with -parallel=1, +# but based on current GODEBUG=fuzzseed=N implementation, not sure if that is useful here. +# env GODEBUG=fuzzseed=1 + +# TODO: ideally we would also start with an empty cached corpus, but 'env GOCACHE=$WORK' also slows down build/test currently. +# Could manually clean cache globally or surgically, or ... + +# Let's go! + +# First, emit the plan and repro for our saved crasher. +env FZDEBUG=plan=1,repro=1 +! exec gotip test -run=/crasher -race +stdout 'WARNING: DATA RACE' +stdout 'MySafeMap\)\.Load' + +# Second, start fuzzing, which should also fail. +env FZDEBUG=plan=0,repro=0 +! exec gotip test -fuzz=. -fuzztime=4s -race +stdout 'race detected' + +# crasher (from run "race52", with loop under control of fuzzer): +# +# FZDEBUG=repro=1 +# gotip test -run=/90daf533e9 -race +# +# PLANNED STEPS: (sequential: false, loop count: 1, spin: true) +# +# // Execute next steps in parallel. +# var wg sync.WaitGroup +# wg.Add(3) +# +# go func() { +# defer wg.Done() +# Fuzz_MySafeMap_Store( +# [16]uint8{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, +# &raceexample.Request{Answer:42,}, +# ) +# }() +# go func() { +# defer wg.Done() +# Fuzz_MySafeMap_Load( +# [16]uint8{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, +# ) +# }() +# go func() { +# defer wg.Done() +# Fuzz_MySafeMap_Load( +# [16]uint8{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, +# ) +# }() +# wg.Wait() + +-- testdata/fuzz/Fuzz_NewMySafeMap_Chain/crasher -- +go test fuzz v1 +[]byte("001000000<000000*\xe5") diff --git a/testscripts/return_reuse_fuzzing.txt b/testscripts/return_reuse_fuzzing.txt new file mode 100644 index 0000000..dead5db --- /dev/null +++ b/testscripts/return_reuse_fuzzing.txt @@ -0,0 +1,41 @@ +# Test end-to-end fuzzing. This test focuses on return value reuse in fz.Chain. +# See github.com/thepudds/fzgen/examples/inputs/return-reuse for comments about target. +# Requires -end2end flag for 'go test'. +# +# Some test details: +# 1. We assume a 1.18 gotip is in the path. (We exit early if no gotip is in path, +# or if it appears to be the wrong version). +# 2. To just run this script, execute from the fzgen directory: +# go test -run=TestScripts/return_reuse_fuzzing -end2end +# 3. Adding '-v -testwork' shows more detail, including the temp dir location, +# which will be left behind for inspection or manual debugging. + +[!exec:gotip$exe] skip 'skipping because no gotip found in path' + +# Validate result is Go 1.18+. +exec gotip version +# We do not envision a Go 2. +stdout 'go version.*go1\.(1[8-9]|[2-9][0-9]|[1-9][0-9][0-9])' + +# Set up a usable module, including the run time dependency for fzgen/fuzzer +# Note: we force use of our local copy of fzgen via a 'replace' directive. +go mod init temp +go mod edit -replace github.com/thepudds/fzgen=$FZLOCALDIR +go get github.com/thepudds/fzgen/fuzzer +go list -m all + +# Create our set of wrappers, which will be chainable. +fzgen -chain github.com/thepudds/fzgen/examples/inputs/return-reuse +exists autofuzzchain_test.go + +# TODO: consider making the cmd/go fuzzing deterministic with -parallel=1, +# but based on current GODEBUG=fuzzseed=N implementation, not sure if that is useful here. +# env GODEBUG=fuzzseed=1 + +# TODO: ideally would also start with an empty cached corpus, but that also slows down builds currently. +# Could do 'env GOCACHE=$WORK', or manually clean cache globally or surgically, or ... + +# Let's go! Start fuzzing our autogenerated loop. +# This can take 20-30 sec to hit the desired panic., +! exec gotip test -fuzz=. -fuzztime=60s +stdout 'panic: bingo'