├── .travis.yml ├── LICENSE ├── README.md ├── examples ├── empty │ └── fuzz.go ├── richsignatures │ └── examples.go └── time │ ├── fuzz.go │ └── testdata │ └── fuzz │ └── FuzzTime │ └── corpus │ └── valid-input ├── fuzz ├── cache.go ├── exec.go ├── flag.go ├── flag_test.go ├── packages.go ├── richsig.go ├── richsig_test.go └── runcorpus.go ├── genfuzzfuncs ├── README.md ├── constructor_injection_test.go ├── examples │ ├── test-constructor-injection │ │ └── examples.go │ ├── test-exported │ │ └── examples.go │ ├── uuid │ │ └── uuid_fuzz.go │ └── x-mod-modules │ │ └── modules_fuzz.go ├── exported_test.go ├── genfuzzfuncs.go ├── long_test.go └── main.go ├── main.go ├── randparam ├── bytes2rand.go ├── bytes2rand_test.go ├── randparam.go └── randparam_test.go ├── script_test.go └── testscripts ├── flags.txt ├── fuzzing.txt ├── fuzzing_rich_signatures.txt ├── help.txt ├── package_patterns.txt └── verify_corpus.txt /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | # fzgo is not yet a proper module, including the tests would need to be updated. 4 | env: 5 | - GO111MODULE=off 6 | 7 | matrix: 8 | include: 9 | - os: linux 10 | go: "1.16.x" 11 | - os: linux 12 | go: "1.15.x" 13 | # - os: linux 14 | # go: "1.14.x" 15 | # - os: osx 16 | # go: "1.16.x" 17 | # - os: osx 18 | # go: "1.15.x" 19 | - os: windows 20 | go: "1.16.x" 21 | # - os: windows 22 | # go: "1.15.x" 23 | 24 | before_install: 25 | # TODO: Sigh. Travis seems to have old ca-certificates or similar issue, or at least, git complains and fails... 26 | - git config --global http.sslverify false 27 | - go get -v -u github.com/dvyukov/go-fuzz/... 28 | - go get -v -u golang.org/x/tools/cmd/goimports 29 | 30 | script: 31 | - go test ./fuzz ./randparam ./genfuzzfuncs 32 | - go test -short . 33 | - go test ./... 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | Copyright 2020 thepudds 179 | 180 | Licensed under the Apache License, Version 2.0 (the "License"); 181 | you may not use this file except in compliance with the License. 182 | You may obtain a copy of the License at 183 | 184 | http://www.apache.org/licenses/LICENSE-2.0 185 | 186 | Unless required by applicable law or agreed to in writing, software 187 | distributed under the License is distributed on an "AS IS" BASIS, 188 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 189 | See the License for the specific language governing permissions and 190 | limitations under the License. 191 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![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) 2 | 3 | 4 | ## fzgo: go-fuzz + 'go test' = fewer bugs 5 | 6 | If you are not familiar with fuzzing, this [motivation](http://tiny.cc/why-go-fuzz) document provides a quick 1-page introduction. 7 | 8 | `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"**. 9 | 10 | `fzgo` supports some conveniences like fuzzing rich signatures and auto-generation of fuzzing functions. 11 | 12 | The basic approach is that `fzgo` integrates [dvyukov/go-fuzz](https://github.com/dvyukov/go-fuzz) 13 | into `go test`, with the heavy lifting being done by `go-fuzz`, `go-fuzz-build`, and the `go` tool. The focus 14 | is on step 1 of a tentative list of "Draft goals for a prototype" outlined in [this 15 | comment](https://github.com/golang/go/issues/19109#issuecomment-441442080) on [#19109](https://golang.org/issue/19109): 16 | 17 | _Step 1. Prototype proposed CLI, including interaction with existing 'go test'._ 18 | 19 | `fzgo` supports the `-fuzz` flag and several other related flags proposed in the March 2017 20 | [#19109 proposal document](https://github.com/golang/go/issues/19109#issuecomment-285456008). `fzgo` also supports typical `go` commands 21 | such as `fzgo build`, `fzgo test`, or `fzgo env` (which are implemented by wrapping the `go` tool). 22 | 23 | Any and all feedback is welcome! 24 | 25 | ### Features 26 | 27 | * 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`. 28 | * The corpus is automatically used as deterministic input to unit tests when running a normal `go test`. 29 | * Individual corpus files can be unit tested via `fzgo test -fuzz=. -run=TestCorpus/`. 30 | * `go-fuzz` requires a two step process. `fzgo` eliminates the separate manual preparation step. 31 | * `fzgo` automatically caches instrumented binaries in `GOPATH/pkg/fuzz` and re-uses them if possible. 32 | * The fuzzing corpus defaults to `GOPATH/pkg/fuzz/corpus`. 33 | * 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). 34 | * `fuzz` and `gofuzz` build tags are allowed but not required. 35 | * 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. 36 | 37 | ## Usage 38 | ``` 39 | Usage: fzgo test [build/test flags] [packages] [build/test flags] 40 | 41 | Examples: 42 | 43 | fzgo test # normal 'go test' of current package, plus run any corpus as unit tests 44 | fzgo test -fuzz=. # fuzz the current package with a function starting with 'Fuzz' 45 | fzgo test -fuzz=FuzzFoo # fuzz the current package with a function matching 'FuzzFoo' 46 | fzgo test ./... -fuzz=FuzzFoo # fuzz a package in ./... with a function matching 'FuzzFoo' 47 | fzgo test sample/pkg -fuzz=FuzzFoo # fuzz 'sample/pkg' with a function matching 'FuzzFoo' 48 | 49 | Rich signatures like Fuzz(re string, input []byte, posix bool)` are supported, as well Fuzz(data []byte) int. 50 | Fuzz functions must start with 'Fuzz'. 51 | 52 | The following flags work with 'fzgo test -fuzz': 53 | 54 | -fuzz regexp 55 | fuzz at most one function matching regexp 56 | -fuzzdir dir 57 | store fuzz artifacts in dir (default pkgpath/testdata/fuzz) 58 | -fuzztime d 59 | fuzz for duration d (default unlimited) 60 | -parallel n 61 | start n fuzzing operations (default GOMAXPROCS) 62 | -timeout d 63 | fail an individual call to a fuzz function after duration d (default 10s, minimum 1s) 64 | -c 65 | compile the instrumented code but do not run it 66 | -v 67 | verbose: print additional output 68 | ``` 69 | 70 | ## Install 71 | 72 | ``` 73 | $ go get -u github.com/thepudds/fzgo/... 74 | $ go get -u github.com/dvyukov/go-fuzz/... 75 | ``` 76 | 77 | Note: if you already have an older `dvyukov/go-fuzz`, you might need to first delete it from GOPATH as described in 78 | the [dvyukov/go-fuzz](https://github.com/dvyukov/go-fuzz#history-rewrite) repo. 79 | 80 | The `go-fuzz` source code must be in your GOPATH, and the `go-fuzz` and `go-fuzz-build` binaries must be 81 | in your path environment variable. 82 | 83 | **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. 84 | 85 | ## Status 86 | 87 | This is a simple prototype. Don't expect great things. ;-) 88 | 89 | 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. 90 | 91 | Testing is primarily done with the nice internal `testscripts` package used by the core Go team to test the `go` tool 92 | and extracted at [rogpeppe/go-internal/testscript](https://github.com/rogpeppe/go-internal/tree/master/testscript). 93 | 94 | #### Changes from the proposal document 95 | 96 | The primary changes between the current fzgo prototype vs. the March 2017 [proposal document](https://github.com/golang/go/issues/19109#issuecomment-285456008): 97 | 98 | 1. fzgo supports rich signatures. 99 | 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). 100 | 3. Initially, fzgo disallowed multiple fuzz functions to match (per the March 2017 proposal), 101 | but as an experiment fzgo now allows multiple fuzz functions to match in order to 102 | support something like 'go test -fuzz=. ./...' when there are multiple fuzz functions 103 | across multiple packages. Fuzzing happens in round-robin manner if multiple fuzz functions match. 104 | 4. The proposal document suggested `GOPATH/pkg/GOOS_GOARCH_fuzz/` for a cache, but the prototype instead 105 | uses `GOPATH/pkg/fuzz/GOOS_GOARCH/`. 106 | 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`. 107 | 6. Some of the commentators at [#19109](https://golang.org/issue/19109) suggested `-fuzztime duration` as a 108 | way of controlling when to stop fuzzing. The proposal document does not include `-fuzztime` and `go-fuzz` 109 | does not support it, but it seems useful in general and `-fuzztime` is in the prototype (and it proved 110 | useful while testing the prototype). This might be removed later. 111 | 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. 112 | 113 | #### Pieces of proposal document not implemented in this prototype 114 | 115 | * `fuzz.F` or `testing.F` signature for fuzzing function. 116 | * Allowing fuzzing functions to reside in `*_test.go` files. 117 | * Anything to do with deeper integration with the compiler for more robust instrumentation. This 118 | prototype is not focused on that area. 119 | * Any of a much larger set of preexisting build flags like `-ldflags`, `-coverprofile`. 120 | * Areas covered in the March 2017 [proposal document](https://github.com/golang/go/issues/19109#issuecomment-285456008), 121 | 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. 122 | 123 | The argument parsing in 'go test' is bespoke, and the argument parsing in `fzgo` is an approximation of that. 124 | That might be OK for an early prototype. The right thing to do might be to extract 125 | [src/cmd/go/internal/test/testflag.go](https://golang.org/src/cmd/go/internal/test/testflag.go), 126 | which includes this comment: 127 | 128 | ``` 129 | // The flag handling part of go test is large and distracting. 130 | // We can't use the flag package because some of the flags from 131 | // our command line are for us, and some are for 6.out, and 132 | // some are for both. 133 | ``` 134 | -------------------------------------------------------------------------------- /examples/empty/fuzz.go: -------------------------------------------------------------------------------- 1 | package fuzzempty 2 | 3 | // FuzzEmpty is an empty placeholder fuzzing function 4 | func FuzzEmpty(data []byte) int { 5 | return 0 6 | } 7 | 8 | // FuzzAnotherEmpty is another empty placeholder fuzzing function 9 | func FuzzAnotherEmpty(data []byte) int { 10 | return 0 11 | } 12 | -------------------------------------------------------------------------------- /examples/richsignatures/examples.go: -------------------------------------------------------------------------------- 1 | package pkgname 2 | 3 | import ( 4 | "regexp" 5 | 6 | "github.com/thepudds/fzgo/fuzz" 7 | ) 8 | 9 | // FuzzWithBasicTypes is a fuzzing function written by a user 10 | // that has a rich signature. All parameters are basic types, 11 | // but is uses stdlib types within (regexp). 12 | // We can fuzz it automatically, even though it doesn't match the standard []data 13 | // signature. This is just a test -- the fuzzing itself is not of interest. 14 | func FuzzWithBasicTypes(re string, input []byte, posix bool) (bool, error) { 15 | 16 | var r *regexp.Regexp 17 | var err error 18 | if posix { 19 | r, err = regexp.CompilePOSIX(re) 20 | } else { 21 | r, err = regexp.Compile(re) 22 | } 23 | if err != nil { 24 | return false, err 25 | } 26 | 27 | return r.Match(input), nil 28 | } 29 | 30 | // FuzzWithStdlibType is a test function using a combination of basic types 31 | // and also one from the stdlib (regexp). 32 | func FuzzWithStdlibType(something, another string, allow bool, re *regexp.Regexp) { 33 | regexp.MatchString(something, another) 34 | } 35 | 36 | // FuzzWithFzgoFunc uses a non-stdlib type 37 | func FuzzWithFzgoFunc(f fuzz.Func) string { 38 | return f.String() 39 | } 40 | 41 | // ExampleType is defined in the same package as the fuzz target that uses it (next func below). 42 | type ExampleType int 43 | 44 | // FuzzWithTargetType shows a type defined in the same file as the fuzz function. 45 | func FuzzWithTargetType(e ExampleType) { 46 | 47 | } 48 | -------------------------------------------------------------------------------- /examples/time/fuzz.go: -------------------------------------------------------------------------------- 1 | package fuzztime 2 | 3 | import "time" 4 | 5 | // FuzzTime is a very simple fuzzing function 6 | func FuzzTime(data []byte) int { 7 | 8 | _, err := time.ParseDuration(string(data)) 9 | 10 | if err != nil { 11 | return 1 12 | } 13 | return 0 14 | } 15 | -------------------------------------------------------------------------------- /examples/time/testdata/fuzz/FuzzTime/corpus/valid-input: -------------------------------------------------------------------------------- 1 | 1h56m23s 2 | -------------------------------------------------------------------------------- /fuzz/cache.go: -------------------------------------------------------------------------------- 1 | package fuzz 2 | 3 | import ( 4 | "bufio" 5 | "crypto/sha256" 6 | "encoding/base64" 7 | "errors" 8 | "fmt" 9 | "go/build" 10 | "io" 11 | "io/ioutil" 12 | "os" 13 | "os/exec" 14 | "path/filepath" 15 | "runtime" 16 | "sort" 17 | "strings" 18 | ) 19 | 20 | // CacheDir returns /pkg/fuzz//// 21 | func CacheDir(hash, pkgName, fuzzName string) string { 22 | gp := Gopath() 23 | s := strings.Split(gp, string(os.PathListSeparator)) 24 | if len(s) > 1 { 25 | gp = s[0] 26 | } 27 | return filepath.Join(gp, "pkg", "fuzz", fmt.Sprintf("%s_%s", runtime.GOOS, runtime.GOARCH), 28 | hash, fuzzName) 29 | } 30 | 31 | // Gopath returns the current effective GOPATH (from the GOPATH env, or the default if env var now set). 32 | func Gopath() string { 33 | gp := os.Getenv("GOPATH") 34 | if gp == "" { 35 | gp = build.Default.GOPATH 36 | } 37 | return gp 38 | } 39 | 40 | // Hash returns a string representing the hash of the files in a package, its dependencies, 41 | // as well as the fuzz func name, the version of go and the go-fuzz-build binary. 42 | func Hash(pkgPath, funcName, trimPrefix string, env []string, verbose bool) (string, error) { 43 | report := func(err error) (string, error) { 44 | return "", fmt.Errorf("fzgo cache hash: %v", err) 45 | } 46 | h := sha256.New() 47 | 48 | // hash the contents of our package and dependencies 49 | dirs, err := goListDeps(pkgPath, env) 50 | if err != nil { 51 | return report(err) 52 | } 53 | sort.Strings(dirs) 54 | for _, dir := range dirs { 55 | hd, err := hashDir(dir, trimPrefix) 56 | if err != nil { 57 | return report(err) 58 | } 59 | 60 | fmt.Fprintf(h, "%s %s\n", hd, strings.TrimPrefix(dir, trimPrefix)) 61 | if verbose { 62 | fmt.Printf("%s %s\n", hd, dir) 63 | } 64 | } 65 | 66 | // hash the go-fuzz-build binary. 67 | // first, check if go-fuzz seems to be installed. 68 | err = checkGoFuzz() 69 | if err != nil { 70 | // err here suggests running 'go get' for go-fuzz 71 | return report(err) 72 | } 73 | path, err := exec.LookPath("go-fuzz-build") 74 | if err != nil { 75 | return report(err) 76 | } 77 | f, err := os.Open(path) 78 | if err != nil { 79 | return report(err) 80 | } 81 | defer f.Close() 82 | hf := sha256.New() 83 | _, err = io.Copy(hf, f) 84 | if err != nil { 85 | return report(err) 86 | } 87 | s := hf.Sum(nil) 88 | fmt.Fprintf(h, "%x %s\n", s, "go-fuzz-build") 89 | if verbose { 90 | fmt.Printf("%x %s\n", s, "go-fuzz-build") 91 | } 92 | 93 | // hash the fuzz func name 94 | fmt.Fprintf(h, "%s fuzzfunc\n", funcName) 95 | 96 | // hash the go version 97 | fmt.Fprintf(h, "%s go version\n", runtime.Version()) 98 | 99 | return fmt.Sprintf("%x", h.Sum(nil)[:10]), nil 100 | } 101 | 102 | // hashDir hashes files without descending into subdirectories. 103 | func hashDir(dir, trimPrefix string) (string, error) { 104 | 105 | var absFiles []string 106 | files, err := ioutil.ReadDir(dir) 107 | if err != nil { 108 | return "", err 109 | } 110 | for _, file := range files { 111 | if file.IsDir() || !file.Mode().IsRegular() { 112 | continue 113 | } 114 | filename := file.Name() 115 | abs, err := filepath.Abs(filepath.Join(dir, filename)) 116 | if err != nil { 117 | return "", err 118 | } 119 | absFiles = append(absFiles, abs) 120 | 121 | } 122 | 123 | return hashFiles(absFiles, trimPrefix) 124 | } 125 | 126 | // Adapted from dirhash.Hash1. The largest difference is 127 | // the filenames within a trimPrefix directory won't use 128 | // the trimPrefix string as part of the hash. 129 | // The file contents are still hashed. 130 | func hashFiles(files []string, trimPrefix string) (string, error) { 131 | h := sha256.New() 132 | files = append([]string(nil), files...) 133 | sort.Strings(files) 134 | for _, file := range files { 135 | if strings.Contains(file, "\n") { 136 | return "", errors.New("filenames with newlines are not supported") 137 | } 138 | r, err := os.Open(file) 139 | if err != nil { 140 | return "", err 141 | } 142 | hf := sha256.New() 143 | _, err = io.Copy(hf, r) 144 | r.Close() 145 | if err != nil { 146 | return "", err 147 | } 148 | fmt.Fprintf(h, "%x %s\n", hf.Sum(nil), strings.TrimPrefix(file, trimPrefix)) 149 | } 150 | return base64.StdEncoding.EncodeToString(h.Sum(nil)), nil 151 | } 152 | 153 | // goListDeps returns a []string of dirs for all dependencies of pkg 154 | func goListDeps(pkg string, env []string) ([]string, error) { 155 | report := func(err error) ([]string, error) { 156 | return nil, fmt.Errorf("go list -deps: %v", err) 157 | } 158 | 159 | if len(env) == 0 { 160 | env = os.Environ() 161 | } 162 | 163 | cmd := exec.Command("go", "list", "-deps", "-f", "{{.Dir}}", buildTagsArg, pkg) 164 | cmd.Env = env 165 | 166 | out, err := cmd.Output() 167 | if err != nil { 168 | ee, ok := err.(*exec.ExitError) 169 | if !ok { 170 | return report(err) 171 | } 172 | return nil, fmt.Errorf("go list -deps: %v: %s", err, ee.Stderr) 173 | } 174 | scanner := bufio.NewScanner(strings.NewReader(string(out))) 175 | results := []string{} 176 | for scanner.Scan() { 177 | results = append(results, scanner.Text()) 178 | } 179 | if err := scanner.Err(); err != nil { 180 | return report(err) 181 | } 182 | return results, nil 183 | } 184 | 185 | // CopyDir is a simple implementation of recursively copying a directory. 186 | // The main use case is copying a corpus directory (which does not have symlinks, etc.). 187 | // Files that already exist in the destination are left alone. 188 | func CopyDir(dst string, src string) error { 189 | report := func(err error) error { 190 | return fmt.Errorf("copy dir failed from %s to %s: %v", dst, src, err) 191 | } 192 | files, err := ioutil.ReadDir(src) 193 | if err != nil { 194 | return report(err) 195 | } 196 | if err := os.MkdirAll(dst, 0700); err != nil { 197 | return report(err) 198 | } 199 | for _, f := range files { 200 | dstName := filepath.Join(dst, f.Name()) 201 | srcName := filepath.Join(src, f.Name()) 202 | if f.IsDir() { 203 | if err := CopyDir(dstName, srcName); err != nil { 204 | return report(err) 205 | } 206 | } else { 207 | if err := CopyFile(dstName, srcName); err != nil { 208 | return report(err) 209 | } 210 | } 211 | } 212 | return nil 213 | } 214 | 215 | // CopyFile copies a file. A dst file that already exists 216 | // is left alone, and is not an error. The main use case 217 | // is updating a corpus from GOPATH/pkg/fuzz/corpus/..., 218 | // and we trust the destination if the file already exists. 219 | func CopyFile(dst string, src string) error { 220 | report := func(err error) error { 221 | return fmt.Errorf("copy file failed from %s to %s: %v", dst, src, err) 222 | } 223 | if PathExists(dst) { 224 | return nil 225 | } 226 | w, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0700) 227 | if err != nil { 228 | return report(err) 229 | } 230 | defer w.Close() 231 | r, err := os.Open(src) 232 | if err != nil { 233 | return report(err) 234 | } 235 | defer r.Close() 236 | if _, err := io.Copy(w, r); err != nil { 237 | return report(err) 238 | } 239 | return nil 240 | } 241 | 242 | // PathExists reports if a path is exists. 243 | func PathExists(path string) bool { 244 | if _, err := os.Stat(path); os.IsNotExist(err) { 245 | return false 246 | } 247 | return true 248 | } 249 | -------------------------------------------------------------------------------- /fuzz/exec.go: -------------------------------------------------------------------------------- 1 | package fuzz 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "os/exec" 7 | "path/filepath" 8 | "strings" 9 | "time" 10 | ) 11 | 12 | // Instrument builds the instrumented binary and fuzz.zip if they do not already 13 | // exist in the fzgo cache. If instead there is a cache hit, Instrument prints to stderr 14 | // that the cached is being used. 15 | // cacheDir is the location for the instrumented binary, and would typically be something like: 16 | // GOPATH/pkg/fuzz/linux_amd64/619f7d77e9cd5d7433f8/fmt.FuzzFmt 17 | func Instrument(function Func, verbose bool) (Target, error) { 18 | report := func(err error) (Target, error) { 19 | return Target{}, fmt.Errorf("instrument %s.%s error: %v", function.PkgName, function.FuncName, err) 20 | } 21 | 22 | // check if go-fuzz and go-fuzz-build seem to be in our path 23 | err := checkGoFuzz() 24 | if err != nil { 25 | return report(err) 26 | } 27 | 28 | if function.FuncName == "" || function.PkgDir == "" || function.PkgPath == "" { 29 | return report(fmt.Errorf("unexpected fuzz function: %#v", function)) 30 | } 31 | 32 | // check if we have a plain data []byte signature, vs. a rich signature 33 | plain, err := IsPlainSig(function.TypesFunc) 34 | if err != nil { 35 | return report(err) 36 | } 37 | 38 | var target Target 39 | if plain { 40 | // create our initial target struct using the actual func supplied by the user. 41 | target = Target{UserFunc: function} 42 | } else { 43 | info("detected rich signature for %v.%v", function.PkgName, function.FuncName) 44 | // create a wrapper function to handle the rich signature. 45 | // When fuzzing, we do not want to print our arguments. 46 | printArgs := false 47 | target, err = CreateRichSigWrapper(function, printArgs) 48 | if err != nil { 49 | return report(err) 50 | } 51 | // CreateRichSigWrapper was successful, which means it populated the temp dir with the wrapper func. 52 | // By the time we leave our current function, we are done with the temp dir 53 | // that CreateRichSigWrapper created, so delete via a defer. 54 | // (We can't delete it immediately because we haven't yet run go-fuzz-build on it). 55 | defer os.RemoveAll(target.wrapperTempDir) 56 | } 57 | 58 | // Determine where our cacheDir is. 59 | // This includes calculating a hash covering the package, its dependencies, and some other items. 60 | cacheDir, err := target.cacheDir(verbose) 61 | if err != nil { 62 | return report(fmt.Errorf("getting cache dir failed: %v", err)) 63 | } 64 | 65 | // set up our cache directory if needed 66 | err = os.MkdirAll(cacheDir, os.ModePerm) 67 | if err != nil { 68 | return report(fmt.Errorf("creating cache dir failed: %v", err)) 69 | } 70 | 71 | // check if our instrumented zip already exists in our cache (in which case we trust it). 72 | finalZipPath, err := target.zipPath(verbose) 73 | if err != nil { 74 | return report(fmt.Errorf("zip path failed: %v", err)) 75 | } 76 | if _, err = os.Stat(finalZipPath); os.IsNotExist(err) { 77 | info("building instrumented binary for %v.%v", function.PkgName, function.FuncName) 78 | outFile := filepath.Join(cacheDir, "fuzz.zip.partial") 79 | 80 | // to support experimentation, initial args for go-fuzz-build are 81 | // populated by the optional FZGOFLAGSBUILD env var 82 | // (or an empty slice if FZGOFLAGSBUILD is not set). 83 | args := fzgoEnvFlags("FZGOFLAGSBUILD") 84 | if !target.hasWrapper { 85 | args = append(args, 86 | "-func="+target.UserFunc.FuncName, 87 | "-o="+outFile, 88 | // "-race", // TODO: make a flag 89 | buildTagsArg, 90 | target.UserFunc.PkgPath, 91 | ) 92 | } else { 93 | args = append(args, 94 | "-func="+target.wrapperFunc.FuncName, 95 | "-o="+outFile, 96 | // "-race", // TODO: make a flag 97 | buildTagsArg, 98 | target.wrapperFunc.PkgPath, 99 | ) 100 | } 101 | 102 | err = execCmd("go-fuzz-build", args, target.wrapperEnv, 0) 103 | if err != nil { 104 | return report(fmt.Errorf("go-fuzz-build failed with args %q: %v", args, err)) 105 | } 106 | 107 | err = os.Rename(outFile, finalZipPath) 108 | if err != nil { 109 | return report(err) 110 | } 111 | } else { 112 | info("using cached instrumented binary for %v.%v", function.PkgName, function.FuncName) 113 | } 114 | return target, nil 115 | } 116 | 117 | // Start begins fuzzing by invoking 'go-fuzz'. 118 | // cacheDir contains the instrumented binary, and would typically be something like: 119 | // GOPATH/pkg/fuzz/linux_amd64/619f7d77e9cd5d7433f8/fmt.FuzzFmt 120 | // workDir contains the corpus, and would typically be something like: 121 | // GOPATH/src/github.com/user/proj/testdata/fuzz/fmt.FuzzFmt 122 | func Start(target Target, workDir string, maxDuration time.Duration, parallel int, funcTimeout time.Duration, v bool) error { 123 | report := func(err error) error { 124 | return fmt.Errorf("start fuzzing %s error: %v", target.FuzzName(), err) 125 | } 126 | 127 | info("starting fuzzing %s", target.FuzzName()) 128 | info("output in %s", workDir) 129 | 130 | // check if go-fuzz and go-fuzz-build seem to be in our path 131 | err := checkGoFuzz() 132 | if err != nil { 133 | return report(err) 134 | } 135 | 136 | // prepare our args 137 | if funcTimeout < 1*time.Second { 138 | return fmt.Errorf("minimum allowed func timeout value is 1 second") 139 | } 140 | verboseLevel := 0 141 | if v { 142 | verboseLevel = 1 143 | } 144 | 145 | zipPath, err := target.zipPath(v) 146 | if err != nil { 147 | return report(fmt.Errorf("zip path failed: %v", err)) 148 | } 149 | 150 | // to support experimentation, initial args for go-fuzz are 151 | // populated by the optional FZGOFLAGSFUZZ env var 152 | // (or an empty slice if FZGOFLAGSFUZZ is not set). 153 | runArgs := fzgoEnvFlags("FZGOFLAGSFUZZ") 154 | runArgs = append(runArgs, 155 | fmt.Sprintf("-bin=%s", zipPath), 156 | fmt.Sprintf("-workdir=%s", workDir), 157 | fmt.Sprintf("-procs=%d", parallel), 158 | fmt.Sprintf("-timeout=%d", int(funcTimeout.Seconds())), // this is not total run time 159 | fmt.Sprintf("-v=%d", verboseLevel), 160 | ) 161 | err = execCmd("go-fuzz", runArgs, nil, maxDuration) 162 | if err != nil { 163 | return report(err) 164 | } 165 | return nil 166 | } 167 | 168 | // Target tracks some metadata about each fuzz target, and is responsible 169 | // for tracking a fuzz.Func found via go/packages and making it useful 170 | // as a fuzz target, including determining where to cache the fuzz.zip 171 | // and what the target's fuzzName should be. 172 | type Target struct { 173 | UserFunc Func // the user's original function 174 | savedCacheDir string // the cacheDir relies on a content hash, so remember the answer 175 | 176 | hasWrapper bool 177 | wrapperFunc Func // synthesized wrapper function, only used if user's func has rich signatures 178 | wrapperEnv []string // env with GOPATH set up to include the temporary 179 | wrapperTempDir string 180 | } 181 | 182 | // FuzzName returns the '.' string. 183 | // For example, it might be 'fmt.FuzzFmt'. This is used 184 | // in messages, as well it is part of the path when creating 185 | // the corpus location under testdata. 186 | func (t *Target) FuzzName() string { 187 | return t.UserFunc.FuzzName() 188 | } 189 | 190 | func (t *Target) zipPath(verbose bool) (string, error) { 191 | cacheDir, err := t.cacheDir(verbose) 192 | if err != nil { 193 | return "", err 194 | } 195 | return filepath.Join(cacheDir, "fuzz.zip"), nil 196 | } 197 | 198 | func (t *Target) cacheDir(verbose bool) (string, error) { 199 | if t.savedCacheDir == "" { 200 | // generate a hash covering the package, its dependencies, and some items like go-fuzz-build binary and go version 201 | // TODO: pass verbose flag around? 202 | var err error 203 | var h string 204 | if !t.hasWrapper { 205 | // use everything directly from the original user function 206 | h, err = Hash(t.UserFunc.PkgPath, t.UserFunc.FuncName, t.UserFunc.PkgDir, nil, verbose) 207 | } else { 208 | // we have a wrapper function, so target that for our hash. 209 | h, err = Hash(t.wrapperFunc.PkgPath, t.wrapperFunc.FuncName, t.wrapperFunc.PkgDir, t.wrapperEnv, verbose) 210 | } 211 | if err != nil { 212 | return "", err 213 | } 214 | // the user facing location on disk is the friendly name (that is, from the original user function) 215 | t.savedCacheDir = CacheDir(h, t.UserFunc.PkgName, t.FuzzName()) 216 | } 217 | 218 | return t.savedCacheDir, nil 219 | } 220 | 221 | // ExecGo invokes the go command. The intended use case is fzgo operating in 222 | // pass-through mode, where an invocation like 'fzgo env GOPATH' 223 | // gets passed to the 'go' tool as 'go env GOPATH'. args typically would be 224 | // os.Args[1:] 225 | func ExecGo(args []string, env []string) error { 226 | if len(env) == 0 { 227 | env = os.Environ() 228 | } 229 | 230 | _, err := exec.LookPath("go") 231 | if err != nil { 232 | return fmt.Errorf("failed to find \"go\" command in path. error: %v", err) 233 | } 234 | return execCmd("go", args, env, 0) 235 | } 236 | 237 | // A maxDuration of 0 means no max time is enforced. 238 | func execCmd(name string, args []string, env []string, maxDuration time.Duration) error { 239 | report := func(err error) error { return fmt.Errorf("exec %v error: %v", name, err) } 240 | 241 | cmd := exec.Command(name, args...) 242 | cmd.Stdout = os.Stdout 243 | cmd.Stderr = os.Stderr 244 | cmd.Stdin = os.Stdin 245 | if len(env) > 0 { 246 | cmd.Env = env 247 | } 248 | 249 | if maxDuration == 0 { 250 | // invoke cmd and let it run until it returns 251 | err := cmd.Run() 252 | if err != nil { 253 | return report(err) 254 | } 255 | return nil 256 | } 257 | 258 | // we have a maxDuration specified. 259 | // start and then manually kill cmd after maxDuration if it doesn't exit on its own. 260 | err := cmd.Start() 261 | if err != nil { 262 | return report(err) 263 | } 264 | timer := time.AfterFunc(maxDuration, func() { 265 | err := cmd.Process.Signal(os.Interrupt) 266 | if err != nil { 267 | // os.Interrupt expected to fail in some cases (e.g., not implemented on Windows) 268 | _ = cmd.Process.Kill() 269 | } 270 | }) 271 | err = cmd.Wait() 272 | if timer.Stop() && err != nil { 273 | // timer.Stop() returned true, which means our kill func never ran, so return this error 274 | return report(err) 275 | } 276 | return nil 277 | } 278 | 279 | // checkGoFuzz lightly validates that dvyukov/go-fuzz seems to have been properly installed. 280 | func checkGoFuzz() error { 281 | for _, cmdName := range []string{"go-fuzz", "go-fuzz-build"} { 282 | _, err := exec.LookPath(cmdName) 283 | if err != nil { 284 | return fmt.Errorf("failed to find %q command in path. please run \"go get -u github.com/dvyukov/go-fuzz/...\" and verify your path settings. error: %v", 285 | cmdName, err) 286 | } 287 | } 288 | return nil 289 | } 290 | 291 | // fzgoEnvFlags gets any whitespace-separated arguments 292 | // in the named environment variable (e.g., FZGOFLAGSBUILD). 293 | func fzgoEnvFlags(name string) []string { 294 | val := os.Getenv(name) 295 | return strings.Fields(val) 296 | } 297 | 298 | func info(s string, args ...interface{}) { 299 | // Related comment from https://golang.org/cmd/go/#hdr-Test_packages 300 | // All test output and summary lines are printed to the go command's standard output, 301 | // even if the test printed them to its own standard error. 302 | // (The go command's standard error is reserved for printing errors building the tests.) 303 | fmt.Println("fzgo:", fmt.Sprintf(s, args...)) 304 | } 305 | -------------------------------------------------------------------------------- /fuzz/flag.go: -------------------------------------------------------------------------------- 1 | // Package fuzz defines the API used by fzgo (a simple prototype of integrating dvyukov/go-fuzz into 'go test'). 2 | // 3 | // See the README at https://github.com/thepudds/fzgo for more details. 4 | package fuzz 5 | 6 | import ( 7 | "flag" 8 | "fmt" 9 | "io/ioutil" 10 | "strings" 11 | "time" 12 | ) 13 | 14 | // UnimplementedBuildFlags is a list of 'go test' build flags that are not implemented 15 | // in this simple 'fzgo' prototype; these will cause an error if used with 'fzgo test -fuzz'. 16 | // (If 'test -fuzz' is not specified, 'fzgo' will pass any of these arguments through to the actual 'go' tool). 17 | var UnimplementedBuildFlags = []string{ 18 | "a", // -a (force rebuilding of packages that are already up-to-date.) 19 | "asmflags", // -asmflags '[pattern=]arg list' (arguments to pass on each go tool asm invocation.) 20 | "buildmode", // -buildmode mode (build mode to use. See 'go help buildmode' for more.) 21 | "compiler", // -compiler name (name of compiler to use, as in runtime.Compiler (gccgo or gc).) 22 | "gccgoflags", // -gccgoflags '[pattern=]arg list' (arguments to pass on each gccgo compiler/linker invocation.) 23 | "gcflags", // -gcflags '[pattern=]arg list' (arguments to pass on each go tool compile invocation.) 24 | "installsuffix", // -installsuffix suffix (a suffix to use in the name of the package installation directory,...) 25 | "ldflags", // -ldflags '[pattern=]arg list' (arguments to pass on each go tool link invocation.) 26 | "linkshared", // -linkshared (link against shared libraries previously created with...) 27 | "mod", // -mod mode (module download mode to use: readonly or vendor.) 28 | "msan", // -msan (enable interoperation with memory sanitizer.) 29 | "n", // -n (print the commands but do not run them.) 30 | "p", // -p n (the number of programs, such as build commands or...) 31 | "pkgdir", // -pkgdir dir (install and load all packages from dir instead of the usual locations.) 32 | "race", // -race (enable data race detection.) 33 | "tags", // -tags 'tag list' (a space-separated list of build tags to consider satisfied during the...) 34 | "toolexec", // -toolexec 'cmd args' (a program to use to invoke toolchain programs like vet and asm.) 35 | "work", // -work (print the name of the temporary work directory and...) 36 | "x", // -x (print the commands.) 37 | } 38 | 39 | // IncompatibleTestFlags is a list of 'go test' flags that might be 40 | // incompatible with a final 'go test -fuzz' based on the March 2017 proposal document. 41 | // These currently cause an error if 'fzgo test -fuzz' is also specified. 42 | // This list likely would change based on more discussion of the proposal. 43 | var IncompatibleTestFlags = []string{ 44 | "args", // -args (Pass the remainder of the command line (everything after -args)...) 45 | "bench", // -bench regexp (Run only those benchmarks matching a regular expression.) 46 | "benchmem", // -benchmem (Print memory allocation statistics for benchmarks.) 47 | "benchtime", // -benchtime t (Run enough iterations of each benchmark to take t, specified...) 48 | "blockprofile", // -blockprofile block.out (Write a goroutine blocking profile to the specified file...) 49 | "blockprofilerate", // -blockprofilerate n (Control the detail provided in goroutine blocking profiles by...) 50 | "count", // -count n (Run each test and benchmark n times (default 1).) 51 | "cover", // -cover (Enable coverage analysis.) 52 | "covermode", // -covermode set,count,atomic (Set the mode for coverage analysis for the package[s]...) 53 | "coverpkg", // -coverpkg pattern1,pattern2,pattern3 (Apply coverage analysis in each test to packages matching the patterns.) 54 | "cpu", // -cpu 1,2,4 (Specify a list of GOMAXPROCS values for which the tests or...) 55 | "cpuprofile", // -cpuprofile cpu.out (Write a CPU profile to the specified file before exiting.) 56 | "exec", // -exec xprog (Run the test binary using xprog. The behavior is the same as...) 57 | "failfast", // -failfast (Do not start new tests after the first test failure.) 58 | "json", // -json (Convert test output to JSON suitable for automated processing.) 59 | "list", // -list regexp (List tests, benchmarks, or examples matching the regular expression.) 60 | "memprofile", // -memprofile mem.out (Write an allocation profile to the file after all tests have passed.) 61 | "memprofilerate", // -memprofilerate n (Enable more precise (and expensive) memory allocation profiles by...) 62 | "mutexprofile", // -mutexprofile mutex.out (Write a mutex contention profile to the specified file...) 63 | "mutexprofilefraction", // -mutexprofilefraction n (Sample 1 in n stack traces of goroutines holding a...) 64 | "o", // -o file (Compile the test binary to the named file.) 65 | "outputdir", // -outputdir directory (Place output files from profiling in the specified directory,...) 66 | "short", // -short (Tell long-running tests to shorten their run time.) 67 | "trace", // -trace trace.out (Write an execution trace to the specified file before exiting.) 68 | "vet", // -vet list (Configure the invocation of "go vet" during "go test"...) 69 | } 70 | 71 | // UnimplementedTestFlags is a list of 'go test' flags that are expected to be 72 | // eventually be compatible with a final 'go test -fuzz' per the March 2017 proposal document, 73 | // but which are not currently implemented in this simple 'fzgo' prototype. These will 74 | // currently cause an error if used with 'fzgo test -fuzz'. 75 | var UnimplementedTestFlags = []string{ 76 | "coverprofile", // -coverprofile cover.out (Write a coverage profile to the file after all tests have passed.) 77 | "i", // -i (Install packages that are dependencies of the test.) 78 | } 79 | 80 | // supportedBools is a list of allowed boolean flags for 'fzgo test -fuzz'. 81 | var supportedBools = []string{"c", "i", "v"} 82 | 83 | // FlagDef holds the definition of an arg we will interpret. 84 | type FlagDef struct { 85 | Name string 86 | Ptr interface{} // *string, *bool, *int, or *time.Duration 87 | Description string 88 | } 89 | 90 | // ParseArgs finds the flags we are going to interpret and sets the values in 91 | // a flag.FlagSet. ParseArgs handles a package pattern that might appear in the middle 92 | // of args in order to allow the flag.FlagSet.Parse() to find flags after the package pattern. 93 | // ParseArgs also returns at most one package pattern, or "." if none was specified in args. 94 | func ParseArgs(args []string, fs *flag.FlagSet) (string, error) { 95 | report := func(err error) error { return fmt.Errorf("failed parsing fuzzing flags: %v", err) } 96 | 97 | // first, check if we are asked to do anything fuzzing-related by 98 | // checking if -fuzz or -test.fuzz is present. 99 | // also check if -run is passed, because we might being asked to 100 | // verify a corpus. 101 | _, _, ok := FindTestFlag(args, []string{"fuzz", "run"}) 102 | if !ok { 103 | // nothing else to do for any fuzz-related args parsing. 104 | return "", nil 105 | } 106 | 107 | // second, to make the 'fzgo' prototype a bit friendlier, 108 | // give more specific errors for 3 categories of illegal flags. 109 | if illegalFlag, _, ok := FindTestFlag(args, IncompatibleTestFlags); ok { 110 | return "", fmt.Errorf("test flag -%s is currently proposed to be incompatible with 'go test -fuzz'", illegalFlag) 111 | } 112 | if illegalFlag, _, ok := FindTestFlag(args, UnimplementedTestFlags); ok { 113 | return "", fmt.Errorf("test flag -%s is not yet implemented by fzgo prototype", illegalFlag) 114 | } 115 | if illegalFlag, _, ok := FindFlag(args, UnimplementedBuildFlags); ok { 116 | return "", fmt.Errorf("build flag -%s is not yet implemented by fzgo prototype", illegalFlag) 117 | } 118 | 119 | // third, find our package argument like './...' or 'fmt', 120 | // as well as nonPkgArgs are our args minus any package arguments (to set us up to use flag.FlagSet.Parse) 121 | pkgPatterns, nonPkgArgs, err := FindPkgs(args) 122 | if err != nil { 123 | return "", report(err) 124 | } 125 | var pkgPattern string 126 | if len(pkgPatterns) == 0 { 127 | pkgPattern = "." 128 | } else { 129 | pkgPattern = strings.Join(pkgPatterns, " ") 130 | } 131 | 132 | // fourth, we now have a clean set of arguments that we can 133 | // hand to the standard library flag parser (via fuzz.ParseFlags). 134 | // any unrecognized flags should be treated as errors, so 135 | // we now parse our non-package args. 136 | err = fs.Parse(nonPkgArgs) 137 | if err == flag.ErrHelp { 138 | return "", err 139 | } else if err != nil { 140 | return "", report(err) 141 | } 142 | if len(fs.Args()) > 0 { 143 | return "", fmt.Errorf("packages are the only non-flag arguments allowed with -fuzz flag. illegal argument: %q", fs.Arg(0)) 144 | } 145 | return pkgPattern, nil 146 | } 147 | 148 | // FindFlag looks for the first matching arg that looks like a flag in the list of flag names, 149 | // and returns the first flag name found. FindFlag does not stop at non-flag arguments (e.g., 150 | // it does not stop at a package pattern). This is a simple scan, and not a complete parse 151 | // of the arguments (and for example does not differentiate between a bool flag vs. a duration flag). 152 | // A client should do a final valiation pass via fuzz.ParseArgs(), which calls the standard flag.FlagSet.Parse() 153 | // and which will reject malformed arguments according to the normal rules of the flag package. 154 | // Returns the found name, and a possible value that is either the value of name=value, or 155 | // the next arg if there is no '=' immediately after name. It is up to the caller to know 156 | // if the possible value should be interpreted as the actual value (because, for example, 157 | // FindFlag has no knowledge of bool flag vs. other flags, etc.). 158 | func FindFlag(args []string, names []string) (string, string, bool) { 159 | nameSet := map[string]bool{} 160 | for _, name := range names { 161 | nameSet[name] = true 162 | } 163 | 164 | for i := 0; i < len(args); i++ { 165 | arg := args[i] 166 | if flag, ok := candidateFlag(arg); ok { 167 | if flag == "args" { 168 | // for 'go test', go tool specific args are defined to stop at '-args' or '--args', 169 | // so stop looking 170 | return "", "", false 171 | } 172 | foundName, foundValue := "", "" 173 | if strings.Contains(flag, "=") { 174 | // found arg '-foo=bar' or '--foo=bar' 175 | splits := strings.SplitN(flag, "=", 2) 176 | foundName = splits[0] 177 | foundValue = splits[1] 178 | } else { 179 | foundName = flag 180 | if i+1 < len(args) { 181 | foundValue = args[i+1] 182 | } 183 | } 184 | 185 | if nameSet[foundName] { 186 | return foundName, foundValue, true 187 | } 188 | 189 | } 190 | } 191 | return "", "", false 192 | } 193 | 194 | // FindTestFlag looks for the first matching arg that looks like a flag in the list of flag names, 195 | // and returns the first flag name found. If passed flag name 'foo', it looks for both '-foo' and '-test.foo'. 196 | // See FindFlag for more details. 197 | func FindTestFlag(args []string, names []string) (string, string, bool) { 198 | var finalNames []string 199 | for _, n := range names { 200 | finalNames = append(finalNames, n) 201 | finalNames = append(finalNames, "test."+n) 202 | } 203 | return FindFlag(args, finalNames) 204 | } 205 | 206 | // FindPkgs looks for args that seem to be package arguments 207 | // and returns them in a slice. 208 | func FindPkgs(args []string) ([]string, []string, error) { 209 | pkgs := []string{} 210 | supportedBoolSet := map[string]bool{} 211 | for _, name := range supportedBools { 212 | supportedBoolSet[name] = true 213 | } 214 | otherArgs := make([]string, len(args)) 215 | copy(otherArgs, args) 216 | 217 | // loop looking for candidate packages, which are the first non-flag arg(s). 218 | // stop our loop at '-args'/'--args', or after we find any package(s). 219 | for i := 0; i < len(args); i++ { 220 | arg := args[i] 221 | if flag, ok := candidateFlag(arg); ok { 222 | if flag == "args" { 223 | // test-specific args are defined to stop at '-args' or '--args' 224 | // stop processing 225 | return pkgs, otherArgs, nil 226 | } else if !supportedBoolSet[flag] { 227 | // we have a flag, and it is not a bool. 228 | if !strings.Contains(flag, "=") { 229 | // next item should be the value for this flag, 230 | // so extra increment here to move ahead over it. 231 | i++ 232 | continue 233 | } 234 | } 235 | } else { 236 | // found our first non-flag. 237 | pkgs = append(pkgs, arg) 238 | // will return this as our result in a momement, but first 239 | // see if the next arg(s) are non-flag as well. 240 | next := i + 1 241 | for next < len(args) { 242 | if _, ok := candidateFlag(args[next]); ok { 243 | break 244 | } 245 | // found another non-flag 246 | pkgs = append(pkgs, args[next]) 247 | next++ 248 | } 249 | otherArgs = append(otherArgs[0:i], otherArgs[next:]...) 250 | return pkgs, otherArgs, nil 251 | } 252 | } 253 | return pkgs, otherArgs, nil 254 | } 255 | 256 | // candidateFlag reports if the arg has leading hyphens (single or double), 257 | // and also returns the (potential) flag name after stripping the leading '-' or '--'. 258 | // If arg is -foo, returns "foo", true. If arg is -foo=bar, returns "foo=bar", true. 259 | // candidateFlag does not validate beyond checking for 1 or 2 leading hyphens and that 260 | // the candidate flag is non-empty. 261 | func candidateFlag(arg string) (string, bool) { 262 | // Look for leading '-' or '--' 263 | flag := "" 264 | if strings.HasPrefix(arg, "--") { 265 | flag = arg[2:] 266 | } else if strings.HasPrefix(arg, "-") { 267 | flag = arg[1:] 268 | } 269 | if len(flag) == 0 || strings.HasPrefix(flag, "-") { 270 | return "", false 271 | } 272 | return flag, true 273 | } 274 | 275 | // Usage is a func that returns a func that can be used as flag.FlagSet.Usage 276 | type Usage func(*flag.FlagSet) func() 277 | 278 | // FlagSet creates a new flag.FlagSet and registers our flags. 279 | // Each flag is registered once as "foo" and once as "test.foo" 280 | func FlagSet(name string, defs []FlagDef, usage Usage) (*flag.FlagSet, error) { 281 | fs := flag.NewFlagSet(name, flag.ContinueOnError) 282 | // we are taking responsibility for outputting errors and formating usage 283 | fs.SetOutput(ioutil.Discard) 284 | fs.Usage = usage(fs) 285 | for _, d := range defs { 286 | switch v := d.Ptr.(type) { 287 | case *string: 288 | fs.StringVar(v, d.Name, "", d.Description) 289 | fs.StringVar(v, "test."+d.Name, "", d.Description) 290 | case *int: 291 | fs.IntVar(v, d.Name, 0, d.Description) 292 | fs.IntVar(v, "test."+d.Name, 0, d.Description) 293 | case *bool: 294 | fs.BoolVar(v, d.Name, false, d.Description) 295 | fs.BoolVar(v, "test."+d.Name, false, d.Description) 296 | case *time.Duration: 297 | fs.DurationVar(v, d.Name, 0, d.Description) 298 | fs.DurationVar(v, "test."+d.Name, 0, d.Description) 299 | default: 300 | // this would be programmer error 301 | return nil, fmt.Errorf("arguments: unexpected type %T registered for flag %s", d.Ptr, d.Name) 302 | } 303 | } 304 | return fs, nil 305 | } 306 | -------------------------------------------------------------------------------- /fuzz/flag_test.go: -------------------------------------------------------------------------------- 1 | package fuzz 2 | 3 | import ( 4 | "flag" 5 | "reflect" 6 | "strings" 7 | "testing" 8 | ) 9 | 10 | func TestParseArgs(t *testing.T) { 11 | tests := []struct { 12 | name string 13 | args string 14 | want string 15 | wantPkgPattern string 16 | wantErr bool 17 | }{ 18 | {"flag with equal sign", "-fuzz=fuzzfunc", "fuzzfunc", ".", false}, 19 | {"flag with double dash", "--fuzz fuzzfunc", "fuzzfunc", ".", false}, 20 | {"flag without equal sign", "-fuzz fuzzfunc", "fuzzfunc", ".", false}, 21 | {"flag with test. prefix", "-test.fuzz fuzzfunc", "fuzzfunc", ".", false}, 22 | 23 | {"one package", "-fuzz=fuzzfunc sample/pkg", "fuzzfunc", "sample/pkg", false}, 24 | {"two packages in a row", "-fuzz=fuzzfunc sample/pkg1 sample/pkg2", "fuzzfunc", "sample/pkg1 sample/pkg2", false}, 25 | {"two packages separated by flag", "sample/pkg1 -fuzz=fuzzfunc sample/pkg2", "fuzzfunc", "", true}, 26 | 27 | {"incompatible test flag", "-fuzz=fuzzfunc -benchtime=10s", "", "", true}, 28 | {"incompatible build flag", "-fuzz=fuzzfunc -gccgoflags=foo", "", "", true}, 29 | {"not yet implemented fuzzing arg", "-fuzz=fuzzfunc -coverprofile=foo", "", "", true}, 30 | 31 | {"no -fuzz", "-fuzznot=fuzzfunc", "", "", false}, 32 | {"empty args", "", "", "", false}, 33 | } 34 | for _, tt := range tests { 35 | t.Run(tt.name, func(t *testing.T) { 36 | var got string 37 | flagDef := FlagDef{Name: "fuzz", Ptr: &got} 38 | usage := func(*flag.FlagSet) func() { return func() {} } // no-op usage function 39 | fs, _ := FlagSet("test fuzz.ParseArgs", []FlagDef{flagDef}, usage) 40 | 41 | args := strings.Fields(tt.args) 42 | gotPkgPattern, err := ParseArgs(args, fs) 43 | if (err != nil) != tt.wantErr { 44 | t.Errorf("ParseArgs() error = %v, wantErr %v", err, tt.wantErr) 45 | return 46 | } 47 | if got != tt.want { 48 | t.Errorf("ParseArgs() = %v, want %v", got, tt.want) 49 | } 50 | if gotPkgPattern != tt.wantPkgPattern { 51 | t.Errorf("ParseArgs() = %v, wantPkgPattern %v", gotPkgPattern, tt.wantPkgPattern) 52 | } 53 | }) 54 | } 55 | } 56 | 57 | func TestFindFlag(t *testing.T) { 58 | tests := []struct { 59 | name string 60 | args []string 61 | wantName string 62 | wantValue string 63 | wantOK bool 64 | }{ 65 | {"match with =", []string{"-foo", "bar", "-fuzz=fuzzfunc"}, "fuzz", "fuzzfunc", true}, 66 | {"match without =", []string{"-foo", "bar", "-fuzz", "fuzzfunc"}, "fuzz", "fuzzfunc", true}, 67 | {"no value last", []string{"-foo", "bar", "-fuzz"}, "fuzz", "", true}, 68 | {"no value with =", []string{"-foo", "bar", "-fuzz="}, "fuzz", "", true}, 69 | {"false match on value", []string{"-fuzz", "-foo", "bar"}, "fuzz", "-foo", true}, 70 | {"no match", []string{"-foo", "bar", "-fuzznot=fuzzfunc"}, "", "", false}, 71 | {"do not match --", []string{"--", "fuzz"}, "", "", false}, 72 | {"do not match ---fuzz", []string{"---fuzz", "fuzzFunc"}, "", "", false}, 73 | {"do not match non-flags", []string{"fuzz", "fuzzFunc"}, "", "", false}, 74 | {"do not match after -args", []string{"-foo", "bar", "-args", "-fuzz=fuzzfunc"}, "", "", false}, 75 | } 76 | for _, tt := range tests { 77 | t.Run(tt.name, func(t *testing.T) { 78 | names := []string{"fuzz", "test.fuzz"} 79 | gotName, gotValue, gotOK := FindFlag(tt.args, names) 80 | if gotName != tt.wantName { 81 | t.Errorf("FindFlag() gotName = %v, want %v", gotName, tt.wantName) 82 | } 83 | if gotValue != tt.wantValue { 84 | t.Errorf("FindFlag() gotValue = %v, want %v", gotValue, tt.wantValue) 85 | } 86 | if gotOK != tt.wantOK { 87 | t.Errorf("FindFlag() got1 = %v, want %v", gotOK, tt.wantOK) 88 | } 89 | }) 90 | } 91 | } 92 | 93 | func TestFindPkg(t *testing.T) { 94 | tests := []struct { 95 | name string 96 | args []string 97 | want []string 98 | wantErr bool 99 | }{ 100 | {"no pkgs", []string{"-f", "-a=val", "-foo", "bar"}, []string{}, false}, 101 | {"one pkg", []string{"-f", "val", "pkg", "-foo", "bar"}, []string{"pkg"}, false}, 102 | {"two pkgs", []string{"-f", "val", "pkg1", "pkg2", "-foo", "bar"}, []string{"pkg1", "pkg2"}, false}, 103 | {"pkg last", []string{"-a=val", "-foo", "bar", "pkg"}, []string{"pkg"}, false}, 104 | {"pkg first", []string{"pkg", "-a=val", "-foo", "bar"}, []string{"pkg"}, false}, 105 | {"known bool", []string{"-v", "pkg1", "pkg2", "-foo", "bar"}, []string{"pkg1", "pkg2"}, false}, 106 | {"triple dot", []string{"-fuzz=Fuzz", "./...", "-foo"}, []string{"./..."}, false}, 107 | {"stop at -args", []string{"-args", "notpkg1", "notpkg2"}, []string{}, false}, 108 | } 109 | for _, tt := range tests { 110 | t.Run(tt.name, func(t *testing.T) { 111 | got, _, err := FindPkgs(tt.args) 112 | if (err != nil) != tt.wantErr { 113 | t.Errorf("findPkg() error = %v, wantErr %v", err, tt.wantErr) 114 | return 115 | } 116 | if !reflect.DeepEqual(got, tt.want) { 117 | t.Errorf("findPkg() = %v, want %v", got, tt.want) 118 | } 119 | }) 120 | } 121 | } 122 | 123 | func Test_isFlag(t *testing.T) { 124 | tests := []struct { 125 | name string 126 | arg string 127 | want string 128 | wantOK bool 129 | }{ 130 | {"single", "-flag", "flag", true}, 131 | {"double", "--flag", "flag", true}, 132 | {"value", "-flag=value", "flag=value", true}, 133 | {"non-flag", "opt", "", false}, 134 | {"only hyphens", "--", "", false}, 135 | } 136 | for _, tt := range tests { 137 | t.Run(tt.name, func(t *testing.T) { 138 | got, gotOK := candidateFlag(tt.arg) 139 | if got != tt.want { 140 | t.Errorf("isFlag() got = %v, want %v", got, tt.want) 141 | } 142 | if gotOK != tt.wantOK { 143 | t.Errorf("isFlag() gotOK = %v, want %v", gotOK, tt.wantOK) 144 | } 145 | }) 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /fuzz/packages.go: -------------------------------------------------------------------------------- 1 | package fuzz 2 | 3 | import ( 4 | "fmt" 5 | "go/types" 6 | "os" 7 | "os/exec" 8 | "regexp" 9 | "sort" 10 | "strings" 11 | 12 | "golang.org/x/tools/go/packages" 13 | ) 14 | 15 | const buildTagsArg = "-tags=gofuzz fuzz" 16 | 17 | // Func represents a discovered function that will be fuzzed. 18 | type Func struct { 19 | FuncName string 20 | PkgName string // package name (should be the same as the package's package statement) 21 | PkgPath string // import path 22 | PkgDir string // local on-disk directory 23 | TypesFunc *types.Func // auxiliary information about a Func from the go/types package 24 | } 25 | 26 | // FuzzName returns the '.' string. 27 | // For example, it might be 'fmt.FuzzFmt'. This is used 28 | // in messages, as well it is part of the path when creating 29 | // the corpus location under testdata. 30 | func (f *Func) FuzzName() string { 31 | return fmt.Sprintf("%s.%s", f.PkgName, f.FuncName) 32 | } 33 | 34 | func (f *Func) String() string { 35 | return f.FuzzName() 36 | } 37 | 38 | // FindFunc searches for a requested function to fuzz. 39 | // It is not an error to not find any -- in that case, it returns a nil list and nil error. 40 | // The March 2017 proposal document https://github.com/golang/go/issues/19109#issuecomment-285456008 41 | // suggests not allowing something like 'go test -fuzz=. ./...' to match multiple fuzz functions. 42 | // As an experiment, allowMultiFuzz flag allows that. 43 | // FindFunc also allows for multiple packages in pkgPattern separated by whitespace. 44 | func FindFunc(pkgPattern, funcPattern string, env []string, allowMultiFuzz bool) ([]Func, error) { 45 | report := func(err error) error { 46 | return fmt.Errorf("error while loading packages for pattern %v: %v", pkgPattern, err) 47 | } 48 | var result []Func 49 | 50 | // load packages based on our package pattern 51 | // build tags example: https://groups.google.com/d/msg/golang-tools/Adwr7jEyDmw/wQZ5qi8ZGAAJ 52 | cfg := &packages.Config{ 53 | Mode: packages.LoadSyntax, 54 | BuildFlags: []string{buildTagsArg}, 55 | } 56 | if len(env) > 0 { 57 | cfg.Env = env 58 | } 59 | pkgs, err := packages.Load(cfg, strings.Fields(pkgPattern)...) 60 | if err != nil { 61 | return nil, report(err) 62 | } 63 | if packages.PrintErrors(pkgs) > 0 { 64 | return nil, fmt.Errorf("package load error for package pattern %v", pkgPattern) 65 | } 66 | 67 | // look for a func that starts with 'Fuzz' and matches our regexp. 68 | // loop over the packages we found and loop over the Defs for each package. 69 | for _, pkg := range pkgs { 70 | for id, obj := range pkg.TypesInfo.Defs { 71 | // check if we have a func 72 | f, ok := obj.(*types.Func) 73 | if ok { 74 | 75 | // check if it starts with "Fuzz" and matches our fuzz function regular expression 76 | if !strings.HasPrefix(id.Name, "Fuzz") { 77 | continue 78 | } 79 | 80 | matchedPattern, err := regexp.MatchString(funcPattern, id.Name) 81 | if err != nil { 82 | return nil, report(err) 83 | } 84 | if matchedPattern { 85 | // found a match. 86 | // check if we already found a match in a prior iteration our of loops. 87 | if len(result) > 0 && !allowMultiFuzz { 88 | return nil, fmt.Errorf("multiple matches not allowed. multiple matches for pattern %v and func %v: %v.%v and %v.%v", 89 | pkgPattern, funcPattern, pkg.PkgPath, id.Name, result[0].PkgPath, result[0].FuncName) 90 | } 91 | pkgDir, err := goListDir(pkg.PkgPath, env) 92 | if err != nil { 93 | return nil, report(err) 94 | } 95 | 96 | function := Func{ 97 | FuncName: id.Name, PkgName: pkg.Name, PkgPath: pkg.PkgPath, PkgDir: pkgDir, 98 | TypesFunc: f, 99 | } 100 | result = append(result, function) 101 | 102 | // keep looping to see if we find another match 103 | } 104 | } 105 | } 106 | } 107 | // done looking 108 | if len(result) == 0 { 109 | return nil, fmt.Errorf("failed to find fuzz function for pattern %v and func %v", pkgPattern, funcPattern) 110 | } 111 | // put our found functions into a deterministic order 112 | sort.Slice(result, func(i, j int) bool { 113 | // types.Func.String outputs strings like: 114 | // func (github.com/thepudds/fzgo/genfuzzfuncs/examples/test-constructor-injection.A).ValMethodWithArg(i int) bool 115 | // works ok for clustering results, though pointer receiver and non-pointer receiver methods don't cluster. 116 | // for direct fuzzing, we only support functions, not methods, though for wrapping (outside of fzgo itself) we support methods. 117 | // could strip '*' or sort another way, but probably ok, at least for now. 118 | return result[i].TypesFunc.String() < result[j].TypesFunc.String() 119 | }) 120 | 121 | return result, nil 122 | } 123 | 124 | // goListDir returns the dir for a package import path 125 | func goListDir(pkgPath string, env []string) (string, error) { 126 | if len(env) == 0 { 127 | env = os.Environ() 128 | } 129 | 130 | cmd := exec.Command("go", "list", "-f", "{{.Dir}}", buildTagsArg, pkgPath) 131 | cmd.Env = env 132 | 133 | out, err := cmd.Output() 134 | if err != nil { 135 | return "", fmt.Errorf("failed to find directory of %v: %v", pkgPath, err) 136 | } 137 | result := strings.TrimSpace(string(out)) 138 | if strings.Contains(result, "\n") { 139 | return "", fmt.Errorf("multiple directory results for package %v", pkgPath) 140 | } 141 | return result, nil 142 | } 143 | -------------------------------------------------------------------------------- /fuzz/richsig.go: -------------------------------------------------------------------------------- 1 | package fuzz 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "go/types" 7 | "io" 8 | "io/ioutil" 9 | "os" 10 | "path/filepath" 11 | "strings" 12 | 13 | _ "github.com/thepudds/fzgo/randparam" // TODO: for now, force import to simplify install 14 | "golang.org/x/tools/imports" 15 | ) 16 | 17 | // richsig enables fuzzing of rich function signatures with fzgo and dvyukov/go-fuzz beyond 18 | // just func([]byte) int. 19 | // 20 | // For example, without manual work, can fuzz functions like: 21 | // func FuzzFunc(re string, input []byte, posix bool) (bool, error) 22 | 23 | // some examples that work: 24 | // ./fzgo test -fuzz=. ./examples/richsignatures 25 | // 26 | // this uses all basic types: 27 | // ./fzgo test ./examples/... -fuzz=FuzzWithBasicTypes 28 | // this uses a non-stdlib type: 29 | // ./fzgo test ./examples/... -fuzz=FuzzWithFzgoFunc 30 | // this uses goimports right now to set up imports: 31 | // ./fzgo test ./examples/... -fuzz=FuzzWithStdlibType 32 | // 33 | // check literal injection (works): 34 | // ./fzgo test ./examples/... -fuzz=FuzzHardToGuessNumber 35 | 36 | // IsPlainSig reports whether a signature is a classic, plain 'func([]bytes) int' 37 | // go-fuzz signature. 38 | func IsPlainSig(f *types.Func) (bool, error) { 39 | sig, ok := f.Type().(*types.Signature) 40 | if !ok { 41 | return false, fmt.Errorf("function is not *types.Signature (%+v)", f) 42 | } 43 | results := sig.Results() 44 | params := sig.Params() 45 | if params.Len() != 1 || results.Len() != 1 { 46 | return false, nil 47 | } 48 | if types.TypeString(params.At(0).Type(), nil) != "[]byte" { 49 | return false, nil 50 | } 51 | if types.TypeString(results.At(0).Type(), nil) != "int" { 52 | return false, nil 53 | } 54 | return true, nil 55 | } 56 | 57 | // CreateRichSigWrapper creates a temp working directory, then 58 | // creates a rich signature wrapping fuzz function. 59 | // Important: don't set printArgs=true when actually fuzzing. (Likely bad for perf, though not yet attempted). 60 | func CreateRichSigWrapper(function Func, printArgs bool) (t Target, err error) { 61 | report := func(err error) (Target, error) { 62 | return Target{}, fmt.Errorf("creating wrapper function for %s: %v", function.FuzzName(), err) 63 | } 64 | 65 | // create temp dir to work in 66 | tempDir, err := ioutil.TempDir("", "fzgo-fuzz-rich-signature") 67 | if err != nil { 68 | return report(fmt.Errorf("create staging temp dir: %v", err)) 69 | } 70 | defer func() { 71 | // conditionally clean up. (this is a bit of an experiment to use named return err here). 72 | if err != nil { 73 | // on our way out, but encountered an error, so delete the temp dir 74 | os.RemoveAll(tempDir) 75 | } 76 | }() 77 | 78 | // to support modules, the first element of our import path must include a '.'. 79 | wrapperDir := filepath.Join(tempDir, "gopath", "src", "fzgo.tmp", "richsigwrapper") 80 | if err := os.MkdirAll(wrapperDir, 0700); err != nil { 81 | return report(fmt.Errorf("failed to create gopath/src in temp dir: %v", err)) 82 | } 83 | 84 | origGp := Gopath() 85 | gp := strings.Join([]string{origGp, filepath.Join(tempDir, "gopath")}, 86 | string(os.PathListSeparator)) 87 | 88 | // cd to our temp dir to simplify things when we indirectly invoke the 89 | // 'go' command (e.g., when searching for funcs below). 90 | oldWd, err := os.Getwd() 91 | if err != nil { 92 | return report(err) 93 | } 94 | err = os.Chdir(wrapperDir) 95 | if err != nil { 96 | return report(err) 97 | } 98 | defer func() { os.Chdir(oldWd) }() 99 | 100 | // create our temporary richsigwrapper.go file 101 | var b bytes.Buffer 102 | err = createWrapper(&b, function, printArgs) 103 | if err != nil { 104 | return report(fmt.Errorf("failed constructing rich signature wrapper: %v", err)) 105 | } 106 | 107 | // fix up any needed imports. 108 | out, err := imports.Process("richsigwrapper.go", b.Bytes(), nil) 109 | if err != nil { 110 | return report(fmt.Errorf("failed adjusting imports: %v", err)) 111 | } 112 | 113 | err = ioutil.WriteFile(filepath.Join(wrapperDir, "richsigwrapper.go"), out, 0700) 114 | if err != nil { 115 | return report(fmt.Errorf("failed to create temporary richsigwrapper.go: %v", err)) 116 | } 117 | 118 | // Create an env map to include our temporary gopath. 119 | // (If env contains duplicate environment keys for GOPATH, only the last value is used). 120 | env := append(os.Environ(), "GOPATH="+gp) 121 | 122 | // Re-use our fuzz.FindFunc to find the newly created wrapper. 123 | // Note: pkg patterns like 'fzgo/...' and 'fzgo/richsigwrapper' don't seem to work, but '.' does. 124 | // (We cd'ed above to the working directory. Maybe a go/packages bug, not liking >1 GOPATH entry?) 125 | functions, err := FindFunc("fzgo.tmp/richsigwrapper", "FuzzRichSigWrapper", env, false) 126 | if err != nil || len(functions) == 0 { 127 | return report(fmt.Errorf("failed to find wrapper func in temp gopath: %v", err)) 128 | } 129 | 130 | // Pull together everything we need about our wrapper into a Target. 131 | // This will be the actual target for go-fuzz-build and go-fuzz, 132 | // though we track the user's original function so we can send 133 | // the output to the proper location under the original location if requested, 134 | // and use the original path for cache computation, 135 | // as well as show friendly names and more generally mask from the user that 136 | // we created a temporary wrapper. 137 | target := Target{ 138 | UserFunc: function, 139 | hasWrapper: true, 140 | wrapperFunc: functions[0], 141 | wrapperEnv: env, 142 | wrapperTempDir: wrapperDir, 143 | } 144 | 145 | return target, nil 146 | } 147 | 148 | func createWrapper(w io.Writer, function Func, printArgs bool) error { 149 | f := function.TypesFunc 150 | sig, ok := f.Type().(*types.Signature) 151 | if !ok { 152 | return fmt.Errorf("function %s is not *types.Signature (%+v)", function, f) 153 | } 154 | 155 | // start emitting the wrapper program! 156 | // TODO: add in something like: fuzzer := gofuzz.New().NilChance(0.1).NumElements(0, 10).MaxDepth(10) 157 | fmt.Fprintf(w, "\npackage richsigwrapper\n") 158 | fmt.Fprintf(w, "\nimport \"%s\"\n", function.PkgPath) 159 | fmt.Fprintf(w, ` 160 | import "github.com/thepudds/fzgo/randparam" 161 | 162 | // FuzzRichSigWrapper is an automatically generated wrapper that is 163 | // compatible with dvyukov/go-fuzz. 164 | func FuzzRichSigWrapper(data []byte) int { 165 | fuzzer := randparam.NewFuzzer(data) 166 | fuzzOne(fuzzer) 167 | return 0 168 | } 169 | 170 | // fuzzOne is an automatically generated function that 171 | // uses fzgo/randparam.Fuzzer to automatically fuzz the arguments for a 172 | // user-supplied function. 173 | func fuzzOne (fuzzer *randparam.Fuzzer) { 174 | 175 | // Create random args for each parameter from the signature. 176 | // fuzzer.Fuzz recursively fills all of obj's fields with something random. 177 | // Only exported (public) fields can be set currently. (That is how google/go-fuzz operates). 178 | `) 179 | 180 | // emit declaring and filling the arguments we will 181 | // pass into the wrapped function. 182 | fillVars(w, sig, printArgs) 183 | 184 | // emit the call to the wrapped function 185 | fmt.Fprintf(w, "\t%s.%s(", f.Pkg().Name(), f.Name()) // was target.%s with f.Name() 186 | 187 | // emit the arguments to the wrapped function 188 | var names []string 189 | for i := 0; i < sig.Params().Len(); i++ { 190 | v := sig.Params().At(i) 191 | names = append(names, v.Name()) 192 | } 193 | fmt.Fprintf(w, "%s)\n\n}\n", strings.Join(names, ", ")) 194 | 195 | return nil 196 | } 197 | 198 | // InterfaceImpl contains the interfaces we can fuzz 199 | // mapped to the implementation approach. 200 | // Anything added here should be added to FuzzInterfaceFullList test in fuzz_rich_signatures.txt. 201 | // Rough counts of most common interfaces in public funcs/methods For stdlib 202 | // (based on output from early version of fzgo that skipped all interfaces): 203 | // $ grep -r 'skipping' | awk '{print $10}' | grep -v 'func' | sort | uniq -c | sort -rn | head -20 204 | // 146 io.Writer 205 | // 122 io.Reader 206 | // 75 reflect.Type 207 | // 64 go/types.Type 208 | // 55 interface{} 209 | // 44 context.Context 210 | // 41 []interface{} 211 | // 22 go/constant.Value 212 | // 17 net.Conn 213 | // 17 math/rand.Source 214 | // 16 net/http.ResponseWriter 215 | // 16 net/http.Handler 216 | // 16 image/color.Color 217 | // 13 io.ReadWriteCloser 218 | // 13 error 219 | // 12 image/color.Palette 220 | // 11 io.ReaderAt 221 | // 9 crypto/cipher.Block 222 | // 8 net.Listener 223 | // 6 go/ast.Node 224 | var InterfaceImpl = map[string]string{ 225 | "io.Writer": "ioutil.Discard", 226 | 227 | "io.Reader": "bytes.Reader", 228 | "io.ReaderAt": "bytes.Reader", 229 | "io.WriterTo": "bytes.Reader", 230 | "io.Seeker": "bytes.Reader", 231 | "io.ByteScanner": "bytes.Reader", 232 | "io.RuneScanner": "bytes.Reader", 233 | "io.ReadSeeker": "bytes.Reader", 234 | "io.ByteReader": "bytes.Reader", 235 | "io.RuneReader": "bytes.Reader", 236 | 237 | "io.ByteWriter": "bytes.Buffer", 238 | "io.ReadWriter": "bytes.Buffer", // TODO: consider a bytes.Reader + ioutil.Discard? 239 | "io.ReaderFrom": "bytes.Buffer", 240 | "io.StringWriter": "bytes.Buffer", 241 | 242 | // TODO: debatable if we should support Closer at all, 243 | // vs. make something that panics if used after a Close (but that is not always desirable), 244 | // vs. using NopCloser. To start simply, we'll use NoopCloser for now. 245 | // Not yet supported include: io.ReadWriteCloser, io.WriteCloser 246 | "io.Closer": "ioutil.NopCloser", 247 | "io.ReadCloser": "ioutil.NopCloser", 248 | 249 | "context.Context": "context.Background", 250 | } 251 | 252 | // fillVars declares and populates each variable for the function under test. 253 | // It iterates over the parameters, emitting the wrapper function as it goes. 254 | func fillVars(w io.Writer, sig *types.Signature, printArgs bool) { 255 | // first version was loosely modeled after PrintHugeParams in https://github.com/golang/example/blob/master/gotypes/hugeparam/main.go#L24 256 | for i := 0; i < sig.Params().Len(); i++ { 257 | v := sig.Params().At(i) 258 | // example: 259 | // var foo string 260 | typeStringWithSelector := types.TypeString(v.Type(), externalQualifier) 261 | fmt.Fprintf(w, "\tvar %s %s\n", v.Name(), typeStringWithSelector) 262 | 263 | // Set the value based on whether this is an interface 264 | // for which we do something special. If we don't find 265 | // anything in our InterfaceImpl, default to attempting to 266 | // fill the variable directly. 267 | switch InterfaceImpl[typeStringWithSelector] { 268 | case "ioutil.Discard": 269 | // example: 270 | // w = ioutil.Discard 271 | fmt.Fprintf(w, "\t%s = ioutil.Discard\n", v.Name()) 272 | case "bytes.Reader": 273 | // example: 274 | // var __fzgoTmp1 []byte 275 | // fuzzer.Fuzz(&__fzgoTmp1) 276 | // r = bytes.NewReader(__fzgoTemp1) 277 | fmt.Fprintf(w, "\tvar __fzgoTmp%d []byte\n", i+1) 278 | fmt.Fprintf(w, "\tfuzzer.Fuzz(&__fzgoTmp%d)\n", i+1) 279 | fmt.Fprintf(w, "\t%s = bytes.NewReader(__fzgoTmp%d)\n", v.Name(), i+1) 280 | case "bytes.Buffer": 281 | // example: 282 | // var __fzgoTmp1 []byte 283 | // fuzzer.Fuzz(&__fzgoTmp1) 284 | // foo = bytes.NewBuffer(__fzgoTemp1) 285 | fmt.Fprintf(w, "\tvar __fzgoTmp%d []byte\n", i+1) 286 | fmt.Fprintf(w, "\tfuzzer.Fuzz(&__fzgoTmp%d)\n", i+1) 287 | fmt.Fprintf(w, "\t%s = bytes.NewBuffer(__fzgoTmp%d)\n", v.Name(), i+1) 288 | case "ioutil.NopCloser": 289 | // example: 290 | // var __fzgoTmp1 []byte 291 | // fuzzer.Fuzz(&__fzgoTmp1) 292 | // r = ioutil.NopCloser(bytes.NewReader(__fzgoTemp1)) 293 | fmt.Fprintf(w, "\tvar __fzgoTmp%d []byte\n", i+1) 294 | fmt.Fprintf(w, "\tfuzzer.Fuzz(&__fzgoTmp%d)\n", i+1) 295 | fmt.Fprintf(w, "\t%s = ioutil.NopCloser(bytes.NewReader(__fzgoTmp%d))\n", v.Name(), i+1) 296 | case "context.Context": 297 | // example: 298 | // ctx = context.Background() 299 | fmt.Fprintf(w, "\t%s = context.Background()\n", v.Name()) 300 | default: 301 | // Use the type directly. 302 | // example: 303 | // fuzzer.Fuzz(&foo) 304 | fmt.Fprintf(w, "\tfuzzer.Fuzz(&%s)\n", v.Name()) 305 | } 306 | 307 | if printArgs { 308 | fmt.Fprintf(w, "\tfmt.Printf(\" %20s: %%#v\\n\", %s)\n", 309 | v.Name(), v.Name()) 310 | } 311 | fmt.Fprintf(w, "\n") 312 | } 313 | } 314 | 315 | // externalQualifier can be used as types.Qualifier in calls to types.TypeString and similar. 316 | func externalQualifier(p *types.Package) string { 317 | // always return the package name, which 318 | // should give us things like pkgname.SomeType 319 | return p.Name() 320 | } 321 | -------------------------------------------------------------------------------- /fuzz/richsig_test.go: -------------------------------------------------------------------------------- 1 | package fuzz 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | 7 | "github.com/google/go-cmp/cmp" 8 | ) 9 | 10 | func TestWrapperGeneration(t *testing.T) { 11 | type args struct { 12 | pkgPattern string 13 | funcPattern string 14 | allowMultiFuzz bool 15 | printArgs bool 16 | } 17 | tests := []struct { 18 | name string 19 | args args 20 | wantOutput string 21 | wantErr bool 22 | }{ 23 | { 24 | name: "only basic types: string, []byte, bool", 25 | args: args{ 26 | funcPattern: "FuzzWithBasicTypes", 27 | pkgPattern: "github.com/thepudds/fzgo/examples/richsignatures", 28 | printArgs: false, 29 | }, 30 | wantErr: false, 31 | wantOutput: ` 32 | package richsigwrapper 33 | 34 | import "github.com/thepudds/fzgo/examples/richsignatures" 35 | 36 | import "github.com/thepudds/fzgo/randparam" 37 | 38 | // FuzzRichSigWrapper is an automatically generated wrapper that is 39 | // compatible with dvyukov/go-fuzz. 40 | func FuzzRichSigWrapper(data []byte) int { 41 | fuzzer := randparam.NewFuzzer(data) 42 | fuzzOne(fuzzer) 43 | return 0 44 | } 45 | 46 | // fuzzOne is an automatically generated function that 47 | // uses fzgo/randparam.Fuzzer to automatically fuzz the arguments for a 48 | // user-supplied function. 49 | func fuzzOne (fuzzer *randparam.Fuzzer) { 50 | 51 | // Create random args for each parameter from the signature. 52 | // fuzzer.Fuzz recursively fills all of obj's fields with something random. 53 | // Only exported (public) fields can be set currently. (That is how google/go-fuzz operates). 54 | var re string 55 | fuzzer.Fuzz(&re) 56 | 57 | var input []byte 58 | fuzzer.Fuzz(&input) 59 | 60 | var posix bool 61 | fuzzer.Fuzz(&posix) 62 | 63 | pkgname.FuzzWithBasicTypes(re, input, posix) 64 | 65 | } 66 | `, 67 | }, 68 | { 69 | name: "type from stdlib: regexp", 70 | args: args{ 71 | funcPattern: "FuzzWithStdlibType", 72 | pkgPattern: "github.com/thepudds/fzgo/examples/richsignatures", 73 | printArgs: false, 74 | }, 75 | wantErr: false, 76 | wantOutput: ` 77 | package richsigwrapper 78 | 79 | import "github.com/thepudds/fzgo/examples/richsignatures" 80 | 81 | import "github.com/thepudds/fzgo/randparam" 82 | 83 | // FuzzRichSigWrapper is an automatically generated wrapper that is 84 | // compatible with dvyukov/go-fuzz. 85 | func FuzzRichSigWrapper(data []byte) int { 86 | fuzzer := randparam.NewFuzzer(data) 87 | fuzzOne(fuzzer) 88 | return 0 89 | } 90 | 91 | // fuzzOne is an automatically generated function that 92 | // uses fzgo/randparam.Fuzzer to automatically fuzz the arguments for a 93 | // user-supplied function. 94 | func fuzzOne (fuzzer *randparam.Fuzzer) { 95 | 96 | // Create random args for each parameter from the signature. 97 | // fuzzer.Fuzz recursively fills all of obj's fields with something random. 98 | // Only exported (public) fields can be set currently. (That is how google/go-fuzz operates). 99 | var something string 100 | fuzzer.Fuzz(&something) 101 | 102 | var another string 103 | fuzzer.Fuzz(&another) 104 | 105 | var allow bool 106 | fuzzer.Fuzz(&allow) 107 | 108 | var re *regexp.Regexp 109 | fuzzer.Fuzz(&re) 110 | 111 | pkgname.FuzzWithStdlibType(something, another, allow, re) 112 | 113 | } 114 | `, 115 | }, 116 | { 117 | name: "type from outside stdlib: github.com/fzgo/fuzz.Func", 118 | args: args{ 119 | funcPattern: "FuzzWithFzgoFunc", 120 | pkgPattern: "github.com/thepudds/fzgo/examples/richsignatures", 121 | }, 122 | wantErr: false, 123 | wantOutput: ` 124 | package richsigwrapper 125 | 126 | import "github.com/thepudds/fzgo/examples/richsignatures" 127 | 128 | import "github.com/thepudds/fzgo/randparam" 129 | 130 | // FuzzRichSigWrapper is an automatically generated wrapper that is 131 | // compatible with dvyukov/go-fuzz. 132 | func FuzzRichSigWrapper(data []byte) int { 133 | fuzzer := randparam.NewFuzzer(data) 134 | fuzzOne(fuzzer) 135 | return 0 136 | } 137 | 138 | // fuzzOne is an automatically generated function that 139 | // uses fzgo/randparam.Fuzzer to automatically fuzz the arguments for a 140 | // user-supplied function. 141 | func fuzzOne (fuzzer *randparam.Fuzzer) { 142 | 143 | // Create random args for each parameter from the signature. 144 | // fuzzer.Fuzz recursively fills all of obj's fields with something random. 145 | // Only exported (public) fields can be set currently. (That is how google/go-fuzz operates). 146 | var f fuzz.Func 147 | fuzzer.Fuzz(&f) 148 | 149 | pkgname.FuzzWithFzgoFunc(f) 150 | 151 | } 152 | `, 153 | }, 154 | { 155 | name: "print args with verbose", 156 | args: args{ 157 | funcPattern: "FuzzWithBasicTypes", 158 | pkgPattern: "github.com/thepudds/fzgo/examples/richsignatures", 159 | printArgs: true, 160 | }, 161 | wantErr: false, 162 | wantOutput: ` 163 | package richsigwrapper 164 | 165 | import "github.com/thepudds/fzgo/examples/richsignatures" 166 | 167 | import "github.com/thepudds/fzgo/randparam" 168 | 169 | // FuzzRichSigWrapper is an automatically generated wrapper that is 170 | // compatible with dvyukov/go-fuzz. 171 | func FuzzRichSigWrapper(data []byte) int { 172 | fuzzer := randparam.NewFuzzer(data) 173 | fuzzOne(fuzzer) 174 | return 0 175 | } 176 | 177 | // fuzzOne is an automatically generated function that 178 | // uses fzgo/randparam.Fuzzer to automatically fuzz the arguments for a 179 | // user-supplied function. 180 | func fuzzOne (fuzzer *randparam.Fuzzer) { 181 | 182 | // Create random args for each parameter from the signature. 183 | // fuzzer.Fuzz recursively fills all of obj's fields with something random. 184 | // Only exported (public) fields can be set currently. (That is how google/go-fuzz operates). 185 | var re string 186 | fuzzer.Fuzz(&re) 187 | fmt.Printf(" re: %#v\n", re) 188 | 189 | var input []byte 190 | fuzzer.Fuzz(&input) 191 | fmt.Printf(" input: %#v\n", input) 192 | 193 | var posix bool 194 | fuzzer.Fuzz(&posix) 195 | fmt.Printf(" posix: %#v\n", posix) 196 | 197 | pkgname.FuzzWithBasicTypes(re, input, posix) 198 | 199 | } 200 | `, 201 | }, 202 | } 203 | for _, tt := range tests { 204 | t.Run(tt.name, func(t *testing.T) { 205 | var b bytes.Buffer 206 | functions, err := FindFunc(tt.args.pkgPattern, tt.args.funcPattern, nil, tt.args.allowMultiFuzz) 207 | if (err != nil) != tt.wantErr { 208 | t.Fatalf("FindFunc() error = %v, wantErr %v", err, tt.wantErr) 209 | } 210 | err = createWrapper(&b, functions[0], tt.args.printArgs) 211 | if err != nil { 212 | t.Fatalf("createWrapper() error = %v", err) 213 | } 214 | gotOutput := b.String() 215 | diff := cmp.Diff(tt.wantOutput, gotOutput) 216 | if diff != "" { 217 | t.Fatalf("createWrapper() failed to match function output. diff:\n%s", diff) 218 | } 219 | }) 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /fuzz/runcorpus.go: -------------------------------------------------------------------------------- 1 | package fuzz 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "io/ioutil" 8 | "os" 9 | "path/filepath" 10 | "regexp" 11 | "strings" 12 | "text/template" 13 | ) 14 | 15 | // ErrGoTestFailed indicates that a 'go test' invocation failed, 16 | // most likely because the test had a legitimate failure. 17 | var ErrGoTestFailed = errors.New("go test failed") 18 | 19 | // VerifyCorpus runs all of the files in a corpus directory as subtests. 20 | // The approach is to create a temp dir, then create a synthetic corpus_test.go 21 | // file with a TestCorpus(t *testing.T) func that loads all the files from the corpus, 22 | // and passes them to the Fuzz function in a t.Run call. 23 | // A standard 'go test .' is then invoked within that temporary directory. 24 | // The inputs used are all deterministic (without generating new fuzzing-based inputs). 25 | // The names used with t.Run mean a 'fzgo test -run=TestCorpus/' works. 26 | // One way to see the file names or otherwise verify execution is to run 'fzgo test -v '. 27 | func VerifyCorpus(function Func, workDir string, run string, verbose bool) error { 28 | corpusDir := filepath.Join(workDir, "corpus") 29 | return verifyFiles(function, corpusDir, run, "TestCorpus", verbose) 30 | } 31 | 32 | // VerifyCrashers is similar to VerifyCorpus, but runs the crashers. It 33 | // can be useful to pass -v to what is causing a crash, such as 'fzgo test -v -fuzz=. -run=TestCrashers' 34 | func VerifyCrashers(function Func, workDir string, run string, verbose bool) error { 35 | crashersDir := filepath.Join(workDir, "crashers") 36 | return verifyFiles(function, crashersDir, run, "TestCrashers", verbose) 37 | } 38 | 39 | // verifyFiles implements the heart of VerifyCorpus and VerifyCrashers 40 | func verifyFiles(function Func, filesDir string, run string, testFunc string, verbose bool) error { 41 | report := func(err error) error { 42 | if err == ErrGoTestFailed { 43 | return err 44 | } 45 | return fmt.Errorf("verify corpus for %s: %v", function.FuzzName(), err) 46 | } 47 | 48 | if _, err := os.Stat(filesDir); os.IsNotExist(err) { 49 | // No corpus to validate. 50 | // TODO: a future real 'go test' invocation should be silent in this case, 51 | // given the proposed intent is to always check for a corpus for normal 'go test' invocations. 52 | // However, maybe fzgo should warn? or warn if -v is passed? or always be silent? 53 | // Right now, main.go is making the decision to skip calling VerifyCorpus if workDir is not found. 54 | return nil 55 | } 56 | 57 | // Check if we have a regex match for -run regexp for this testFunc 58 | // TODO: add test for TestCorpus/fced9f7db3881a5250d7e287ab8c33f2952f0e99-8 59 | // cd fzgo/examples 60 | // fzgo test -fuzz=FuzzWithBasicTypes -run=TestCorpus/fced9f7db3881a5250d7e287ab8c33f2952f0e99-8 ./... -v 61 | // Doesn't print anything? 62 | if run == "" { 63 | report(fmt.Errorf("invalid empty run argument")) 64 | } 65 | runFields := strings.SplitN(run, "/", 2) 66 | re1 := runFields[0] 67 | ok, err := regexp.MatchString(re1, testFunc) 68 | if err != nil { 69 | report(fmt.Errorf("invalid regexp %q for -run: %v", run, err)) 70 | } 71 | if !ok { 72 | // Nothing to do. Return now to avoid 'go test' saying nothing to do. 73 | return nil 74 | } 75 | 76 | // Do a light test to see if there are any files in the filesDir. 77 | // This avoids 'go test' from reporting 'no tests' (and does not need to be perfect check). 78 | re2 := "." 79 | matchedFile := false 80 | if len(runFields) > 1 { 81 | re2 = runFields[1] 82 | } 83 | entries, err := ioutil.ReadDir(filesDir) 84 | if err != nil { 85 | report(err) 86 | } 87 | for i := range entries { 88 | ok, err := regexp.MatchString(re2, entries[i].Name()) 89 | if err != nil { 90 | report(fmt.Errorf("invalid regexp %q for -run: %v", run, err)) 91 | } 92 | if ok { 93 | matchedFile = true 94 | break 95 | } 96 | } 97 | if !matchedFile { 98 | return nil 99 | } 100 | 101 | // check if we have a plain data []byte signature, vs. a rich signature 102 | plain, err := IsPlainSig(function.TypesFunc) 103 | if err != nil { 104 | return report(err) 105 | } 106 | 107 | var target Target 108 | if plain { 109 | // create our initial target struct using the actual func supplied by the user. 110 | target = Target{UserFunc: function} 111 | } else { 112 | info("detected rich signature for %v.%v", function.PkgName, function.FuncName) 113 | // create a wrapper function to handle the rich signature. 114 | // if both -v and -run is set (presumaly to some corpus file), 115 | // as a convinience also print the deserialized arguments 116 | printArgs := verbose && run != "" 117 | 118 | target, err = CreateRichSigWrapper(function, printArgs) 119 | if err != nil { 120 | return report(err) 121 | } 122 | // CreateRichSigWrapper was successful, which means it populated the temp dir with the wrapper func. 123 | // By the time we leave our current function, we are done with the temp dir 124 | // that CreateRichSigWrapper created, so delete via a defer. 125 | // (We can't delete it immediately because we haven't yet run go-fuzz-build on it). 126 | defer os.RemoveAll(target.wrapperTempDir) 127 | 128 | // TODO: consider moving gopath setup into richsig, store on Target 129 | // also set up a second entry in GOPATH to include our temporary directory containing the rich sig wrapper. 130 | } 131 | 132 | // create temp dir to work in. 133 | // this is where we will create a corpus test wrapper suitable for running a normal 'go test'. 134 | tempDir, err := ioutil.TempDir("", "fzgo-verify-corpus") 135 | if err != nil { 136 | return report(fmt.Errorf("failed to create temp dir: %v", err)) 137 | } 138 | defer os.RemoveAll(tempDir) 139 | 140 | // cd to our temp dir to simplify invoking 'go test' 141 | oldWd, err := os.Getwd() 142 | if err != nil { 143 | return err 144 | } 145 | err = os.Chdir(tempDir) 146 | if err != nil { 147 | return err 148 | } 149 | defer func() { os.Chdir(oldWd) }() 150 | 151 | var pkgPath, funcName string 152 | var env []string 153 | if target.hasWrapper { 154 | pkgPath = target.wrapperFunc.PkgPath 155 | funcName = target.wrapperFunc.FuncName 156 | env = target.wrapperEnv 157 | } else { 158 | pkgPath = target.UserFunc.PkgPath 159 | funcName = target.UserFunc.FuncName 160 | env = nil 161 | } 162 | 163 | // write out temporary corpus_test.go file 164 | vals := map[string]string{"pkgPath": pkgPath, "filesDir": filesDir, "testFunc": testFunc, "funcName": funcName} 165 | buf := new(bytes.Buffer) 166 | if err := corpusTestSrc.Execute(buf, vals); err != nil { 167 | report(fmt.Errorf("could not execute template: %v", err)) 168 | } 169 | // return buf.Bytes() 170 | // src := fmt.Sprintf(corpusTestSrc, 171 | // pkgPath, 172 | // filesDir, 173 | // testFunc, 174 | // testFunc, 175 | // funcName) 176 | // err = ioutil.WriteFile(filepath.Join(tempDir, "corpus_test.go"), []byte(src), 0700) 177 | err = ioutil.WriteFile(filepath.Join(tempDir, "corpus_test.go"), buf.Bytes(), 0700) 178 | if err != nil { 179 | return report(fmt.Errorf("failed to create temporary corpus_test.go: %v", err)) 180 | } 181 | 182 | // actually run 'go test .' now! 183 | runArgs := []string{ 184 | "test", 185 | buildTagsArg, 186 | ".", 187 | } 188 | // formerly, we passed through nonPkgArgs here from fzgo flag parsing. 189 | // now, we choose which flags explicitly to pass on (currently -run and -v). 190 | // we could return to passing through everything, but would need to strip things like -fuzzdir 191 | // that 'go test' does not understand. 192 | if run != "" { 193 | runArgs = append(runArgs, fmt.Sprintf("-run=%s", run)) 194 | } 195 | if verbose { 196 | runArgs = append(runArgs, "-v") 197 | } 198 | 199 | err = ExecGo(runArgs, env) 200 | if err != nil { 201 | // we will guess for now at least that this was due to a test failure. 202 | // the 'go' command should have already printed the details on the failure. 203 | // return a sentinel error here so that a caller can exit with non-zero exit code 204 | // without printing any additional error beyond what the 'go' command printed. 205 | return ErrGoTestFailed 206 | } 207 | return nil 208 | } 209 | 210 | // corpusTestTemplate provides a test function that runs 211 | // all of the files in a corpus directory as subtests. 212 | // This template needs three string variables to be supplied: 213 | // 1. an import path to the fuzzer, such as: 214 | // github.com/dvyukov/go-fuzz-corpus/png 215 | // 2. the directory path to the corpus, such as: 216 | // /tmp/gopath/src/github.com/dvyukov/go-fuzz-corpus/png/testdata/fuzz/png.Fuzz/corpus/ 217 | // 3. the fuzz function name, such as: 218 | // Fuzz 219 | var corpusTestSrc = template.Must(template.New("CorpusTest").Parse(` 220 | package corpustest 221 | 222 | import ( 223 | "io/ioutil" 224 | "path/filepath" 225 | {{if eq .testFunc "TestCrashers"}} 226 | "strings" 227 | {{end}} 228 | "testing" 229 | 230 | fuzzer "{{.pkgPath}}" 231 | ) 232 | 233 | var corpusPath = ` + "`{{.filesDir}}`" + ` 234 | 235 | // %s executes a fuzzing function against each file in a corpus directory 236 | // as subtests. 237 | func {{.testFunc}}(t *testing.T) { 238 | 239 | files, err := ioutil.ReadDir(corpusPath) 240 | if err != nil { 241 | t.Fatal(err) 242 | } 243 | 244 | for _, file := range files { 245 | if file.IsDir() { 246 | continue 247 | } 248 | 249 | {{if eq .testFunc "TestCrashers"}} 250 | // exclude auxillary files that reside in the crashers directory. 251 | if strings.HasSuffix(file.Name(), ".output") || strings.HasSuffix(file.Name(), ".quoted") { 252 | continue 253 | } 254 | {{end}} 255 | 256 | t.Run(file.Name(), func(t *testing.T) { 257 | dat, err := ioutil.ReadFile(filepath.Join(corpusPath, file.Name())) 258 | if err != nil { 259 | t.Error(err) 260 | } 261 | fuzzer.{{.funcName}}(dat) 262 | }) 263 | 264 | } 265 | } 266 | `)) 267 | -------------------------------------------------------------------------------- /genfuzzfuncs/README.md: -------------------------------------------------------------------------------- 1 | ## genfuzzfuncs: generate fuzz functions from user code 2 | 3 | `genfuzzfuncs` is an early stage prototype for automatically generating fuzz functions, similar in spirit to [cweill/gotests](https://github.com/cweill/gotests). It is not intended to be part of the "first class fuzzing" proposal for cmd/go. 4 | 5 | For example, if you run genfuzzfuncs against github.com/google/uuid, it generates a [uuid_fuzz.go](https://github.com/thepudds/fzgo/blob/master/genfuzzfuncs/examples/uuid/uuid_fuzz.go) file with 30 or so functions like: 6 | 7 | ``` 8 | func Fuzz_UUID_MarshalText(u1 uuid.UUID) { 9 | u1.MarshalText() 10 | } 11 | 12 | func Fuzz_UUID_UnmarshalText(u1 *uuid.UUID, data []byte) { 13 | if u1 == nil { 14 | return 15 | } 16 | u1.UnmarshalText(data) 17 | } 18 | ``` 19 | 20 | You can then edit or delete as desired, and then fuzz using the rich signature fuzzing support in thepudds/fzgo, such as: 21 | 22 | ``` 23 | fzgo test -fuzz=. ./... 24 | ``` 25 | -------------------------------------------------------------------------------- /genfuzzfuncs/constructor_injection_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/google/go-cmp/cmp" 7 | ) 8 | 9 | // the simplest to run is: 10 | // go test -run=ConstructorInjection/constructor_injection:_exported_only,_not_local_pkg 11 | 12 | func TestConstructorInjection(t *testing.T) { 13 | tests := []struct { 14 | name string 15 | onlyExported bool 16 | qualifyAll bool 17 | injectConstructors bool 18 | want string 19 | }{ 20 | { 21 | // this corresponds roughly to: 22 | // genfuzzfuncs -ctors -pkg=github.com/thepudds/fzgo/genfuzzfuncs/examples/test-constructor-injection 23 | name: "constructor injection: exported only, not local pkg", 24 | onlyExported: true, 25 | qualifyAll: true, 26 | injectConstructors: true, 27 | want: `package fuzzwrapexamplesfuzz // rename if needed 28 | 29 | // if needed, fill in imports or run 'goimports' 30 | import "bufio" 31 | 32 | func Fuzz_A_PtrMethodNoArg(c int) { 33 | r := fuzzwrapexamples.NewAPtr(c) 34 | r.PtrMethodNoArg() 35 | } 36 | 37 | func Fuzz_A_PtrMethodWithArg(c int, i int) { 38 | r := fuzzwrapexamples.NewAPtr(c) 39 | r.PtrMethodWithArg(i) 40 | } 41 | 42 | func Fuzz_B_PtrMethodNoArg(c int) { 43 | r := fuzzwrapexamples.NewBVal(c) 44 | r.PtrMethodNoArg() 45 | } 46 | 47 | func Fuzz_B_PtrMethodWithArg(c int, i int) { 48 | r := fuzzwrapexamples.NewBVal(c) 49 | r.PtrMethodWithArg(i) 50 | } 51 | 52 | func Fuzz_Package_SetName(path string, n2 string, n3 string) { 53 | pkg := fuzzwrapexamples.NewPackage(path, n2) 54 | pkg.SetName(n3) 55 | } 56 | 57 | func Fuzz_Z_ReadLine(z *bufio.Reader) { 58 | if z == nil { 59 | return 60 | } 61 | z1 := fuzzwrapexamples.NewZ(z) 62 | z1.ReadLine() 63 | } 64 | 65 | func Fuzz_A_ValMethodNoArg(c int) { 66 | r := fuzzwrapexamples.NewAPtr(c) 67 | r.ValMethodNoArg() 68 | } 69 | 70 | func Fuzz_A_ValMethodWithArg(c int, i int) { 71 | r := fuzzwrapexamples.NewAPtr(c) 72 | r.ValMethodWithArg(i) 73 | } 74 | 75 | func Fuzz_B_ValMethodNoArg(c int) { 76 | r := fuzzwrapexamples.NewBVal(c) 77 | r.ValMethodNoArg() 78 | } 79 | 80 | func Fuzz_B_ValMethodWithArg(c int, i int) { 81 | r := fuzzwrapexamples.NewBVal(c) 82 | r.ValMethodWithArg(i) 83 | } 84 | 85 | func Fuzz_NewAPtr(c int) { 86 | fuzzwrapexamples.NewAPtr(c) 87 | } 88 | 89 | func Fuzz_NewBVal(c int) { 90 | fuzzwrapexamples.NewBVal(c) 91 | } 92 | 93 | func Fuzz_NewPackage(path string, name string) { 94 | fuzzwrapexamples.NewPackage(path, name) 95 | } 96 | 97 | func Fuzz_NewZ(z *bufio.Reader) { 98 | if z == nil { 99 | return 100 | } 101 | fuzzwrapexamples.NewZ(z) 102 | } 103 | `}, 104 | { 105 | // this corresponds roughly to: 106 | // genfuzzfuncs -ctors=false -pkg=github.com/thepudds/fzgo/genfuzzfuncs/examples/test-constructor-injection 107 | name: "no constructor injection: exported only, not local pkg", 108 | onlyExported: true, 109 | qualifyAll: true, 110 | injectConstructors: false, 111 | want: `package fuzzwrapexamplesfuzz // rename if needed 112 | 113 | // if needed, fill in imports or run 'goimports' 114 | import "bufio" 115 | 116 | func Fuzz_A_PtrMethodNoArg(r *fuzzwrapexamples.A) { 117 | if r == nil { 118 | return 119 | } 120 | r.PtrMethodNoArg() 121 | } 122 | 123 | func Fuzz_A_PtrMethodWithArg(r *fuzzwrapexamples.A, i int) { 124 | if r == nil { 125 | return 126 | } 127 | r.PtrMethodWithArg(i) 128 | } 129 | 130 | func Fuzz_B_PtrMethodNoArg(r *fuzzwrapexamples.B) { 131 | if r == nil { 132 | return 133 | } 134 | r.PtrMethodNoArg() 135 | } 136 | 137 | func Fuzz_B_PtrMethodWithArg(r *fuzzwrapexamples.B, i int) { 138 | if r == nil { 139 | return 140 | } 141 | r.PtrMethodWithArg(i) 142 | } 143 | 144 | func Fuzz_Package_SetName(pkg *fuzzwrapexamples.Package, name string) { 145 | if pkg == nil { 146 | return 147 | } 148 | pkg.SetName(name) 149 | } 150 | 151 | func Fuzz_Z_ReadLine(z *fuzzwrapexamples.Z) { 152 | if z == nil { 153 | return 154 | } 155 | z.ReadLine() 156 | } 157 | 158 | func Fuzz_A_ValMethodNoArg(r fuzzwrapexamples.A) { 159 | r.ValMethodNoArg() 160 | } 161 | 162 | func Fuzz_A_ValMethodWithArg(r fuzzwrapexamples.A, i int) { 163 | r.ValMethodWithArg(i) 164 | } 165 | 166 | func Fuzz_B_ValMethodNoArg(r fuzzwrapexamples.B) { 167 | r.ValMethodNoArg() 168 | } 169 | 170 | func Fuzz_B_ValMethodWithArg(r fuzzwrapexamples.B, i int) { 171 | r.ValMethodWithArg(i) 172 | } 173 | 174 | func Fuzz_NewAPtr(c int) { 175 | fuzzwrapexamples.NewAPtr(c) 176 | } 177 | 178 | func Fuzz_NewBVal(c int) { 179 | fuzzwrapexamples.NewBVal(c) 180 | } 181 | 182 | func Fuzz_NewPackage(path string, name string) { 183 | fuzzwrapexamples.NewPackage(path, name) 184 | } 185 | 186 | func Fuzz_NewZ(z *bufio.Reader) { 187 | if z == nil { 188 | return 189 | } 190 | fuzzwrapexamples.NewZ(z) 191 | } 192 | `}, 193 | { 194 | // this corresponds roughly to: 195 | // genfuzzfuncs -ctors -qualifyall=false -pkg=github.com/thepudds/fzgo/genfuzzfuncs/examples/test-constructor-injection 196 | name: "constructor injection: exported only, local pkg", 197 | onlyExported: true, 198 | qualifyAll: false, 199 | injectConstructors: true, 200 | want: `package fuzzwrapexamples 201 | 202 | // if needed, fill in imports or run 'goimports' 203 | import "bufio" 204 | 205 | func Fuzz_A_PtrMethodNoArg(c int) { 206 | r := NewAPtr(c) 207 | r.PtrMethodNoArg() 208 | } 209 | 210 | func Fuzz_A_PtrMethodWithArg(c int, i int) { 211 | r := NewAPtr(c) 212 | r.PtrMethodWithArg(i) 213 | } 214 | 215 | func Fuzz_B_PtrMethodNoArg(c int) { 216 | r := NewBVal(c) 217 | r.PtrMethodNoArg() 218 | } 219 | 220 | func Fuzz_B_PtrMethodWithArg(c int, i int) { 221 | r := NewBVal(c) 222 | r.PtrMethodWithArg(i) 223 | } 224 | 225 | func Fuzz_Package_SetName(path string, n2 string, n3 string) { 226 | pkg := NewPackage(path, n2) 227 | pkg.SetName(n3) 228 | } 229 | 230 | func Fuzz_Z_ReadLine(z *bufio.Reader) { 231 | if z == nil { 232 | return 233 | } 234 | z1 := NewZ(z) 235 | z1.ReadLine() 236 | } 237 | 238 | func Fuzz_A_ValMethodNoArg(c int) { 239 | r := NewAPtr(c) 240 | r.ValMethodNoArg() 241 | } 242 | 243 | func Fuzz_A_ValMethodWithArg(c int, i int) { 244 | r := NewAPtr(c) 245 | r.ValMethodWithArg(i) 246 | } 247 | 248 | func Fuzz_B_ValMethodNoArg(c int) { 249 | r := NewBVal(c) 250 | r.ValMethodNoArg() 251 | } 252 | 253 | func Fuzz_B_ValMethodWithArg(c int, i int) { 254 | r := NewBVal(c) 255 | r.ValMethodWithArg(i) 256 | } 257 | 258 | func Fuzz_NewAPtr(c int) { 259 | NewAPtr(c) 260 | } 261 | 262 | func Fuzz_NewBVal(c int) { 263 | NewBVal(c) 264 | } 265 | 266 | func Fuzz_NewPackage(path string, name string) { 267 | NewPackage(path, name) 268 | } 269 | 270 | func Fuzz_NewZ(z *bufio.Reader) { 271 | if z == nil { 272 | return 273 | } 274 | NewZ(z) 275 | } 276 | `}, 277 | { 278 | // this corresponds roughly to: 279 | // genfuzzfuncs -ctors=false -qualifyall=false -pkg=github.com/thepudds/fzgo/genfuzzfuncs/examples/test-constructor-injection 280 | name: "no constructor injection: exported only, local pkg", 281 | onlyExported: true, 282 | qualifyAll: false, 283 | injectConstructors: false, 284 | want: `package fuzzwrapexamples 285 | 286 | // if needed, fill in imports or run 'goimports' 287 | import "bufio" 288 | 289 | func Fuzz_A_PtrMethodNoArg(r *A) { 290 | if r == nil { 291 | return 292 | } 293 | r.PtrMethodNoArg() 294 | } 295 | 296 | func Fuzz_A_PtrMethodWithArg(r *A, i int) { 297 | if r == nil { 298 | return 299 | } 300 | r.PtrMethodWithArg(i) 301 | } 302 | 303 | func Fuzz_B_PtrMethodNoArg(r *B) { 304 | if r == nil { 305 | return 306 | } 307 | r.PtrMethodNoArg() 308 | } 309 | 310 | func Fuzz_B_PtrMethodWithArg(r *B, i int) { 311 | if r == nil { 312 | return 313 | } 314 | r.PtrMethodWithArg(i) 315 | } 316 | 317 | func Fuzz_Package_SetName(pkg *Package, name string) { 318 | if pkg == nil { 319 | return 320 | } 321 | pkg.SetName(name) 322 | } 323 | 324 | func Fuzz_Z_ReadLine(z *Z) { 325 | if z == nil { 326 | return 327 | } 328 | z.ReadLine() 329 | } 330 | 331 | func Fuzz_A_ValMethodNoArg(r A) { 332 | r.ValMethodNoArg() 333 | } 334 | 335 | func Fuzz_A_ValMethodWithArg(r A, i int) { 336 | r.ValMethodWithArg(i) 337 | } 338 | 339 | func Fuzz_B_ValMethodNoArg(r B) { 340 | r.ValMethodNoArg() 341 | } 342 | 343 | func Fuzz_B_ValMethodWithArg(r B, i int) { 344 | r.ValMethodWithArg(i) 345 | } 346 | 347 | func Fuzz_NewAPtr(c int) { 348 | NewAPtr(c) 349 | } 350 | 351 | func Fuzz_NewBVal(c int) { 352 | NewBVal(c) 353 | } 354 | 355 | func Fuzz_NewPackage(path string, name string) { 356 | NewPackage(path, name) 357 | } 358 | 359 | func Fuzz_NewZ(z *bufio.Reader) { 360 | if z == nil { 361 | return 362 | } 363 | NewZ(z) 364 | } 365 | `}, 366 | } 367 | for _, tt := range tests { 368 | tt := tt 369 | t.Run(tt.name, func(t *testing.T) { 370 | t.Parallel() 371 | pkgPattern := "github.com/thepudds/fzgo/genfuzzfuncs/examples/test-constructor-injection" 372 | options := flagExcludeFuzzPrefix | flagAllowMultiFuzz 373 | if tt.onlyExported { 374 | options |= flagRequireExported 375 | } 376 | functions, err := FindFunc(pkgPattern, ".", nil, options) 377 | if err != nil { 378 | t.Errorf("FindFuncfail() failed: %v", err) 379 | } 380 | 381 | wrapperOpts := wrapperOptions{ 382 | qualifyAll: tt.qualifyAll, 383 | insertConstructors: tt.injectConstructors, 384 | constructorPattern: "^New", 385 | } 386 | out, err := createWrappers(pkgPattern, functions, wrapperOpts) 387 | if err != nil { 388 | t.Errorf("createWrappers() failed: %v", err) 389 | } 390 | 391 | got := string(out) 392 | if diff := cmp.Diff(tt.want, got); diff != "" { 393 | t.Errorf("createWrappers() mismatch (-want +got):\n%s", diff) 394 | } 395 | }) 396 | } 397 | } 398 | -------------------------------------------------------------------------------- /genfuzzfuncs/examples/test-constructor-injection/examples.go: -------------------------------------------------------------------------------- 1 | package fuzzwrapexamples 2 | 3 | import "bufio" 4 | 5 | // ---- Constructor injection examples/tests ---- 6 | 7 | // When fuzzing a method, by default genfuzzfuncs puts the receiver's 8 | // type into the parameter list of the wrapper function. 9 | // This works reasonably well if the receiver's type has public 10 | // member variables -- we can set and mutate public member variables while fuzzing. 11 | // However, that works less well if there are no public member variables, 12 | // such as for regex.Regexp. 13 | // If asked, we can inject constructors like so: 14 | // * we determine the type of the receiver for the method we want to fuzz. 15 | // * we look for a constructor capable of creating the receiver's type. 16 | // * we "promote" the parameters from the constructor into the params for the wrapper function. 17 | // * we insert a call to the constructor using those params. 18 | // * we use the result of the constructor to call method we want to fuzz. 19 | 20 | type A struct{ c int } 21 | 22 | func NewAPtr(c int) *A { return &A{c} } 23 | func (r *A) PtrMethodWithArg(i int) bool { return r.c == i } 24 | func (r *A) PtrMethodNoArg() bool { return r.c == 0 } 25 | func (r A) ValMethodWithArg(i int) bool { return r.c == i } 26 | func (r A) ValMethodNoArg() bool { return r.c == 0 } 27 | 28 | type B struct{ c int } 29 | 30 | func NewBVal(c int) B { return B{c} } 31 | func (r *B) PtrMethodWithArg(i int) bool { return r.c == i } 32 | func (r *B) PtrMethodNoArg() bool { return r.c == 0 } 33 | func (r B) ValMethodWithArg(i int) bool { return r.c == i } 34 | func (r B) ValMethodNoArg() bool { return r.c == 0 } 35 | 36 | // Package is roughly modeled on go/types.Package, 37 | // which has a collision between a parameter name used for 38 | // for the constructor and a parameter name used in a later function 39 | // under test. (The colliding paramater name happens to be 'name', 40 | // though the actual name doesn't matter -- just that it collides). 41 | type Package struct { 42 | path string 43 | name string 44 | } 45 | 46 | func NewPackage(path, name string) *Package { 47 | return &Package{path: path, name: name} 48 | } 49 | 50 | func (pkg *Package) SetName(name string) { pkg.name = name } 51 | 52 | // Reader is roughly modeled on textproto.Reader 53 | // We want to avoid generating a mismatched object variable name: 54 | // func Fuzz_Reader_ReadLine(r *bufio.Reader) { 55 | // if r == nil { 56 | // return 57 | // } 58 | // r1 := textproto.NewReader(r) 59 | // r2.ReadLine() 60 | // } 61 | 62 | type Z struct{} 63 | 64 | func NewZ(z *bufio.Reader) *Z { 65 | return &Z{} 66 | } 67 | 68 | func (z *Z) ReadLine() (string, error) { 69 | return "", nil 70 | } 71 | -------------------------------------------------------------------------------- /genfuzzfuncs/examples/test-exported/examples.go: -------------------------------------------------------------------------------- 1 | package fuzzwrapexamples 2 | 3 | import "io" 4 | 5 | // ---- Export examples/tests ---- 6 | 7 | // FuncExported is a test function to make sure we emit exported functions. 8 | func FuncExported(i int) {} 9 | func funcNotExported(i int) {} 10 | 11 | type typeNotExported int 12 | 13 | func (t *typeNotExported) pointerRcvNotExportedMethod(i int) {} 14 | func (t typeNotExported) nonPointerRcvNotExportedMethod(i int) {} 15 | func (t *typeNotExported) PointerExportedMethod(i int) {} 16 | func (t typeNotExported) NonPointerExportedMethod(i int) {} 17 | 18 | // TypeExported is a test type to make sure we emit exported methods. 19 | type TypeExported int 20 | 21 | func (t *TypeExported) pointerRcvNotExportedMethod(i int) {} 22 | func (t TypeExported) nonPointerRcvNotExportedMethod(i int) {} 23 | func (t *TypeExported) PointerExportedMethod(i int) {} 24 | func (t TypeExported) NonPointerExportedMethod(i int) {} 25 | 26 | var ExportedFuncVar = func(i int) {} 27 | var notExportedFuncVar = func(i int) {} 28 | 29 | // ---- Interface examples/tests ---- 30 | 31 | // ExportedInterface is a test interface to make sure 32 | // we don't emit anything for the declaration of an interface. 33 | type ExportedInterface interface { 34 | ExportedFunc() 35 | } 36 | 37 | // FuncExportedUsesUnsupportedInterface is a test func to make sure 38 | // we don't emit a wrapper for functions that use unsupported interfaces. 39 | func FuncExportedUsesUnsupportedInterface(e ExportedInterface) {} 40 | 41 | // FuncExportedUsesSupportedInterface is a test func to make sure 42 | // we do emit a wrapper for functions that use supported interfaces. 43 | func FuncExportedUsesSupportedInterface(w io.Reader) {} 44 | -------------------------------------------------------------------------------- /genfuzzfuncs/examples/uuid/uuid_fuzz.go: -------------------------------------------------------------------------------- 1 | package uuidfuzz 2 | 3 | import "github.com/google/uuid" 4 | 5 | // Automatically generated via: 6 | // genfuzzfuncs -pkg=github.com/google/uuid > uuid_fuzz.go 7 | // goimports -w uuid_fuzz.go 8 | // You can then fuzz these rich signatures via: 9 | // fzgo test -fuzz=. 10 | 11 | func Fuzz_UUID_MarshalBinary(u1 uuid.UUID) { 12 | u1.MarshalBinary() 13 | } 14 | 15 | func Fuzz_UUID_MarshalText(u1 uuid.UUID) { 16 | u1.MarshalText() 17 | } 18 | 19 | func Fuzz_UUID_UnmarshalBinary(u1 *uuid.UUID, data []byte) { 20 | if u1 == nil { 21 | return 22 | } 23 | u1.UnmarshalBinary(data) 24 | } 25 | 26 | func Fuzz_UUID_UnmarshalText(u1 *uuid.UUID, data []byte) { 27 | if u1 == nil { 28 | return 29 | } 30 | u1.UnmarshalText(data) 31 | } 32 | 33 | // skipping Fuzz_UUID_Scan because parameters include interfaces or funcs: interface{} 34 | 35 | func Fuzz_Time_UnixTime(t uuid.Time) { 36 | t.UnixTime() 37 | } 38 | 39 | func Fuzz_UUID_ClockSequence(u1 uuid.UUID) { 40 | u1.ClockSequence() 41 | } 42 | 43 | func Fuzz_UUID_Domain(u1 uuid.UUID) { 44 | u1.Domain() 45 | } 46 | 47 | func Fuzz_UUID_ID(u1 uuid.UUID) { 48 | u1.ID() 49 | } 50 | 51 | func Fuzz_UUID_NodeID(u1 uuid.UUID) { 52 | u1.NodeID() 53 | } 54 | 55 | func Fuzz_Domain_String(d uuid.Domain) { 56 | d.String() 57 | } 58 | 59 | func Fuzz_UUID_String(u1 uuid.UUID) { 60 | u1.String() 61 | } 62 | 63 | func Fuzz_UUID_Time(u1 uuid.UUID) { 64 | u1.Time() 65 | } 66 | 67 | func Fuzz_UUID_URN(u1 uuid.UUID) { 68 | u1.URN() 69 | } 70 | 71 | func Fuzz_UUID_Value(u1 uuid.UUID) { 72 | u1.Value() 73 | } 74 | 75 | func Fuzz_UUID_Variant(u1 uuid.UUID) { 76 | u1.Variant() 77 | } 78 | 79 | func Fuzz_UUID_Version(u1 uuid.UUID) { 80 | u1.Version() 81 | } 82 | 83 | func Fuzz_Variant_String(v uuid.Variant) { 84 | v.String() 85 | } 86 | 87 | func Fuzz_Version_String(v uuid.Version) { 88 | v.String() 89 | } 90 | 91 | func Fuzz_FromBytes(b []byte) { 92 | uuid.FromBytes(b) 93 | } 94 | 95 | // skipping Fuzz_Must because parameters include interfaces or funcs: error 96 | 97 | func Fuzz_MustParse(s string) { 98 | uuid.MustParse(s) 99 | } 100 | 101 | func Fuzz_NewDCESecurity(domain uuid.Domain, id uint32) { 102 | uuid.NewDCESecurity(domain, id) 103 | } 104 | 105 | // skipping Fuzz_NewHash because parameters include interfaces or funcs: hash.Hash 106 | 107 | func Fuzz_NewMD5(space uuid.UUID, data []byte) { 108 | uuid.NewMD5(space, data) 109 | } 110 | 111 | // skipping Fuzz_NewRandomFromReader because parameters include interfaces or funcs: io.Reader 112 | 113 | func Fuzz_NewSHA1(space uuid.UUID, data []byte) { 114 | uuid.NewSHA1(space, data) 115 | } 116 | 117 | func Fuzz_Parse(s string) { 118 | uuid.Parse(s) 119 | } 120 | 121 | func Fuzz_ParseBytes(b []byte) { 122 | uuid.ParseBytes(b) 123 | } 124 | 125 | func Fuzz_SetClockSequence(seq int) { 126 | uuid.SetClockSequence(seq) 127 | } 128 | 129 | func Fuzz_SetNodeID(id []byte) { 130 | uuid.SetNodeID(id) 131 | } 132 | 133 | func Fuzz_SetNodeInterface(name string) { 134 | uuid.SetNodeInterface(name) 135 | } 136 | 137 | // skipping Fuzz_SetRand because parameters include interfaces or funcs: io.Reader 138 | -------------------------------------------------------------------------------- /genfuzzfuncs/examples/x-mod-modules/modules_fuzz.go: -------------------------------------------------------------------------------- 1 | package modulefuzz 2 | 3 | import "golang.org/x/mod/module" 4 | 5 | // Automatically generated via: 6 | // genfuzzfuncs -pkg=golang.org/x/mod/module > module_fuzz.go 7 | // goimports -w module_fuzz.go 8 | // You can then fuzz these rich signatures via: 9 | // fzgo test -fuzz=. 10 | 11 | func Fuzz_Sort(list []module.Version) { 12 | module.Sort(list) 13 | } 14 | 15 | func Fuzz_Version_String(m module.Version) { 16 | m.String() 17 | } 18 | 19 | func Fuzz_Check(path string, version string) { 20 | module.Check(path, version) 21 | } 22 | 23 | func Fuzz_CanonicalVersion(v string) { 24 | module.CanonicalVersion(v) 25 | } 26 | 27 | func Fuzz_CheckFilePath(path string) { 28 | module.CheckFilePath(path) 29 | } 30 | 31 | func Fuzz_CheckImportPath(path string) { 32 | module.CheckImportPath(path) 33 | } 34 | 35 | func Fuzz_CheckPath(path string) { 36 | module.CheckPath(path) 37 | } 38 | 39 | func Fuzz_EscapePath(path string) { 40 | module.EscapePath(path) 41 | } 42 | 43 | func Fuzz_EscapeVersion(v string) { 44 | module.EscapeVersion(v) 45 | } 46 | 47 | func Fuzz_MatchPathMajor(v string, pathMajor string) { 48 | module.MatchPathMajor(v, pathMajor) 49 | } 50 | 51 | func Fuzz_SplitPathVersion(path string) { 52 | module.SplitPathVersion(path) 53 | } 54 | 55 | func Fuzz_UnescapePath(escaped string) { 56 | module.UnescapePath(escaped) 57 | } 58 | 59 | func Fuzz_UnescapeVersion(escaped string) { 60 | module.UnescapeVersion(escaped) 61 | } 62 | -------------------------------------------------------------------------------- /genfuzzfuncs/exported_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/google/go-cmp/cmp" 7 | ) 8 | 9 | // the simplest one to run is: 10 | // go test -run=Exported/exported_tests:_exported_only,_not_local_pkg 11 | 12 | func TestExported(t *testing.T) { 13 | tests := []struct { 14 | name string 15 | onlyExported bool 16 | qualifyAll bool 17 | want string 18 | }{ 19 | { 20 | name: "exported tests: exported only, not local pkg", 21 | onlyExported: true, 22 | qualifyAll: true, 23 | want: `package fuzzwrapexamplesfuzz // rename if needed 24 | 25 | // if needed, fill in imports or run 'goimports' 26 | import "io" 27 | 28 | func Fuzz_TypeExported_PointerExportedMethod(t *fuzzwrapexamples.TypeExported, i int) { 29 | if t == nil { 30 | return 31 | } 32 | t.PointerExportedMethod(i) 33 | } 34 | 35 | func Fuzz_TypeExported_NonPointerExportedMethod(t fuzzwrapexamples.TypeExported, i int) { 36 | t.NonPointerExportedMethod(i) 37 | } 38 | 39 | func Fuzz_FuncExported(i int) { 40 | fuzzwrapexamples.FuncExported(i) 41 | } 42 | 43 | func Fuzz_FuncExportedUsesSupportedInterface(w io.Reader) { 44 | fuzzwrapexamples.FuncExportedUsesSupportedInterface(w) 45 | } 46 | 47 | // skipping Fuzz_FuncExportedUsesUnsupportedInterface because parameters include interfaces or funcs: github.com/thepudds/fzgo/genfuzzfuncs/examples/test-exported.ExportedInterface 48 | `}, 49 | { 50 | name: "exported tests: exported only, local pkg", 51 | onlyExported: true, 52 | qualifyAll: false, 53 | want: `package fuzzwrapexamples 54 | 55 | // if needed, fill in imports or run 'goimports' 56 | import "io" 57 | 58 | func Fuzz_TypeExported_PointerExportedMethod(t *TypeExported, i int) { 59 | if t == nil { 60 | return 61 | } 62 | t.PointerExportedMethod(i) 63 | } 64 | 65 | func Fuzz_TypeExported_NonPointerExportedMethod(t TypeExported, i int) { 66 | t.NonPointerExportedMethod(i) 67 | } 68 | 69 | func Fuzz_FuncExported(i int) { 70 | FuncExported(i) 71 | } 72 | 73 | func Fuzz_FuncExportedUsesSupportedInterface(w io.Reader) { 74 | FuncExportedUsesSupportedInterface(w) 75 | } 76 | 77 | // skipping Fuzz_FuncExportedUsesUnsupportedInterface because parameters include interfaces or funcs: github.com/thepudds/fzgo/genfuzzfuncs/examples/test-exported.ExportedInterface 78 | `}, 79 | { 80 | name: "exported tests: exported and not exported, not local package", 81 | onlyExported: false, 82 | qualifyAll: true, 83 | want: `package fuzzwrapexamplesfuzz // rename if needed 84 | 85 | // if needed, fill in imports or run 'goimports' 86 | import "io" 87 | 88 | func Fuzz_TypeExported_PointerExportedMethod(t *fuzzwrapexamples.TypeExported, i int) { 89 | if t == nil { 90 | return 91 | } 92 | t.PointerExportedMethod(i) 93 | } 94 | 95 | func Fuzz_TypeExported_pointerRcvNotExportedMethod(t *fuzzwrapexamples.TypeExported, i int) { 96 | if t == nil { 97 | return 98 | } 99 | t.pointerRcvNotExportedMethod(i) 100 | } 101 | 102 | func Fuzz_typeNotExported_PointerExportedMethod(t *fuzzwrapexamples.typeNotExported, i int) { 103 | if t == nil { 104 | return 105 | } 106 | t.PointerExportedMethod(i) 107 | } 108 | 109 | func Fuzz_typeNotExported_pointerRcvNotExportedMethod(t *fuzzwrapexamples.typeNotExported, i int) { 110 | if t == nil { 111 | return 112 | } 113 | t.pointerRcvNotExportedMethod(i) 114 | } 115 | 116 | func Fuzz_TypeExported_NonPointerExportedMethod(t fuzzwrapexamples.TypeExported, i int) { 117 | t.NonPointerExportedMethod(i) 118 | } 119 | 120 | func Fuzz_TypeExported_nonPointerRcvNotExportedMethod(t fuzzwrapexamples.TypeExported, i int) { 121 | t.nonPointerRcvNotExportedMethod(i) 122 | } 123 | 124 | func Fuzz_typeNotExported_NonPointerExportedMethod(t fuzzwrapexamples.typeNotExported, i int) { 125 | t.NonPointerExportedMethod(i) 126 | } 127 | 128 | func Fuzz_typeNotExported_nonPointerRcvNotExportedMethod(t fuzzwrapexamples.typeNotExported, i int) { 129 | t.nonPointerRcvNotExportedMethod(i) 130 | } 131 | 132 | func Fuzz_FuncExported(i int) { 133 | fuzzwrapexamples.FuncExported(i) 134 | } 135 | 136 | func Fuzz_FuncExportedUsesSupportedInterface(w io.Reader) { 137 | fuzzwrapexamples.FuncExportedUsesSupportedInterface(w) 138 | } 139 | 140 | // skipping Fuzz_FuncExportedUsesUnsupportedInterface because parameters include interfaces or funcs: github.com/thepudds/fzgo/genfuzzfuncs/examples/test-exported.ExportedInterface 141 | 142 | func Fuzz_funcNotExported(i int) { 143 | fuzzwrapexamples.funcNotExported(i) 144 | } 145 | `}, 146 | { 147 | name: "exported tests: exported and not exported, local package", 148 | onlyExported: false, 149 | qualifyAll: false, 150 | want: `package fuzzwrapexamples 151 | 152 | // if needed, fill in imports or run 'goimports' 153 | import "io" 154 | 155 | func Fuzz_TypeExported_PointerExportedMethod(t *TypeExported, i int) { 156 | if t == nil { 157 | return 158 | } 159 | t.PointerExportedMethod(i) 160 | } 161 | 162 | func Fuzz_TypeExported_pointerRcvNotExportedMethod(t *TypeExported, i int) { 163 | if t == nil { 164 | return 165 | } 166 | t.pointerRcvNotExportedMethod(i) 167 | } 168 | 169 | func Fuzz_typeNotExported_PointerExportedMethod(t *typeNotExported, i int) { 170 | if t == nil { 171 | return 172 | } 173 | t.PointerExportedMethod(i) 174 | } 175 | 176 | func Fuzz_typeNotExported_pointerRcvNotExportedMethod(t *typeNotExported, i int) { 177 | if t == nil { 178 | return 179 | } 180 | t.pointerRcvNotExportedMethod(i) 181 | } 182 | 183 | func Fuzz_TypeExported_NonPointerExportedMethod(t TypeExported, i int) { 184 | t.NonPointerExportedMethod(i) 185 | } 186 | 187 | func Fuzz_TypeExported_nonPointerRcvNotExportedMethod(t TypeExported, i int) { 188 | t.nonPointerRcvNotExportedMethod(i) 189 | } 190 | 191 | func Fuzz_typeNotExported_NonPointerExportedMethod(t typeNotExported, i int) { 192 | t.NonPointerExportedMethod(i) 193 | } 194 | 195 | func Fuzz_typeNotExported_nonPointerRcvNotExportedMethod(t typeNotExported, i int) { 196 | t.nonPointerRcvNotExportedMethod(i) 197 | } 198 | 199 | func Fuzz_FuncExported(i int) { 200 | FuncExported(i) 201 | } 202 | 203 | func Fuzz_FuncExportedUsesSupportedInterface(w io.Reader) { 204 | FuncExportedUsesSupportedInterface(w) 205 | } 206 | 207 | // skipping Fuzz_FuncExportedUsesUnsupportedInterface because parameters include interfaces or funcs: github.com/thepudds/fzgo/genfuzzfuncs/examples/test-exported.ExportedInterface 208 | 209 | func Fuzz_funcNotExported(i int) { 210 | funcNotExported(i) 211 | } 212 | `}, 213 | } 214 | for _, tt := range tests { 215 | tt := tt 216 | t.Run(tt.name, func(t *testing.T) { 217 | t.Parallel() 218 | pkgPattern := "github.com/thepudds/fzgo/genfuzzfuncs/examples/test-exported" 219 | options := flagExcludeFuzzPrefix | flagAllowMultiFuzz 220 | if tt.onlyExported { 221 | options |= flagRequireExported 222 | } 223 | functions, err := FindFunc(pkgPattern, ".", nil, options) 224 | if err != nil { 225 | t.Errorf("FindFuncfail() failed: %v", err) 226 | } 227 | 228 | wrapperOpts := wrapperOptions{ 229 | qualifyAll: tt.qualifyAll, 230 | insertConstructors: true, 231 | constructorPattern: "^New", 232 | } 233 | out, err := createWrappers(pkgPattern, functions, wrapperOpts) 234 | if err != nil { 235 | t.Errorf("createWrappers() failed: %v", err) 236 | } 237 | 238 | got := string(out) 239 | if diff := cmp.Diff(tt.want, got); diff != "" { 240 | t.Errorf("createWrappers() mismatch (-want +got):\n%s", diff) 241 | } 242 | }) 243 | } 244 | } 245 | -------------------------------------------------------------------------------- /genfuzzfuncs/long_test.go: -------------------------------------------------------------------------------- 1 | // +build go1.13 2 | 3 | package main 4 | 5 | import ( 6 | "testing" 7 | 8 | "github.com/google/go-cmp/cmp" 9 | ) 10 | 11 | func TestStrings(t *testing.T) { 12 | if testing.Short() { 13 | // TODO: probably remove this test at some point? 14 | // It is long, and sensitive to changes in stdlib strings pkg. 15 | t.Skip("skipping test in short mode. also, currently relies on strings package from Go 1.13") 16 | } 17 | tests := []struct { 18 | name string 19 | onlyExported bool 20 | qualifyAll bool 21 | insertConstructors bool 22 | want string 23 | }{ 24 | { 25 | name: "strings: exported only, not local pkg", 26 | onlyExported: true, 27 | qualifyAll: true, 28 | insertConstructors: true, 29 | want: `package stringsfuzz // rename if needed 30 | 31 | // if needed, fill in imports or run 'goimports' 32 | import ( 33 | "io" 34 | "strings" 35 | "unicode" 36 | ) 37 | 38 | func Fuzz_Builder_Cap(b *strings.Builder) { 39 | if b == nil { 40 | return 41 | } 42 | b.Cap() 43 | } 44 | 45 | func Fuzz_Builder_Grow(b *strings.Builder, n int) { 46 | if b == nil { 47 | return 48 | } 49 | b.Grow(n) 50 | } 51 | 52 | func Fuzz_Builder_Len(b *strings.Builder) { 53 | if b == nil { 54 | return 55 | } 56 | b.Len() 57 | } 58 | 59 | func Fuzz_Builder_Reset(b *strings.Builder) { 60 | if b == nil { 61 | return 62 | } 63 | b.Reset() 64 | } 65 | 66 | func Fuzz_Builder_String(b *strings.Builder) { 67 | if b == nil { 68 | return 69 | } 70 | b.String() 71 | } 72 | 73 | func Fuzz_Builder_Write(b *strings.Builder, p []byte) { 74 | if b == nil { 75 | return 76 | } 77 | b.Write(p) 78 | } 79 | 80 | func Fuzz_Builder_WriteByte(b *strings.Builder, c byte) { 81 | if b == nil { 82 | return 83 | } 84 | b.WriteByte(c) 85 | } 86 | 87 | func Fuzz_Builder_WriteRune(b *strings.Builder, r rune) { 88 | if b == nil { 89 | return 90 | } 91 | b.WriteRune(r) 92 | } 93 | 94 | func Fuzz_Builder_WriteString(b *strings.Builder, s string) { 95 | if b == nil { 96 | return 97 | } 98 | b.WriteString(s) 99 | } 100 | 101 | func Fuzz_Reader_Len(s string) { 102 | r := strings.NewReader(s) 103 | r.Len() 104 | } 105 | 106 | func Fuzz_Reader_Read(s string, b []byte) { 107 | r := strings.NewReader(s) 108 | r.Read(b) 109 | } 110 | 111 | func Fuzz_Reader_ReadAt(s string, b []byte, off int64) { 112 | r := strings.NewReader(s) 113 | r.ReadAt(b, off) 114 | } 115 | 116 | func Fuzz_Reader_ReadByte(s string) { 117 | r := strings.NewReader(s) 118 | r.ReadByte() 119 | } 120 | 121 | func Fuzz_Reader_ReadRune(s string) { 122 | r := strings.NewReader(s) 123 | r.ReadRune() 124 | } 125 | 126 | func Fuzz_Reader_Reset(s1 string, s2 string) { 127 | r := strings.NewReader(s1) 128 | r.Reset(s2) 129 | } 130 | 131 | func Fuzz_Reader_Seek(s string, offset int64, whence int) { 132 | r := strings.NewReader(s) 133 | r.Seek(offset, whence) 134 | } 135 | 136 | func Fuzz_Reader_Size(s string) { 137 | r := strings.NewReader(s) 138 | r.Size() 139 | } 140 | 141 | func Fuzz_Reader_UnreadByte(s string) { 142 | r := strings.NewReader(s) 143 | r.UnreadByte() 144 | } 145 | 146 | func Fuzz_Reader_UnreadRune(s string) { 147 | r := strings.NewReader(s) 148 | r.UnreadRune() 149 | } 150 | 151 | func Fuzz_Reader_WriteTo(s string, w io.Writer) { 152 | r := strings.NewReader(s) 153 | r.WriteTo(w) 154 | } 155 | 156 | func Fuzz_Replacer_Replace(oldnew []string, s string) { 157 | r := strings.NewReplacer(oldnew...) 158 | r.Replace(s) 159 | } 160 | 161 | func Fuzz_Replacer_WriteString(oldnew []string, w io.Writer, s string) { 162 | r := strings.NewReplacer(oldnew...) 163 | r.WriteString(w, s) 164 | } 165 | 166 | func Fuzz_Compare(a string, b string) { 167 | strings.Compare(a, b) 168 | } 169 | 170 | func Fuzz_Contains(s string, substr string) { 171 | strings.Contains(s, substr) 172 | } 173 | 174 | func Fuzz_ContainsAny(s string, chars string) { 175 | strings.ContainsAny(s, chars) 176 | } 177 | 178 | func Fuzz_ContainsRune(s string, r rune) { 179 | strings.ContainsRune(s, r) 180 | } 181 | 182 | func Fuzz_Count(s string, substr string) { 183 | strings.Count(s, substr) 184 | } 185 | 186 | func Fuzz_EqualFold(s string, t string) { 187 | strings.EqualFold(s, t) 188 | } 189 | 190 | func Fuzz_Fields(s string) { 191 | strings.Fields(s) 192 | } 193 | 194 | // skipping Fuzz_FieldsFunc because parameters include interfaces or funcs: func(rune) bool 195 | 196 | func Fuzz_HasPrefix(s string, prefix string) { 197 | strings.HasPrefix(s, prefix) 198 | } 199 | 200 | func Fuzz_HasSuffix(s string, suffix string) { 201 | strings.HasSuffix(s, suffix) 202 | } 203 | 204 | func Fuzz_Index(s string, substr string) { 205 | strings.Index(s, substr) 206 | } 207 | 208 | func Fuzz_IndexAny(s string, chars string) { 209 | strings.IndexAny(s, chars) 210 | } 211 | 212 | func Fuzz_IndexByte(s string, c byte) { 213 | strings.IndexByte(s, c) 214 | } 215 | 216 | // skipping Fuzz_IndexFunc because parameters include interfaces or funcs: func(rune) bool 217 | 218 | func Fuzz_IndexRune(s string, r rune) { 219 | strings.IndexRune(s, r) 220 | } 221 | 222 | func Fuzz_Join(elems []string, sep string) { 223 | strings.Join(elems, sep) 224 | } 225 | 226 | func Fuzz_LastIndex(s string, substr string) { 227 | strings.LastIndex(s, substr) 228 | } 229 | 230 | func Fuzz_LastIndexAny(s string, chars string) { 231 | strings.LastIndexAny(s, chars) 232 | } 233 | 234 | func Fuzz_LastIndexByte(s string, c byte) { 235 | strings.LastIndexByte(s, c) 236 | } 237 | 238 | // skipping Fuzz_LastIndexFunc because parameters include interfaces or funcs: func(rune) bool 239 | 240 | // skipping Fuzz_Map because parameters include interfaces or funcs: func(rune) rune 241 | 242 | func Fuzz_NewReader(s string) { 243 | strings.NewReader(s) 244 | } 245 | 246 | func Fuzz_NewReplacer(oldnew []string) { 247 | strings.NewReplacer(oldnew...) 248 | } 249 | 250 | func Fuzz_Repeat(s string, count int) { 251 | strings.Repeat(s, count) 252 | } 253 | 254 | func Fuzz_Replace(s string, old string, new string, n int) { 255 | strings.Replace(s, old, new, n) 256 | } 257 | 258 | func Fuzz_ReplaceAll(s string, old string, new string) { 259 | strings.ReplaceAll(s, old, new) 260 | } 261 | 262 | func Fuzz_Split(s string, sep string) { 263 | strings.Split(s, sep) 264 | } 265 | 266 | func Fuzz_SplitAfter(s string, sep string) { 267 | strings.SplitAfter(s, sep) 268 | } 269 | 270 | func Fuzz_SplitAfterN(s string, sep string, n int) { 271 | strings.SplitAfterN(s, sep, n) 272 | } 273 | 274 | func Fuzz_SplitN(s string, sep string, n int) { 275 | strings.SplitN(s, sep, n) 276 | } 277 | 278 | func Fuzz_Title(s string) { 279 | strings.Title(s) 280 | } 281 | 282 | func Fuzz_ToLower(s string) { 283 | strings.ToLower(s) 284 | } 285 | 286 | func Fuzz_ToLowerSpecial(c unicode.SpecialCase, s string) { 287 | strings.ToLowerSpecial(c, s) 288 | } 289 | 290 | func Fuzz_ToTitle(s string) { 291 | strings.ToTitle(s) 292 | } 293 | 294 | func Fuzz_ToTitleSpecial(c unicode.SpecialCase, s string) { 295 | strings.ToTitleSpecial(c, s) 296 | } 297 | 298 | func Fuzz_ToUpper(s string) { 299 | strings.ToUpper(s) 300 | } 301 | 302 | func Fuzz_ToUpperSpecial(c unicode.SpecialCase, s string) { 303 | strings.ToUpperSpecial(c, s) 304 | } 305 | 306 | func Fuzz_ToValidUTF8(s string, replacement string) { 307 | strings.ToValidUTF8(s, replacement) 308 | } 309 | 310 | func Fuzz_Trim(s string, cutset string) { 311 | strings.Trim(s, cutset) 312 | } 313 | 314 | // skipping Fuzz_TrimFunc because parameters include interfaces or funcs: func(rune) bool 315 | 316 | func Fuzz_TrimLeft(s string, cutset string) { 317 | strings.TrimLeft(s, cutset) 318 | } 319 | 320 | // skipping Fuzz_TrimLeftFunc because parameters include interfaces or funcs: func(rune) bool 321 | 322 | func Fuzz_TrimPrefix(s string, prefix string) { 323 | strings.TrimPrefix(s, prefix) 324 | } 325 | 326 | func Fuzz_TrimRight(s string, cutset string) { 327 | strings.TrimRight(s, cutset) 328 | } 329 | 330 | // skipping Fuzz_TrimRightFunc because parameters include interfaces or funcs: func(rune) bool 331 | 332 | func Fuzz_TrimSpace(s string) { 333 | strings.TrimSpace(s) 334 | } 335 | 336 | func Fuzz_TrimSuffix(s string, suffix string) { 337 | strings.TrimSuffix(s, suffix) 338 | } 339 | `}, 340 | { 341 | name: "strings: exported only, local pkg", 342 | onlyExported: true, 343 | qualifyAll: false, 344 | insertConstructors: true, 345 | want: `package strings 346 | 347 | // if needed, fill in imports or run 'goimports' 348 | import ( 349 | "io" 350 | "unicode" 351 | ) 352 | 353 | func Fuzz_Builder_Cap(b *Builder) { 354 | if b == nil { 355 | return 356 | } 357 | b.Cap() 358 | } 359 | 360 | func Fuzz_Builder_Grow(b *Builder, n int) { 361 | if b == nil { 362 | return 363 | } 364 | b.Grow(n) 365 | } 366 | 367 | func Fuzz_Builder_Len(b *Builder) { 368 | if b == nil { 369 | return 370 | } 371 | b.Len() 372 | } 373 | 374 | func Fuzz_Builder_Reset(b *Builder) { 375 | if b == nil { 376 | return 377 | } 378 | b.Reset() 379 | } 380 | 381 | func Fuzz_Builder_String(b *Builder) { 382 | if b == nil { 383 | return 384 | } 385 | b.String() 386 | } 387 | 388 | func Fuzz_Builder_Write(b *Builder, p []byte) { 389 | if b == nil { 390 | return 391 | } 392 | b.Write(p) 393 | } 394 | 395 | func Fuzz_Builder_WriteByte(b *Builder, c byte) { 396 | if b == nil { 397 | return 398 | } 399 | b.WriteByte(c) 400 | } 401 | 402 | func Fuzz_Builder_WriteRune(b *Builder, r rune) { 403 | if b == nil { 404 | return 405 | } 406 | b.WriteRune(r) 407 | } 408 | 409 | func Fuzz_Builder_WriteString(b *Builder, s string) { 410 | if b == nil { 411 | return 412 | } 413 | b.WriteString(s) 414 | } 415 | 416 | func Fuzz_Reader_Len(s string) { 417 | r := NewReader(s) 418 | r.Len() 419 | } 420 | 421 | func Fuzz_Reader_Read(s string, b []byte) { 422 | r := NewReader(s) 423 | r.Read(b) 424 | } 425 | 426 | func Fuzz_Reader_ReadAt(s string, b []byte, off int64) { 427 | r := NewReader(s) 428 | r.ReadAt(b, off) 429 | } 430 | 431 | func Fuzz_Reader_ReadByte(s string) { 432 | r := NewReader(s) 433 | r.ReadByte() 434 | } 435 | 436 | func Fuzz_Reader_ReadRune(s string) { 437 | r := NewReader(s) 438 | r.ReadRune() 439 | } 440 | 441 | func Fuzz_Reader_Reset(s1 string, s2 string) { 442 | r := NewReader(s1) 443 | r.Reset(s2) 444 | } 445 | 446 | func Fuzz_Reader_Seek(s string, offset int64, whence int) { 447 | r := NewReader(s) 448 | r.Seek(offset, whence) 449 | } 450 | 451 | func Fuzz_Reader_Size(s string) { 452 | r := NewReader(s) 453 | r.Size() 454 | } 455 | 456 | func Fuzz_Reader_UnreadByte(s string) { 457 | r := NewReader(s) 458 | r.UnreadByte() 459 | } 460 | 461 | func Fuzz_Reader_UnreadRune(s string) { 462 | r := NewReader(s) 463 | r.UnreadRune() 464 | } 465 | 466 | func Fuzz_Reader_WriteTo(s string, w io.Writer) { 467 | r := NewReader(s) 468 | r.WriteTo(w) 469 | } 470 | 471 | func Fuzz_Replacer_Replace(oldnew []string, s string) { 472 | r := NewReplacer(oldnew...) 473 | r.Replace(s) 474 | } 475 | 476 | func Fuzz_Replacer_WriteString(oldnew []string, w io.Writer, s string) { 477 | r := NewReplacer(oldnew...) 478 | r.WriteString(w, s) 479 | } 480 | 481 | func Fuzz_Compare(a string, b string) { 482 | Compare(a, b) 483 | } 484 | 485 | func Fuzz_Contains(s string, substr string) { 486 | Contains(s, substr) 487 | } 488 | 489 | func Fuzz_ContainsAny(s string, chars string) { 490 | ContainsAny(s, chars) 491 | } 492 | 493 | func Fuzz_ContainsRune(s string, r rune) { 494 | ContainsRune(s, r) 495 | } 496 | 497 | func Fuzz_Count(s string, substr string) { 498 | Count(s, substr) 499 | } 500 | 501 | func Fuzz_EqualFold(s string, t string) { 502 | EqualFold(s, t) 503 | } 504 | 505 | func Fuzz_Fields(s string) { 506 | Fields(s) 507 | } 508 | 509 | // skipping Fuzz_FieldsFunc because parameters include interfaces or funcs: func(rune) bool 510 | 511 | func Fuzz_HasPrefix(s string, prefix string) { 512 | HasPrefix(s, prefix) 513 | } 514 | 515 | func Fuzz_HasSuffix(s string, suffix string) { 516 | HasSuffix(s, suffix) 517 | } 518 | 519 | func Fuzz_Index(s string, substr string) { 520 | Index(s, substr) 521 | } 522 | 523 | func Fuzz_IndexAny(s string, chars string) { 524 | IndexAny(s, chars) 525 | } 526 | 527 | func Fuzz_IndexByte(s string, c byte) { 528 | IndexByte(s, c) 529 | } 530 | 531 | // skipping Fuzz_IndexFunc because parameters include interfaces or funcs: func(rune) bool 532 | 533 | func Fuzz_IndexRune(s string, r rune) { 534 | IndexRune(s, r) 535 | } 536 | 537 | func Fuzz_Join(elems []string, sep string) { 538 | Join(elems, sep) 539 | } 540 | 541 | func Fuzz_LastIndex(s string, substr string) { 542 | LastIndex(s, substr) 543 | } 544 | 545 | func Fuzz_LastIndexAny(s string, chars string) { 546 | LastIndexAny(s, chars) 547 | } 548 | 549 | func Fuzz_LastIndexByte(s string, c byte) { 550 | LastIndexByte(s, c) 551 | } 552 | 553 | // skipping Fuzz_LastIndexFunc because parameters include interfaces or funcs: func(rune) bool 554 | 555 | // skipping Fuzz_Map because parameters include interfaces or funcs: func(rune) rune 556 | 557 | func Fuzz_NewReader(s string) { 558 | NewReader(s) 559 | } 560 | 561 | func Fuzz_NewReplacer(oldnew []string) { 562 | NewReplacer(oldnew...) 563 | } 564 | 565 | func Fuzz_Repeat(s string, count int) { 566 | Repeat(s, count) 567 | } 568 | 569 | func Fuzz_Replace(s string, old string, new string, n int) { 570 | Replace(s, old, new, n) 571 | } 572 | 573 | func Fuzz_ReplaceAll(s string, old string, new string) { 574 | ReplaceAll(s, old, new) 575 | } 576 | 577 | func Fuzz_Split(s string, sep string) { 578 | Split(s, sep) 579 | } 580 | 581 | func Fuzz_SplitAfter(s string, sep string) { 582 | SplitAfter(s, sep) 583 | } 584 | 585 | func Fuzz_SplitAfterN(s string, sep string, n int) { 586 | SplitAfterN(s, sep, n) 587 | } 588 | 589 | func Fuzz_SplitN(s string, sep string, n int) { 590 | SplitN(s, sep, n) 591 | } 592 | 593 | func Fuzz_Title(s string) { 594 | Title(s) 595 | } 596 | 597 | func Fuzz_ToLower(s string) { 598 | ToLower(s) 599 | } 600 | 601 | func Fuzz_ToLowerSpecial(c unicode.SpecialCase, s string) { 602 | ToLowerSpecial(c, s) 603 | } 604 | 605 | func Fuzz_ToTitle(s string) { 606 | ToTitle(s) 607 | } 608 | 609 | func Fuzz_ToTitleSpecial(c unicode.SpecialCase, s string) { 610 | ToTitleSpecial(c, s) 611 | } 612 | 613 | func Fuzz_ToUpper(s string) { 614 | ToUpper(s) 615 | } 616 | 617 | func Fuzz_ToUpperSpecial(c unicode.SpecialCase, s string) { 618 | ToUpperSpecial(c, s) 619 | } 620 | 621 | func Fuzz_ToValidUTF8(s string, replacement string) { 622 | ToValidUTF8(s, replacement) 623 | } 624 | 625 | func Fuzz_Trim(s string, cutset string) { 626 | Trim(s, cutset) 627 | } 628 | 629 | // skipping Fuzz_TrimFunc because parameters include interfaces or funcs: func(rune) bool 630 | 631 | func Fuzz_TrimLeft(s string, cutset string) { 632 | TrimLeft(s, cutset) 633 | } 634 | 635 | // skipping Fuzz_TrimLeftFunc because parameters include interfaces or funcs: func(rune) bool 636 | 637 | func Fuzz_TrimPrefix(s string, prefix string) { 638 | TrimPrefix(s, prefix) 639 | } 640 | 641 | func Fuzz_TrimRight(s string, cutset string) { 642 | TrimRight(s, cutset) 643 | } 644 | 645 | // skipping Fuzz_TrimRightFunc because parameters include interfaces or funcs: func(rune) bool 646 | 647 | func Fuzz_TrimSpace(s string) { 648 | TrimSpace(s) 649 | } 650 | 651 | func Fuzz_TrimSuffix(s string, suffix string) { 652 | TrimSuffix(s, suffix) 653 | } 654 | `}, 655 | { 656 | name: "strings: exported only, not local pkg, no constructors", 657 | onlyExported: true, 658 | qualifyAll: true, 659 | insertConstructors: false, 660 | want: `package stringsfuzz // rename if needed 661 | 662 | // if needed, fill in imports or run 'goimports' 663 | import ( 664 | "io" 665 | "strings" 666 | "unicode" 667 | ) 668 | 669 | func Fuzz_Builder_Cap(b *strings.Builder) { 670 | if b == nil { 671 | return 672 | } 673 | b.Cap() 674 | } 675 | 676 | func Fuzz_Builder_Grow(b *strings.Builder, n int) { 677 | if b == nil { 678 | return 679 | } 680 | b.Grow(n) 681 | } 682 | 683 | func Fuzz_Builder_Len(b *strings.Builder) { 684 | if b == nil { 685 | return 686 | } 687 | b.Len() 688 | } 689 | 690 | func Fuzz_Builder_Reset(b *strings.Builder) { 691 | if b == nil { 692 | return 693 | } 694 | b.Reset() 695 | } 696 | 697 | func Fuzz_Builder_String(b *strings.Builder) { 698 | if b == nil { 699 | return 700 | } 701 | b.String() 702 | } 703 | 704 | func Fuzz_Builder_Write(b *strings.Builder, p []byte) { 705 | if b == nil { 706 | return 707 | } 708 | b.Write(p) 709 | } 710 | 711 | func Fuzz_Builder_WriteByte(b *strings.Builder, c byte) { 712 | if b == nil { 713 | return 714 | } 715 | b.WriteByte(c) 716 | } 717 | 718 | func Fuzz_Builder_WriteRune(b *strings.Builder, r rune) { 719 | if b == nil { 720 | return 721 | } 722 | b.WriteRune(r) 723 | } 724 | 725 | func Fuzz_Builder_WriteString(b *strings.Builder, s string) { 726 | if b == nil { 727 | return 728 | } 729 | b.WriteString(s) 730 | } 731 | 732 | func Fuzz_Reader_Len(r *strings.Reader) { 733 | if r == nil { 734 | return 735 | } 736 | r.Len() 737 | } 738 | 739 | func Fuzz_Reader_Read(r *strings.Reader, b []byte) { 740 | if r == nil { 741 | return 742 | } 743 | r.Read(b) 744 | } 745 | 746 | func Fuzz_Reader_ReadAt(r *strings.Reader, b []byte, off int64) { 747 | if r == nil { 748 | return 749 | } 750 | r.ReadAt(b, off) 751 | } 752 | 753 | func Fuzz_Reader_ReadByte(r *strings.Reader) { 754 | if r == nil { 755 | return 756 | } 757 | r.ReadByte() 758 | } 759 | 760 | func Fuzz_Reader_ReadRune(r *strings.Reader) { 761 | if r == nil { 762 | return 763 | } 764 | r.ReadRune() 765 | } 766 | 767 | func Fuzz_Reader_Reset(r *strings.Reader, s string) { 768 | if r == nil { 769 | return 770 | } 771 | r.Reset(s) 772 | } 773 | 774 | func Fuzz_Reader_Seek(r *strings.Reader, offset int64, whence int) { 775 | if r == nil { 776 | return 777 | } 778 | r.Seek(offset, whence) 779 | } 780 | 781 | func Fuzz_Reader_Size(r *strings.Reader) { 782 | if r == nil { 783 | return 784 | } 785 | r.Size() 786 | } 787 | 788 | func Fuzz_Reader_UnreadByte(r *strings.Reader) { 789 | if r == nil { 790 | return 791 | } 792 | r.UnreadByte() 793 | } 794 | 795 | func Fuzz_Reader_UnreadRune(r *strings.Reader) { 796 | if r == nil { 797 | return 798 | } 799 | r.UnreadRune() 800 | } 801 | 802 | func Fuzz_Reader_WriteTo(r *strings.Reader, w io.Writer) { 803 | if r == nil { 804 | return 805 | } 806 | r.WriteTo(w) 807 | } 808 | 809 | func Fuzz_Replacer_Replace(r *strings.Replacer, s string) { 810 | if r == nil { 811 | return 812 | } 813 | r.Replace(s) 814 | } 815 | 816 | func Fuzz_Replacer_WriteString(r *strings.Replacer, w io.Writer, s string) { 817 | if r == nil { 818 | return 819 | } 820 | r.WriteString(w, s) 821 | } 822 | 823 | func Fuzz_Compare(a string, b string) { 824 | strings.Compare(a, b) 825 | } 826 | 827 | func Fuzz_Contains(s string, substr string) { 828 | strings.Contains(s, substr) 829 | } 830 | 831 | func Fuzz_ContainsAny(s string, chars string) { 832 | strings.ContainsAny(s, chars) 833 | } 834 | 835 | func Fuzz_ContainsRune(s string, r rune) { 836 | strings.ContainsRune(s, r) 837 | } 838 | 839 | func Fuzz_Count(s string, substr string) { 840 | strings.Count(s, substr) 841 | } 842 | 843 | func Fuzz_EqualFold(s string, t string) { 844 | strings.EqualFold(s, t) 845 | } 846 | 847 | func Fuzz_Fields(s string) { 848 | strings.Fields(s) 849 | } 850 | 851 | // skipping Fuzz_FieldsFunc because parameters include interfaces or funcs: func(rune) bool 852 | 853 | func Fuzz_HasPrefix(s string, prefix string) { 854 | strings.HasPrefix(s, prefix) 855 | } 856 | 857 | func Fuzz_HasSuffix(s string, suffix string) { 858 | strings.HasSuffix(s, suffix) 859 | } 860 | 861 | func Fuzz_Index(s string, substr string) { 862 | strings.Index(s, substr) 863 | } 864 | 865 | func Fuzz_IndexAny(s string, chars string) { 866 | strings.IndexAny(s, chars) 867 | } 868 | 869 | func Fuzz_IndexByte(s string, c byte) { 870 | strings.IndexByte(s, c) 871 | } 872 | 873 | // skipping Fuzz_IndexFunc because parameters include interfaces or funcs: func(rune) bool 874 | 875 | func Fuzz_IndexRune(s string, r rune) { 876 | strings.IndexRune(s, r) 877 | } 878 | 879 | func Fuzz_Join(elems []string, sep string) { 880 | strings.Join(elems, sep) 881 | } 882 | 883 | func Fuzz_LastIndex(s string, substr string) { 884 | strings.LastIndex(s, substr) 885 | } 886 | 887 | func Fuzz_LastIndexAny(s string, chars string) { 888 | strings.LastIndexAny(s, chars) 889 | } 890 | 891 | func Fuzz_LastIndexByte(s string, c byte) { 892 | strings.LastIndexByte(s, c) 893 | } 894 | 895 | // skipping Fuzz_LastIndexFunc because parameters include interfaces or funcs: func(rune) bool 896 | 897 | // skipping Fuzz_Map because parameters include interfaces or funcs: func(rune) rune 898 | 899 | func Fuzz_NewReader(s string) { 900 | strings.NewReader(s) 901 | } 902 | 903 | func Fuzz_NewReplacer(oldnew []string) { 904 | strings.NewReplacer(oldnew...) 905 | } 906 | 907 | func Fuzz_Repeat(s string, count int) { 908 | strings.Repeat(s, count) 909 | } 910 | 911 | func Fuzz_Replace(s string, old string, new string, n int) { 912 | strings.Replace(s, old, new, n) 913 | } 914 | 915 | func Fuzz_ReplaceAll(s string, old string, new string) { 916 | strings.ReplaceAll(s, old, new) 917 | } 918 | 919 | func Fuzz_Split(s string, sep string) { 920 | strings.Split(s, sep) 921 | } 922 | 923 | func Fuzz_SplitAfter(s string, sep string) { 924 | strings.SplitAfter(s, sep) 925 | } 926 | 927 | func Fuzz_SplitAfterN(s string, sep string, n int) { 928 | strings.SplitAfterN(s, sep, n) 929 | } 930 | 931 | func Fuzz_SplitN(s string, sep string, n int) { 932 | strings.SplitN(s, sep, n) 933 | } 934 | 935 | func Fuzz_Title(s string) { 936 | strings.Title(s) 937 | } 938 | 939 | func Fuzz_ToLower(s string) { 940 | strings.ToLower(s) 941 | } 942 | 943 | func Fuzz_ToLowerSpecial(c unicode.SpecialCase, s string) { 944 | strings.ToLowerSpecial(c, s) 945 | } 946 | 947 | func Fuzz_ToTitle(s string) { 948 | strings.ToTitle(s) 949 | } 950 | 951 | func Fuzz_ToTitleSpecial(c unicode.SpecialCase, s string) { 952 | strings.ToTitleSpecial(c, s) 953 | } 954 | 955 | func Fuzz_ToUpper(s string) { 956 | strings.ToUpper(s) 957 | } 958 | 959 | func Fuzz_ToUpperSpecial(c unicode.SpecialCase, s string) { 960 | strings.ToUpperSpecial(c, s) 961 | } 962 | 963 | func Fuzz_ToValidUTF8(s string, replacement string) { 964 | strings.ToValidUTF8(s, replacement) 965 | } 966 | 967 | func Fuzz_Trim(s string, cutset string) { 968 | strings.Trim(s, cutset) 969 | } 970 | 971 | // skipping Fuzz_TrimFunc because parameters include interfaces or funcs: func(rune) bool 972 | 973 | func Fuzz_TrimLeft(s string, cutset string) { 974 | strings.TrimLeft(s, cutset) 975 | } 976 | 977 | // skipping Fuzz_TrimLeftFunc because parameters include interfaces or funcs: func(rune) bool 978 | 979 | func Fuzz_TrimPrefix(s string, prefix string) { 980 | strings.TrimPrefix(s, prefix) 981 | } 982 | 983 | func Fuzz_TrimRight(s string, cutset string) { 984 | strings.TrimRight(s, cutset) 985 | } 986 | 987 | // skipping Fuzz_TrimRightFunc because parameters include interfaces or funcs: func(rune) bool 988 | 989 | func Fuzz_TrimSpace(s string) { 990 | strings.TrimSpace(s) 991 | } 992 | 993 | func Fuzz_TrimSuffix(s string, suffix string) { 994 | strings.TrimSuffix(s, suffix) 995 | } 996 | `}, 997 | } 998 | 999 | for _, tt := range tests { 1000 | tt := tt 1001 | t.Run(tt.name, func(t *testing.T) { 1002 | t.Parallel() 1003 | pkgPattern := "strings" 1004 | options := flagExcludeFuzzPrefix | flagAllowMultiFuzz 1005 | if tt.onlyExported { 1006 | options |= flagRequireExported 1007 | } 1008 | functions, err := FindFunc(pkgPattern, ".", nil, options) 1009 | if err != nil { 1010 | t.Errorf("FindFuncfail() failed: %v", err) 1011 | } 1012 | 1013 | wrapperOpts := wrapperOptions{ 1014 | qualifyAll: tt.qualifyAll, 1015 | insertConstructors: tt.insertConstructors, 1016 | constructorPattern: "^New", 1017 | } 1018 | out, err := createWrappers(pkgPattern, functions, wrapperOpts) 1019 | if err != nil { 1020 | t.Errorf("createWrappers() failed: %v", err) 1021 | } 1022 | 1023 | got := string(out) 1024 | if diff := cmp.Diff(tt.want, got); diff != "" { 1025 | t.Errorf("createWrappers() mismatch (-want +got):\n%s", diff) 1026 | } 1027 | }) 1028 | } 1029 | } 1030 | -------------------------------------------------------------------------------- /genfuzzfuncs/main.go: -------------------------------------------------------------------------------- 1 | // genfuzzfuncs is an early stage prototype for automatically generating 2 | // fuzz functions, similar in spirit to cweill/gotests. 3 | // 4 | // For example, if you run genfuzzfuncs against github.com/google/uuid, it generates 5 | // a uuid_fuzz.go file with 30 or so functions like: 6 | // 7 | // func Fuzz_UUID_MarshalText(u1 uuid.UUID) { 8 | // u1.MarshalText() 9 | // } 10 | // 11 | // func Fuzz_UUID_UnmarshalText(u1 *uuid.UUID, data []byte) { 12 | // if u1 == nil { 13 | // return 14 | // } 15 | // u1.UnmarshalText(data) 16 | // } 17 | // 18 | // You can then edit or delete indivdual fuzz funcs as desired, and then fuzz 19 | // using the rich signature fuzzing support in thepudds/fzgo, such as: 20 | // 21 | // fzgo test -fuzz=. ./... 22 | package main 23 | 24 | import ( 25 | "flag" 26 | "fmt" 27 | "io/ioutil" 28 | "os" 29 | ) 30 | 31 | // one way to test this on the stdlib: 32 | // 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 33 | // current stats: 34 | // grep -r '^func Fuzz' | wc -l 35 | // 2775 36 | // grep -r 'skipping' | wc -l 37 | // 603 38 | // to also test that fzgo can build the resulting rich signatures 39 | // mkdir ~/go/src/fzgo.test 40 | // cd ~/go/src/fzgo.test 41 | // 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 42 | 43 | // Usage contains short usage information. 44 | var Usage = ` 45 | usage: 46 | genfuzzfuncs [-pkg=pkgPattern] [-func=regexp] [-unexported] [-qualifyall] [-ctors=false] [-ctorspattern=regexp] 47 | 48 | Running genfuzzfuncs without any arguments targets the package in the current directory. 49 | 50 | genfuzzfuncs outputs a set of wrapper functions for all functions 51 | matching the func regex in the target package, which defaults to current directory. 52 | Any function that already starts with 'Fuzz' is skipped, and so are any functions 53 | with zero parameters or that have interface parameters. 54 | 55 | The resulting wrapper functions will all start with 'Fuzz', and are candidates 56 | for use with fuzzing via thepudds/fzgo. 57 | 58 | genfuzzfuncs does not attempt to populate imports, but 'goimports -w ' 59 | should usaully be able to do so. 60 | 61 | ` 62 | 63 | func main() { 64 | // handle flags 65 | flag.Usage = func() { 66 | fmt.Fprint(os.Stderr, Usage) 67 | flag.PrintDefaults() 68 | } 69 | pkgFlag := flag.String("pkg", ".", "package pattern, defaults to current package") 70 | funcFlag := flag.String("func", ".", "function regex, defaults to matching all") 71 | unexportedFlag := flag.Bool("unexported", false, "emit wrappers for unexported functions in addition to exported functions") 72 | qualifyAllFlag := flag.Bool("qualifyall", true, "all identifiers are qualified with package, including identifiers from the target package. "+ 73 | "If the package is '.' or not set, this defaults to false. Else, it defaults to true.") 74 | constructorFlag := flag.Bool("ctors", true, "automatically insert constructors when wrapping a method call "+ 75 | "if a suitable constructor can be found in the same package.") 76 | constructorPatternFlag := flag.String("ctorspattern", "^New", "regexp to use if searching for constructors to automatically use.") 77 | outFileFlag := flag.String("o", "autogeneratedfuzz.go", "output file name.") 78 | 79 | flag.Parse() 80 | if len(flag.Args()) != 0 { 81 | flag.Usage() 82 | os.Exit(2) 83 | } 84 | 85 | // search for functions in the requested package that 86 | // matches the supplied func regex 87 | options := flagExcludeFuzzPrefix | flagAllowMultiFuzz 88 | if !*unexportedFlag { 89 | options |= flagRequireExported 90 | } 91 | var qualifyAll bool 92 | if *pkgFlag == "." { 93 | qualifyAll = false 94 | } else { 95 | // qualifyAllFlag defaults to true, which is what we want 96 | // for non-local package. 97 | qualifyAll = *qualifyAllFlag 98 | } 99 | 100 | functions, err := FindFunc(*pkgFlag, *funcFlag, nil, options) 101 | if err != nil { 102 | fail(err) 103 | } 104 | 105 | wrapperOpts := wrapperOptions{ 106 | qualifyAll: qualifyAll, 107 | insertConstructors: *constructorFlag, 108 | constructorPattern: *constructorPatternFlag, 109 | } 110 | 111 | out, err := createWrappers(*pkgFlag, functions, wrapperOpts) 112 | if err != nil { 113 | fail(err) 114 | } 115 | err = ioutil.WriteFile(*outFileFlag, out, 0644) 116 | if err != nil { 117 | fail(err) 118 | } 119 | 120 | } 121 | 122 | func fail(err error) { 123 | fmt.Fprintf(os.Stderr, "genfuzzfuncs: error: %v\n", err) 124 | os.Exit(1) 125 | } 126 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // fzgo is a simple prototype of integrating dvyukov/go-fuzz into 'go test'. 2 | // 3 | // See the README at https://github.com/thepudds/fzgo for more details. 4 | // 5 | // There are three main directories used: 6 | // 7 | // 1. cacheDir is the location for the instrumented binary, and would typically be something like: 8 | // GOPATH/pkg/fuzz/linux_amd64/619f7d77e9cd5d7433f8/fmt.FuzzFmt 9 | // 10 | // 2. fuzzDir is the destination supplied by the user via the -fuzzdir argument, and contains the workDir. 11 | // 12 | // 3. workDir is passed to go-fuzz-build and go-fuzz as the -workdir argument: 13 | // if -fuzzdir is not specified: workDir is GOPATH/pkg/fuzz/corpus// 14 | // if -fuzzdir is '/some/path': workDir is /some/path// 15 | // if -fuzzdir is 'testdata': workDir is /testdata/fuzz/ 16 | package main 17 | 18 | import ( 19 | "flag" 20 | "fmt" 21 | "os" 22 | "path/filepath" 23 | "runtime" 24 | "strings" 25 | "time" 26 | 27 | "github.com/thepudds/fzgo/fuzz" 28 | ) 29 | 30 | var ( 31 | flagCompile bool 32 | flagFuzzFunc string 33 | flagFuzzDir string 34 | flagFuzzTime time.Duration 35 | flagParallel int 36 | flagRun string 37 | flagTimeout time.Duration 38 | flagVerbose bool 39 | flagDebug string 40 | ) 41 | 42 | var flagDefs = []fuzz.FlagDef{ 43 | {Name: "fuzz", Ptr: &flagFuzzFunc, Description: "fuzz at most one function matching `regexp`"}, 44 | {Name: "fuzzdir", Ptr: &flagFuzzDir, Description: "store fuzz artifacts in `dir` (default pkgpath/testdata/fuzz)"}, 45 | {Name: "fuzztime", Ptr: &flagFuzzTime, Description: "fuzz for duration `d` (default unlimited)"}, 46 | {Name: "parallel", Ptr: &flagParallel, Description: "start `n` fuzzing operations (default GOMAXPROCS)"}, 47 | {Name: "run", Ptr: &flagRun, Description: "if supplied with -fuzz, -run=Corpus/123ABCD executes corpus file matching regexp 123ABCD as a unit test." + 48 | "Otherwise, run normal 'go test' with only those tests and examples matching the regexp."}, 49 | {Name: "timeout", Ptr: &flagTimeout, Description: "fail an individual call to a fuzz function after duration `d` (default 10s, minimum 1s)"}, 50 | {Name: "c", Ptr: &flagCompile, Description: "compile the instrumented code but do not run it"}, 51 | {Name: "v", Ptr: &flagVerbose, Description: "verbose: print additional output"}, 52 | {Name: "debug", Ptr: &flagDebug, Description: "comma separated list of debug options; currently only supports 'nomultifuzz'"}, 53 | } 54 | 55 | // constants for status codes for os.Exit() 56 | const ( 57 | Success = 0 58 | OtherErr = 1 59 | ArgErr = 2 60 | ) 61 | 62 | func main() { 63 | os.Exit(fzgoMain()) 64 | } 65 | 66 | // fzgoMain implements main(), returning a status code usable by os.Exit() and the testscripts package. 67 | // Success is status code 0. 68 | func fzgoMain() int { 69 | 70 | // register our flags 71 | fs, err := fuzz.FlagSet("fzgo test -fuzz", flagDefs, usage) 72 | if err != nil { 73 | fmt.Println("fzgo:", err) 74 | return OtherErr 75 | } 76 | 77 | // print our fzgo-specific help for variations like 'fzgo', 'fzgo help', 'fzgo -h', 'fzgo --help', 'fzgo help foo' 78 | if len(os.Args) < 2 || os.Args[1] == "help" { 79 | fs.Usage() 80 | return ArgErr 81 | } 82 | if _, _, ok := fuzz.FindFlag(os.Args[1:2], []string{"h", "help"}); ok { 83 | fs.Usage() 84 | return ArgErr 85 | } 86 | 87 | if os.Args[1] != "test" { 88 | // pass through to 'go' command 89 | err = fuzz.ExecGo(os.Args[1:], nil) 90 | if err != nil { 91 | // ExecGo prints error if 'go' tool is not in path. 92 | // Other than that, we currently rely on the 'go' tool to print any errors itself. 93 | return OtherErr 94 | } 95 | return Success 96 | } 97 | 98 | // 'test' command is specified. 99 | // check to see if we have a -fuzz flag, and if so, parse the args we will interpret. 100 | pkgPattern, err := fuzz.ParseArgs(os.Args[2:], fs) 101 | if err == flag.ErrHelp { 102 | // if we get here, we already printed usage. 103 | return ArgErr 104 | } else if err != nil { 105 | fmt.Println("fzgo:", err) 106 | return ArgErr 107 | } 108 | 109 | if flagFuzzFunc == "" { 110 | // 'fzgo test' without '-fuzz' 111 | // We have not been asked to generate new fuzz-based inputs, 112 | // but will instead: 113 | // 1. we deterministically validate our corpus. 114 | // it might be a subset or a single file if have something like -run=Corpus/01FFABCD. 115 | // we don't try any crashers given those are expected to fail (prior to a fix, of course). 116 | status := verifyCorpus(os.Args, 117 | verifyCorpusOptions{run: flagRun, tryCrashers: false, verbose: flagVerbose}) 118 | if status != Success { 119 | return status 120 | } 121 | // Because -fuzz is not set, we also: 122 | // 2. pass our arguments through to the normal 'go' command, which will run normal 'go test'. 123 | if flagFuzzFunc == "" { 124 | err = fuzz.ExecGo(os.Args[1:], nil) 125 | if err != nil { 126 | return OtherErr 127 | } 128 | } 129 | return Success 130 | } else if flagFuzzFunc != "" && flagRun != "" { 131 | //'fzgo test -fuzz=foo -run=bar' 132 | // The -run means we have not been asked to generate new fuzz-based inputs, 133 | // but instead will run our corpus, and possibly any crashers if 134 | // -run matches (e.g., -run=TestCrashers or -run=TestCrashers/02ABCDEF). 135 | // Crashers will only be executed if the -run argument matches. 136 | return verifyCorpus(os.Args, 137 | verifyCorpusOptions{run: flagRun, tryCrashers: true, verbose: flagVerbose}) 138 | } 139 | 140 | // we now know we have been asked to do fuzzing. 141 | // gather the basic fuzzing settings from our flags. 142 | allowMultiFuzz := flagDebug != "nomultifuzz" 143 | parallel := flagParallel 144 | if parallel == 0 { 145 | parallel = runtime.GOMAXPROCS(0) 146 | } 147 | funcTimeout := flagTimeout 148 | if funcTimeout == 0 { 149 | funcTimeout = 10 * time.Second 150 | } else if funcTimeout < 1*time.Second { 151 | fmt.Printf("fzgo: fuzz function timeout value %s in -timeout flag is less than minimum of 1 second\n", funcTimeout) 152 | return ArgErr 153 | } 154 | 155 | // look for the functions we have been asked to fuzz. 156 | functions, err := fuzz.FindFunc(pkgPattern, flagFuzzFunc, nil, allowMultiFuzz) 157 | if err != nil { 158 | fmt.Println("fzgo:", err) 159 | return OtherErr 160 | } else if len(functions) == 0 { 161 | fmt.Printf("fzgo: failed to find fuzz function for pattern %v and func %v\n", pkgPattern, flagFuzzFunc) 162 | return OtherErr 163 | } 164 | if flagVerbose { 165 | var names []string 166 | for _, function := range functions { 167 | names = append(names, function.String()) 168 | } 169 | fmt.Printf("fzgo: found functions %s\n", strings.Join(names, ", ")) 170 | } 171 | 172 | // build our instrumented code, or find if is is already built in the fzgo cache 173 | var targets []fuzz.Target 174 | for _, function := range functions { 175 | target, err := fuzz.Instrument(function, flagVerbose) 176 | if err != nil { 177 | fmt.Println("fzgo:", err) 178 | return OtherErr 179 | } 180 | targets = append(targets, target) 181 | } 182 | 183 | if flagCompile { 184 | fmt.Println("fzgo: finished instrumenting binaries") 185 | return Success 186 | } 187 | 188 | // run forever if flagFuzzTime was not set (that is, has default value of 0). 189 | loopForever := flagFuzzTime == 0 190 | timeQuantum := 5 * time.Second 191 | for { 192 | for _, target := range targets { 193 | // pull our last bit of info out of our arguments. 194 | workDir := determineWorkDir(target.UserFunc, flagFuzzDir) 195 | 196 | // seed our workDir with any other corpus that might exist from other known locations. 197 | // see comment for copyCachedCorpus for discussion of current behavior vs. desired behavior. 198 | if err = copyCachedCorpus(target.UserFunc, workDir); err != nil { 199 | fmt.Println("fzgo:", err) 200 | return OtherErr 201 | } 202 | 203 | // determine how long we will execute this particular fuzz invocation. 204 | var fuzzDuration time.Duration 205 | if !loopForever { 206 | fuzzDuration = flagFuzzTime 207 | } else { 208 | if len(targets) > 1 { 209 | fuzzDuration = timeQuantum 210 | } else { 211 | fuzzDuration = 0 // unlimited 212 | } 213 | } 214 | 215 | // fuzz! 216 | err = fuzz.Start(target, workDir, fuzzDuration, parallel, funcTimeout, flagVerbose) 217 | if err != nil { 218 | fmt.Println("fzgo:", err) 219 | return OtherErr 220 | } 221 | fmt.Println() // blank separator line at end of one target's fuzz run. 222 | } 223 | // run forever if flagFuzzTime was not set, 224 | // but otherwise break after fuzzing each target once for flagFuzzTime above. 225 | if !loopForever { 226 | break 227 | } 228 | timeQuantum *= 2 229 | if timeQuantum > 10*time.Minute { 230 | timeQuantum = 10 * time.Minute 231 | } 232 | 233 | } 234 | return Success 235 | } 236 | 237 | type verifyCorpusOptions struct { 238 | run string 239 | tryCrashers bool 240 | verbose bool 241 | } 242 | 243 | // verifyCorpus validates our corpus by executing any fuzz functions in our package pattern 244 | // against any files in the corresponding corpus. This is an automatic form of regression test. 245 | // args is os.Args. 246 | func verifyCorpus(args []string, opt verifyCorpusOptions) int { 247 | // we do this by first searching for any fuzz func ("." regexp) in our package pattern. 248 | // TODO: move this elsewhere? Taken from fuzz.ParseArgs, but we can't use fuzz.ParseArgs as is. 249 | // formerly, we used to also obtain nonPkgArgs here and pass them through, but now we effectively 250 | // whitelist what we want to pass through to 'go test' (now including -run and -v). 251 | testPkgPatterns, _, err := fuzz.FindPkgs(args[2:]) 252 | if err != nil { 253 | fmt.Println("fzgo:", err) 254 | return OtherErr 255 | } 256 | var testPkgPattern string 257 | if len(testPkgPatterns) > 1 { 258 | fmt.Printf("fzgo: more than one package pattern not allowed: %q", testPkgPatterns) 259 | return ArgErr 260 | } else if len(testPkgPatterns) == 0 { 261 | testPkgPattern = "." 262 | } else { 263 | testPkgPattern = testPkgPatterns[0] 264 | } 265 | 266 | functions, err := fuzz.FindFunc(testPkgPattern, flagFuzzFunc, nil, true) 267 | if err != nil { 268 | fmt.Println("fzgo:", err) 269 | return OtherErr 270 | } 271 | 272 | status := Success 273 | for _, function := range functions { 274 | 275 | // work through how many places we need to check based on 276 | // what the user specified in flagFuzzDir. 277 | var dirsToCheck []string 278 | 279 | // we always check the "testdata" dir if it exists. 280 | testdataWorkDir := determineWorkDir(function, "testdata") 281 | dirsToCheck = append(dirsToCheck, testdataWorkDir) 282 | 283 | // we also always check under GOPATH/pkg/fuzz/corpus/... if it exists. 284 | gopathPkgWorkDir := determineWorkDir(function, "") 285 | dirsToCheck = append(dirsToCheck, gopathPkgWorkDir) 286 | 287 | // see if we need to check elsewhere as well. 288 | if flagFuzzDir == "" { 289 | // nothing else to do; the user did not specify a dir. 290 | } else if flagFuzzDir == "testdata" { 291 | // nothing else to do; we already added testdata dir. 292 | } else { 293 | // the user supplied a destination 294 | userWorkDir := determineWorkDir(function, flagFuzzDir) 295 | dirsToCheck = append(dirsToCheck, userWorkDir) 296 | } 297 | 298 | // we have 2 or 3 places to check 299 | foundWorkDir := false 300 | for _, workDir := range dirsToCheck { 301 | if !fuzz.PathExists(filepath.Join(workDir, "corpus")) { 302 | // corpus dir in this workDir does not exist, so skip. 303 | continue 304 | } 305 | foundWorkDir = true 306 | 307 | err := fuzz.VerifyCorpus(function, workDir, opt.run, opt.verbose) 308 | if err == fuzz.ErrGoTestFailed { 309 | // 'go test' itself should have printed an informative error, 310 | // so here we just set a non-zero status code and continue. 311 | status = OtherErr 312 | } else if err != nil { 313 | fmt.Println("fzgo:", err) 314 | return OtherErr 315 | } 316 | if opt.tryCrashers { 317 | // This might not end up matching anything based on the -run=foo regexp, 318 | // but we try it anyway and let cmd/go skip executing the test if it doesn't match. 319 | err = fuzz.VerifyCrashers(function, workDir, opt.run, opt.verbose) 320 | if err == fuzz.ErrGoTestFailed { 321 | // Similar to above, 'go test' itself should have printed an informative error. 322 | status = OtherErr 323 | } else if err != nil { 324 | fmt.Println("fzgo:", err) 325 | return OtherErr 326 | } 327 | } 328 | } 329 | if !foundWorkDir { 330 | // TODO: consider emitting a warning? Or too noisy? 331 | // Would be too noisy for cmd/go, but consider for now? 332 | // fmt.Println("fzgo: did not find any corpus location for", function.FuzzName()) 333 | } 334 | } 335 | 336 | return status 337 | } 338 | 339 | // determineWorkDir translates from the user's specified -fuzzdir to an actual 340 | // location on disk, including the default location if the user does not specify a -fuzzdir. 341 | func determineWorkDir(function fuzz.Func, requestedFuzzDir string) string { 342 | var workDir string 343 | importPathDirs := filepath.FromSlash(function.PkgPath) // convert import path into filepath 344 | if requestedFuzzDir == "" { 345 | // default to GOPATH/pkg/fuzz/corpus/import/path/ 346 | gp := fuzz.Gopath() 347 | workDir = filepath.Join(gp, "pkg", "fuzz", "corpus", importPathDirs, function.FuncName) 348 | } else if requestedFuzzDir == "testdata" { 349 | // place under the package of interest in the testdata directory. 350 | workDir = filepath.Join(function.PkgDir, "testdata", "fuzz", function.FuncName) 351 | } else { 352 | // requestedFuzzDir was specified to be an actual directory. 353 | // still use the import path to handle fuzzing multiple functions across multiple packages. 354 | workDir = filepath.Join(requestedFuzzDir, importPathDirs, function.FuncName) 355 | } 356 | return workDir 357 | } 358 | 359 | // copyCachedCorpus desired bheavior (or at least proposed-by-me behavior): 360 | // 1. if destination corpus location doesn't exist, seed it from GOPATH/pkg/fuzz/corpus/import/path/ 361 | // 2. related: fuzz while reading from all known locations that exist (e.g,. testdata if it exists, GOPATH/pkg/fuzz/corpus/...) 362 | // 363 | // However, 2. is not possible currently to do directly with dvyukov/go-fuzz for more than 1 corpus. 364 | // 365 | // Therefore, the current behavior of copyCachedCorpus approximates 1. and 2. like so: 366 | // 1'. always copy all known corpus entries to the destination corpus location in all cases. 367 | // 368 | // Also, that current behavior could be reasonable for the proposed behavior in the sense that it is simple. 369 | // Filenames that already exist in the destination are not updated. 370 | // TODO: it is debatable if it should copy crashers and suppressions as well. 371 | // For clarity, it only copies the corpus directory itself, and not crashers and supressions. 372 | // This avoids making sometone think they have a new crasher after copying a crasher to a new location, for example, 373 | // especially at this current prototype phase where the crasher reporting in 374 | // go-fuzz does not know anything about multi-corpus locations. 375 | func copyCachedCorpus(function fuzz.Func, dstWorkDir string) error { 376 | dstCorpusDir := filepath.Join(dstWorkDir, "corpus") 377 | 378 | gopathPkgWorkDir := determineWorkDir(function, "") 379 | testdataWorkDir := determineWorkDir(function, "testdata") 380 | 381 | for _, srcWorkDir := range []string{gopathPkgWorkDir, testdataWorkDir} { 382 | srcCorpusDir := filepath.Join(srcWorkDir, "corpus") 383 | if srcCorpusDir == dstCorpusDir { 384 | // nothing to do 385 | continue 386 | } 387 | if fuzz.PathExists(srcCorpusDir) { 388 | // copyDir will create dstDir if needed, and won't overwrite files 389 | // in dstDir that already exist. 390 | if err := fuzz.CopyDir(dstCorpusDir, srcCorpusDir); err != nil { 391 | return fmt.Errorf("failed seeding destination corpus: %v", err) 392 | } 393 | } 394 | } 395 | return nil 396 | } 397 | 398 | func usage(fs *flag.FlagSet) func() { 399 | return func() { 400 | fmt.Printf("\nfzgo is a simple prototype of integrating dvyukov/go-fuzz into 'go test'.\n\n") 401 | fmt.Printf("fzgo supports typical go commands such as 'fzgo build', 'fgzo test', or 'fzgo env', and also supports\n") 402 | fmt.Printf("the '-fuzz' flag and several other related flags proposed in https://golang.org/issue/19109.\n\n") 403 | fmt.Printf("Instrumented binaries are automatically cached in GOPATH/pkg/fuzz.\n\n") 404 | fmt.Printf("Sample usage:\n\n") 405 | fmt.Printf(" fzgo test # test the current package\n") 406 | fmt.Printf(" fzgo test -fuzz . # fuzz the current package with a function starting with 'Fuzz'\n") 407 | fmt.Printf(" fzgo test -fuzz FuzzFoo # fuzz the current package with a function matching 'FuzzFoo'\n") 408 | fmt.Printf(" fzgo test ./... -fuzz FuzzFoo # fuzz a package in ./... with a function matching 'FuzzFoo'\n") 409 | fmt.Printf(" fzgo test sample/pkg -fuzz FuzzFoo # fuzz 'sample/pkg' with a function matching 'FuzzFoo'\n\n") 410 | fmt.Printf("The following flags work with 'fzgo test -fuzz':\n\n") 411 | 412 | for _, d := range flagDefs { 413 | f := fs.Lookup(d.Name) 414 | argname, usage := flag.UnquoteUsage(f) 415 | fmt.Printf(" -%s %s\n %s\n", f.Name, argname, usage) 416 | } 417 | fmt.Println() 418 | } 419 | } 420 | -------------------------------------------------------------------------------- /randparam/bytes2rand.go: -------------------------------------------------------------------------------- 1 | package randparam 2 | 3 | import ( 4 | "encoding/binary" 5 | ) 6 | 7 | // randSource supplies a stream of data via the rand.Source64 interface, 8 | // but does so using an input data []byte. If randSource exhausts the 9 | // data []byte, it start returning zeros. 10 | type randSource struct { 11 | data []byte // data is the remaining byte stream to use for random values. 12 | } 13 | 14 | // Remaining reports how many bytes remain in our original input []byte. 15 | func (s *randSource) Remaining() int { 16 | return len(s.data) 17 | } 18 | 19 | // Drain removes all remaining bytes in the input []byte. 20 | func (s *randSource) Drain() { 21 | s.data = nil 22 | } 23 | 24 | // PeekByte looks at the next byte without consuming it, 25 | // also reporting whether it is an actual byte vs. a zero due to running out of bytes. 26 | // TODO: remove? No longer using. 27 | func (s *randSource) PeekByte() (byte, bool) { 28 | if len(s.data) > 0 { 29 | return s.data[0], true 30 | } 31 | return 0, false 32 | } 33 | 34 | func (s *randSource) Uint64() uint64 { 35 | if len(s.data) >= 8 { 36 | valBytes := s.data[:8] 37 | s.data = s.data[8:] 38 | return binary.LittleEndian.Uint64(valBytes) 39 | } else if len(s.data) > 0 { 40 | grab := len(s.data) // will be < 8 41 | valBytes := s.data[:grab] 42 | s.data = s.data[grab:] 43 | var val uint64 44 | for i, b := range valBytes { 45 | val |= uint64(b) << uint64(i*8) 46 | } 47 | return val 48 | } 49 | 50 | // we are out of bytes in our input stream. 51 | // fall back to zero. 52 | return 0 53 | } 54 | 55 | // Byte returns one byte, consuming only one byte of our input data. 56 | // This is not part of rand.Source64 interface, but useful 57 | // in our custom fuzzing functions so that we don't waste input 58 | // bytes in the data []byte we receive from go-fuzz. 59 | func (s *randSource) Byte() byte { 60 | if len(s.data) > 0 { 61 | val := s.data[0] 62 | s.data = s.data[1:] 63 | return val 64 | } 65 | // we are out of bytes in our input stream. 66 | // fall back to zero. 67 | return 0 68 | } 69 | 70 | // Int63 is needed for rand.Source64 interface. 71 | func (s *randSource) Int63() int64 { 72 | return int64(s.Uint64() & ^uint64(1<<63)) 73 | } 74 | 75 | // Seed is needed for rand.Source64 interface. 76 | // It is a no-op for this package. 77 | func (s *randSource) Seed(seed int64) { 78 | // no-op 79 | } 80 | -------------------------------------------------------------------------------- /randparam/bytes2rand_test.go: -------------------------------------------------------------------------------- 1 | package randparam 2 | 3 | import ( 4 | "encoding/binary" 5 | "testing" 6 | ) 7 | 8 | func TestRandSource_Uint64(t *testing.T) { 9 | tests := []struct { 10 | name string 11 | input []uint64 12 | wantDraw1 uint64 13 | wantDraw2 uint64 14 | }{ 15 | {"0 bytes", []uint64{}, 0x0, 0x0}, 16 | {"4 bytes", []uint64{0xdeadbeef}, 0xdeadbeef, 0x0}, 17 | {"8 bytes", []uint64{0xfeedfacedeadbeef}, 0xfeedfacedeadbeef, 0x0}, 18 | {"16 bytes", []uint64{0xfeedfacedeadbeef, 0x1234}, 0xfeedfacedeadbeef, 0x1234}, 19 | // TODO: Add test cases. 20 | } 21 | for _, tt := range tests { 22 | t.Run(tt.name, func(t *testing.T) { 23 | data := make([]byte, 8*len(tt.input)) 24 | for i := range tt.input { 25 | binary.LittleEndian.PutUint64(data[i*8:], tt.input[i]) 26 | } 27 | src := randSource{data: data} 28 | if gotValue1 := src.Uint64(); gotValue1 != tt.wantDraw1 { 29 | t.Errorf("first RandSource.Uint64() = 0x%x, want 0x%x", gotValue1, tt.wantDraw1) 30 | } 31 | if gotValue2 := src.Uint64(); gotValue2 != tt.wantDraw2 { 32 | t.Errorf("second RandSource.Uint64() = 0x%x, want 0x%x", gotValue2, tt.wantDraw2) 33 | } 34 | 35 | }) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /randparam/randparam.go: -------------------------------------------------------------------------------- 1 | // Package randparam allows a []byte to be used as a source of random parameter values. 2 | // 3 | // The primary use case is to allow fzgo to use dvyukov/go-fuzz to fuzz rich signatures such as: 4 | // FuzzFunc(re string, input string, posix bool) 5 | // google/gofuzz is used to walk the structure of parameters, but randparam uses custom random generators, 6 | // including in the hopes of allowing dvyukov/go-fuzz literal injection to work, 7 | // as well as to better exploit the genetic mutations of dvyukov/go-fuzz, etc. 8 | package randparam 9 | 10 | import ( 11 | "fmt" 12 | "math/rand" 13 | 14 | gofuzz "github.com/google/gofuzz" 15 | ) 16 | 17 | // Fuzzer generates random values for public members. 18 | // It wires together dvyukov/go-fuzz (for randomness, instrumentation, managing corpus, etc.) 19 | // with google/gofuzz (for walking a structure recursively), though it uses functions from 20 | // this package to actually fill in string, []byte, and number values. 21 | type Fuzzer struct { 22 | gofuzzFuzzer *gofuzz.Fuzzer 23 | } 24 | 25 | // randFuncs is a list of our custom variable generation functions 26 | // that tap into our custom random number generator to pull values from 27 | // the initial input []byte. 28 | var randFuncs = []interface{}{ 29 | randInt, 30 | randInt8, 31 | randInt16, 32 | randInt32, 33 | randInt64, 34 | randUint, 35 | randUint8, 36 | randUint16, 37 | randUint32, 38 | randUint64, 39 | randFloat32, 40 | randFloat64, 41 | randByte, 42 | randRune, 43 | } 44 | 45 | // NewFuzzer returns a *Fuzzer, initialized with the []byte as an input stream for drawing values via rand.Rand. 46 | func NewFuzzer(data []byte) *Fuzzer { 47 | // create our random data stream that fill use data []byte for results. 48 | fzgoSrc := &randSource{data} 49 | randSrc := rand.New(fzgoSrc) 50 | 51 | // create some closures for custom fuzzing (so that we have direct access to fzgoSrc). 52 | randFuncsWithFzgoSrc := []interface{}{ 53 | func(ptr *[]byte, c gofuzz.Continue) { 54 | randBytes(ptr, c, fzgoSrc) 55 | }, 56 | func(ptr *string, c gofuzz.Continue) { 57 | randString(ptr, c, fzgoSrc) 58 | }, 59 | func(ptr *[]string, c gofuzz.Continue) { 60 | randStringSlice(ptr, c, fzgoSrc) 61 | }, 62 | } 63 | 64 | // combine our two custom fuzz function lists. 65 | funcs := append(randFuncs, randFuncsWithFzgoSrc...) 66 | 67 | // create the google/gofuzz fuzzer 68 | gofuzzFuzzer := gofuzz.New().RandSource(randSrc).Funcs(funcs...) 69 | 70 | // gofuzzFuzzer.NilChance(0).NumElements(2, 2) 71 | // TODO: pick parameters for NilChance, NumElements, e.g.: 72 | // gofuzzFuzzer.NilChance(0.1).NumElements(0, 10) 73 | // Initially allowing too much variability with NumElements seemed 74 | // to be a problem, but more likely that was an early indication of 75 | // the need to better tune the exact string/[]byte encoding to work 76 | // better with sonar. 77 | 78 | // TODO: consider if we want to use the first byte for meta parameters. 79 | firstByte := fzgoSrc.Byte() 80 | switch { 81 | case firstByte < 32: 82 | gofuzzFuzzer.NilChance(0).NumElements(2, 2) 83 | case firstByte < 64: 84 | gofuzzFuzzer.NilChance(0).NumElements(1, 1) 85 | case firstByte < 96: 86 | gofuzzFuzzer.NilChance(0).NumElements(3, 3) 87 | case firstByte < 128: 88 | gofuzzFuzzer.NilChance(0).NumElements(4, 4) 89 | case firstByte <= 255: 90 | gofuzzFuzzer.NilChance(0.1).NumElements(0, 10) 91 | } 92 | 93 | // TODO: probably delete the alternative string encoding code. 94 | // Probably DON'T have different string encodings. 95 | // (I suspect it helped the fuzzer get 'stuck' if there multiple ways 96 | // to encode same "interesting" inputs). 97 | // if bits.OnesCount8(firstByte)%2 == 0 { 98 | // fzgoSrc.lengthEncodedStrings = false 99 | // } 100 | 101 | f := &Fuzzer{gofuzzFuzzer: gofuzzFuzzer} 102 | return f 103 | } 104 | 105 | // Fuzz fills in public members of obj. For numbers, strings, []bytes, it tries to populate the 106 | // obj value with literals found in the initial input []byte. 107 | func (f *Fuzzer) Fuzz(obj interface{}) { 108 | f.gofuzzFuzzer.Fuzz(obj) 109 | } 110 | 111 | // Fill fills in public members of obj. For numbers, strings, []bytes, it tries to populate the 112 | // obj value with literals found in the initial input []byte. 113 | // TODO: decide to call this Fill or Fuzz or something else. We support both Fill and Fuzz for now. 114 | func (f *Fuzzer) Fill(obj interface{}) { 115 | f.gofuzzFuzzer.Fuzz(obj) 116 | } 117 | 118 | // Override google/gofuzz fuzzing approach for strings, []byte, and numbers 119 | 120 | // randBytes is a custom fill function so that we have exact control over how 121 | // strings and []byte are encoded. 122 | // 123 | // randBytes generates a byte slice using the input []byte stream. 124 | // []byte are deserialized as length encoded, where a leading byte 125 | // encodes the length in range [0-255], but the exact interpretation is a little subtle. 126 | // There is surely room for improvement here, but this current approach is the result of some 127 | // some basic experimentation with some different alternatives, with this approach 128 | // yielding decent results in terms of fuzzing efficiency on basic tests, 129 | // so using this approach at least for now. 130 | // 131 | // The current approach: 132 | // 133 | // 1. Do not use 0x0 to encode a zero length string (or zero length []byte). 134 | // 135 | // We need some way to encode nil byte slices and empty strings 136 | // in the input data []byte. Using 0x0 is the obvious way to encode 137 | // a zero length, but that was not a good choice based on some experimentation. 138 | // I suspect partly because fuzzers (e.g,. go-fuzz) like to insert zeros, 139 | // but more importantly because a 0x0 length field does not give go-fuzz sonar 140 | // anything to work with when looking to substitute a value back in. 141 | // If sonar sees [0x1][0x42] in the input data, and observes 0x42 being used live 142 | // in a string comparison against the value "bingo", sonar can update the data 143 | // to be [0x5][b][i][n][g][o] based on finding the 0x42 and guessing the 0x1 144 | // is a length field that it then updates. In contrast, if sonar sees [0x0] in the input 145 | // data and observes "" being used in a string comparison against "bingo", 146 | // sonar can't currently hunt to find "" in the input data (though I suspect in 147 | // theory sonar could be updated to look for a 0x0 and guess it is a zero length string). 148 | // Net, we want something other than 0x0 to indicate a zero length string or byte slice. 149 | // We pick 0xFF to indicate a zero length. 150 | // 151 | // 2. Do not cap the size at the bytes remaining. 152 | // 153 | // I suspect that also interferes with go-fuzz sonar, which attempts 154 | // to find length fields to adjust when substituting literals. 155 | // If we cap the number of bytes, it means the length field in the input []byte 156 | // would not agree with the actual length used, which means 157 | // sonar does not adjust the length field correctly. 158 | // A concrete example is that if we were to cap the size of what we read, 159 | // the meaning of [0xF1][0x1][0x2][EOD] would change once new data is appended, 160 | // but more importantly sonar would not properly adjust the 0xF1 as a length 161 | // field if sonar substituted in a more interesting string value in place of [0x1][0x2]. 162 | // 163 | // 3. Do not drawing zeros past the end of the input []byte. 164 | // 165 | // This is similar reasons as 1 and 2. Drawing zeros past the end 166 | // also means a value that shows up in the live code under test 167 | // does not have a byte-for-byte match with something in the input []byte. 168 | // 169 | // 4. Skip over any 0x0 byte values that would otherwise have been a size field. 170 | // 171 | // This is effectively an implementation detail of 1. In other words, 172 | // if we don't use 0x0 to ecode a zero length string, we need to do 173 | // something when we find a 0x0 in the spot where a length field would go. 174 | // 175 | // Summary: one way to think about it is the encoding of a length field is: 176 | // * 0-N 0x0 bytes prior to a non-zero byte, and 177 | // * that non-zero byte is the actual length used, unless that non-zero byte 178 | // is 0xFF, in which case that signals a zero-length string/[]byte, and 179 | // * the length value used must be able to draw enough real random bytes from the input []byte. 180 | func randBytes(ptr *[]byte, c gofuzz.Continue, fzgoSrc *randSource) { 181 | verbose := false // TODO: probably remove eventually. 182 | if verbose { 183 | fmt.Println("randBytes verbose:", verbose) 184 | } 185 | 186 | var bs []byte 187 | var size int 188 | 189 | // try to find a size field. 190 | // this is slightly more subtle than just reading one byte, 191 | // mainly in order to better work with go-fuzz sonar. 192 | // see long comment above. 193 | for { 194 | if fzgoSrc.Remaining() == 0 { 195 | if verbose { 196 | fmt.Println("ran out of bytes, 0 remaining") 197 | } 198 | // return nil slice (which will be empty string for string) 199 | *ptr = nil 200 | return 201 | 202 | } 203 | 204 | // draw a size in [0, 255] from our input byte[] stream 205 | sizeField := int(fzgoSrc.Byte()) 206 | if verbose { 207 | fmt.Println("sizeField:", sizeField) 208 | } 209 | 210 | // If we don't have enough data, we want to 211 | // *not* use the size field or the data after sizeField, 212 | // in order to work better with sonar. 213 | if sizeField > fzgoSrc.Remaining() { 214 | if verbose { 215 | fmt.Printf("%d bytes requested via size field, %d remaining, drain rest\n", 216 | sizeField, fzgoSrc.Remaining()) 217 | } 218 | // return nil slice (which will be empty string for string). 219 | // however, before we return, we consume all of our remaining bytes. 220 | fzgoSrc.Drain() 221 | 222 | *ptr = nil 223 | return 224 | } 225 | 226 | // skip over any zero bytes for our size field 227 | // In other words, the encoding is 0-N 0x0 bytes prior to a useful length 228 | // field we will use. 229 | if sizeField == 0x0 { 230 | continue 231 | } 232 | 233 | // 0xFF is our chosen value to represent a zero length string/[]byte. 234 | // (See long comment above for some rationale). 235 | if sizeField == 0xFF { 236 | size = 0 237 | } else { 238 | size = sizeField 239 | } 240 | 241 | // found a usable, non-zero sizeField. let's move on to use it on the next bytes! 242 | break 243 | } 244 | 245 | bs = make([]byte, size) 246 | for i := range bs { 247 | bs[i] = fzgoSrc.Byte() 248 | } 249 | *ptr = bs 250 | } 251 | 252 | // randString is a custom fill function so that we have exact control over how 253 | // strings are encoded. It is a thin wrapper over randBytes. 254 | func randString(s *string, c gofuzz.Continue, fzgoSrc *randSource) { 255 | var bs []byte 256 | randBytes(&bs, c, fzgoSrc) 257 | *s = string(bs) 258 | } 259 | 260 | // TODO: this might be temporary. Here we handle slices of strings as a preview of 261 | // improvements we might get by dropping google/gofuzz for walking some of the data structures. 262 | func randStringSlice(s *[]string, c gofuzz.Continue, fzgoSrc *randSource) { 263 | size, ok := calcSize(fzgoSrc) 264 | if !ok { 265 | *s = nil 266 | return 267 | } 268 | ss := make([]string, size) 269 | for i := range ss { 270 | var str string 271 | randString(&str, c, fzgoSrc) 272 | ss[i] = str 273 | } 274 | *s = ss 275 | } 276 | 277 | // TODO: temporarily extracted this from randBytes. Decide to drop vs. keep/unify. 278 | func calcSize(fzgoSrc *randSource) (size int, ok bool) { 279 | verbose := false // TODO: probably remove eventually. 280 | 281 | // try to find a size field. 282 | // this is slightly more subtle than just reading one byte, 283 | // mainly in order to better work with go-fuzz sonar. 284 | // see long comment above. 285 | for { 286 | if fzgoSrc.Remaining() == 0 { 287 | if verbose { 288 | fmt.Println("ran out of bytes, 0 remaining") 289 | } 290 | // return nil slice (which will be empty string for string) 291 | 292 | return 0, false 293 | } 294 | 295 | // draw a size in [0, 255] from our input byte[] stream 296 | sizeField := int(fzgoSrc.Byte()) 297 | if verbose { 298 | fmt.Println("sizeField:", sizeField) 299 | } 300 | 301 | // If we don't have enough data, we want to 302 | // *not* use the size field or the data after sizeField, 303 | // in order to work better with sonar. 304 | if sizeField > fzgoSrc.Remaining() { 305 | if verbose { 306 | fmt.Printf("%d bytes requested via size field, %d remaining, drain rest\n", 307 | sizeField, fzgoSrc.Remaining()) 308 | } 309 | // return nil slice (which will be empty string for string). 310 | // however, before we return, we consume all of our remaining bytes. 311 | fzgoSrc.Drain() 312 | 313 | return 0, false 314 | } 315 | 316 | // skip over any zero bytes for our size field 317 | // In other words, the encoding is 0-N 0x0 bytes prior to a useful length 318 | // field we will use. 319 | if sizeField == 0x0 { 320 | continue 321 | } 322 | 323 | // 0xFF is our chosen value to represent a zero length string/[]byte. 324 | // (See long comment above for some rationale). 325 | if sizeField == 0xFF { 326 | size = 0 327 | } else { 328 | size = sizeField 329 | } 330 | 331 | // found a usable, non-zero sizeField. let's move on to use it on the next bytes! 332 | break 333 | } 334 | return size, true 335 | } 336 | 337 | // A set of custom numeric value filling funcs follows. 338 | // These are currently simple implementations that only use gofuzz.Continue 339 | // as a source for data, which means obtaining 64-bits of the input stream 340 | // at a time. For sizes < 64 bits, this could be tighted up to waste less of the input stream 341 | // by getting access to fzgo/randparam.randSource. 342 | // 343 | // Once the end of the input []byte is reached, zeros are drawn, including 344 | // if in the middle of obtaining bytes for a >1 bye number. 345 | // Tt is probably ok to draw zeros past the end 346 | // for numbers because we use a little endian interpretation 347 | // for numbers (which means if we find byte 0x1 then that's the end 348 | // and we draw zeros for say a uint32, the result is 1; sonar 349 | // seems to guess the length of numeric values, so it likely 350 | // works end to end even if we draw zeros. 351 | // TODO: The next bytes appended (via some mutation) after a number can change 352 | // the result (e.g., if a 0x2 is appended in example above, result is no longer 1), 353 | // so maybe better to also not draw zeros for numeric values? 354 | 355 | func randInt(val *int, c gofuzz.Continue) { 356 | *val = int(c.Rand.Uint64()) 357 | } 358 | 359 | func randInt8(val *int8, c gofuzz.Continue) { 360 | *val = int8(c.Rand.Uint64()) 361 | } 362 | 363 | func randInt16(val *int16, c gofuzz.Continue) { 364 | *val = int16(c.Rand.Uint64()) 365 | } 366 | 367 | func randInt32(val *int32, c gofuzz.Continue) { 368 | *val = int32(c.Rand.Uint64()) 369 | } 370 | 371 | func randInt64(val *int64, c gofuzz.Continue) { 372 | *val = int64(c.Rand.Uint64()) 373 | } 374 | 375 | func randUint(val *uint, c gofuzz.Continue) { 376 | *val = uint(c.Rand.Uint64()) 377 | } 378 | 379 | func randUint8(val *uint8, c gofuzz.Continue) { 380 | *val = uint8(c.Rand.Uint64()) 381 | } 382 | 383 | func randUint16(val *uint16, c gofuzz.Continue) { 384 | *val = uint16(c.Rand.Uint64()) 385 | } 386 | 387 | func randUint32(val *uint32, c gofuzz.Continue) { 388 | *val = uint32(c.Rand.Uint64()) 389 | } 390 | 391 | func randUint64(val *uint64, c gofuzz.Continue) { 392 | *val = uint64(c.Rand.Uint64()) 393 | } 394 | 395 | func randFloat32(val *float32, c gofuzz.Continue) { 396 | *val = float32(c.Rand.Uint64()) 397 | } 398 | 399 | func randFloat64(val *float64, c gofuzz.Continue) { 400 | *val = float64(c.Rand.Uint64()) 401 | } 402 | 403 | func randByte(val *byte, c gofuzz.Continue) { 404 | *val = byte(c.Rand.Uint64()) 405 | } 406 | 407 | func randRune(val *rune, c gofuzz.Continue) { 408 | *val = rune(c.Rand.Uint64()) 409 | } 410 | 411 | // Note: complex64, complex128, uintptr are not supported by google/gofuzz, I think. 412 | // TODO: Interfaces are also not currently supported by google/gofuzz, or at least not 413 | // easily as far as I am aware. That said, currently have most of the pieces elsewhere 414 | // for us to handle common interfaces like io.Writer, io.Reader, etc. 415 | -------------------------------------------------------------------------------- /randparam/randparam_test.go: -------------------------------------------------------------------------------- 1 | package randparam 2 | 3 | import ( 4 | "encoding/binary" 5 | "testing" 6 | 7 | "github.com/google/go-cmp/cmp" 8 | ) 9 | 10 | func TestFuzzingParams(t *testing.T) { 11 | 12 | t.Run("string - 8 byte length, 8 bytes of string input", func(t *testing.T) { 13 | input := append([]byte{0x0, 0x8}, []byte("12345678")...) 14 | want := "12345678" 15 | 16 | fuzzer := NewFuzzer(input) 17 | var got string 18 | fuzzer.Fuzz(&got) 19 | if diff := cmp.Diff(want, got); diff != "" { 20 | t.Errorf("fuzzer.Fuzz() mismatch (-want +got):\n%s", diff) 21 | } 22 | }) 23 | 24 | t.Run("string - 9 byte length, 9 bytes of string input", func(t *testing.T) { 25 | input := append([]byte{0x0, 0x9}, []byte("123456789")...) 26 | want := "123456789" 27 | 28 | fuzzer := NewFuzzer(input) 29 | var got string 30 | fuzzer.Fuzz(&got) 31 | if diff := cmp.Diff(want, got); diff != "" { 32 | t.Errorf("fuzzer.Fuzz() mismatch (-want +got):\n%s", diff) 33 | } 34 | }) 35 | 36 | t.Run("string - 5 byte length, 6 bytes of string input", func(t *testing.T) { 37 | input := append([]byte{0x0, 0x5}, []byte("123456")...) 38 | want := "12345" 39 | 40 | fuzzer := NewFuzzer(input) 41 | var got string 42 | fuzzer.Fuzz(&got) 43 | if diff := cmp.Diff(want, got); diff != "" { 44 | t.Errorf("fuzzer.Fuzz() mismatch (-want +got):\n%s", diff) 45 | } 46 | }) 47 | 48 | t.Run("string - 9 byte length, 2 bytes of string input", func(t *testing.T) { 49 | input := append([]byte{0x9}, []byte("12")...) 50 | want := "" 51 | 52 | fuzzer := NewFuzzer(input) 53 | var got string 54 | fuzzer.Fuzz(&got) 55 | if diff := cmp.Diff(want, got); diff != "" { 56 | t.Errorf("fuzzer.Fuzz() mismatch (-want +got):\n%s", diff) 57 | } 58 | }) 59 | 60 | t.Run("string - zero length string explicitly encoded", func(t *testing.T) { 61 | longByteSlice := make([]byte, 1000) 62 | input := append([]byte{0xFF}, longByteSlice...) 63 | want := "" 64 | 65 | fuzzer := NewFuzzer(input) 66 | var got string 67 | fuzzer.Fuzz(&got) 68 | if diff := cmp.Diff(want, got); diff != "" { 69 | t.Errorf("fuzzer.Fuzz() mismatch (-want +got):\n%s", diff) 70 | } 71 | }) 72 | 73 | t.Run("string - skip 0x0 size fields", func(t *testing.T) { 74 | input := append([]byte{0x0, 0x0, 0x2}, []byte("12")...) 75 | want := "12" 76 | 77 | fuzzer := NewFuzzer(input) 78 | var got string 79 | fuzzer.Fuzz(&got) 80 | if diff := cmp.Diff(want, got); diff != "" { 81 | t.Errorf("fuzzer.Fuzz() mismatch (-want +got):\n%s", diff) 82 | } 83 | }) 84 | 85 | t.Run("string - two strings", func(t *testing.T) { 86 | input := []byte{0x0, 0x1, 0x42, 0x2, 0x43, 0x44} 87 | want1 := string([]byte{0x42}) 88 | want2 := string([]byte{0x43, 0x44}) 89 | 90 | fuzzer := NewFuzzer(input) 91 | var got1, got2 string 92 | fuzzer.Fuzz(&got1) 93 | fuzzer.Fuzz(&got2) 94 | 95 | if diff := cmp.Diff(want1, got1); diff != "" { 96 | t.Errorf("fuzzer.Fuzz() mismatch (-want1 +got1):\n%s", diff) 97 | } 98 | if diff := cmp.Diff(want2, got2); diff != "" { 99 | t.Errorf("fuzzer.Fuzz() mismatch (-want2 +got2):\n%s", diff) 100 | } 101 | }) 102 | 103 | t.Run("string - exactly run out of bytes", func(t *testing.T) { 104 | input := []byte{0x0, 0x1, 0x42} 105 | want1 := string([]byte{0x42}) 106 | want2 := "" 107 | 108 | fuzzer := NewFuzzer(input) 109 | var got1, got2 string 110 | fuzzer.Fuzz(&got1) 111 | fuzzer.Fuzz(&got2) 112 | 113 | if diff := cmp.Diff(want1, got1); diff != "" { 114 | t.Errorf("fuzzer.Fuzz() mismatch (-want1 +got1):\n%s", diff) 115 | } 116 | if diff := cmp.Diff(want2, got2); diff != "" { 117 | t.Errorf("fuzzer.Fuzz() mismatch (-want2 +got2):\n%s", diff) 118 | } 119 | }) 120 | 121 | t.Run("byte slice - 8 byte length, 8 input bytes", func(t *testing.T) { 122 | input := append([]byte{0x0, 0x8}, []byte("12345678")...) 123 | want := []byte("12345678") 124 | 125 | fuzzer := NewFuzzer(input) 126 | var got []byte 127 | fuzzer.Fuzz(&got) 128 | if diff := cmp.Diff(want, got); diff != "" { 129 | t.Errorf("fuzzer.Fuzz() mismatch (-want +got):\n%s", diff) 130 | } 131 | }) 132 | 133 | t.Run("byte slice - 3 byte length, 8 input bytes", func(t *testing.T) { 134 | input := append([]byte{0x0, 0x3}, []byte("12345678")...) 135 | want := []byte("123") 136 | 137 | fuzzer := NewFuzzer(input) 138 | var got []byte 139 | fuzzer.Fuzz(&got) 140 | if diff := cmp.Diff(want, got); diff != "" { 141 | t.Errorf("fuzzer.Fuzz() mismatch (-want +got):\n%s", diff) 142 | } 143 | }) 144 | 145 | t.Run("uint64 - 8 bytes input", func(t *testing.T) { 146 | input := append([]byte{0x0}, make([]byte, 8)...) 147 | i := uint64(0xfeedfacedeadbeef) 148 | binary.LittleEndian.PutUint64(input[1:], i) 149 | want := uint64(0xfeedfacedeadbeef) 150 | 151 | fuzzer := NewFuzzer(input) 152 | var got uint64 153 | fuzzer.Fuzz(&got) 154 | if diff := cmp.Diff(want, got); diff != "" { 155 | t.Errorf("fuzzer.Fuzz() mismatch (-want +got):\n%s", diff) 156 | } 157 | }) 158 | 159 | t.Run("uint64 - 4 bytes input", func(t *testing.T) { 160 | input := []byte{0x0, 0xef, 0xbe, 0xad, 0xde} 161 | want := uint64(0xdeadbeef) 162 | 163 | fuzzer := NewFuzzer(input) 164 | var got uint64 165 | fuzzer.Fuzz(&got) 166 | if diff := cmp.Diff(want, got); diff != "" { 167 | t.Errorf("fuzzer.Fuzz() mismatch (-want +got):\n%s", diff) 168 | } 169 | }) 170 | 171 | t.Run("int32 - 4 bytes input with zeros", func(t *testing.T) { 172 | input := []byte{0x0, 0x42, 0x00, 0x00, 0x00} 173 | want := int32(0x42) 174 | 175 | fuzzer := NewFuzzer(input) 176 | var got int32 177 | fuzzer.Fuzz(&got) 178 | if diff := cmp.Diff(want, got); diff != "" { 179 | t.Errorf("fuzzer.Fuzz() mismatch (-want +got):\n%s", diff) 180 | } 181 | }) 182 | 183 | t.Run("int32 - 1 byte input", func(t *testing.T) { 184 | input := []byte{0x0, 0x42} 185 | want := int32(0x42) 186 | 187 | fuzzer := NewFuzzer(input) 188 | var got int32 189 | fuzzer.Fuzz(&got) 190 | if diff := cmp.Diff(want, got); diff != "" { 191 | t.Errorf("fuzzer.Fuzz() mismatch (-want +got):\n%s", diff) 192 | } 193 | }) 194 | } 195 | -------------------------------------------------------------------------------- /script_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "github.com/rogpeppe/go-internal/gotooltest" 8 | "github.com/rogpeppe/go-internal/testscript" 9 | ) 10 | 11 | func TestMain(m *testing.M) { 12 | os.Exit(testscript.RunMain(fzgoTestingMain{m}, map[string]func() int{ 13 | "fzgo": fzgoMain, 14 | })) 15 | } 16 | 17 | type fzgoTestingMain struct { 18 | m *testing.M 19 | } 20 | 21 | func (m fzgoTestingMain) Run() int { 22 | // could do additional setup here if needed (e.g., check or set env vars, start a Go proxy server, etc.) 23 | return m.m.Run() 24 | } 25 | 26 | func TestScripts(t *testing.T) { 27 | p := testscript.Params{Dir: "testscripts"} 28 | if err := gotooltest.Setup(&p); err != nil { 29 | t.Fatal(err) 30 | } 31 | testscript.Run(t, p) 32 | } 33 | -------------------------------------------------------------------------------- /testscripts/flags.txt: -------------------------------------------------------------------------------- 1 | # Fail due to a 'go test' flag that is incompatible with 'go test -fuzz'. 2 | ! fzgo test -fuzz=FuzzOther sample/pkg1 -fuzztime=10s -benchtime=10s 3 | stdout 'test flag -benchtime is currently proposed to be incompatible with ''go test -fuzz''' 4 | 5 | # Fail due to a build flag that is incompatible with 'go test -fuzz'. 6 | ! fzgo test -fuzz=FuzzOther sample/pkg1 -fuzztime=10s -ldflags=foo 7 | stdout 'build flag -ldflags is not yet implemented by fzgo prototype' 8 | 9 | # Fail due to a test flag that is incompatible with 'go test -fuzz'. 10 | ! fzgo test -fuzz=FuzzOther sample/pkg1 -fuzztime=10s -coverprofile=foo 11 | stdout 'test flag -coverprofile is not yet implemented by fzgo prototype' 12 | 13 | # Fail due to a test flag starting '-test.' that is incompatible with 'go test -fuzz'. 14 | ! fzgo test -fuzz=FuzzOther sample/pkg1 -fuzztime=10s -test.coverprofile=foo 15 | stdout 'test flag -test.coverprofile is not yet implemented by fzgo prototype' 16 | 17 | # Fail due to an unknown extra flag. 18 | ! fzgo test -fuzz=FuzzOther sample/pkg1 -fuzztime=10s -someflag 19 | stdout 'flag provided but not defined: -someflag' 20 | 21 | # Fail due to an non-flag argument that is not a package (that is, a package then at least one flag then a non-flag). 22 | ! fzgo test -fuzz=FuzzOther sample/pkg1 -fuzztime=10s nonflag 23 | stdout 'packages are the only non-flag arguments allowed with -fuzz flag. illegal argument: "nonflag"' 24 | 25 | # Fail due to an arg that is incompatible with 'test -fuzz' after -args. 26 | # TODO probably not quite the right error message, but probably OK for now. 27 | ! fzgo test -fuzz=FuzzOther sample/pkg1 -fuzztime=10s -args -benchtime 28 | stdout 'flag provided but not defined: -args' 29 | 30 | -------------------------------------------------------------------------------- /testscripts/fuzzing.txt: -------------------------------------------------------------------------------- 1 | # Test fuzzing. We could assume go-fuzz and go-fuzz-build binaries are in the path, 2 | # but we start these tests doing 'go get' on github.com/dvyukov/go-fuzz/... because we need 3 | # the go-fuzz-dep source code to be findable by go-fuzz-build (when it invokes 'go list'). 4 | # TODO: $WORK/gopath/bin is actually not in the path; installing a binary here does not mean we can use it. 5 | # We might need to adjust the PATH in our testscript setup to include $WORK/gopath/bin, 6 | # or provide a FZGO_PATH_SEPARATOR variable to enable us to do the same from within a testscript, or some other soln. 7 | # For now, we rely on the binaries being in the system path. 8 | # Reminder: the tests here can be run by themselves from the fzgo directory via: 9 | # go test -run=TestScripts/fuzzing$ . 10 | 11 | # Exit early if -short was specified. 12 | [short] skip 'skipping building instrumented binary because -short was specified' 13 | 14 | # Explicitly set GO111MODULE off for now. (testscripts seemingly by design do not pick up this value from actual env). 15 | env GO111MODULE=off 16 | 17 | # Get go-fuzz (go-fuzz-dep needed by go-fuzz-build). 18 | go get -v -u github.com/dvyukov/go-fuzz/... 19 | go install github.com/dvyukov/go-fuzz/... 20 | 21 | # Verify the go-fuzz binaries seem to exist in our test environment 22 | exists $WORK/gopath/bin/go-fuzz$exe 23 | exists $WORK/gopath/bin/go-fuzz-build$exe 24 | 25 | # First fuzz test: no fzgo cache, so we build the instrumented binary from scratch. 26 | # This also creates our corpus directory in the default location. 27 | fzgo test -fuzz=FuzzTime example.com/pkg1 -fuzztime=5s 28 | stdout 'building instrumented binary for pkg1.FuzzTime' 29 | stderr 'workers: \d+, corpus: ' 30 | exists $WORK/gopath/pkg/fuzz/corpus/example.com/pkg1/FuzzTime/corpus 31 | # Verify a known input was copied from /testdata/... 32 | # to our user-supplied destination corpus location. 33 | exists $WORK/gopath/pkg/fuzz/corpus/example.com/pkg1/FuzzTime/corpus/valid-input 34 | 35 | # Second fuzz test: now we use the fzgo instrumentation cache. 36 | # Even though -fuzz regexp and package pattern specified differently, they match the same package and func. 37 | # We also specify -parallel=1 to reduce CPU usage for our remaining tests. 38 | fzgo test -fuzz=Time .../pkg1 -parallel=1 -fuzztime 5s 39 | stdout 'fzgo: using cached instrumented binary for pkg1.FuzzTime' 40 | stderr 'workers: \d+, corpus: ' 41 | 42 | # Third fuzz test: match two fuzz targets in pkg1. 43 | # Order of which fuzz function goes first might not be deterministic, 44 | # so check that one appears during the instrumentation phase and 45 | # the other appears later during the fuzzing phase, which should be deterministic. 46 | fzgo test -fuzz=. .../pkg1 -parallel=1 -fuzztime 5s 47 | stdout 'fzgo: building instrumented binary for pkg1.FuzzTwo' 48 | stdout 'fzgo: starting fuzzing pkg1.FuzzTime' 49 | stderr 'workers: \d+, corpus: ' 50 | 51 | # Flag -fuzzdir controls where the corpus goes (which could be in a different repo). 52 | # This invocation still uses the binary from the instrumentation cache, 53 | # as do all subsequent invocations in this script. 54 | fzgo test -fuzz=FuzzTime example.com/pkg1 -parallel=1 -fuzztime=5s -fuzzdir=$WORK/myfuzzdir 55 | stdout 'fzgo: using cached instrumented binary for pkg1.FuzzTime' 56 | stderr 'workers: \d+, corpus: ' 57 | exists $WORK/myfuzzdir/example.com/pkg1/FuzzTime/corpus 58 | 59 | # Verify a known input was copied from /testdata/... 60 | # to our user-supplied destination corpus location supplied in -fuzzdir above. 61 | # See comments in copyCachedCorpus comments in main.go for description of this behavior. 62 | exists $WORK/myfuzzdir/example.com/pkg1/FuzzTime/corpus/valid-input 63 | 64 | # Verify we can ask that our corpus go the /testdata/... via -fuzzdir=testdata. 65 | # We also verify that it is seeded from our ephemeral cached corpus in GOPATH/pkg/fuzz/corpus... 66 | # To do that, we clear a file from /testdata/... that we know should have been 67 | # already moved to GOPATH/pkg/fuzz/corpus... above, and then verify it shows up again in /testdata/... 68 | rm $WORK/gopath/src/example.com/pkg1/testdata/fuzz/FuzzTime/corpus/valid-input 69 | fzgo test -fuzz=FuzzTime example.com/pkg1 -parallel=1 -fuzztime=5s -fuzzdir=testdata 70 | stdout 'fzgo: using cached instrumented binary for pkg1.FuzzTime' 71 | stderr 'workers: \d+, corpus: ' 72 | 73 | # Our 'valid-input' file should now have been seeded back into testdata. 74 | exists $WORK/gopath/src/example.com/pkg1/testdata/fuzz/FuzzTime/corpus/valid-input 75 | 76 | # Flag -parallel controls the worker count. Verify we can set it to 2. 77 | fzgo test -fuzz FuzzTime example.com/pkg1 -parallel=2 -fuzztime=5s 78 | stdout 'fzgo: using cached instrumented binary for pkg1.FuzzTime' 79 | stderr 'workers: 2, corpus: ' 80 | 81 | # Flag -c asks to instrument and compile without running (so here we don't limit the duration with -fuzztime). 82 | # TODO revisit what goes to stdout vs stderr 83 | fzgo test -fuzz FuzzTime example.com/pkg1 -c 84 | stdout 'fzgo: using cached instrumented binary for pkg1.FuzzTime' 85 | stdout 'fzgo: finished instrumenting binaries' 86 | ! stderr 'workers: \d+, corpus: ' 87 | 88 | # Flag --test.c also works. 89 | fzgo test -fuzz FuzzTime example.com/pkg1 -test.c 90 | stdout 'fzgo: using cached instrumented binary for pkg1.FuzzTime' 91 | stdout 'fzgo: finished instrumenting binaries' 92 | ! stderr 'workers: \d+, corpus: ' 93 | 94 | # Flag -v outputs more verbose information when fuzzing. 95 | fzgo test -fuzz FuzzTime example.com/pkg1 -fuzztime=5s -parallel=1 -v 96 | stdout 'fzgo: using cached instrumented binary for pkg1.FuzzTime' 97 | stderr 'workers: 1, corpus: ' 98 | 99 | # FZGOFLAGSBUILD env var passes extra args to go-fuzz-build 100 | env FZGOFLAGSBUILD=-foo=bar 101 | ! fzgo test -fuzz=FuzzTwo example.com/pkg2 -fuzztime=5s -parallel=1 102 | stderr 'flag provided but not defined: -foo' 103 | stderr 'Usage of go-fuzz-build:' 104 | ! stderr 'workers: 1, corpus: ' 105 | env FZGOFLAGSBUILD= 106 | 107 | # FZGOFLAGSFUZZ env var passes extra args to go-fuzz-build 108 | env FZGOFLAGSFUZZ=-bar=foo 109 | ! fzgo test -fuzz=FuzzTwo example.com/pkg2 -fuzztime=5s -parallel=1 110 | stderr 'flag provided but not defined: -bar' 111 | stderr 'Usage of go-fuzz:' 112 | ! stderr 'workers: 1, corpus: ' 113 | env FZGOFLAGSFUZZ= 114 | 115 | # Two test files, with three fuzz funcs. The same test files are used in the package_patterns.txt test script. 116 | # One file uses '+build fuzz', the other '+build gofuzz'. 117 | 118 | -- gopath/src/example.com/pkg1/fuzz.go -- 119 | // +build fuzz 120 | 121 | package pkg1 122 | 123 | import "time" 124 | 125 | // FuzzTime is a very simple fuzzing function 126 | func FuzzTime(data []byte) int { 127 | 128 | _, err := time.ParseDuration(string(data)) 129 | 130 | if err != nil { 131 | return 1 132 | } 133 | return 0 134 | } 135 | 136 | // FuzzTwo is a placeholder fuzzing function, and has same name as func in pkg2 137 | func FuzzTwo(data []byte) int { 138 | return 0 139 | } 140 | 141 | -- gopath/src/example.com/pkg2/fuzz.go -- 142 | // +build gofuzz 143 | 144 | package pkg2 145 | 146 | // FuzzTwo is another placeholder fuzzing function, and has same name as func in pkg1 147 | func FuzzTwo(data []byte) int { 148 | return 0 149 | } 150 | 151 | 152 | -- gopath/src/example.com/pkg1/testdata/fuzz/FuzzTime/corpus/valid-input -- 153 | 1h56m23s 154 | -------------------------------------------------------------------------------- /testscripts/fuzzing_rich_signatures.txt: -------------------------------------------------------------------------------- 1 | # Test fuzzing rich sigs. We could assume go-fuzz and go-fuzz-build binaries are in the path, 2 | # but we start these tests doing 'go get' on github.com/dvyukov/go-fuzz/... because we need 3 | # the go-fuzz-dep source code to be findable by go-fuzz-build (when it invokes 'go list'). 4 | # Reminder: the tests here can be run by themselves from the fzgo directory via: 5 | # go test -run=TestScripts/fuzzing_rich_signatures . 6 | 7 | # Exit early if -short was specified. 8 | [short] skip 'skipping building instrumented binary because -short was specified' 9 | 10 | # Explicitly set GO111MODULE off for now. (testscripts seemingly by design do not pick up this value from actual env). 11 | env GO111MODULE=off 12 | 13 | # get our dependencies. 14 | # it should be sufficient to get fzgo/randparam, rather than fzgo/... 15 | # TODO: it would be better to use local copy. currently this gets the copy of fzgo/randparam from github. 16 | go get -v -u github.com/thepudds/fzgo/randparam 17 | go get -v -u github.com/google/gofuzz 18 | 19 | # TODO: at some point between 2019-11-03 and 2020-02-15, this became a needed workaround. 20 | go get -v -u golang.org/x/mod/... 21 | 22 | # Get go-fuzz (go-fuzz-dep needed by go-fuzz-build). 23 | go get -v -u github.com/dvyukov/go-fuzz/... 24 | go install github.com/dvyukov/go-fuzz/... 25 | 26 | # Verify the go-fuzz binaries seem to exist in our test environment 27 | exists $WORK/gopath/bin/go-fuzz$exe 28 | exists $WORK/gopath/bin/go-fuzz-build$exe 29 | 30 | # Check we can get a crasher relatively quickly by finding a 64 bit int via a rich signature, which 31 | # should imply go-fuzz literal injection is working end-to-end with fzgo's rich signatures. 32 | fzgo test -fuzz=FuzzHardToGuessNumber example.com/richsignatures -parallel=1 -fuzztime=10s 33 | stdout 'building instrumented binary for pkgname.FuzzHardToGuessNumber' 34 | stderr 'workers: \d+, corpus: .* crashers: [^0]' 35 | exists $WORK/gopath/pkg/fuzz/corpus/example.com/richsignatures/FuzzHardToGuessNumber/corpus 36 | 37 | # Verify we can get it to print the discovered value by asking to run 38 | # the crashers directly (without additional fuzzing) with verbose mode. 39 | # It exits with an error code given this is akin to 'go test' seeing a panic. 40 | ! fzgo test -v -run=TestCrashers -fuzz=FuzzHardToGuessNumber example.com/richsignatures 41 | stdout 'guessMe: .* 0x123456789' 42 | stdout 'panic: bingo' 43 | ! stderr 'workers: \d+, corpus: .*' 44 | 45 | # Check we can get a crasher relatively quickly by finding a long string via a rich signature, which 46 | # is harder than the uint64 test. This relies on fzgo's approach 47 | # to deserializing working well with go-fuzz sonar's approach for variable length strings. 48 | # When working properly, this is typically found within 10 sec or so but sometimes 30 sec or so. 49 | # This might have become slower between something like a March 2018 go-fuzz release vs. Aug 2018, 50 | # but not clear. It might just instead have high variance. 51 | # Also, versions after 'fc0bf08 go-fuzz-build: improve pkg resolution' avoid a gcc error that started 52 | # some time prior to that. Spot checked go-fuzz commits 7f2a1780, fc0bf087, 193030f. 53 | # go-fuzz-build with sha256 14288638034fb712... might have been faster than all of those. 54 | # I was likely using 7f2a1780 previously. 55 | # Seemed to get good results with 7f2a1780 if: 56 | # used first byte for meta params, with 0x0 ==> fixed 2. 57 | # had the gofuzz edit: f.nilChance == 0.0 { return true } 58 | # TODO: skipping harder FuzzHardToGuessStringInSlice for now, which is often 10-15 sec, 59 | # but the two strings in a slice version was sometimes slow and timed out on 1m, and similar for one string in a slice. 60 | # fzgo test -fuzz=FuzzHardToGuessStringInSlice example.com/richsignatures -parallel=2 -v -fuzztime=1m 61 | # stdout 'building instrumented binary for pkgname.FuzzHardToGuessStringInSlice' 62 | # ! stdout 'randBytes verbose' 63 | # stderr 'workers: \d+, corpus: .* crashers: [^0]' 64 | # exists $WORK/gopath/pkg/fuzz/corpus/example.com/richsignatures/FuzzHardToGuessStringInSlice/corpus 65 | 66 | # TODO: skipping single string example, which sometimes times out at 30s on travis on Windows. 67 | # We simplified the test to one string for now. 68 | # fzgo test -fuzz=FuzzHardToGuessString example.com/richsignatures -parallel=2 -v -fuzztime=30s 69 | # stdout 'building instrumented binary for pkgname.FuzzHardToGuessString' 70 | # ! stdout 'randBytes verbose' 71 | # stderr 'workers: \d+, corpus: .* crashers: [^0]' 72 | # exists $WORK/gopath/pkg/fuzz/corpus/example.com/richsignatures/FuzzHardToGuessString/corpus 73 | 74 | # Verify with no fzgo cache that we build the instrumented binary from scratch. 75 | # This also creates our corpus directory in the default location. 76 | # We also specify -parallel=1 to reduce CPU usage for our remaining tests. 77 | fzgo test -fuzz=FuzzWithBasicTypes example.com/richsignatures -parallel=1 -fuzztime=5s 78 | stdout 'building instrumented binary for pkgname.FuzzWithBasicTypes' 79 | stderr 'workers: \d+, corpus: ' 80 | exists $WORK/gopath/pkg/fuzz/corpus/example.com/richsignatures/FuzzWithBasicTypes/corpus 81 | 82 | # Verify we now we use the fzgo cache. 83 | # We also specify -parallel=1 to reduce CPU usage for our remaining tests. 84 | fzgo test -fuzz=FuzzWithBasicTypes example.com/richsignatures -parallel=1 -fuzztime=5s 85 | stdout 'fzgo: using cached instrumented binary for pkgname.FuzzWithBasicTypes' 86 | stderr 'workers: \d+, corpus: ' 87 | 88 | # Flag -fuzzdir controls where the corpus goes (which could be in a different repo). 89 | # This invocation still uses the cache, as do all subsequent invocations in this script. 90 | fzgo test -fuzz=FuzzWithBasicTypes example.com/richsignatures -parallel=1 -fuzztime=5s -fuzzdir=$WORK/myfuzzdir 91 | stdout 'fzgo: using cached instrumented binary for pkgname.FuzzWithBasicTypes' 92 | stderr 'workers: \d+, corpus: ' 93 | exists $WORK/myfuzzdir/example.com/richsignatures/FuzzWithBasicTypes/corpus 94 | 95 | # Check rich signature from stdlib (uses regexp) 96 | fzgo test -fuzz=FuzzWithStdlibType example.com/richsignatures -parallel=1 -fuzztime=5s 97 | stdout 'building instrumented binary for pkgname.FuzzWithStdlibType' 98 | stderr 'workers: \d+, corpus: ' 99 | exists $WORK/gopath/pkg/fuzz/corpus/example.com/richsignatures/FuzzWithStdlibType/corpus 100 | 101 | # Check rich signature that uses supported interfaces (io.Reader, io.Writer) 102 | fzgo test -fuzz=FuzzInterfacesShortList example.com/richsignatures -parallel=1 -fuzztime=5s 103 | stdout 'building instrumented binary for pkgname.FuzzInterfacesShortList' 104 | stderr 'workers: \d+, corpus: ' 105 | exists $WORK/gopath/pkg/fuzz/corpus/example.com/richsignatures/FuzzInterfacesShortList/corpus 106 | 107 | # Check rich signature that uses full list of supported interfaces 108 | fzgo test -fuzz=FuzzInterfacesFullList example.com/richsignatures -parallel=1 -fuzztime=5s 109 | stdout 'building instrumented binary for pkgname.FuzzInterfacesFullList' 110 | stderr 'workers: \d+, corpus: ' 111 | exists $WORK/gopath/pkg/fuzz/corpus/example.com/richsignatures/FuzzInterfacesFullList/corpus 112 | 113 | # Verify we can use -run flag to select a specific file from the corpus for a rich signature. 114 | # We will guess that we have a zero length file. (Probably it will consistently be there, but we'll see). 115 | # This relies on go-fuzz SHA256 calc being stable. 116 | # We are not time limiting this, so this also relies on us interpreting -run to mean verify corpus 117 | # (otherwise, this will never return until the testscript package times out at 10 minutes or so). 118 | fzgo test -v -run=TestCorpus/da39a3ee5e6b4 -fuzz=FuzzHardToGuessNumber example.com/richsignatures 119 | stdout '=== RUN TestCorpus/da39a3ee5e6b4' 120 | stdout '--- PASS: TestCorpus/da39a3ee5e6b4' 121 | stdout '^ok .*fzgo-verify-corpus' 122 | 123 | # Check a signature that almost matches a plain 'func([]byte) int' signature. 124 | fzgo test -fuzz=FuzzAlmostPlain example.com/richsignatures -parallel=1 -fuzztime=5s 125 | stdout 'detected rich signature for pkgname.FuzzAlmostPlain' 126 | stdout 'building instrumented binary for pkgname.FuzzAlmostPlain' 127 | stderr 'workers: \d+, corpus: .*' 128 | 129 | # NOTE: currently this is cloned from examples dir in fzgo repo. (Probably good to have locally here?) 130 | 131 | -- gopath/src/example.com/richsignatures/richsignatures.go -- 132 | package pkgname 133 | 134 | import ( 135 | "regexp" 136 | 137 | "github.com/thepudds/fzgo/fuzz" 138 | ) 139 | 140 | // FuzzWithBasicTypes is a fuzzing function written by a user 141 | // that has a rich signature. All parameters are basic types, 142 | // but is uses stdlib types within (regexp). 143 | // We can fuzz it automatically, even though it doesn't match the standard []data 144 | // signature. This is just a test -- the fuzzing itself is not of interest. 145 | func FuzzWithBasicTypes(re string, input []byte, posix bool) (bool, error) { 146 | 147 | var r *regexp.Regexp 148 | var err error 149 | if posix { 150 | r, err = regexp.CompilePOSIX(re) 151 | } else { 152 | r, err = regexp.Compile(re) 153 | } 154 | if err != nil { 155 | return false, err 156 | } 157 | 158 | return r.Match(input), nil 159 | } 160 | 161 | // FuzzWithStdlibType is a test function using a combination of basic types 162 | // and also one from the stdlib (regexp). 163 | func FuzzWithStdlibType(something, another string, allow bool, re *regexp.Regexp) { 164 | regexp.MatchString(something, another) 165 | } 166 | 167 | // FuzzWithFzgoFunc uses a non-stdlib type 168 | func FuzzWithFzgoFunc(f fuzz.Func) string { 169 | return f.String() 170 | } 171 | 172 | // ExampleType is defined in the same package as the fuzz target that uses it (next func below). 173 | type ExampleType int 174 | 175 | // FuzzWithTargetType shows a type defined in the same file as the fuzz function. 176 | func FuzzWithTargetType(e ExampleType) { 177 | 178 | } 179 | 180 | // FuzzAlmostPlain excercises our rich sig detection logic slightly more as a "near miss" to an older-style sig. 181 | func FuzzAlmostPlain(data []byte) string { 182 | return "" 183 | } 184 | 185 | // FuzzHardToGuessNumber is a sanity check that go-fuzz literal injection seems to be working end-to-end. 186 | // This is typically found within a few seconds when properly hooked up. 187 | func FuzzHardToGuessNumber(guessMe uint64) { 188 | 189 | if guessMe == 0x123456789 { 190 | panic("bingo") 191 | } 192 | } 193 | 194 | // FuzzHardToGuessString is harder to get right, and relies on fzgo's approach 195 | // to deserializing working well with go-fuzz sonar's approach for variable length strings. 196 | // This is typically found within a few seconds when working properly. 197 | // Note: the string length should <= 20 to work with sonar. 198 | func FuzzHardToGuessString(s string) { 199 | if s == "ZZZ hard to guess" { 200 | panic("bingo") 201 | } 202 | } 203 | 204 | // FuzzHardToGuessStringInSlice is even harder to get right, and relies on fzgo's approach 205 | // to deserializing working well with go-fuzz sonar's approach for variable length strings. 206 | // This is typically found within a few seconds when working properly, but sometimes times out after 1m. 207 | // Note: the string length should <= 20 to work with sonar. 208 | func FuzzHardToGuessStringInSlice(list []string) { 209 | if len(list) > 0 && list[0] == "ZZZ hard to guess" { 210 | panic("bingo") 211 | } 212 | } 213 | 214 | -- gopath/src/example.com/richsignatures/interface.go -- 215 | package pkgname 216 | 217 | import ( 218 | "context" 219 | "io" 220 | ) 221 | 222 | // This checks each of the major approaches for interfaces in the fuzz.InterfaceImpl map 223 | // as implemented in fuzz.fillVars in richsig.go. 224 | func FuzzInterfacesShortList(ctx context.Context, w io.Writer, r io.Reader, sw io.StringWriter, rc io.ReadCloser) { 225 | ctx.Err() 226 | io.Copy(w, r) 227 | sw.WriteString("hello") 228 | rc.Close() 229 | } 230 | 231 | // This is the full list from fuzz.InterfaceImpl. 232 | func FuzzInterfacesFullList( 233 | x1 io.Writer, 234 | x2 io.Reader, 235 | x3 io.ReaderAt, 236 | x4 io.WriterTo, 237 | x5 io.Seeker, 238 | x6 io.ByteScanner, 239 | x7 io.RuneScanner, 240 | x8 io.ReadSeeker, 241 | x9 io.ByteReader, 242 | x10 io.RuneReader, 243 | x11 io.ByteWriter, 244 | x12 io.ReadWriter, 245 | x13 io.ReaderFrom, 246 | x14 io.StringWriter, 247 | x15 io.Closer, 248 | x16 io.ReadCloser, 249 | x17 context.Context) {} 250 | -------------------------------------------------------------------------------- /testscripts/help.txt: -------------------------------------------------------------------------------- 1 | # 'fzgo help' shows fuzzing help 2 | 3 | # Exit early if -short was specified. 4 | [short] skip 'skipping help tests because -short was specified' 5 | 6 | ! fzgo help 7 | stdout '^fzgo is a simple prototype' 8 | stdout '^[ ]*-fuzz regexp' 9 | stdout '^[ ]*fuzz at most one function matching regexp' 10 | ! stderr .+ 11 | 12 | # 'fzgo' also shows fuzzing help 13 | ! fzgo 14 | stdout '^fzgo is a simple prototype' 15 | 16 | # 'fzgo -h' and 'fzgo --help' show fuzzing help 17 | ! fzgo -h 18 | stdout '^fzgo is a simple prototype' 19 | ! fzgo --help 20 | stdout '^fzgo is a simple prototype' 21 | 22 | # '-h' and '--help' with 'fzgo test -fuzz' show fuzzing help 23 | ! fzgo test -fuzz . --help 24 | stdout '^fzgo is a simple prototype' 25 | ! fzgo test -fuzz . -h 26 | stdout '^fzgo is a simple prototype' 27 | 28 | -------------------------------------------------------------------------------- /testscripts/package_patterns.txt: -------------------------------------------------------------------------------- 1 | # Test package patterns like './...' vs. '.', including no package supplied. 2 | # Rather than waiting for fuzzing to start, this is set up to have different 3 | # package patterns trigger different failure modes to indicate that the patterns 4 | # are being interpreted as expected. 5 | # 6 | # Initially, fzgo disallowed multiple fuzz functions to match (per the March 2017 proposal), 7 | # but as an experiment fzgo now allows multiple fuzz functions to match in order to 8 | # support something like 'go test -fuzz=. ./...' when there are multiple fuzz functions 9 | # across multiple packages. However, these tests were built before that change, 10 | # so we use the -debug=nomultifuzz option below to preserve the original behavior. 11 | 12 | # Explicitly set GO111MODULE off for now. (testscripts seemingly by design do not pick up this value from actual env). 13 | env GO111MODULE=off 14 | 15 | # Fail when more than one fuzz func matches in two different packages. 16 | ! fzgo test -fuzz=FuzzTwo ./... -fuzztime=10s -debug=nomultifuzz 17 | stdout '^fzgo: multiple matches not allowed' 18 | ! fzgo test -fuzz=FuzzTwo sample/... -fuzztime=10s -debug=nomultifuzz 19 | stdout '^fzgo: multiple matches not allowed' 20 | 21 | # Fail with multiple matches no matter where the package pattern is in our args. 22 | ! fzgo test ./... -fuzz=FuzzTwo -fuzztime=10s -debug=nomultifuzz 23 | stdout '^fzgo: multiple matches not allowed' 24 | ! fzgo test -fuzz=FuzzTwo -fuzztime=10s ./... -debug=nomultifuzz 25 | stdout '^fzgo: multiple matches not allowed' 26 | 27 | # cd to a package directory that has two functions 'FuzzTime' and 'FuzzTwo'. 28 | cd $WORK/gopath/src/sample/pkg1 29 | 30 | # Fail with './...', '.', no pattern, or full path with '-fuzz=Fuzz' because two fuzz funcs match. 31 | ! fzgo test -fuzz=Fuzz -fuzztime=10s -debug=nomultifuzz 32 | stdout '^fzgo: multiple matches not allowed' 33 | ! fzgo test -fuzz=Fuzz ./... -fuzztime=10s -debug=nomultifuzz 34 | stdout '^fzgo: multiple matches not allowed' 35 | ! fzgo test -fuzz=Fuzz . -fuzztime=10s -debug=nomultifuzz 36 | stdout '^fzgo: multiple matches not allowed' 37 | ! fzgo test -fuzz=Fuzz sample/pkg1 -fuzztime=10s -debug=nomultifuzz 38 | stdout '^fzgo: multiple matches not allowed' 39 | 40 | # Functions that don't exist should also be reported as errors with patterns './...', '.', full path, or no pattern. 41 | ! fzgo test -fuzz=DoesNotExist -fuzztime=10s 42 | stdout 'fzgo: failed to find fuzz function for pattern' 43 | ! fzgo test -fuzz=DoesNotExist ./... -fuzztime=10s 44 | stdout 'fzgo: failed to find fuzz function for pattern' 45 | ! fzgo test -fuzz=DoesNotExist . -fuzztime=10s 46 | stdout 'fzgo: failed to find fuzz function for pattern' 47 | ! fzgo test -fuzz=DoesNotExist sample/pkg1 -fuzztime=10s 48 | stdout 'fzgo: failed to find fuzz function for pattern' 49 | 50 | # Two test files, with three fuzz funcs. The same test files are used in the fuzzing.txt test script. 51 | # One file uses '+build fuzz', the other '+build gofuzz'. 52 | 53 | -- gopath/src/sample/pkg1/fuzz.go -- 54 | // +build fuzz 55 | 56 | package pkg1 57 | 58 | import "time" 59 | 60 | // FuzzTime is a very simple fuzzing function 61 | func FuzzTime(data []byte) int { 62 | 63 | _, err := time.ParseDuration(string(data)) 64 | 65 | if err != nil { 66 | return 1 67 | } 68 | return 0 69 | } 70 | 71 | // FuzzTwo is a placeholder fuzzing function, and has same name as func in pkg2 72 | func FuzzTwo(data []byte) int { 73 | return 0 74 | } 75 | 76 | -- gopath/src/sample/pkg2/fuzz.go -- 77 | // +build gofuzz 78 | 79 | package pkg2 80 | 81 | // FuzzTwo is another placeholder fuzzing function, and has same name as func in pkg1 82 | func FuzzTwo(data []byte) int { 83 | return 0 84 | } 85 | -------------------------------------------------------------------------------- /testscripts/verify_corpus.txt: -------------------------------------------------------------------------------- 1 | # Test using the corpus as unit tests (deterministically, without generating new fuzz-based inputs). 2 | # Note that we don't install go-fuzz. 3 | 4 | # TODO: test multiple packages at once. (Probably should push a couple more example fuzz functions). 5 | 6 | # Side note: can run this by itself with: 7 | # go test -run=TestScript/verify_corpus 8 | # (permuatations like -run=TestScript/corpus or -run=TestScript/.*corpus.* should also work) 9 | 10 | # Explicitly set GO111MODULE off for now. (testscripts seemingly by design do not pick up this value from actual env). 11 | env GO111MODULE=off 12 | 13 | # Download the FuzzTime example from the fzgo repo 14 | go get -v -u github.com/thepudds/fzgo/examples/... 15 | 16 | # Verify the corpus for FuzzTime. This automatically also passes through to 17 | # the normal 'go test', which causes it to report 'no test files' 18 | fzgo test github.com/thepudds/fzgo/examples/time 19 | exists $WORK/gopath/src/github.com/thepudds/fzgo/examples/time/testdata/fuzz/FuzzTime 20 | stdout '^ok .*fzgo-verify-corpus' 21 | stdout 'github.com/thepudds/fzgo/examples/time.*\[no test files\]' 22 | 23 | # Verify we can use -run flag to select a specific file from the corpus. 24 | # The time corpus has a file 'valid-input'. 25 | fzgo test -v -run=TestCorpus/valid-input github.com/thepudds/fzgo/examples/time 26 | stdout '=== RUN TestCorpus/valid-input' 27 | stdout '--- PASS: TestCorpus/valid-input' 28 | stdout '^ok .*fzgo-verify-corpus' 29 | stdout 'github.com/thepudds/fzgo/examples/time.*\[no test files\]' 30 | 31 | # Verify a package without a corpus is handled gracefully. 32 | fzgo test github.com/thepudds/fzgo/examples/empty 33 | ! stdout 'fzgo-verify-corpus' 34 | stdout 'github.com/thepudds/fzgo/examples/empty.*\[no test files\]' 35 | --------------------------------------------------------------------------------