golang.org/x/tools@v0.21.1-0.20240520172518-788d39e776b1/cmd/signature-fuzzer/README.md (about) 1 # signature-fuzzer 2 3 This directory contains utilities for fuzz testing of Go function signatures, for use in developing/testing a Go compiler. 4 5 The basic idea of the fuzzer is that it emits source code for a stand-alone Go program; this generated program is a series of pairs of functions, a "Caller" function and a "Checker" function. The signature of the Checker function is generated randomly (random number of parameters and returns, each with randomly chosen types). The "Caller" func contains invocations of the "Checker" function, each passing randomly chosen values to the params of the "Checker", then the caller verifies that expected values are returned correctly. The "Checker" function in turn has code to verify that the expected values (more details below). 6 7 There are three main parts to the fuzzer: a generator package, a driver package, and a runner package. 8 9 The "generator" contains the guts of the fuzzer, the bits that actually emit the random code. 10 11 The "driver" is a stand-alone program that invokes the generator to create a single test program. It is not terribly useful on its own (since it doesn't actually build or run the generated program), but it is handy for debugging the generator or looking at examples of the emitted code. 12 13 The "runner" is a more complete test harness; it repeatedly runs the generator to create a new test program, builds the test program, then runs it (checking for errors along the way). If at any point a build or test fails, the "runner" harness attempts a minimization process to try to narrow down the failure to a single package and/or function. 14 15 ## What the generated code looks like 16 17 Generated Go functions will have an "interesting" set of signatures (mix of 18 arrays, scalars, structs), intended to pick out corner cases and odd bits in the 19 Go compiler's code that handles function calls and returns. 20 21 The first generated file is genChecker.go, which contains function that look something 22 like this (simplified): 23 24 ``` 25 type StructF4S0 struct { 26 F0 float64 27 F1 int16 28 F2 uint16 29 } 30 31 // 0 returns 2 params 32 func Test4(p0 int8, p1 StructF4S0) { 33 c0 := int8(-1) 34 if p0 != c0 { 35 NoteFailure(4, "parm", 0) 36 } 37 c1 := StructF4S0{float64(2), int16(-3), uint16(4)} 38 if p1 != c1 { 39 NoteFailure(4, "parm", 1) 40 } 41 return 42 } 43 ``` 44 45 Here the test generator has randomly selected 0 return values and 2 params, then randomly generated types for the params. 46 47 The generator then emits code on the calling side into the file "genCaller.go", which might look like: 48 49 ``` 50 func Caller4() { 51 var p0 int8 52 p0 = int8(-1) 53 var p1 StructF4S0 54 p1 = StructF4S0{float64(2), int16(-3), uint16(4)} 55 // 0 returns 2 params 56 Test4(p0, p1) 57 } 58 ``` 59 60 The generator then emits some utility functions (ex: NoteFailure) and a main routine that cycles through all of the tests. 61 62 ## Trying a single run of the generator 63 64 To generate a set of source files just to see what they look like, you can build and run the test generator as follows. This creates a new directory "cabiTest" containing generated test files: 65 66 ``` 67 $ git clone https://golang.org/x/tools 68 $ cd tools/cmd/signature-fuzzer/fuzz-driver 69 $ go build . 70 $ ./fuzz-driver -numpkgs 3 -numfcns 5 -seed 12345 -outdir /tmp/sigfuzzTest -pkgpath foobar 71 $ cd /tmp/sigfuzzTest 72 $ find . -type f -print 73 ./genCaller1/genCaller1.go 74 ./genUtils/genUtils.go 75 ./genChecker1/genChecker1.go 76 ./genChecker0/genChecker0.go 77 ./genCaller2/genCaller2.go 78 ./genCaller0/genCaller0.go 79 ./genMain.go 80 ./go.mod 81 ./genChecker2/genChecker2.go 82 $ 83 ``` 84 85 You can build and run the generated files in the usual way: 86 87 ``` 88 $ cd /tmp/sigfuzzTest 89 $ go build . 90 $ ./foobar 91 starting main 92 finished 15 tests 93 $ 94 95 ``` 96 97 ## Example usage for the test runner 98 99 The test runner orchestrates multiple runs of the fuzzer, iteratively emitting code, building it, and testing the resulting binary. To use the runner, build and invoke it with a specific number of iterations; it will select a new random seed on each invocation. The runner will terminate as soon as it finds a failure. Example: 100 101 ``` 102 $ git clone https://golang.org/x/tools 103 $ cd tools/cmd/signature-fuzzer/fuzz-runner 104 $ go build . 105 $ ./fuzz-runner -numit=3 106 ... begin iteration 0 with current seed 67104558 107 starting main 108 finished 1000 tests 109 ... begin iteration 1 with current seed 67104659 110 starting main 111 finished 1000 tests 112 ... begin iteration 2 with current seed 67104760 113 starting main 114 finished 1000 tests 115 $ 116 ``` 117 118 If the runner encounters a failure, it will try to perform test-case "minimization", e.g. attempt to isolate the failure 119 120 ``` 121 $ cd tools/cmd/signature-fuzzer/fuzz-runner 122 $ go build . 123 $ ./fuzz-runner -n=10 124 ./fuzz-runner -n=10 125 ... begin iteration 0 with current seed 40661762 126 Error: fail [reflect] |20|3|1| =Checker3.Test1= return 1 127 error executing cmd ./fzTest: exit status 1 128 ... starting minimization for failed directory /tmp/fuzzrun1005327337/fuzzTest 129 package minimization succeeded: found bad pkg 3 130 function minimization succeeded: found bad fcn 1 131 $ 132 ``` 133 134 Here the runner has generates a failure, minimized it down to a single function and package, and left the resulting program in the output directory /tmp/fuzzrun1005327337/fuzzTest. 135 136 ## Limitations, future work 137 138 No support yet for variadic functions. 139 140 The set of generated types is still a bit thin; it has fairly limited support for interface values, and doesn't include channels. 141 142 Todos: 143 144 - better interface value coverage 145 146 - implement testing of reflect.MakeFunc 147 148 - extend to work with generic code of various types 149 150 - extend to work in a debugging scenario (e.g. instead of just emitting code, 151 emit a script of debugger commands to run the program with expected 152 responses from the debugger) 153 154 - rework things so that instead of always checking all of a given parameter 155 value, we sometimes skip over elements (or just check the length of a slice 156 or string as opposed to looking at its value) 157 158 - consider adding runtime.GC() calls at some points in the generated code 159