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