GoPLS Viewer

Home|gopls/cmd/signature-fuzzer/README.md
1# signature-fuzzer
2
3This directory contains utilities for fuzz testing of Go function signatures, for use in developing/testing a Go compiler.
4
5The 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
7There are three main parts to the fuzzer: a generator package, a driver package, and a runner package.
8
9The "generator" contains the guts of the fuzzer, the bits that actually emit the random code.
10
11The "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
13The "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
17Generated Go functions will have an "interesting" set of signatures (mix of
18arrays, scalars, structs), intended to pick out corner cases and odd bits in the
19Go compiler's code that handles function calls and returns.
20
21The first generated file is genChecker.go, which contains function that look something
22like this (simplified):
23
24```
25type StructF4S0 struct {
26F0 float64
27F1 int16
28F2 uint16
29}
30
31// 0 returns 2 params
32func 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
45Here the test generator has randomly selected 0 return values and 2 params, then randomly generated types for the params.
46
47The generator then emits code on the calling side into the file "genCaller.go", which might look like:
48
49```
50func Caller4() {
51var p0 int8
52p0 = int8(-1)
53var p1 StructF4S0
54p1 = StructF4S0{float64(2), int16(-3), uint16(4)}
55// 0 returns 2 params
56Test4(p0, p1)
57}
58```
59
60The 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
64To 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
85You can build and run the generated files in the usual way:
86
87```
88$ cd /tmp/sigfuzzTest
89$ go build .
90$ ./foobar
91starting main
92finished 15 tests
93$
94
95```
96
97## Example usage for the test runner
98
99The 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
107starting main
108finished 1000 tests
109... begin iteration 1 with current seed 67104659
110starting main
111finished 1000 tests
112... begin iteration 2 with current seed 67104760
113starting main
114finished 1000 tests
115$
116```
117
118If 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
126Error: fail [reflect] |20|3|1| =Checker3.Test1= return 1
127error executing cmd ./fzTest: exit status 1
128... starting minimization for failed directory /tmp/fuzzrun1005327337/fuzzTest
129package minimization succeeded: found bad pkg 3
130function minimization succeeded: found bad fcn 1
131$
132```
133
134Here 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
138No support yet for variadic functions.
139
140The set of generated types is still a bit thin; it has fairly limited support for interface values, and doesn't include channels.
141
142Todos:
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
160
MembersX
Members
X