From fb3622a92854f70eb5735efb558dc85398101994 Mon Sep 17 00:00:00 2001 From: Than McIntosh Date: Thu, 16 Sep 2021 12:46:51 -0400 Subject: [PATCH] signature-generator: add Go func signature fuzzing tools Add a new subdirectory 'signature-fuzzer' to the x/tools/cmd directory, with contents: internal/fuzz-generator Guts of the signature fuzzer fuzz-driver Simple driver for the fuzzer fuzz-runner Test harness for doing iterative fuzzing This set of tools is primarily of interest to compiler developers as opposed to end users. The fuzzer generates random Go code with "interesting" randomly chosen function signatures (random numbers of parameters of randomly generated types), then emits code to call these random functions with known inputs, as well as code that checks to make sure parameter values were passed correctly and that returns worked. The generator tries to include odd scenarios and corner cases (e.g. reflect calls, recursion, blank params, zero sized types, etc) so as to explore all the nooks and crannies of the compiler. This fuzzer was used during the bringup of the new register ABI (as part of Go 1.17) to identify odd corner cases and flush out bugs. It may be useful in the future if we have to support new ABI versions or bring up register ABIs on new architectures. See the README.md for more details. Change-Id: Icbc89ddd07c3909a44a005deab43720c7a0dffde Reviewed-on: https://go-review.googlesource.com/c/tools/+/350329 Reviewed-by: Jeremy Faller Trust: Than McIntosh Run-TryBot: Than McIntosh gopls-CI: kokoro TryBot-Result: Gopher Robot --- cmd/signature-fuzzer/README.md | 159 ++ cmd/signature-fuzzer/fuzz-driver/driver.go | 168 ++ cmd/signature-fuzzer/fuzz-driver/drv_test.go | 73 + cmd/signature-fuzzer/fuzz-runner/rnr_test.go | 145 ++ cmd/signature-fuzzer/fuzz-runner/runner.go | 443 ++++ .../fuzz-runner/testdata/himom.go | 9 + .../internal/fuzz-generator/arrayparm.go | 108 + .../internal/fuzz-generator/gen_test.go | 322 +++ .../internal/fuzz-generator/generator.go | 2269 +++++++++++++++++ .../internal/fuzz-generator/mapparm.go | 91 + .../internal/fuzz-generator/numparm.go | 144 ++ .../internal/fuzz-generator/parm.go | 216 ++ .../internal/fuzz-generator/pointerparm.go | 75 + .../internal/fuzz-generator/stringparm.go | 64 + .../internal/fuzz-generator/structparm.go | 163 ++ .../internal/fuzz-generator/typedefparm.go | 90 + .../internal/fuzz-generator/wraprand.go | 136 + 17 files changed, 4675 insertions(+) create mode 100644 cmd/signature-fuzzer/README.md create mode 100644 cmd/signature-fuzzer/fuzz-driver/driver.go create mode 100644 cmd/signature-fuzzer/fuzz-driver/drv_test.go create mode 100644 cmd/signature-fuzzer/fuzz-runner/rnr_test.go create mode 100644 cmd/signature-fuzzer/fuzz-runner/runner.go create mode 100644 cmd/signature-fuzzer/fuzz-runner/testdata/himom.go create mode 100644 cmd/signature-fuzzer/internal/fuzz-generator/arrayparm.go create mode 100644 cmd/signature-fuzzer/internal/fuzz-generator/gen_test.go create mode 100644 cmd/signature-fuzzer/internal/fuzz-generator/generator.go create mode 100644 cmd/signature-fuzzer/internal/fuzz-generator/mapparm.go create mode 100644 cmd/signature-fuzzer/internal/fuzz-generator/numparm.go create mode 100644 cmd/signature-fuzzer/internal/fuzz-generator/parm.go create mode 100644 cmd/signature-fuzzer/internal/fuzz-generator/pointerparm.go create mode 100644 cmd/signature-fuzzer/internal/fuzz-generator/stringparm.go create mode 100644 cmd/signature-fuzzer/internal/fuzz-generator/structparm.go create mode 100644 cmd/signature-fuzzer/internal/fuzz-generator/typedefparm.go create mode 100644 cmd/signature-fuzzer/internal/fuzz-generator/wraprand.go diff --git a/cmd/signature-fuzzer/README.md b/cmd/signature-fuzzer/README.md new file mode 100644 index 0000000000..a7de540b94 --- /dev/null +++ b/cmd/signature-fuzzer/README.md @@ -0,0 +1,159 @@ +# signature-fuzzer + +This directory contains utilities for fuzz testing of Go function signatures, for use in developing/testing a Go compiler. + +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). + +There are three main parts to the fuzzer: a generator package, a driver package, and a runner package. + +The "generator" contains the guts of the fuzzer, the bits that actually emit the random code. + +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. + +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. + +## What the generated code looks like + +Generated Go functions will have an "interesting" set of signatures (mix of +arrays, scalars, structs), intended to pick out corner cases and odd bits in the +Go compiler's code that handles function calls and returns. + +The first generated file is genChecker.go, which contains function that look something +like this (simplified): + +``` +type StructF4S0 struct { +F0 float64 +F1 int16 +F2 uint16 +} + +// 0 returns 2 params +func Test4(p0 int8, p1 StructF4S0) { + c0 := int8(-1) + if p0 != c0 { + NoteFailure(4, "parm", 0) + } + c1 := StructF4S0{float64(2), int16(-3), uint16(4)} + if p1 != c1 { + NoteFailure(4, "parm", 1) + } + return +} +``` + +Here the test generator has randomly selected 0 return values and 2 params, then randomly generated types for the params. + +The generator then emits code on the calling side into the file "genCaller.go", which might look like: + +``` +func Caller4() { +var p0 int8 +p0 = int8(-1) +var p1 StructF4S0 +p1 = StructF4S0{float64(2), int16(-3), uint16(4)} +// 0 returns 2 params +Test4(p0, p1) +} +``` + +The generator then emits some utility functions (ex: NoteFailure) and a main routine that cycles through all of the tests. + +## Trying a single run of the generator + +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: + +``` +$ git clone https://golang.org/x/tools +$ cd tools/cmd/signature-fuzzer/fuzz-driver +$ go build . +$ ./fuzz-driver -numpkgs 3 -numfcns 5 -seed 12345 -outdir /tmp/sigfuzzTest -pkgpath foobar +$ cd /tmp/sigfuzzTest +$ find . -type f -print +./genCaller1/genCaller1.go +./genUtils/genUtils.go +./genChecker1/genChecker1.go +./genChecker0/genChecker0.go +./genCaller2/genCaller2.go +./genCaller0/genCaller0.go +./genMain.go +./go.mod +./genChecker2/genChecker2.go +$ +``` + +You can build and run the generated files in the usual way: + +``` +$ cd /tmp/sigfuzzTest +$ go build . +$ ./foobar +starting main +finished 15 tests +$ + +``` + +## Example usage for the test runner + +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: + +``` +$ git clone https://golang.org/x/tools +$ cd tools/cmd/signature-fuzzer/fuzz-runner +$ go build . +$ ./fuzz-runner -numit=3 +... begin iteration 0 with current seed 67104558 +starting main +finished 1000 tests +... begin iteration 1 with current seed 67104659 +starting main +finished 1000 tests +... begin iteration 2 with current seed 67104760 +starting main +finished 1000 tests +$ +``` + +If the runner encounters a failure, it will try to perform test-case "minimization", e.g. attempt to isolate the failure + +``` +$ cd tools/cmd/signature-fuzzer/fuzz-runner +$ go build . +$ ./fuzz-runner -n=10 +./fuzz-runner -n=10 +... begin iteration 0 with current seed 40661762 +Error: fail [reflect] |20|3|1| =Checker3.Test1= return 1 +error executing cmd ./fzTest: exit status 1 +... starting minimization for failed directory /tmp/fuzzrun1005327337/fuzzTest +package minimization succeeded: found bad pkg 3 +function minimization succeeded: found bad fcn 1 +$ +``` + +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. + +## Limitations, future work + +No support yet for variadic functions. + +The set of generated types is still a bit thin; it has fairly limited support for interface values, and doesn't include channels. + +Todos: + +- better interface value coverage + +- implement testing of reflect.MakeFunc + +- extend to work with generic code of various types + +- extend to work in a debugging scenario (e.g. instead of just emitting code, + emit a script of debugger commands to run the program with expected + responses from the debugger) + +- rework things so that instead of always checking all of a given parameter + value, we sometimes skip over elements (or just check the length of a slice + or string as opposed to looking at its value) + +- consider adding runtime.GC() calls at some points in the generated code + diff --git a/cmd/signature-fuzzer/fuzz-driver/driver.go b/cmd/signature-fuzzer/fuzz-driver/driver.go new file mode 100644 index 0000000000..f61ca4b4b5 --- /dev/null +++ b/cmd/signature-fuzzer/fuzz-driver/driver.go @@ -0,0 +1,168 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Stand-alone driver for emitting function-signature test code. This +// program is mainly just a wrapper around the code that lives in the +// fuzz-generator package; it is useful for generating a specific bad +// code scenario for a given seed, or for doing development on the +// fuzzer, but for doing actual fuzz testing, better to use +// fuzz-runner. + +package main + +import ( + "flag" + "fmt" + "log" + "math/rand" + "os" + "time" + + generator "golang.org/x/tools/cmd/signature-fuzzer/internal/fuzz-generator" +) + +// Basic options +var numfcnflag = flag.Int("numfcns", 10, "Number of test func pairs to emit in each package") +var numpkgflag = flag.Int("numpkgs", 1, "Number of test packages to emit") +var seedflag = flag.Int64("seed", -1, "Random seed") +var tagflag = flag.String("tag", "gen", "Prefix name of go files/pkgs to generate") +var outdirflag = flag.String("outdir", "", "Output directory for generated files") +var pkgpathflag = flag.String("pkgpath", "gen", "Base package path for generated files") + +// Options used for test case minimization. +var fcnmaskflag = flag.String("fcnmask", "", "Mask containing list of fcn numbers to emit") +var pkmaskflag = flag.String("pkgmask", "", "Mask containing list of pkg numbers to emit") + +// Options used to control which features are used in the generated code. +var reflectflag = flag.Bool("reflect", true, "Include testing of reflect.Call.") +var deferflag = flag.Bool("defer", true, "Include testing of defer stmts.") +var recurflag = flag.Bool("recur", true, "Include testing of recursive calls.") +var takeaddrflag = flag.Bool("takeaddr", true, "Include functions that take the address of their parameters and results.") +var methodflag = flag.Bool("method", true, "Include testing of method calls.") +var inlimitflag = flag.Int("inmax", -1, "Max number of input params.") +var outlimitflag = flag.Int("outmax", -1, "Max number of input params.") +var pragmaflag = flag.String("pragma", "", "Tag generated test routines with pragma //go:.") +var maxfailflag = flag.Int("maxfail", 10, "Maximum runtime failures before test self-terminates") +var stackforceflag = flag.Bool("forcestackgrowth", true, "Use hooks to force stack growth.") + +// Debugging options +var verbflag = flag.Int("v", 0, "Verbose trace output level") + +// Debugging/testing options. These tell the generator to emit "bad" code so as to +// test the logic for detecting errors and/or minimization (in the fuzz runner). +var emitbadflag = flag.Int("emitbad", 0, "[Testing only] force generator to emit 'bad' code.") +var selbadpkgflag = flag.Int("badpkgidx", 0, "[Testing only] select index of bad package (used with -emitbad)") +var selbadfcnflag = flag.Int("badfcnidx", 0, "[Testing only] select index of bad function (used with -emitbad)") + +// Misc options +var goimpflag = flag.Bool("goimports", false, "Run 'goimports' on generated code.") +var randctlflag = flag.Int("randctl", generator.RandCtlChecks|generator.RandCtlPanic, "Wraprand control flag") + +func verb(vlevel int, s string, a ...interface{}) { + if *verbflag >= vlevel { + fmt.Printf(s, a...) + fmt.Printf("\n") + } +} + +func usage(msg string) { + if len(msg) > 0 { + fmt.Fprintf(os.Stderr, "error: %s\n", msg) + } + fmt.Fprintf(os.Stderr, "usage: fuzz-driver [flags]\n\n") + flag.PrintDefaults() + fmt.Fprintf(os.Stderr, "Example:\n\n") + fmt.Fprintf(os.Stderr, " fuzz-driver -numpkgs=23 -numfcns=19 -seed 10101 -outdir gendir\n\n") + fmt.Fprintf(os.Stderr, " \tgenerates a Go program with 437 test cases (23 packages, each \n") + fmt.Fprintf(os.Stderr, " \twith 19 functions, for a total of 437 funcs total) into a set of\n") + fmt.Fprintf(os.Stderr, " \tsub-directories in 'gendir', using random see 10101\n") + + os.Exit(2) +} + +func setupTunables() { + tunables := generator.DefaultTunables() + if !*reflectflag { + tunables.DisableReflectionCalls() + } + if !*deferflag { + tunables.DisableDefer() + } + if !*recurflag { + tunables.DisableRecursiveCalls() + } + if !*takeaddrflag { + tunables.DisableTakeAddr() + } + if !*methodflag { + tunables.DisableMethodCalls() + } + if *inlimitflag != -1 { + tunables.LimitInputs(*inlimitflag) + } + if *outlimitflag != -1 { + tunables.LimitOutputs(*outlimitflag) + } + generator.SetTunables(tunables) +} + +func main() { + log.SetFlags(0) + log.SetPrefix("fuzz-driver: ") + flag.Parse() + generator.Verbctl = *verbflag + if *outdirflag == "" { + usage("select an output directory with -o flag") + } + verb(1, "in main verblevel=%d", *verbflag) + if *seedflag == -1 { + // user has not selected a specific seed -- pick one. + now := time.Now() + *seedflag = now.UnixNano() % 123456789 + verb(0, "selected seed: %d", *seedflag) + } + rand.Seed(*seedflag) + if flag.NArg() != 0 { + usage("unknown extra arguments") + } + verb(1, "tag is %s", *tagflag) + + fcnmask, err := generator.ParseMaskString(*fcnmaskflag, "fcn") + if err != nil { + usage(fmt.Sprintf("mangled fcn mask arg: %v", err)) + } + pkmask, err := generator.ParseMaskString(*pkmaskflag, "pkg") + if err != nil { + usage(fmt.Sprintf("mangled pkg mask arg: %v", err)) + } + verb(2, "pkg mask is %v", pkmask) + verb(2, "fn mask is %v", fcnmask) + + verb(1, "starting generation") + setupTunables() + config := generator.GenConfig{ + PkgPath: *pkgpathflag, + Tag: *tagflag, + OutDir: *outdirflag, + NumTestPackages: *numpkgflag, + NumTestFunctions: *numfcnflag, + Seed: *seedflag, + Pragma: *pragmaflag, + FcnMask: fcnmask, + PkgMask: pkmask, + MaxFail: *maxfailflag, + ForceStackGrowth: *stackforceflag, + RandCtl: *randctlflag, + RunGoImports: *goimpflag, + EmitBad: *emitbadflag, + BadPackageIdx: *selbadpkgflag, + BadFuncIdx: *selbadfcnflag, + } + errs := generator.Generate(config) + if errs != 0 { + log.Fatal("errors during generation") + } + verb(1, "... files written to directory %s", *outdirflag) + verb(1, "leaving main") +} diff --git a/cmd/signature-fuzzer/fuzz-driver/drv_test.go b/cmd/signature-fuzzer/fuzz-driver/drv_test.go new file mode 100644 index 0000000000..7de74c6478 --- /dev/null +++ b/cmd/signature-fuzzer/fuzz-driver/drv_test.go @@ -0,0 +1,73 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "os" + "os/exec" + "path/filepath" + "runtime" + "testing" + + "golang.org/x/tools/internal/testenv" +) + +// buildDriver builds the fuzz-driver executable, returning its path. +func buildDriver(t *testing.T) string { + t.Helper() + if runtime.GOOS == "android" { + t.Skipf("the dependencies are not available on android") + return "" + } + bindir := filepath.Join(t.TempDir(), "bin") + err := os.Mkdir(bindir, os.ModePerm) + if err != nil { + t.Fatal(err) + } + binary := filepath.Join(bindir, "driver") + if runtime.GOOS == "windows" { + binary += ".exe" + } + cmd := exec.Command("go", "build", "-o", binary) + if err := cmd.Run(); err != nil { + t.Fatalf("Building fuzz-driver: %v", err) + } + return binary +} + +func TestEndToEndIntegration(t *testing.T) { + testenv.NeedsTool(t, "go") + td := t.TempDir() + + // Build the fuzz-driver binary. + // Note: if more tests are added to this package, move this to single setup fcn, so + // that we don't have to redo the build each time. + binary := buildDriver(t) + + // Kick off a run. + gendir := filepath.Join(td, "gen") + args := []string{"-numfcns", "3", "-numpkgs", "1", "-seed", "101", "-outdir", gendir} + c := exec.Command(binary, args...) + b, err := c.CombinedOutput() + if err != nil { + t.Fatalf("error invoking fuzz-driver: %v\n%s", err, b) + } + + found := "" + walker := func(path string, info os.FileInfo, err error) error { + found = found + ":" + info.Name() + return nil + } + + // Make sure it emits something. + err2 := filepath.Walk(gendir, walker) + if err2 != nil { + t.Fatalf("error from filepath.Walk: %v", err2) + } + const expected = ":gen:genCaller0:genCaller0.go:genChecker0:genChecker0.go:genMain.go:genUtils:genUtils.go:go.mod" + if found != expected { + t.Errorf("walk of generated code: got %s want %s", found, expected) + } +} diff --git a/cmd/signature-fuzzer/fuzz-runner/rnr_test.go b/cmd/signature-fuzzer/fuzz-runner/rnr_test.go new file mode 100644 index 0000000000..2bab5b41ad --- /dev/null +++ b/cmd/signature-fuzzer/fuzz-runner/rnr_test.go @@ -0,0 +1,145 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" + "testing" + + "golang.org/x/tools/internal/testenv" +) + +func canRace(t *testing.T) bool { + _, err := exec.Command("go", "run", "-race", "./testdata/himom.go").CombinedOutput() + return err == nil +} + +// buildRunner builds the fuzz-runner executable, returning its path. +func buildRunner(t *testing.T) string { + bindir := filepath.Join(t.TempDir(), "bin") + err := os.Mkdir(bindir, os.ModePerm) + if err != nil { + t.Fatal(err) + } + binary := filepath.Join(bindir, "runner") + if runtime.GOOS == "windows" { + binary += ".exe" + } + cmd := exec.Command("go", "build", "-o", binary) + if err := cmd.Run(); err != nil { + t.Fatalf("Building fuzz-runner: %v", err) + } + return binary +} + +// TestRunner builds the binary, then kicks off a collection of sub-tests that invoke it. +func TestRunner(t *testing.T) { + testenv.NeedsTool(t, "go") + if runtime.GOOS == "android" { + t.Skipf("the dependencies are not available on android") + } + binaryPath := buildRunner(t) + + // Sub-tests using the binary built above. + t.Run("Basic", func(t *testing.T) { testBasic(t, binaryPath) }) + t.Run("Race", func(t *testing.T) { testRace(t, binaryPath) }) + t.Run("Minimization1", func(t *testing.T) { testMinimization1(t, binaryPath) }) + t.Run("Minimization2", func(t *testing.T) { testMinimization2(t, binaryPath) }) +} + +func testBasic(t *testing.T, binaryPath string) { + t.Parallel() + args := []string{"-numit=1", "-numfcns=1", "-numpkgs=1", "-seed=103", "-cleancache=0"} + c := exec.Command(binaryPath, args...) + b, err := c.CombinedOutput() + t.Logf("%s\n", b) + if err != nil { + t.Fatalf("error invoking fuzz-runner: %v", err) + } +} + +func testRace(t *testing.T, binaryPath string) { + t.Parallel() + // For this test to work, the current test platform has to support the + // race detector. Check to see if that is the case by running a very + // simple Go program through it. + if !canRace(t) { + t.Skip("current platform does not appear to support the race detector") + } + + args := []string{"-v=1", "-numit=1", "-race", "-numfcns=3", "-numpkgs=3", "-seed=987", "-cleancache=0"} + c := exec.Command(binaryPath, args...) + b, err := c.CombinedOutput() + t.Logf("%s\n", b) + if err != nil { + t.Fatalf("error invoking fuzz-runner: %v", err) + } +} + +func testMinimization1(t *testing.T, binaryPath string) { + if binaryPath == "" { + t.Skipf("No runner binary") + } + t.Parallel() + // Fire off the runner passing it -emitbad=1, so that the generated code + // contains illegal Go code (which will force the build to fail). Verify that + // it does fail, that the error reflects the nature of the failure, and that + // we can minimize the error down to a single package. + args := []string{"-emitbad=1", "-badfcnidx=2", "-badpkgidx=2", + "-forcetmpclean", "-cleancache=0", + "-numit=1", "-numfcns=3", "-numpkgs=3", "-seed=909"} + invocation := fmt.Sprintf("%s %v", binaryPath, args) + c := exec.Command(binaryPath, args...) + b, err := c.CombinedOutput() + t.Logf("%s\n", b) + if err == nil { + t.Fatalf("unexpected pass of fuzz-runner (invocation %q): %v", invocation, err) + } + result := string(b) + if !strings.Contains(result, "syntax error") { + t.Fatalf("-emitbad=1 did not trigger syntax error (invocation %q): output: %s", invocation, result) + } + if !strings.Contains(result, "package minimization succeeded: found bad pkg 2") { + t.Fatalf("failed to minimize package (invocation %q): output: %s", invocation, result) + } + if !strings.Contains(result, "function minimization succeeded: found bad fcn 2") { + t.Fatalf("failed to minimize package (invocation %q): output: %s", invocation, result) + } +} + +func testMinimization2(t *testing.T, binaryPath string) { + if binaryPath == "" { + t.Skipf("No runner binary") + } + t.Parallel() + // Fire off the runner passing it -emitbad=2, so that the + // generated code forces a runtime error. Verify that it does + // fail, and that the error is reflective. + args := []string{"-emitbad=2", "-badfcnidx=1", "-badpkgidx=1", + "-forcetmpclean", "-cleancache=0", + "-numit=1", "-numfcns=3", "-numpkgs=3", "-seed=55909"} + invocation := fmt.Sprintf("%s %v", binaryPath, args) + c := exec.Command(binaryPath, args...) + b, err := c.CombinedOutput() + t.Logf("%s\n", b) + if err == nil { + t.Fatalf("unexpected pass of fuzz-runner (invocation %q): %v", invocation, err) + } + result := string(b) + if !strings.Contains(result, "Error: fail") || !strings.Contains(result, "Checker1.Test1") { + t.Fatalf("-emitbad=2 did not trigger runtime error (invocation %q): output: %s", invocation, result) + } + if !strings.Contains(result, "package minimization succeeded: found bad pkg 1") { + t.Fatalf("failed to minimize package (invocation %q): output: %s", invocation, result) + } + if !strings.Contains(result, "function minimization succeeded: found bad fcn 1") { + t.Fatalf("failed to minimize package (invocation %q): output: %s", invocation, result) + } +} diff --git a/cmd/signature-fuzzer/fuzz-runner/runner.go b/cmd/signature-fuzzer/fuzz-runner/runner.go new file mode 100644 index 0000000000..4e5b413f3f --- /dev/null +++ b/cmd/signature-fuzzer/fuzz-runner/runner.go @@ -0,0 +1,443 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Program for performing test runs using "fuzz-driver". +// Main loop iteratively runs "fuzz-driver" to create a corpus, +// then builds and runs the code. If a failure in the run is +// detected, then a testcase minimization phase kicks in. + +package main + +import ( + "flag" + "fmt" + "io/ioutil" + "log" + "os" + "os/exec" + "path/filepath" + "runtime" + "strconv" + "strings" + "time" + + generator "golang.org/x/tools/cmd/signature-fuzzer/internal/fuzz-generator" +) + +const pkName = "fzTest" + +// Basic options +var verbflag = flag.Int("v", 0, "Verbose trace output level") +var loopitflag = flag.Int("numit", 10, "Number of main loop iterations to run") +var seedflag = flag.Int64("seed", -1, "Random seed") +var execflag = flag.Bool("execdriver", false, "Exec fuzz-driver binary instead of invoking generator directly") +var numpkgsflag = flag.Int("numpkgs", 50, "Number of test packages") +var numfcnsflag = flag.Int("numfcns", 20, "Number of test functions per package.") + +// Debugging/testing options. These tell the generator to emit "bad" code so as to +// test the logic for detecting errors and/or minimization. +var emitbadflag = flag.Int("emitbad", -1, "[Testing only] force generator to emit 'bad' code.") +var selbadpkgflag = flag.Int("badpkgidx", 0, "[Testing only] select index of bad package (used with -emitbad)") +var selbadfcnflag = flag.Int("badfcnidx", 0, "[Testing only] select index of bad function (used with -emitbad)") +var forcetmpcleanflag = flag.Bool("forcetmpclean", false, "[Testing only] force cleanup of temp dir") +var cleancacheflag = flag.Bool("cleancache", true, "[Testing only] don't clean the go cache") +var raceflag = flag.Bool("race", false, "[Testing only] build generated code with -race") + +func verb(vlevel int, s string, a ...interface{}) { + if *verbflag >= vlevel { + fmt.Printf(s, a...) + fmt.Printf("\n") + } +} + +func warn(s string, a ...interface{}) { + fmt.Fprintf(os.Stderr, s, a...) + fmt.Fprintf(os.Stderr, "\n") +} + +func fatal(s string, a ...interface{}) { + fmt.Fprintf(os.Stderr, s, a...) + fmt.Fprintf(os.Stderr, "\n") + os.Exit(1) +} + +type config struct { + generator.GenConfig + tmpdir string + gendir string + buildOutFile string + runOutFile string + gcflags string + nerrors int +} + +func usage(msg string) { + if len(msg) > 0 { + fmt.Fprintf(os.Stderr, "error: %s\n", msg) + } + fmt.Fprintf(os.Stderr, "usage: fuzz-runner [flags]\n\n") + flag.PrintDefaults() + fmt.Fprintf(os.Stderr, "Example:\n\n") + fmt.Fprintf(os.Stderr, " fuzz-runner -numit=500 -numpkgs=11 -numfcns=13 -seed=10101\n\n") + fmt.Fprintf(os.Stderr, " \tRuns 500 rounds of test case generation\n") + fmt.Fprintf(os.Stderr, " \tusing random see 10101, in each round emitting\n") + fmt.Fprintf(os.Stderr, " \t11 packages each with 13 function pairs.\n") + + os.Exit(2) +} + +// docmd executes the specified command in the dir given and pipes the +// output to stderr. return status is 0 if command passed, 1 +// otherwise. +func docmd(cmd []string, dir string) int { + verb(2, "docmd: %s", strings.Join(cmd, " ")) + c := exec.Command(cmd[0], cmd[1:]...) + if dir != "" { + c.Dir = dir + } + b, err := c.CombinedOutput() + st := 0 + if err != nil { + warn("error executing cmd %s: %v", + strings.Join(cmd, " "), err) + st = 1 + } + os.Stderr.Write(b) + return st +} + +// docodmout forks and execs command 'cmd' in dir 'dir', redirecting +// stderr and stdout from the execution to file 'outfile'. +func docmdout(cmd []string, dir string, outfile string) int { + of, err := os.OpenFile(outfile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) + if err != nil { + fatal("opening outputfile %s: %v", outfile, err) + } + c := exec.Command(cmd[0], cmd[1:]...) + defer of.Close() + if dir != "" { + verb(2, "setting cmd.Dir to %s", dir) + c.Dir = dir + } + verb(2, "docmdout: %s > %s", strings.Join(cmd, " "), outfile) + c.Stdout = of + c.Stderr = of + err = c.Run() + st := 0 + if err != nil { + warn("error executing cmd %s: %v", + strings.Join(cmd, " "), err) + st = 1 + } + return st +} + +// gen is the main hook for kicking off code generation. For +// non-minimization runs, 'singlepk' and 'singlefn' will both be -1 +// (indicating that we want all functions and packages to be +// generated). If 'singlepk' is set to a non-negative value, then +// code generation will be restricted to the single package with that +// index (as a try at minimization), similarly with 'singlefn' +// restricting the codegen to a single specified function. +func (c *config) gen(singlepk int, singlefn int) { + + // clean the output dir + verb(2, "cleaning outdir %s", c.gendir) + if err := os.RemoveAll(c.gendir); err != nil { + fatal("error cleaning gen dir %s: %v", c.gendir, err) + } + + // emit code into the output dir. Here we either invoke the + // generator directly, or invoke fuzz-driver if -execflag is + // set. If the code generation process itself fails, this is + // typically a bug in the fuzzer itself, so it gets reported + // as a fatal error. + if *execflag { + args := []string{"fuzz-driver", + "-numpkgs", strconv.Itoa(c.NumTestPackages), + "-numfcns", strconv.Itoa(c.NumTestFunctions), + "-seed", strconv.Itoa(int(c.Seed)), + "-outdir", c.OutDir, + "-pkgpath", pkName, + "-maxfail", strconv.Itoa(c.MaxFail)} + if singlepk != -1 { + args = append(args, "-pkgmask", strconv.Itoa(singlepk)) + } + if singlefn != -1 { + args = append(args, "-fcnmask", strconv.Itoa(singlefn)) + } + if *emitbadflag != 0 { + args = append(args, "-emitbad", strconv.Itoa(*emitbadflag), + "-badpkgidx", strconv.Itoa(*selbadpkgflag), + "-badfcnidx", strconv.Itoa(*selbadfcnflag)) + } + verb(1, "invoking fuzz-driver with args: %v", args) + st := docmd(args, "") + if st != 0 { + fatal("fatal error: generation failed, cmd was: %v", args) + } + } else { + if singlepk != -1 { + c.PkgMask = map[int]int{singlepk: 1} + } + if singlefn != -1 { + c.FcnMask = map[int]int{singlefn: 1} + } + verb(1, "invoking generator.Generate with config: %v", c.GenConfig) + errs := generator.Generate(c.GenConfig) + if errs != 0 { + log.Fatal("errors during generation") + } + } +} + +// action performs a selected action/command in the generated code dir. +func (c *config) action(cmd []string, outfile string, emitout bool) int { + st := docmdout(cmd, c.gendir, outfile) + if emitout { + content, err := ioutil.ReadFile(outfile) + if err != nil { + log.Fatal(err) + } + fmt.Fprintf(os.Stderr, "%s", content) + } + return st +} + +func binaryName() string { + if runtime.GOOS == "windows" { + return pkName + ".exe" + } else { + return "./" + pkName + } +} + +// build builds a generated corpus of Go code. If 'emitout' is set, then dump out the +// results of the build after it completes (during minimization emitout is set to false, +// since there is no need to see repeated errors). +func (c *config) build(emitout bool) int { + // Issue a build of the generated code. + c.buildOutFile = filepath.Join(c.tmpdir, "build.err.txt") + cmd := []string{"go", "build", "-o", binaryName()} + if c.gcflags != "" { + cmd = append(cmd, "-gcflags=all="+c.gcflags) + } + if *raceflag { + cmd = append(cmd, "-race") + } + cmd = append(cmd, ".") + verb(1, "build command is: %v", cmd) + return c.action(cmd, c.buildOutFile, emitout) +} + +// run invokes a binary built from a generated corpus of Go code. If +// 'emitout' is set, then dump out the results of the run after it +// completes. +func (c *config) run(emitout bool) int { + // Issue a run of the generated code. + c.runOutFile = filepath.Join(c.tmpdir, "run.err.txt") + cmd := []string{filepath.Join(c.gendir, binaryName())} + verb(1, "run command is: %v", cmd) + return c.action(cmd, c.runOutFile, emitout) +} + +type minimizeMode int + +const ( + minimizeBuildFailure = iota + minimizeRuntimeFailure +) + +// minimize tries to minimize a failing scenario down to a single +// package and/or function if possible. This is done using an +// iterative search. Here 'minimizeMode' tells us whether we're +// looking for a compile-time error or a runtime error. +func (c *config) minimize(mode minimizeMode) int { + + verb(0, "... starting minimization for failed directory %s", c.gendir) + + foundPkg := -1 + foundFcn := -1 + + // Locate bad package. Uses brute-force linear search, could do better... + for pidx := 0; pidx < c.NumTestPackages; pidx++ { + verb(1, "minimization: trying package %d", pidx) + c.gen(pidx, -1) + st := c.build(false) + if mode == minimizeBuildFailure { + if st != 0 { + // Found. + foundPkg = pidx + c.nerrors++ + break + } + } else { + if st != 0 { + warn("run minimization: unexpected build failed while searching for bad pkg") + return 1 + } + st := c.run(false) + if st != 0 { + // Found. + c.nerrors++ + verb(1, "run minimization found bad package: %d", pidx) + foundPkg = pidx + break + } + } + } + if foundPkg == -1 { + verb(0, "** minimization failed, could not locate bad package") + return 1 + } + warn("package minimization succeeded: found bad pkg %d", foundPkg) + + // clean unused packages + for pidx := 0; pidx < c.NumTestPackages; pidx++ { + if pidx != foundPkg { + chp := filepath.Join(c.gendir, fmt.Sprintf("%s%s%d", c.Tag, generator.CheckerName, pidx)) + if err := os.RemoveAll(chp); err != nil { + fatal("failed to clean pkg subdir %s: %v", chp, err) + } + clp := filepath.Join(c.gendir, fmt.Sprintf("%s%s%d", c.Tag, generator.CallerName, pidx)) + if err := os.RemoveAll(clp); err != nil { + fatal("failed to clean pkg subdir %s: %v", clp, err) + } + } + } + + // Locate bad function. Again, brute force. + for fidx := 0; fidx < c.NumTestFunctions; fidx++ { + c.gen(foundPkg, fidx) + st := c.build(false) + if mode == minimizeBuildFailure { + if st != 0 { + // Found. + verb(1, "build minimization found bad function: %d", fidx) + foundFcn = fidx + break + } + } else { + if st != 0 { + warn("run minimization: unexpected build failed while searching for bad fcn") + return 1 + } + st := c.run(false) + if st != 0 { + // Found. + verb(1, "run minimization found bad function: %d", fidx) + foundFcn = fidx + break + } + } + // not the function we want ... continue the hunt + } + if foundFcn == -1 { + verb(0, "** function minimization failed, could not locate bad function") + return 1 + } + warn("function minimization succeeded: found bad fcn %d", foundFcn) + + return 0 +} + +// cleanTemp removes the temp dir we've been working with. +func (c *config) cleanTemp() { + if !*forcetmpcleanflag { + if c.nerrors != 0 { + verb(1, "preserving temp dir %s", c.tmpdir) + return + } + } + verb(1, "cleaning temp dir %s", c.tmpdir) + os.RemoveAll(c.tmpdir) +} + +// perform is the top level driver routine for the program, containing the +// main loop. Each iteration of the loop performs a generate/build/run +// sequence, and then updates the seed afterwards if no failure is found. +// If a failure is detected, we try to minimize it and then return without +// attempting any additional tests. +func (c *config) perform() int { + defer c.cleanTemp() + + // Main loop + for iter := 0; iter < *loopitflag; iter++ { + if iter != 0 && iter%50 == 0 { + // Note: cleaning the Go cache periodically is + // pretty much a requirement if you want to do + // things like overnight runs of the fuzzer, + // but it is also a very unfriendly thing do + // to if we're executing as part of a unit + // test run (in which case there may be other + // tests running in parallel with this + // one). Check the "cleancache" flag before + // doing this. + if *cleancacheflag { + docmd([]string{"go", "clean", "-cache"}, "") + } + } + verb(0, "... begin iteration %d with current seed %d", iter, c.Seed) + c.gen(-1, -1) + st := c.build(true) + if st != 0 { + c.minimize(minimizeBuildFailure) + return 1 + } + st = c.run(true) + if st != 0 { + c.minimize(minimizeRuntimeFailure) + return 1 + } + // update seed so that we get different code on the next iter. + c.Seed += 101 + } + return 0 +} + +func main() { + log.SetFlags(0) + log.SetPrefix("fuzz-runner: ") + flag.Parse() + if flag.NArg() != 0 { + usage("unknown extra arguments") + } + verb(1, "in main, verblevel=%d", *verbflag) + + tmpdir, err := ioutil.TempDir("", "fuzzrun") + if err != nil { + fatal("creation of tempdir failed: %v", err) + } + gendir := filepath.Join(tmpdir, "fuzzTest") + + // select starting seed + if *seedflag == -1 { + now := time.Now() + *seedflag = now.UnixNano() % 123456789 + } + + // set up params for this run + c := &config{ + GenConfig: generator.GenConfig{ + NumTestPackages: *numpkgsflag, // 100 + NumTestFunctions: *numfcnsflag, // 20 + Seed: *seedflag, + OutDir: gendir, + Pragma: "-maxfail=9999", + PkgPath: pkName, + EmitBad: *emitbadflag, + BadPackageIdx: *selbadpkgflag, + BadFuncIdx: *selbadfcnflag, + }, + tmpdir: tmpdir, + gendir: gendir, + } + + // kick off the main loop. + st := c.perform() + + // done + verb(1, "leaving main, num errors=%d", c.nerrors) + os.Exit(st) +} diff --git a/cmd/signature-fuzzer/fuzz-runner/testdata/himom.go b/cmd/signature-fuzzer/fuzz-runner/testdata/himom.go new file mode 100644 index 0000000000..5ba783db5c --- /dev/null +++ b/cmd/signature-fuzzer/fuzz-runner/testdata/himom.go @@ -0,0 +1,9 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +func main() { + println("hi mom!") +} diff --git a/cmd/signature-fuzzer/internal/fuzz-generator/arrayparm.go b/cmd/signature-fuzzer/internal/fuzz-generator/arrayparm.go new file mode 100644 index 0000000000..32ccf7e313 --- /dev/null +++ b/cmd/signature-fuzzer/internal/fuzz-generator/arrayparm.go @@ -0,0 +1,108 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package generator + +import ( + "bytes" + "fmt" +) + +// arrayparm describes a parameter of array type; it implements the +// "parm" interface. +type arrayparm struct { + aname string + qname string + nelements uint8 + eltype parm + slice bool + isBlank + addrTakenHow + isGenValFunc + skipCompare +} + +func (p arrayparm) IsControl() bool { + return false +} + +func (p arrayparm) TypeName() string { + return p.aname +} + +func (p arrayparm) QualName() string { + return p.qname +} + +func (p arrayparm) Declare(b *bytes.Buffer, prefix string, suffix string, caller bool) { + n := p.aname + if caller { + n = p.qname + } + b.WriteString(fmt.Sprintf("%s %s%s", prefix, n, suffix)) +} + +func (p arrayparm) String() string { + return fmt.Sprintf("%s %d-element array of %s", p.aname, p.nelements, p.eltype.String()) +} + +func (p arrayparm) GenValue(s *genstate, f *funcdef, value int, caller bool) (string, int) { + var buf bytes.Buffer + + verb(5, "arrayparm.GenValue(%d)", value) + + n := p.aname + if caller { + n = p.qname + } + buf.WriteString(fmt.Sprintf("%s{", n)) + for i := 0; i < int(p.nelements); i++ { + var valstr string + valstr, value = s.GenValue(f, p.eltype, value, caller) + writeCom(&buf, i) + buf.WriteString(valstr) + } + buf.WriteString("}") + return buf.String(), value +} + +func (p arrayparm) GenElemRef(elidx int, path string) (string, parm) { + ene := p.eltype.NumElements() + verb(4, "begin GenElemRef(%d,%s) on %s ene %d", elidx, path, p.String(), ene) + + // For empty arrays, convention is to return empty string + if ene == 0 { + return "", &p + } + + // Find slot within array of element of interest + slot := elidx / ene + + // If this is the element we're interested in, return it + if ene == 1 { + verb(4, "hit scalar element") + epath := fmt.Sprintf("%s[%d]", path, slot) + if path == "_" || p.IsBlank() { + epath = "_" + } + return epath, p.eltype + } + + verb(4, "recur slot=%d GenElemRef(%d,...)", slot, elidx-(slot*ene)) + + // Otherwise our victim is somewhere inside the slot + ppath := fmt.Sprintf("%s[%d]", path, slot) + if p.IsBlank() { + ppath = "_" + } + return p.eltype.GenElemRef(elidx-(slot*ene), ppath) +} + +func (p arrayparm) NumElements() int { + return p.eltype.NumElements() * int(p.nelements) +} + +func (p arrayparm) HasPointer() bool { + return p.eltype.HasPointer() || p.slice +} diff --git a/cmd/signature-fuzzer/internal/fuzz-generator/gen_test.go b/cmd/signature-fuzzer/internal/fuzz-generator/gen_test.go new file mode 100644 index 0000000000..4bd5bab7c3 --- /dev/null +++ b/cmd/signature-fuzzer/internal/fuzz-generator/gen_test.go @@ -0,0 +1,322 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package generator + +import ( + "bytes" + "os" + "os/exec" + "path/filepath" + "runtime" + "testing" + + "golang.org/x/tools/internal/testenv" +) + +func mkGenState() *genstate { + + return &genstate{ + GenConfig: GenConfig{ + Tag: "gen", + OutDir: "/tmp", + NumTestPackages: 1, + NumTestFunctions: 10, + }, + ipref: "foo/", + derefFuncs: make(map[string]string), + assignFuncs: make(map[string]string), + allocFuncs: make(map[string]string), + globVars: make(map[string]string), + } +} + +func TestBasic(t *testing.T) { + checkTunables(tunables) + s := mkGenState() + for i := 0; i < 1000; i++ { + s.wr = NewWrapRand(int64(i), RandCtlChecks|RandCtlPanic) + fp := s.GenFunc(i, i) + var buf bytes.Buffer + var b *bytes.Buffer = &buf + wr := NewWrapRand(int64(i), RandCtlChecks|RandCtlPanic) + s.wr = wr + s.emitCaller(fp, b, i) + s.wr = NewWrapRand(int64(i), RandCtlChecks|RandCtlPanic) + s.emitChecker(fp, b, i, true) + wr.Check(s.wr) + } + if s.errs != 0 { + t.Errorf("%d errors during Generate", s.errs) + } +} + +func TestMoreComplicated(t *testing.T) { + saveit := tunables + defer func() { tunables = saveit }() + + checkTunables(tunables) + s := mkGenState() + for i := 0; i < 10000; i++ { + s.wr = NewWrapRand(int64(i), RandCtlChecks|RandCtlPanic) + fp := s.GenFunc(i, i) + var buf bytes.Buffer + var b *bytes.Buffer = &buf + wr := NewWrapRand(int64(i), RandCtlChecks|RandCtlPanic) + s.wr = wr + s.emitCaller(fp, b, i) + verb(1, "finished iter %d caller", i) + s.wr = NewWrapRand(int64(i), RandCtlChecks|RandCtlPanic) + s.emitChecker(fp, b, i, true) + verb(1, "finished iter %d checker", i) + wr.Check(s.wr) + if s.errs != 0 { + t.Errorf("%d errors during Generate iter %d", s.errs, i) + } + } +} + +func TestIsBuildable(t *testing.T) { + testenv.NeedsTool(t, "go") + if runtime.GOOS == "android" { + t.Skipf("the dependencies are not available on android") + } + + td := t.TempDir() + verb(1, "generating into temp dir %s", td) + checkTunables(tunables) + pack := filepath.Base(td) + s := GenConfig{ + Tag: "x", + OutDir: td, + PkgPath: pack, + NumTestFunctions: 10, + NumTestPackages: 10, + MaxFail: 10, + RandCtl: RandCtlChecks | RandCtlPanic, + } + errs := Generate(s) + if errs != 0 { + t.Errorf("%d errors during Generate", errs) + } + + verb(1, "building %s\n", td) + + cmd := exec.Command("go", "run", ".") + cmd.Dir = td + coutput, cerr := cmd.CombinedOutput() + if cerr != nil { + t.Errorf("go build command failed: %s\n", string(coutput)) + } + verb(1, "output is: %s\n", string(coutput)) +} + +// TestExhaustive does a series of code genreation runs, starting with +// (relatively) simple code and then getting progressively more +// complex (more params, deeper structs, turning on additional +// features such as address-taken vars and reflect testing). The +// intent here is mainly to insure that the tester still works if you +// turn things on and off, e.g. that each feature is separately +// controllable and not linked to other things. +func TestExhaustive(t *testing.T) { + testenv.NeedsTool(t, "go") + if runtime.GOOS == "android" { + t.Skipf("the dependencies are not available on android") + } + + if testing.Short() { + t.Skip("skipping test in short mode.") + } + + td := t.TempDir() + verb(1, "generating into temp dir %s", td) + + scenarios := []struct { + name string + adjuster func() + }{ + { + "minimal", + func() { + tunables.nParmRange = 3 + tunables.nReturnRange = 3 + tunables.structDepth = 1 + tunables.recurPerc = 0 + tunables.methodPerc = 0 + tunables.doReflectCall = false + tunables.doDefer = false + tunables.takeAddress = false + tunables.doFuncCallValues = false + tunables.doSkipCompare = false + checkTunables(tunables) + }, + }, + { + "moreparms", + func() { + tunables.nParmRange = 15 + tunables.nReturnRange = 7 + tunables.structDepth = 3 + checkTunables(tunables) + }, + }, + { + "addrecur", + func() { + tunables.recurPerc = 20 + checkTunables(tunables) + }, + }, + { + "addmethod", + func() { + tunables.methodPerc = 25 + tunables.pointerMethodCallPerc = 30 + checkTunables(tunables) + }, + }, + { + "addtakeaddr", + func() { + tunables.takeAddress = true + tunables.takenFraction = 20 + checkTunables(tunables) + }, + }, + { + "addreflect", + func() { + tunables.doReflectCall = true + checkTunables(tunables) + }, + }, + { + "adddefer", + func() { + tunables.doDefer = true + checkTunables(tunables) + }, + }, + { + "addfuncval", + func() { + tunables.doFuncCallValues = true + checkTunables(tunables) + }, + }, + { + "addfuncval", + func() { + tunables.doSkipCompare = true + checkTunables(tunables) + }, + }, + } + + // Loop over scenarios and make sure each one works properly. + for i, s := range scenarios { + t.Logf("running %s\n", s.name) + s.adjuster() + os.RemoveAll(td) + pack := filepath.Base(td) + c := GenConfig{ + Tag: "x", + OutDir: td, + PkgPath: pack, + NumTestFunctions: 10, + NumTestPackages: 10, + Seed: int64(i + 9), + MaxFail: 10, + RandCtl: RandCtlChecks | RandCtlPanic, + } + errs := Generate(c) + if errs != 0 { + t.Errorf("%d errors during scenarios %q Generate", errs, s.name) + } + cmd := exec.Command("go", "run", ".") + cmd.Dir = td + coutput, cerr := cmd.CombinedOutput() + if cerr != nil { + t.Fatalf("run failed for scenario %q: %s\n", s.name, string(coutput)) + } + verb(1, "output is: %s\n", string(coutput)) + } +} + +func TestEmitBadBuildFailure(t *testing.T) { + testenv.NeedsTool(t, "go") + if runtime.GOOS == "android" { + t.Skipf("the dependencies are not available on android") + } + + td := t.TempDir() + verb(1, "generating into temp dir %s", td) + + checkTunables(tunables) + pack := filepath.Base(td) + s := GenConfig{ + Tag: "x", + OutDir: td, + PkgPath: pack, + NumTestFunctions: 10, + NumTestPackages: 10, + MaxFail: 10, + RandCtl: RandCtlChecks | RandCtlPanic, + EmitBad: 1, + } + errs := Generate(s) + if errs != 0 { + t.Errorf("%d errors during Generate", errs) + } + + cmd := exec.Command("go", "build", ".") + cmd.Dir = td + coutput, cerr := cmd.CombinedOutput() + if cerr == nil { + t.Errorf("go build command passed, expected failure. output: %s\n", string(coutput)) + } +} + +func TestEmitBadRunFailure(t *testing.T) { + testenv.NeedsTool(t, "go") + if runtime.GOOS == "android" { + t.Skipf("the dependencies are not available on android") + } + + td := t.TempDir() + verb(1, "generating into temp dir %s", td) + + checkTunables(tunables) + pack := filepath.Base(td) + s := GenConfig{ + Tag: "x", + OutDir: td, + PkgPath: pack, + NumTestFunctions: 10, + NumTestPackages: 10, + MaxFail: 10, + RandCtl: RandCtlChecks | RandCtlPanic, + EmitBad: 2, + } + errs := Generate(s) + if errs != 0 { + t.Errorf("%d errors during Generate", errs) + } + + // build + cmd := exec.Command("go", "build", ".") + cmd.Dir = td + coutput, cerr := cmd.CombinedOutput() + if cerr != nil { + t.Fatalf("build failed: %s\n", string(coutput)) + } + + // run + cmd = exec.Command("./" + pack) + cmd.Dir = td + coutput, cerr = cmd.CombinedOutput() + if cerr == nil { + t.Fatalf("run passed, expected failure -- run output: %s", string(coutput)) + } +} diff --git a/cmd/signature-fuzzer/internal/fuzz-generator/generator.go b/cmd/signature-fuzzer/internal/fuzz-generator/generator.go new file mode 100644 index 0000000000..bbe53fb101 --- /dev/null +++ b/cmd/signature-fuzzer/internal/fuzz-generator/generator.go @@ -0,0 +1,2269 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This package generates source code for a stand-alone Go program +// useful for function signature fuzzing. The generated program is a +// series of function pairs, 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 arrive correctly, and so +// on. +// +// The main exported items of interest for this package are: +// +// - the Generate function, which takes a GenConfig object and emits +// code according to the config's specification +// +// - the GenConfig struct, which is basically a large collection of +// knobs/switches to control the mechanics of how/where code is +// generated +// +// - the TunableParams struct, which controls the nature of the +// generated code (for example, the maximum number of function +// parameters, etc), and the SetTunables func which tells the +// package what tunable parameters to use. + +// Notes for posterity: +// - many parts of this package would have been better off being written +// using text/template instead of generating code directly; perhaps +// at some point it could be converted over (big job). +// - for the various 'fractions' fields in the TunableParams struct, +// it would be good to have a named type of some sort, with methods +// for managing things like checking to make sure values sum to 100. + +package generator + +import ( + "bytes" + "crypto/sha1" + "errors" + "fmt" + "html/template" + "log" + "os" + "os/exec" + "path/filepath" + "strconv" + "strings" +) + +// GenConfig contains configuration parameters relating to the +// mechanics of the code generation, e.g. how many packages/functions +// to emit, path to a directory into which we place the generated +// code, prefixes/packagenames for the generate code, and so on. +type GenConfig struct { + // Tag is a string prefix prepended to functions within + // the generated code. + Tag string + + // Output directory in to which we'll emit generated code. + // This will be created if it does not exist. + OutDir string + + // Packagepath prefix given to the generated code. + PkgPath string + + // Number of test packages created within the generated corpus. + // Each test package is essentially an independent collection + // generated code; the point of having multiple packages is to + // be able to get faster builds (more parallelism), and to avoid + // the compile time issues that crop up with 'giant' packages. + NumTestPackages int + + // Number of test function pairs within each generated test package. + // Each pair consists of a "caller" function and "callee" function. + NumTestFunctions int + + // Seed for random number generator. + Seed int64 + + // Pragma is a "// go:..." compiler directive to apply to the + // callee function as part of a generated function pair. + Pragma string + + // Function and package mask used for minimization purposes. + // If a given mask is non-nil, then the generator will only + // emit code for a given func or package if its index is + // present in the mask map. + FcnMask map[int]int + PkgMask map[int]int + + // Maximum number of failures to encounter before bailing out. + MaxFail int + + // forcestackgrowth if set tells the generator to insert + // calls to runtime.gcTestMoveStackOnNextCall at various points + // in the generated code. + ForceStackGrowth bool + + // Random number generator control flag (debugging) + RandCtl int + + // Tells the generator to run "goimports" on the emitted code. + RunGoImports bool + + // Debugging/testing hook. If set to 1, emit code that will cause the + // build to fail; if set to 2, emit code that will cause a test to fail. + EmitBad int + + // If EmitBad above is set, then these can be used to select the ID of + // a specific bad func/package. + BadPackageIdx int + BadFuncIdx int +} + +const CallerName = "Caller" +const CheckerName = "Checker" + +// TunableParams contains configuration parameters that control the +// flavor of code generated for a given test function. This includes +// things like the number of params/returns, the percentages of types +// (int, struct, etc) of the params/returns, and so on. +type TunableParams struct { + // between 0 and N params + nParmRange uint8 + + // between 0 and N returns + nReturnRange uint8 + + // structs have between 0 and N members + nStructFields uint8 + + // arrays/slices have between 0 and N elements + nArrayElements uint8 + + // fraction of slices vs arrays. This is a value between 0 and 100 (0 meaning + // no slices [only arrays] and 100 meaning all slices, no arrays). + sliceFraction uint8 + + // Controls how often "int" vars wind up as 8/16/32/64, should + // add up to 100. Ex: 100 0 0 0 means all ints are 8 bit, 25 + // 25 25 25 means equal likelihood of all types. + intBitRanges [4]uint8 + + // Similar to the above but for 32/64 float types + floatBitRanges [2]uint8 + + // Similar to the above but for unsigned, signed ints. + unsignedRanges [2]uint8 + + // Percentage of params, struct fields that should be "_". Ranges + // from 0 to 100. + blankPerc uint8 + + // How deeply structs are allowed to be nested (ranges from 0 to N). + structDepth uint8 + + // Fraction of param and return types assigned to each of: + // struct/array/map/pointer/int/float/complex/byte/string at the + // top level. If nesting precludes using a struct, other types + // are chosen from instead according to same proportions. The sum + // of typeFractions values should add up to 100. + typeFractions [9]uint8 + + // Percentage of the time we'll emit recursive calls, from 0 to 100. + recurPerc uint8 + + // Percentage of time that we turn the test function into a method, + // and if it is a method, fraction of time that we use a pointer + // method call vs value method call. Each range from 0 to 100. + methodPerc uint8 + pointerMethodCallPerc uint8 + + // If true, test reflect.Call path as well. + doReflectCall bool + + // If true, then randomly take addresses of params/returns. + takeAddress bool + + // Fraction of the time that any params/returns are address taken. + // Ranges from 0 to 100. + takenFraction uint8 + + // For a given address-taken param or return, controls the + // manner in which the indirect read or write takes + // place. This is a set of percentages for + // not/simple/passed/heap, where "not" means not address + // taken, "simple" means a simple read or write, "passed" + // means that the address is passed to a well-behaved + // function, and "heap" means that the address is assigned to + // a global. Values in addrFractions should add up to 100. + addrFractions [4]uint8 + + // If true, then perform testing of go/defer statements. + doDefer bool + + // fraction of test functions for which we emit a defer. Ranges from 0 to 100. + deferFraction uint8 + + // If true, randomly pick between emitting a value by literal + // (e.g. "int(1)" vs emitting a call to a function that + // will produce the same value (e.g. "myHelperEmitsInt1()"). + doFuncCallValues bool + + // Fraction of the time that we emit a function call to create + // a param value vs emitting a literal. Ranges from 0 to 100. + funcCallValFraction uint8 + + // If true, randomly decide to not check selected components of + // a composite value (e.g. for a struct, check field F1 but not F2). + // The intent is to generate partially live values. + doSkipCompare bool + + // Fraction of the time that we decided to skip sub-components of + // composite values. Ranges from 0 to 100. + skipCompareFraction uint8 +} + +// SetTunables accepts a TunableParams object, checks to make sure +// that the settings in it are sane/logical, and applies the +// parameters for any subsequent calls to the Generate function. This +// function will issue a fatal error if any of the tunable params are +// incorrect/insane (for example, a 'percentage' value outside the +// range of 0-100). +func SetTunables(t TunableParams) { + checkTunables(t) + tunables = t +} + +var defaultTypeFractions = [9]uint8{ + 10, // struct + 10, // array + 10, // map + 15, // pointer + 20, // numeric + 15, // float + 5, // complex + 5, // byte + 10, // string +} + +const ( + // Param not address taken. + StructTfIdx = iota + ArrayTfIdx + MapTfIdx + PointerTfIdx + NumericTfIdx + FloatTfIdx + ComplexTfIdx + ByteTfIdx + StringTfIdx +) + +var tunables = TunableParams{ + nParmRange: 15, + nReturnRange: 7, + nStructFields: 7, + nArrayElements: 5, + sliceFraction: 50, + intBitRanges: [4]uint8{30, 20, 20, 30}, + floatBitRanges: [2]uint8{50, 50}, + unsignedRanges: [2]uint8{50, 50}, + blankPerc: 15, + structDepth: 3, + typeFractions: defaultTypeFractions, + recurPerc: 20, + methodPerc: 10, + pointerMethodCallPerc: 50, + doReflectCall: true, + doDefer: true, + takeAddress: true, + doFuncCallValues: true, + takenFraction: 20, + deferFraction: 30, + funcCallValFraction: 5, + doSkipCompare: true, + skipCompareFraction: 10, + addrFractions: [4]uint8{50, 25, 15, 10}, +} + +func DefaultTunables() TunableParams { + return tunables +} + +func checkTunables(t TunableParams) { + var s int = 0 + + for _, v := range t.intBitRanges { + s += int(v) + } + if s != 100 { + log.Fatal(errors.New("intBitRanges tunable does not sum to 100")) + } + + s = 0 + for _, v := range t.unsignedRanges { + s += int(v) + } + if s != 100 { + log.Fatal(errors.New("unsignedRanges tunable does not sum to 100")) + } + + if t.blankPerc > 100 { + log.Fatal(errors.New("blankPerc bad value, over 100")) + } + if t.recurPerc > 100 { + log.Fatal(errors.New("recurPerc bad value, over 100")) + } + if t.methodPerc > 100 { + log.Fatal(errors.New("methodPerc bad value, over 100")) + } + if t.pointerMethodCallPerc > 100 { + log.Fatal(errors.New("pointerMethodCallPerc bad value, over 100")) + } + + s = 0 + for _, v := range t.floatBitRanges { + s += int(v) + } + if s != 100 { + log.Fatal(errors.New("floatBitRanges tunable does not sum to 100")) + } + + s = 0 + for _, v := range t.typeFractions { + s += int(v) + } + if s != 100 { + panic(errors.New("typeFractions tunable does not sum to 100")) + } + + s = 0 + for _, v := range t.addrFractions { + s += int(v) + } + if s != 100 { + log.Fatal(errors.New("addrFractions tunable does not sum to 100")) + } + if t.takenFraction > 100 { + log.Fatal(errors.New("takenFraction not between 0 and 100")) + } + if t.deferFraction > 100 { + log.Fatal(errors.New("deferFraction not between 0 and 100")) + } + if t.sliceFraction > 100 { + log.Fatal(errors.New("sliceFraction not between 0 and 100")) + } + if t.skipCompareFraction > 100 { + log.Fatal(errors.New("skipCompareFraction not between 0 and 100")) + } +} + +func (t *TunableParams) DisableReflectionCalls() { + t.doReflectCall = false +} + +func (t *TunableParams) DisableRecursiveCalls() { + t.recurPerc = 0 +} + +func (t *TunableParams) DisableMethodCalls() { + t.methodPerc = 0 +} + +func (t *TunableParams) DisableTakeAddr() { + t.takeAddress = false +} + +func (t *TunableParams) DisableDefer() { + t.doDefer = false +} + +func (t *TunableParams) LimitInputs(n int) error { + if n > 100 { + return fmt.Errorf("value %d passed to LimitInputs is too large *(max 100)", n) + } + if n < 0 { + return fmt.Errorf("value %d passed to LimitInputs is invalid", n) + } + t.nParmRange = uint8(n) + return nil +} + +func (t *TunableParams) LimitOutputs(n int) error { + if n > 100 { + return fmt.Errorf("value %d passed to LimitOutputs is too large *(max 100)", n) + } + if n < 0 { + return fmt.Errorf("value %d passed to LimitOutputs is invalid", n) + } + t.nReturnRange = uint8(n) + return nil +} + +// ParseMaskString parses a string of the form K,J,...,M-N,Q-R,...,Z +// e.g. comma-separated integers or ranges of integers, returning the +// result in a form suitable for FcnMask or PkgMask fields in a +// Config. Here "tag" holds the mask flavor (fcn or pkg) and "arg" is +// the string argument to be parsed. +func ParseMaskString(arg string, tag string) (map[int]int, error) { + if arg == "" { + return nil, nil + } + verb(1, "%s mask is %s", tag, arg) + m := make(map[int]int) + ss := strings.Split(arg, ":") + for _, s := range ss { + if strings.Contains(s, "-") { + rng := strings.Split(s, "-") + if len(rng) != 2 { + return nil, fmt.Errorf("malformed range %s in %s mask arg", s, tag) + } + i, err := strconv.Atoi(rng[0]) + if err != nil { + return nil, fmt.Errorf("malformed range value %s in %s mask arg", rng[0], tag) + } + j, err2 := strconv.Atoi(rng[1]) + if err2 != nil { + return nil, fmt.Errorf("malformed range value %s in %s mask arg", rng[1], tag) + } + for k := i; k < j; k++ { + m[k] = 1 + } + } else { + i, err := strconv.Atoi(s) + if err != nil { + return nil, fmt.Errorf("malformed value %s in %s mask arg", s, tag) + } + m[i] = 1 + } + } + return m, nil +} + +func writeCom(b *bytes.Buffer, i int) { + if i != 0 { + b.WriteString(", ") + } +} + +var Verbctl int = 0 + +func verb(vlevel int, s string, a ...interface{}) { + if Verbctl >= vlevel { + fmt.Printf(s, a...) + fmt.Printf("\n") + } +} + +type funcdef struct { + idx int + structdefs []structparm + arraydefs []arrayparm + typedefs []typedefparm + mapdefs []mapparm + mapkeytypes []parm + mapkeytmps []string + mapkeyts string + receiver parm + params []parm + returns []parm + values []int + dodefc uint8 + dodefp []uint8 + rstack int + recur bool + isMethod bool +} + +type genstate struct { + GenConfig + ipref string + //tag string + //numtpk int + pkidx int + errs int + //pragma string + //sforce bool + //randctl int + tunables TunableParams + tstack []TunableParams + derefFuncs map[string]string + newDerefFuncs []funcdesc + assignFuncs map[string]string + newAssignFuncs []funcdesc + allocFuncs map[string]string + newAllocFuncs []funcdesc + genvalFuncs map[string]string + newGenvalFuncs []funcdesc + globVars map[string]string + newGlobVars []funcdesc + wr *wraprand +} + +func (s *genstate) intFlavor() string { + which := uint8(s.wr.Intn(100)) + if which < s.tunables.unsignedRanges[0] { + return "uint" + } + return "int" +} + +func (s *genstate) intBits() uint32 { + which := uint8(s.wr.Intn(100)) + var t uint8 = 0 + var bits uint32 = 8 + for _, v := range s.tunables.intBitRanges { + t += v + if which < t { + return bits + } + bits *= 2 + } + return uint32(s.tunables.intBitRanges[3]) +} + +func (s *genstate) floatBits() uint32 { + which := uint8(s.wr.Intn(100)) + if which < s.tunables.floatBitRanges[0] { + return uint32(32) + } + return uint32(64) +} + +func (s *genstate) genAddrTaken() addrTakenHow { + which := uint8(s.wr.Intn(100)) + res := notAddrTaken + var t uint8 = 0 + for _, v := range s.tunables.addrFractions { + t += v + if which < t { + return res + } + res++ + } + return notAddrTaken +} + +func (s *genstate) pushTunables() { + s.tstack = append(s.tstack, s.tunables) +} + +func (s *genstate) popTunables() { + if len(s.tstack) == 0 { + panic("untables stack underflow") + } + s.tunables = s.tstack[0] + s.tstack = s.tstack[1:] +} + +// redistributeFraction accepts a value 'toIncorporate' and updates +// 'typeFraction' to add in the values from 'toIncorporate' equally to +// all slots not in 'avoid'. This is done by successively walking +// through 'typeFraction' adding 1 to each non-avoid slot, then +// repeating until we've added a total of 'toIncorporate' elements. +// See precludeSelectedTypes below for more info. +func (s *genstate) redistributeFraction(toIncorporate uint8, avoid []int) { + inavoid := func(j int) bool { + for _, k := range avoid { + if j == k { + return true + } + } + return false + } + + doredis := func() { + for { + for i := range s.tunables.typeFractions { + if inavoid(i) { + continue + } + s.tunables.typeFractions[i]++ + toIncorporate-- + if toIncorporate == 0 { + return + } + } + } + } + doredis() + checkTunables(s.tunables) +} + +// precludeSelectedTypes accepts a set of values (t, t2, ...) +// corresponding to slots in 'typeFractions', sums up the values from +// the slots, zeroes out the slots, and finally takes the values and +// redistributes them equally to the other slots. For example, +// suppose 'typeFractions' starts as [10, 10, 10, 15, 20, 15, 5, 5, 10], +// then we decide we want to eliminate or 'knock out' map types and +// pointer types (slots 2 and 3 in the array above) going forward. To +// restore the invariant that values in 'typeFractions' sum to 100, we +// take the values from slots 2 and 3 (a total of 25) and evenly +// distribute those values to the other slots in the array. +func (s *genstate) precludeSelectedTypes(t int, t2 ...int) { + avoid := []int{t} + avoid = append(avoid, t2...) + f := uint8(0) + for _, idx := range avoid { + f += s.tunables.typeFractions[idx] + s.tunables.typeFractions[idx] = 0 + } + s.redistributeFraction(f, avoid) +} + +func (s *genstate) GenMapKeyType(f *funcdef, depth int, pidx int) parm { + s.pushTunables() + defer s.popTunables() + // maps we can't allow at all; pointers might be possible but + // would be too much work to arrange. Avoid slices as well. + s.tunables.sliceFraction = 0 + s.precludeSelectedTypes(MapTfIdx, PointerTfIdx) + return s.GenParm(f, depth+1, false, pidx) +} + +func (s *genstate) GenParm(f *funcdef, depth int, mkctl bool, pidx int) parm { + + // Enforcement for struct/array/map/pointer array nesting depth. + toodeep := depth >= int(s.tunables.structDepth) + if toodeep { + s.pushTunables() + defer s.popTunables() + s.precludeSelectedTypes(StructTfIdx, ArrayTfIdx, MapTfIdx, PointerTfIdx) + } + + // Convert tf into a cumulative sum + tf := s.tunables.typeFractions + sum := uint8(0) + for i := 0; i < len(tf); i++ { + sum += tf[i] + tf[i] = sum + } + + isblank := uint8(s.wr.Intn(100)) < s.tunables.blankPerc + addrTaken := notAddrTaken + if depth == 0 && tunables.takeAddress && !isblank { + addrTaken = s.genAddrTaken() + } + isGenValFunc := tunables.doFuncCallValues && + uint8(s.wr.Intn(100)) < s.tunables.funcCallValFraction + + // Make adjusted selection (pick a bucket within tf) + which := uint8(s.wr.Intn(100)) + verb(3, "which=%d", which) + var retval parm + switch { + case which < tf[StructTfIdx]: + { + if toodeep { + panic("should not be here") + } + var sp structparm + ns := len(f.structdefs) + sp.sname = fmt.Sprintf("StructF%dS%d", f.idx, ns) + sp.qname = fmt.Sprintf("%s.StructF%dS%d", + s.checkerPkg(pidx), f.idx, ns) + f.structdefs = append(f.structdefs, sp) + tnf := int64(s.tunables.nStructFields) / int64(depth+1) + nf := int(s.wr.Intn(tnf)) + for fi := 0; fi < nf; fi++ { + fp := s.GenParm(f, depth+1, false, pidx) + skComp := tunables.doSkipCompare && + uint8(s.wr.Intn(100)) < s.tunables.skipCompareFraction + if skComp && checkableElements(fp) != 0 { + fp.SetSkipCompare(SkipAll) + } + sp.fields = append(sp.fields, fp) + } + f.structdefs[ns] = sp + retval = &sp + } + case which < tf[ArrayTfIdx]: + { + if toodeep { + panic("should not be here") + } + var ap arrayparm + ns := len(f.arraydefs) + nel := uint8(s.wr.Intn(int64(s.tunables.nArrayElements))) + issl := uint8(s.wr.Intn(100)) < s.tunables.sliceFraction + ap.aname = fmt.Sprintf("ArrayF%dS%dE%d", f.idx, ns, nel) + ap.qname = fmt.Sprintf("%s.ArrayF%dS%dE%d", s.checkerPkg(pidx), + f.idx, ns, nel) + f.arraydefs = append(f.arraydefs, ap) + ap.nelements = nel + ap.slice = issl + ap.eltype = s.GenParm(f, depth+1, false, pidx) + ap.eltype.SetBlank(false) + skComp := tunables.doSkipCompare && + uint8(s.wr.Intn(100)) < s.tunables.skipCompareFraction + if skComp && checkableElements(ap.eltype) != 0 { + if issl { + ap.SetSkipCompare(SkipPayload) + } + } + f.arraydefs[ns] = ap + retval = &ap + } + case which < tf[MapTfIdx]: + { + if toodeep { + panic("should not be here") + } + var mp mapparm + ns := len(f.mapdefs) + + // append early, since calls below might also append + f.mapdefs = append(f.mapdefs, mp) + f.mapkeytmps = append(f.mapkeytmps, "") + f.mapkeytypes = append(f.mapkeytypes, mp.keytype) + mp.aname = fmt.Sprintf("MapF%dM%d", f.idx, ns) + if f.mapkeyts == "" { + f.mapkeyts = fmt.Sprintf("MapKeysF%d", f.idx) + } + mp.qname = fmt.Sprintf("%s.MapF%dM%d", s.checkerPkg(pidx), + f.idx, ns) + mkt := fmt.Sprintf("Mk%dt%d", f.idx, ns) + mp.keytmp = mkt + mk := s.GenMapKeyType(f, depth+1, pidx) + mp.keytype = mk + mp.valtype = s.GenParm(f, depth+1, false, pidx) + mp.valtype.SetBlank(false) + mp.keytype.SetBlank(false) + // now update the previously appended placeholders + f.mapdefs[ns] = mp + f.mapkeytypes[ns] = mk + f.mapkeytmps[ns] = mkt + retval = &mp + } + case which < tf[PointerTfIdx]: + { + if toodeep { + panic("should not be here") + } + pp := mkPointerParm(s.GenParm(f, depth+1, false, pidx)) + retval = &pp + } + case which < tf[NumericTfIdx]: + { + var ip numparm + ip.tag = s.intFlavor() + ip.widthInBits = s.intBits() + if mkctl { + ip.ctl = true + } + retval = &ip + } + case which < tf[FloatTfIdx]: + { + var fp numparm + fp.tag = "float" + fp.widthInBits = s.floatBits() + retval = &fp + } + case which < tf[ComplexTfIdx]: + { + var fp numparm + fp.tag = "complex" + fp.widthInBits = s.floatBits() * 2 + retval = &fp + } + case which < tf[ByteTfIdx]: + { + var bp numparm + bp.tag = "byte" + bp.widthInBits = 8 + retval = &bp + } + case which < tf[StringTfIdx]: + { + var sp stringparm + sp.tag = "string" + skComp := tunables.doSkipCompare && + uint8(s.wr.Intn(100)) < s.tunables.skipCompareFraction + if skComp { + sp.SetSkipCompare(SkipPayload) + } + retval = &sp + } + default: + { + // fallback + var ip numparm + ip.tag = "uint" + ip.widthInBits = 8 + retval = &ip + } + } + if !mkctl { + retval.SetBlank(isblank) + } + retval.SetAddrTaken(addrTaken) + retval.SetIsGenVal(isGenValFunc) + return retval +} + +func (s *genstate) GenReturn(f *funcdef, depth int, pidx int) parm { + return s.GenParm(f, depth, false, pidx) +} + +// GenFunc cooks up the random signature (and other attributes) of a +// given checker function, returning a funcdef object that describes +// the new fcn. +func (s *genstate) GenFunc(fidx int, pidx int) *funcdef { + f := new(funcdef) + f.idx = fidx + numParams := int(s.wr.Intn(int64(1 + int(s.tunables.nParmRange)))) + numReturns := int(s.wr.Intn(int64(1 + int(s.tunables.nReturnRange)))) + f.recur = uint8(s.wr.Intn(100)) < s.tunables.recurPerc + f.isMethod = uint8(s.wr.Intn(100)) < s.tunables.methodPerc + genReceiverType := func() { + // Receiver type can't be pointer type. Temporarily update + // tunables to eliminate that possibility. + s.pushTunables() + defer s.popTunables() + s.precludeSelectedTypes(PointerTfIdx) + target := s.GenParm(f, 0, false, pidx) + target.SetBlank(false) + f.receiver = s.makeTypedefParm(f, target, pidx) + if f.receiver.IsBlank() { + f.recur = false + } + } + if f.isMethod { + genReceiverType() + } + needControl := f.recur + f.dodefc = uint8(s.wr.Intn(100)) + pTaken := uint8(s.wr.Intn(100)) < s.tunables.takenFraction + for pi := 0; pi < numParams; pi++ { + newparm := s.GenParm(f, 0, needControl, pidx) + if !pTaken { + newparm.SetAddrTaken(notAddrTaken) + } + if newparm.IsControl() { + needControl = false + } + f.params = append(f.params, newparm) + f.dodefp = append(f.dodefp, uint8(s.wr.Intn(100))) + } + if f.recur && needControl { + f.recur = false + } + + rTaken := uint8(s.wr.Intn(100)) < s.tunables.takenFraction + for ri := 0; ri < numReturns; ri++ { + r := s.GenReturn(f, 0, pidx) + if !rTaken { + r.SetAddrTaken(notAddrTaken) + } + f.returns = append(f.returns, r) + } + spw := uint(s.wr.Intn(11)) + rstack := 1 << spw + if rstack < 4 { + rstack = 4 + } + f.rstack = rstack + return f +} + +func genDeref(p parm) (parm, string) { + curp := p + star := "" + for { + if pp, ok := curp.(*pointerparm); ok { + star += "*" + curp = pp.totype + } else { + return curp, star + } + } +} + +func (s *genstate) eqFuncRef(f *funcdef, t parm, caller bool) string { + cp := "" + if f.mapkeyts != "" { + cp = "mkt." + } else if caller { + cp = s.checkerPkg(s.pkidx) + "." + } + return cp + "Equal" + t.TypeName() +} + +// emitCompareFunc creates an 'equals' function for a specific +// generated type (this is basically a way to compare objects that +// contain pointer fields / pointery things). +func (s *genstate) emitCompareFunc(f *funcdef, b *bytes.Buffer, p parm) { + if !p.HasPointer() { + return + } + + tn := p.TypeName() + b.WriteString(fmt.Sprintf("// equal func for %s\n", tn)) + b.WriteString("//go:noinline\n") + rcvr := "" + if f.mapkeyts != "" { + rcvr = fmt.Sprintf("(mkt *%s) ", f.mapkeyts) + } + b.WriteString(fmt.Sprintf("func %sEqual%s(left %s, right %s) bool {\n", rcvr, tn, tn, tn)) + b.WriteString(" return ") + numel := p.NumElements() + ncmp := 0 + for i := 0; i < numel; i++ { + lelref, lelparm := p.GenElemRef(i, "left") + relref, _ := p.GenElemRef(i, "right") + if lelref == "" || lelref == "_" { + continue + } + basep, star := genDeref(lelparm) + // Handle *p where p is an empty struct. + if basep.NumElements() == 0 { + continue + } + if ncmp != 0 { + b.WriteString(" && ") + } + ncmp++ + if basep.HasPointer() { + efn := s.eqFuncRef(f, basep, false) + b.WriteString(fmt.Sprintf(" %s(%s%s, %s%s)", efn, star, lelref, star, relref)) + } else { + b.WriteString(fmt.Sprintf("%s%s == %s%s", star, lelref, star, relref)) + } + } + if ncmp == 0 { + b.WriteString("true") + } + b.WriteString("\n}\n\n") +} + +// emitStructAndArrayDefs writes out definitions of the random types +// we happened to cook up while generating code for a specific +// function pair. +func (s *genstate) emitStructAndArrayDefs(f *funcdef, b *bytes.Buffer) { + for _, str := range f.structdefs { + b.WriteString(fmt.Sprintf("type %s struct {\n", str.sname)) + for fi, sp := range str.fields { + sp.Declare(b, " "+str.FieldName(fi), "\n", false) + } + b.WriteString("}\n\n") + s.emitCompareFunc(f, b, &str) + } + for _, a := range f.arraydefs { + elems := fmt.Sprintf("%d", a.nelements) + if a.slice { + elems = "" + } + b.WriteString(fmt.Sprintf("type %s [%s]%s\n\n", a.aname, + elems, a.eltype.TypeName())) + s.emitCompareFunc(f, b, &a) + } + for _, a := range f.mapdefs { + b.WriteString(fmt.Sprintf("type %s map[%s]%s\n\n", a.aname, + a.keytype.TypeName(), a.valtype.TypeName())) + s.emitCompareFunc(f, b, &a) + } + for _, td := range f.typedefs { + b.WriteString(fmt.Sprintf("type %s %s\n\n", td.aname, + td.target.TypeName())) + s.emitCompareFunc(f, b, &td) + } + if f.mapkeyts != "" { + b.WriteString(fmt.Sprintf("type %s struct {\n", f.mapkeyts)) + for i := range f.mapkeytypes { + f.mapkeytypes[i].Declare(b, " "+f.mapkeytmps[i], "\n", false) + } + b.WriteString("}\n\n") + } +} + +// GenValue method of genstate wraps the parm method of the same +// name, but optionally returns a call to a function to produce +// the value as opposed to a literal value. +func (s *genstate) GenValue(f *funcdef, p parm, value int, caller bool) (string, int) { + var valstr string + valstr, value = p.GenValue(s, f, value, caller) + if !s.tunables.doFuncCallValues || !p.IsGenVal() || caller { + return valstr, value + } + + mkInvoc := func(fname string) string { + meth := "" + if f.mapkeyts != "" { + meth = "mkt." + } + return fmt.Sprintf("%s%s()", meth, fname) + } + + b := bytes.NewBuffer(nil) + p.Declare(b, "x", "", false) + h := sha1.New() + h.Write([]byte(valstr)) + h.Write(b.Bytes()) + if f.mapkeyts != "" { + h.Write([]byte(f.mapkeyts)) + } + h.Write(b.Bytes()) + bs := h.Sum(nil) + hashstr := fmt.Sprintf("%x", bs) + b.WriteString(hashstr) + tag := b.String() + fname, ok := s.genvalFuncs[tag] + if ok { + return mkInvoc(fname), value + } + + fname = fmt.Sprintf("genval_%d", len(s.genvalFuncs)) + s.newGenvalFuncs = append(s.newGenvalFuncs, funcdesc{p: p, name: fname, tag: tag, payload: valstr}) + s.genvalFuncs[tag] = fname + return mkInvoc(fname), value +} + +func (s *genstate) emitMapKeyTmps(f *funcdef, b *bytes.Buffer, pidx int, value int, caller bool) int { + if f.mapkeyts == "" { + return value + } + // map key tmps + cp := "" + if caller { + cp = s.checkerPkg(pidx) + "." + } + b.WriteString(" var mkt " + cp + f.mapkeyts + "\n") + for i, t := range f.mapkeytypes { + var keystr string + keystr, value = s.GenValue(f, t, value, caller) + tname := f.mapkeytmps[i] + b.WriteString(fmt.Sprintf(" %s := %s\n", tname, keystr)) + b.WriteString(fmt.Sprintf(" mkt.%s = %s\n", tname, tname)) + } + return value +} + +func (s *genstate) emitCheckReturnsInCaller(f *funcdef, b *bytes.Buffer, pidx int, reflectCall bool) { + cm := f.complexityMeasure() + rvalp := func(ri int) string { + if reflectCall { + return fmt.Sprintf("rr%dv", ri) + } + return fmt.Sprintf("r%d", ri) + } + failTag := "\"return\"" + if reflectCall { + failTag = "\"reflect return\"" + } + for ri, rp := range f.returns { + if reflectCall { + b.WriteString(fmt.Sprintf(" rr%di := rvslice[%d].Interface()\n", ri, ri)) + b.WriteString(fmt.Sprintf(" rr%dv:= rr%di.(", ri, ri)) + rp.Declare(b, "", "", true) + b.WriteString(")\n") + } + pfc := "" + curp, star := genDeref(rp) + // Handle *p where p is an empty struct. + if curp.NumElements() == 0 { + b.WriteString(fmt.Sprintf(" _, _ = %s, c%d // zero size\n", rvalp(ri), ri)) + continue + } + if star != "" { + pfc = fmt.Sprintf("ParamFailCount[%d] == 0 && ", pidx) + } + if curp.HasPointer() { + efn := "!" + s.eqFuncRef(f, curp, true) + b.WriteString(fmt.Sprintf(" if %s%s(%s%s, %sc%d) {\n", pfc, efn, star, rvalp(ri), star, ri)) + } else { + b.WriteString(fmt.Sprintf(" if %s%s%s != %sc%d {\n", pfc, star, rvalp(ri), star, ri)) + } + b.WriteString(fmt.Sprintf(" NoteFailure(%d, %d, %d, \"%s\", %s, %d, true, uint64(0))\n", cm, pidx, f.idx, s.checkerPkg(pidx), failTag, ri)) + b.WriteString(" }\n") + } +} + +func (s *genstate) emitCaller(f *funcdef, b *bytes.Buffer, pidx int) { + + b.WriteString(fmt.Sprintf("func %s%d(mode string) {\n", CallerName, f.idx)) + + b.WriteString(fmt.Sprintf(" BeginFcn(%d)\n", pidx)) + + if s.EmitBad == 1 { + if s.BadPackageIdx == pidx && s.BadFuncIdx == f.idx { + b.WriteString(" bad code here, should cause build failure <<==\n") + } + } + + var value int = 1 + + s.wr.Checkpoint("before mapkeytmps") + value = s.emitMapKeyTmps(f, b, pidx, value, true) + + // generate return constants + s.wr.Checkpoint("before return constants") + for ri, r := range f.returns { + rc := fmt.Sprintf("c%d", ri) + value = s.emitVarAssign(f, b, r, rc, value, true) + } + + // generate param constants + s.wr.Checkpoint("before param constants") + for pi, p := range f.params { + verb(4, "emitCaller gen p%d value=%d", pi, value) + if p.IsControl() { + _ = uint8(s.wr.Intn(100)) < 50 + p.Declare(b, fmt.Sprintf(" var p%d ", pi), " = 10\n", true) + } else { + pc := fmt.Sprintf("p%d", pi) + value = s.emitVarAssign(f, b, p, pc, value, true) + } + f.values = append(f.values, value) + } + + // generate receiver constant if applicable + if f.isMethod { + s.wr.Checkpoint("before receiver constant") + f.receiver.Declare(b, " var rcvr", "\n", true) + valstr, value := s.GenValue(f, f.receiver, value, true) + b.WriteString(fmt.Sprintf(" rcvr = %s\n", valstr)) + f.values = append(f.values, value) + } + + b.WriteString(fmt.Sprintf(" Mode[%d] = \"\"\n", pidx)) + + // calling code + b.WriteString(fmt.Sprintf(" // %d returns %d params\n", + len(f.returns), len(f.params))) + if s.ForceStackGrowth { + b.WriteString(" hackStack() // force stack growth on next call\n") + } + b.WriteString(" if mode == \"normal\" {\n") + b.WriteString(" ") + for ri := range f.returns { + writeCom(b, ri) + b.WriteString(fmt.Sprintf("r%d", ri)) + } + if len(f.returns) > 0 { + b.WriteString(" := ") + } + pref := s.checkerPkg(pidx) + if f.isMethod { + pref = "rcvr" + } + b.WriteString(fmt.Sprintf("%s.Test%d(", pref, f.idx)) + for pi := range f.params { + writeCom(b, pi) + b.WriteString(fmt.Sprintf("p%d", pi)) + } + b.WriteString(")\n") + + // check values returned (normal call case) + s.emitCheckReturnsInCaller(f, b, pidx, false /* not a reflect call */) + b.WriteString(" }") // end of 'if normal call' block + if s.tunables.doReflectCall { + b.WriteString("else {\n") // beginning of reflect call block + // now make the same call via reflection + b.WriteString(" // same call via reflection\n") + b.WriteString(fmt.Sprintf(" Mode[%d] = \"reflect\"\n", pidx)) + if f.isMethod { + b.WriteString(" rcv := reflect.ValueOf(rcvr)\n") + b.WriteString(fmt.Sprintf(" rc := rcv.MethodByName(\"Test%d\")\n", f.idx)) + } else { + b.WriteString(fmt.Sprintf(" rc := reflect.ValueOf(%s.Test%d)\n", + s.checkerPkg(pidx), f.idx)) + } + b.WriteString(" ") + if len(f.returns) > 0 { + b.WriteString("rvslice := ") + } + b.WriteString(" rc.Call([]reflect.Value{") + for pi := range f.params { + writeCom(b, pi) + b.WriteString(fmt.Sprintf("reflect.ValueOf(p%d)", pi)) + } + b.WriteString("})\n") + + // check values returned (reflect call case) + s.emitCheckReturnsInCaller(f, b, pidx, true /* is a reflect call */) + b.WriteString("}\n") // end of reflect call block + } + + b.WriteString(fmt.Sprintf("\n EndFcn(%d)\n", pidx)) + + b.WriteString("}\n\n") +} + +func checkableElements(p parm) int { + if p.IsBlank() { + return 0 + } + sp, isstruct := p.(*structparm) + if isstruct { + s := 0 + for fi := range sp.fields { + s += checkableElements(sp.fields[fi]) + } + return s + } + ap, isarray := p.(*arrayparm) + if isarray { + if ap.nelements == 0 { + return 0 + } + return int(ap.nelements) * checkableElements(ap.eltype) + } + return 1 +} + +// funcdesc describes an auto-generated helper function or global +// variable, such as an allocation function (returns new(T)) or a +// pointer assignment function (assigns value of T to type *T). Here +// 'p' is a param type T, 'pp' is a pointer type *T, 'name' is the +// name within the generated code of the function or variable and +// 'tag' is a descriptive tag used to look up the entity in a map (so +// that we don't have to emit multiple copies of a function that +// assigns int to *int, for example). +type funcdesc struct { + p parm + pp parm + name string + tag string + payload string +} + +func (s *genstate) emitDerefFuncs(b *bytes.Buffer, emit bool) { + b.WriteString("// dereference helpers\n") + for _, fd := range s.newDerefFuncs { + if !emit { + b.WriteString(fmt.Sprintf("\n// skip derefunc %s\n", fd.name)) + delete(s.derefFuncs, fd.tag) + continue + } + b.WriteString("\n//go:noinline\n") + b.WriteString(fmt.Sprintf("func %s(", fd.name)) + fd.pp.Declare(b, "x", "", false) + b.WriteString(") ") + fd.p.Declare(b, "", "", false) + b.WriteString(" {\n") + b.WriteString(" return *x\n") + b.WriteString("}\n") + } + s.newDerefFuncs = nil +} + +func (s *genstate) emitAssignFuncs(b *bytes.Buffer, emit bool) { + b.WriteString("// assign helpers\n") + for _, fd := range s.newAssignFuncs { + if !emit { + b.WriteString(fmt.Sprintf("\n// skip assignfunc %s\n", fd.name)) + delete(s.assignFuncs, fd.tag) + continue + } + b.WriteString("\n//go:noinline\n") + b.WriteString(fmt.Sprintf("func %s(", fd.name)) + fd.pp.Declare(b, "x", "", false) + b.WriteString(", ") + fd.p.Declare(b, "v", "", false) + b.WriteString(") {\n") + b.WriteString(" *x = v\n") + b.WriteString("}\n") + } + s.newAssignFuncs = nil +} + +func (s *genstate) emitNewFuncs(b *bytes.Buffer, emit bool) { + b.WriteString("// 'new' funcs\n") + for _, fd := range s.newAllocFuncs { + if !emit { + b.WriteString(fmt.Sprintf("\n// skip newfunc %s\n", fd.name)) + delete(s.allocFuncs, fd.tag) + continue + } + b.WriteString("\n//go:noinline\n") + b.WriteString(fmt.Sprintf("func %s(", fd.name)) + fd.p.Declare(b, "i", "", false) + b.WriteString(") ") + fd.pp.Declare(b, "", "", false) + b.WriteString(" {\n") + b.WriteString(" x := new(") + fd.p.Declare(b, "", "", false) + b.WriteString(")\n") + b.WriteString(" *x = i\n") + b.WriteString(" return x\n") + b.WriteString("}\n\n") + } + s.newAllocFuncs = nil +} + +func (s *genstate) emitGlobalVars(b *bytes.Buffer, emit bool) { + b.WriteString("// global vars\n") + for _, fd := range s.newGlobVars { + if !emit { + b.WriteString(fmt.Sprintf("\n// skip gvar %s\n", fd.name)) + delete(s.globVars, fd.tag) + continue + } + b.WriteString("var ") + fd.pp.Declare(b, fd.name, "", false) + b.WriteString("\n") + } + s.newGlobVars = nil + b.WriteString("\n") +} + +func (s *genstate) emitGenValFuncs(f *funcdef, b *bytes.Buffer, emit bool) { + b.WriteString("// genval helpers\n") + for _, fd := range s.newGenvalFuncs { + if !emit { + b.WriteString(fmt.Sprintf("\n// skip genvalfunc %s\n", fd.name)) + delete(s.genvalFuncs, fd.tag) + continue + } + b.WriteString("\n//go:noinline\n") + rcvr := "" + if f.mapkeyts != "" { + rcvr = fmt.Sprintf("(mkt *%s) ", f.mapkeyts) + } + b.WriteString(fmt.Sprintf("func %s%s() ", rcvr, fd.name)) + fd.p.Declare(b, "", "", false) + b.WriteString(" {\n") + if f.mapkeyts != "" { + contained := containedParms(fd.p) + for _, cp := range contained { + mp, ismap := cp.(*mapparm) + if ismap { + b.WriteString(fmt.Sprintf(" %s := mkt.%s\n", + mp.keytmp, mp.keytmp)) + b.WriteString(fmt.Sprintf(" _ = %s\n", mp.keytmp)) + } + } + } + b.WriteString(fmt.Sprintf(" return %s\n", fd.payload)) + b.WriteString("}\n") + } + s.newGenvalFuncs = nil +} + +func (s *genstate) emitAddrTakenHelpers(f *funcdef, b *bytes.Buffer, emit bool) { + b.WriteString("// begin addr taken helpers\n") + s.emitDerefFuncs(b, emit) + s.emitAssignFuncs(b, emit) + s.emitNewFuncs(b, emit) + s.emitGlobalVars(b, emit) + s.emitGenValFuncs(f, b, emit) + b.WriteString("// end addr taken helpers\n") +} + +func (s *genstate) genGlobVar(p parm) string { + var pp parm + ppp := mkPointerParm(p) + pp = &ppp + b := bytes.NewBuffer(nil) + pp.Declare(b, "gv", "", false) + tag := b.String() + gv, ok := s.globVars[tag] + if ok { + return gv + } + gv = fmt.Sprintf("gvar_%d", len(s.globVars)) + s.newGlobVars = append(s.newGlobVars, funcdesc{pp: pp, p: p, name: gv, tag: tag}) + s.globVars[tag] = gv + return gv +} + +func (s *genstate) genParamDerefFunc(p parm) string { + var pp parm + ppp := mkPointerParm(p) + pp = &ppp + b := bytes.NewBuffer(nil) + pp.Declare(b, "x", "", false) + tag := b.String() + f, ok := s.derefFuncs[tag] + if ok { + return f + } + f = fmt.Sprintf("deref_%d", len(s.derefFuncs)) + s.newDerefFuncs = append(s.newDerefFuncs, funcdesc{pp: pp, p: p, name: f, tag: tag}) + s.derefFuncs[tag] = f + return f +} + +func (s *genstate) genAssignFunc(p parm) string { + var pp parm + ppp := mkPointerParm(p) + pp = &ppp + b := bytes.NewBuffer(nil) + pp.Declare(b, "x", "", false) + tag := b.String() + f, ok := s.assignFuncs[tag] + if ok { + return f + } + f = fmt.Sprintf("retassign_%d", len(s.assignFuncs)) + s.newAssignFuncs = append(s.newAssignFuncs, funcdesc{pp: pp, p: p, name: f, tag: tag}) + s.assignFuncs[tag] = f + return f +} + +func (s *genstate) genAllocFunc(p parm) string { + var pp parm + ppp := mkPointerParm(p) + pp = &ppp + b := bytes.NewBuffer(nil) + pp.Declare(b, "x", "", false) + tag := b.String() + f, ok := s.allocFuncs[tag] + if ok { + return f + } + f = fmt.Sprintf("New_%d", len(s.allocFuncs)) + s.newAllocFuncs = append(s.newAllocFuncs, funcdesc{pp: pp, p: p, name: f, tag: tag}) + s.allocFuncs[tag] = f + return f +} + +func (s *genstate) genParamRef(p parm, idx int) string { + switch p.AddrTaken() { + case notAddrTaken: + return fmt.Sprintf("p%d", idx) + case addrTakenSimple, addrTakenHeap: + return fmt.Sprintf("(*ap%d)", idx) + case addrTakenPassed: + f := s.genParamDerefFunc(p) + return fmt.Sprintf("%s(ap%d)", f, idx) + default: + panic("bad") + } +} + +func (s *genstate) genReturnAssign(b *bytes.Buffer, r parm, idx int, val string) { + switch r.AddrTaken() { + case notAddrTaken: + b.WriteString(fmt.Sprintf(" r%d = %s\n", idx, val)) + case addrTakenSimple, addrTakenHeap: + b.WriteString(fmt.Sprintf(" (*ar%d) = %v\n", idx, val)) + case addrTakenPassed: + f := s.genAssignFunc(r) + b.WriteString(fmt.Sprintf(" %s(ar%d, %v)\n", f, idx, val)) + default: + panic("bad") + } +} + +func (s *genstate) emitParamElemCheck(f *funcdef, b *bytes.Buffer, p parm, pvar string, cvar string, paramidx int, elemidx int) { + if p.SkipCompare() == SkipAll { + b.WriteString(fmt.Sprintf(" // selective skip of %s\n", pvar)) + b.WriteString(fmt.Sprintf(" _ = %s\n", cvar)) + return + } else if p.SkipCompare() == SkipPayload { + switch p.(type) { + case *stringparm, *arrayparm: + b.WriteString(fmt.Sprintf(" if len(%s) != len(%s) { // skip payload\n", + pvar, cvar)) + default: + panic("should never happen") + } + } else { + basep, star := genDeref(p) + // Handle *p where p is an empty struct. + if basep.NumElements() == 0 { + return + } + if basep.HasPointer() { + efn := s.eqFuncRef(f, basep, false) + b.WriteString(fmt.Sprintf(" if !%s(%s%s, %s%s) {\n", + efn, star, pvar, star, cvar)) + } else { + b.WriteString(fmt.Sprintf(" if %s%s != %s%s {\n", + star, pvar, star, cvar)) + } + } + cm := f.complexityMeasure() + b.WriteString(fmt.Sprintf(" NoteFailureElem(%d, %d, %d, \"%s\", \"parm\", %d, %d, false, pad[0])\n", cm, s.pkidx, f.idx, s.checkerPkg(s.pkidx), paramidx, elemidx)) + b.WriteString(" return\n") + b.WriteString(" }\n") +} + +func (s *genstate) emitParamChecks(f *funcdef, b *bytes.Buffer, pidx int, value int) (int, bool) { + var valstr string + haveControl := false + dangling := []int{} + for pi, p := range f.params { + verb(4, "emitting parmcheck p%d numel=%d pt=%s value=%d", + pi, p.NumElements(), p.TypeName(), value) + // To balance code in caller + _ = uint8(s.wr.Intn(100)) < 50 + if p.IsControl() { + b.WriteString(fmt.Sprintf(" if %s == 0 {\n", + s.genParamRef(p, pi))) + s.emitReturn(f, b, false) + b.WriteString(" }\n") + haveControl = true + + } else if p.IsBlank() { + valstr, value = s.GenValue(f, p, value, false) + if f.recur { + b.WriteString(fmt.Sprintf(" brc%d := %s\n", pi, valstr)) + } else { + b.WriteString(fmt.Sprintf(" _ = %s\n", valstr)) + } + } else { + numel := p.NumElements() + cel := checkableElements(p) + for i := 0; i < numel; i++ { + verb(4, "emitting check-code for p%d el %d value=%d", pi, i, value) + elref, elparm := p.GenElemRef(i, s.genParamRef(p, pi)) + valstr, value = s.GenValue(f, elparm, value, false) + if elref == "" || elref == "_" || cel == 0 { + b.WriteString(fmt.Sprintf(" // blank skip: %s\n", valstr)) + continue + } else { + basep, _ := genDeref(elparm) + // Handle *p where p is an empty struct. + if basep.NumElements() == 0 { + continue + } + cvar := fmt.Sprintf("p%df%dc", pi, i) + b.WriteString(fmt.Sprintf(" %s := %s\n", cvar, valstr)) + s.emitParamElemCheck(f, b, elparm, elref, cvar, pi, i) + } + } + if p.AddrTaken() != notAddrTaken { + dangling = append(dangling, pi) + } + } + if value != f.values[pi] { + fmt.Fprintf(os.Stderr, "internal error: checker/caller value mismatch after emitting param %d func Test%d pkg %s: caller %d checker %d\n", pi, f.idx, s.checkerPkg(pidx), f.values[pi], value) + s.errs++ + } + } + for _, pi := range dangling { + b.WriteString(fmt.Sprintf(" _ = ap%d // ref\n", pi)) + } + + // receiver value check + if f.isMethod { + numel := f.receiver.NumElements() + for i := 0; i < numel; i++ { + verb(4, "emitting check-code for rcvr el %d value=%d", i, value) + elref, elparm := f.receiver.GenElemRef(i, "rcvr") + valstr, value = s.GenValue(f, elparm, value, false) + if elref == "" || strings.HasPrefix(elref, "_") || f.receiver.IsBlank() { + verb(4, "empty skip rcvr el %d", i) + continue + } else { + + basep, _ := genDeref(elparm) + // Handle *p where p is an empty struct. + if basep.NumElements() == 0 { + continue + } + cvar := fmt.Sprintf("rcvrf%dc", i) + b.WriteString(fmt.Sprintf(" %s := %s\n", cvar, valstr)) + s.emitParamElemCheck(f, b, elparm, elref, cvar, -1, i) + } + } + } + + return value, haveControl +} + +// emitDeferChecks creates code like +// +// defer func(...args...) { +// check arg +// check param +// }(...) +// +// where we randomly choose to either pass a param through to the +// function literal, or have the param captured by the closure, then +// check its value in the defer. +func (s *genstate) emitDeferChecks(f *funcdef, b *bytes.Buffer, pidx int, value int) int { + + if len(f.params) == 0 { + return value + } + + // make a pass through the params and randomly decide which will be passed into the func. + passed := []bool{} + for i := range f.params { + p := f.dodefp[i] < 50 + passed = append(passed, p) + } + + b.WriteString(" defer func(") + pc := 0 + for pi, p := range f.params { + if p.IsControl() || p.IsBlank() { + continue + } + if passed[pi] { + writeCom(b, pc) + n := fmt.Sprintf("p%d", pi) + p.Declare(b, n, "", false) + pc++ + } + } + b.WriteString(") {\n") + + for pi, p := range f.params { + if p.IsControl() || p.IsBlank() { + continue + } + which := "passed" + if !passed[pi] { + which = "captured" + } + b.WriteString(" // check parm " + which + "\n") + numel := p.NumElements() + cel := checkableElements(p) + for i := 0; i < numel; i++ { + elref, elparm := p.GenElemRef(i, s.genParamRef(p, pi)) + if elref == "" || elref == "_" || cel == 0 { + verb(4, "empty skip p%d el %d", pi, i) + continue + } else { + basep, _ := genDeref(elparm) + // Handle *p where p is an empty struct. + if basep.NumElements() == 0 { + continue + } + cvar := fmt.Sprintf("p%df%dc", pi, i) + s.emitParamElemCheck(f, b, elparm, elref, cvar, pi, i) + } + } + } + b.WriteString(" } (") + pc = 0 + for pi, p := range f.params { + if p.IsControl() || p.IsBlank() { + continue + } + if passed[pi] { + writeCom(b, pc) + b.WriteString(fmt.Sprintf("p%d", pi)) + pc++ + } + } + b.WriteString(")\n\n") + + return value +} + +func (s *genstate) emitVarAssign(f *funcdef, b *bytes.Buffer, r parm, rname string, value int, caller bool) int { + var valstr string + isassign := uint8(s.wr.Intn(100)) < 50 + if rmp, ismap := r.(*mapparm); ismap && isassign { + // emit: var m ... ; m[k] = v + r.Declare(b, " "+rname+" := make(", ")\n", caller) + valstr, value = s.GenValue(f, rmp.valtype, value, caller) + b.WriteString(fmt.Sprintf(" %s[mkt.%s] = %s\n", + rname, rmp.keytmp, valstr)) + } else { + // emit r = c + valstr, value = s.GenValue(f, r, value, caller) + b.WriteString(fmt.Sprintf(" %s := %s\n", rname, valstr)) + } + return value +} + +func (s *genstate) emitChecker(f *funcdef, b *bytes.Buffer, pidx int, emit bool) { + verb(4, "emitting struct and array defs") + s.emitStructAndArrayDefs(f, b) + b.WriteString(fmt.Sprintf("// %d returns %d params\n", len(f.returns), len(f.params))) + if s.Pragma != "" { + b.WriteString("//go:" + s.Pragma + "\n") + } + b.WriteString("//go:noinline\n") + + b.WriteString("func") + + if f.isMethod { + b.WriteString(" (") + n := "rcvr" + if f.receiver.IsBlank() { + n = "_" + } + f.receiver.Declare(b, n, "", false) + b.WriteString(")") + } + + b.WriteString(fmt.Sprintf(" Test%d(", f.idx)) + + verb(4, "emitting checker p%d/Test%d", pidx, f.idx) + + // params + for pi, p := range f.params { + writeCom(b, pi) + n := fmt.Sprintf("p%d", pi) + if p.IsBlank() { + n = "_" + } + p.Declare(b, n, "", false) + } + b.WriteString(") ") + + // returns + if len(f.returns) > 0 { + b.WriteString("(") + } + for ri, r := range f.returns { + writeCom(b, ri) + r.Declare(b, fmt.Sprintf("r%d", ri), "", false) + } + if len(f.returns) > 0 { + b.WriteString(")") + } + b.WriteString(" {\n") + + // local storage + b.WriteString(" // consume some stack space, so as to trigger morestack\n") + b.WriteString(fmt.Sprintf(" var pad [%d]uint64\n", f.rstack)) + b.WriteString(fmt.Sprintf(" pad[FailCount[%d] & 0x1]++\n", pidx)) + + value := 1 + + // generate map key tmps + s.wr.Checkpoint("before map key temps") + value = s.emitMapKeyTmps(f, b, pidx, value, false) + + // generate return constants + s.wr.Checkpoint("before return constants") + for ri, r := range f.returns { + rc := fmt.Sprintf("rc%d", ri) + value = s.emitVarAssign(f, b, r, rc, value, false) + } + + // Prepare to reference params/returns by address. + lists := [][]parm{f.params, f.returns} + names := []string{"p", "r"} + var aCounts [2]int + for i, lst := range lists { + for pi, p := range lst { + if p.AddrTaken() == notAddrTaken { + continue + } + aCounts[i]++ + n := names[i] + b.WriteString(fmt.Sprintf(" a%s%d := &%s%d\n", n, pi, n, pi)) + if p.AddrTaken() == addrTakenHeap { + gv := s.genGlobVar(p) + b.WriteString(fmt.Sprintf(" %s = a%s%d\n", gv, n, pi)) + } + } + } + + if s.EmitBad == 2 { + if s.BadPackageIdx == pidx && s.BadFuncIdx == f.idx { + b.WriteString(" // force runtime failure here (debugging)\n") + b.WriteString(fmt.Sprintf(" NoteFailure(%d, %d, %d, \"%s\", \"artificial\", %d, true, uint64(0))\n", f.complexityMeasure(), pidx, f.idx, s.checkerPkg(pidx), 0)) + } + } + + // parameter checking code + var haveControl bool + s.wr.Checkpoint("before param checks") + value, haveControl = s.emitParamChecks(f, b, pidx, value) + + // defer testing + if s.tunables.doDefer && f.dodefc < s.tunables.deferFraction { + s.wr.Checkpoint("before defer checks") + _ = s.emitDeferChecks(f, b, pidx, value) + } + + // returns + s.emitReturn(f, b, haveControl) + + b.WriteString(fmt.Sprintf(" // %d addr-taken params, %d addr-taken returns\n", + aCounts[0], aCounts[1])) + + b.WriteString("}\n\n") + + // emit any new helper funcs referenced by this test function + s.emitAddrTakenHelpers(f, b, emit) +} + +// complexityMeasure returns an integer that estimates how complex a +// given test function is relative to some other function. The more +// parameters + returns and the more complicated the types of the +// params/returns, the higher the number returned here. In theory this +// could be worked into the minimization process (e.g. pick the least +// complex func that reproduces the failure), but for now that isn't +// wired up yet. +func (f *funcdef) complexityMeasure() int { + v := int(0) + if f.isMethod { + v += f.receiver.NumElements() + } + for _, p := range f.params { + v += p.NumElements() + } + for _, r := range f.returns { + v += r.NumElements() + } + return v +} + +// emitRecursiveCall generates a recursive call to the test function in question. +func (s *genstate) emitRecursiveCall(f *funcdef) string { + b := bytes.NewBuffer(nil) + rcvr := "" + if f.isMethod { + rcvr = "rcvr." + } + b.WriteString(fmt.Sprintf(" %sTest%d(", rcvr, f.idx)) + for pi, p := range f.params { + writeCom(b, pi) + if p.IsControl() { + b.WriteString(fmt.Sprintf(" %s-1", s.genParamRef(p, pi))) + } else { + if !p.IsBlank() { + b.WriteString(fmt.Sprintf(" %s", s.genParamRef(p, pi))) + } else { + b.WriteString(fmt.Sprintf(" brc%d", pi)) + } + } + } + b.WriteString(")") + return b.String() +} + +// emitReturn generates a return sequence. +func (s *genstate) emitReturn(f *funcdef, b *bytes.Buffer, doRecursiveCall bool) { + // If any of the return values are address-taken, then instead of + // + // return x, y, z + // + // we emit + // + // r1 = ... + // r2 = ... + // ... + // return + // + // Make an initial pass through the returns to see if we need to do this. + // Figure out the final return values in the process. + indirectReturn := false + retvals := []string{} + for ri, r := range f.returns { + if r.AddrTaken() != notAddrTaken { + indirectReturn = true + } + t := "" + if doRecursiveCall { + t = "t" + } + retvals = append(retvals, fmt.Sprintf("rc%s%d", t, ri)) + } + + // generate the recursive call itself if applicable + if doRecursiveCall { + b.WriteString(" // recursive call\n ") + if s.ForceStackGrowth { + b.WriteString(" hackStack() // force stack growth on next call\n") + } + rcall := s.emitRecursiveCall(f) + if indirectReturn { + for ri := range f.returns { + writeCom(b, ri) + b.WriteString(fmt.Sprintf(" rct%d", ri)) + } + b.WriteString(" := ") + b.WriteString(rcall) + b.WriteString("\n") + } else { + if len(f.returns) == 0 { + b.WriteString(fmt.Sprintf("%s\n return\n", rcall)) + } else { + b.WriteString(fmt.Sprintf(" return %s\n", rcall)) + } + return + } + } + + // now the actual return + if indirectReturn { + for ri, r := range f.returns { + s.genReturnAssign(b, r, ri, retvals[ri]) + } + b.WriteString(" return\n") + } else { + b.WriteString(" return ") + for ri := range f.returns { + writeCom(b, ri) + b.WriteString(retvals[ri]) + } + b.WriteString("\n") + } +} + +func (s *genstate) GenPair(calloutfile *os.File, checkoutfile *os.File, fidx int, pidx int, b *bytes.Buffer, seed int64, emit bool) int64 { + + verb(1, "gen fidx %d pidx %d", fidx, pidx) + + checkTunables(tunables) + s.tunables = tunables + + // Generate a function with a random number of params and returns + s.wr = NewWrapRand(seed, s.RandCtl) + s.wr.tag = "genfunc" + fp := s.GenFunc(fidx, pidx) + + // Emit caller side + wrcaller := NewWrapRand(seed, s.RandCtl) + s.wr = wrcaller + s.wr.tag = "caller" + s.emitCaller(fp, b, pidx) + if emit { + b.WriteTo(calloutfile) + } + b.Reset() + + // Emit checker side + wrchecker := NewWrapRand(seed, s.RandCtl) + s.wr = wrchecker + s.wr.tag = "checker" + s.emitChecker(fp, b, pidx, emit) + if emit { + b.WriteTo(checkoutfile) + } + b.Reset() + wrchecker.Check(wrcaller) + + return seed + 1 +} + +func (s *genstate) openOutputFile(filename string, pk string, imports []string, ipref string) *os.File { + iprefix := func(f string) string { + if ipref == "" { + return f + } + return ipref + "/" + f + } + verb(1, "opening %s", filename) + outf, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666) + if err != nil { + log.Fatal(err) + } + haveunsafe := false + outf.WriteString(fmt.Sprintf("package %s\n\n", pk)) + for _, imp := range imports { + if imp == "reflect" { + outf.WriteString("import \"reflect\"\n") + continue + } + if imp == "unsafe" { + outf.WriteString("import _ \"unsafe\"\n") + haveunsafe = true + continue + } + if imp == s.utilsPkg() { + + outf.WriteString(fmt.Sprintf("import . \"%s\"\n", iprefix(imp))) + continue + } + outf.WriteString(fmt.Sprintf("import \"%s\"\n", iprefix(imp))) + } + outf.WriteString("\n") + if s.ForceStackGrowth && haveunsafe { + outf.WriteString("// Hack: reach into runtime to grab this testing hook.\n") + outf.WriteString("//go:linkname hackStack runtime.gcTestMoveStackOnNextCall\n") + outf.WriteString("func hackStack()\n\n") + } + return outf +} + +type miscVals struct { + NumTpk int + MaxFail int + NumTests int +} + +const utilsTemplate = ` + +import ( + "fmt" + "os" +) + +type UtilsType int +var ParamFailCount [{{.NumTpk}}]int +var ReturnFailCount [{{.NumTpk}}]int +var FailCount [{{.NumTpk}}]int +var Mode [{{.NumTpk}}]string + +//go:noinline +func NoteFailure(cm int, pidx int, fidx int, pkg string, pref string, parmNo int, isret bool, _ uint64) { + if isret { + if ParamFailCount[pidx] != 0 { + return + } + ReturnFailCount[pidx]++ + } else { + ParamFailCount[pidx]++ + } + fmt.Fprintf(os.Stderr, "Error: fail %s |%d|%d|%d| =%s.Test%d= %s %d\n", Mode, cm, pidx, fidx, pkg, fidx, pref, parmNo) + + if ParamFailCount[pidx]+FailCount[pidx]+ReturnFailCount[pidx] > {{.MaxFail}} { + os.Exit(1) + } +} + +//go:noinline +func NoteFailureElem(cm int, pidx int, fidx int, pkg string, pref string, parmNo int, elem int, isret bool, _ uint64) { + + if isret { + if ParamFailCount[pidx] != 0 { + return + } + ReturnFailCount[pidx]++ + } else { + ParamFailCount[pidx]++ + } + fmt.Fprintf(os.Stderr, "Error: fail %s |%d|%d|%d| =%s.Test%d= %s %d elem %d\n", Mode, cm, pidx, fidx, pkg, fidx, pref, parmNo, elem) + + if ParamFailCount[pidx]+FailCount[pidx]+ReturnFailCount[pidx] > {{.MaxFail}} { + os.Exit(1) + } +} + +func BeginFcn(p int) { + ParamFailCount[p] = 0 + ReturnFailCount[p] = 0 +} + +func EndFcn(p int) { + FailCount[p] += ParamFailCount[p] + FailCount[p] += ReturnFailCount[p] +} +` + +func (s *genstate) emitUtils(outf *os.File, maxfail int, numtpk int) { + vals := miscVals{ + NumTpk: numtpk, + MaxFail: maxfail, + } + t := template.Must(template.New("utils").Parse(utilsTemplate)) + err := t.Execute(outf, vals) + if err != nil { + log.Fatal(err) + } +} + +const mainPreamble = ` + +import ( + "fmt" + "os" +) + +func main() { + fmt.Fprintf(os.Stderr, "starting main\n") +` + +func (s *genstate) emitMain(outf *os.File, numit int, fcnmask map[int]int, pkmask map[int]int) { + fmt.Fprintf(outf, "%s", mainPreamble) + fmt.Fprintf(outf, " pch := make(chan bool, %d)\n", s.NumTestPackages) + for k := 0; k < s.NumTestPackages; k++ { + cp := fmt.Sprintf("%s%s%d", s.Tag, CallerName, k) + fmt.Fprintf(outf, " go func(ch chan bool) {\n") + for i := 0; i < numit; i++ { + if shouldEmitFP(i, k, fcnmask, pkmask) { + fmt.Fprintf(outf, " %s.%s%d(\"normal\")\n", cp, CallerName, i) + if s.tunables.doReflectCall { + fmt.Fprintf(outf, " %s.%s%d(\"reflect\")\n", cp, CallerName, i) + } + } + } + fmt.Fprintf(outf, " pch <- true\n") + fmt.Fprintf(outf, " }(pch)\n") + } + fmt.Fprintf(outf, " for pidx := 0; pidx < %d; pidx++ {\n", s.NumTestPackages) + fmt.Fprintf(outf, " _ = <- pch\n") + fmt.Fprintf(outf, " }\n") + fmt.Fprintf(outf, " tf := 0\n") + fmt.Fprintf(outf, " for pidx := 0; pidx < %d; pidx++ {\n", s.NumTestPackages) + fmt.Fprintf(outf, " tf += FailCount[pidx]\n") + fmt.Fprintf(outf, " }\n") + fmt.Fprintf(outf, " if tf != 0 {\n") + fmt.Fprintf(outf, " fmt.Fprintf(os.Stderr, \"FAILURES: %%d\\n\", tf)\n") + fmt.Fprintf(outf, " os.Exit(2)\n") + fmt.Fprintf(outf, " }\n") + fmt.Fprintf(outf, " fmt.Fprintf(os.Stderr, \"finished %d tests\\n\")\n", numit*s.NumTestPackages) + fmt.Fprintf(outf, "}\n") +} + +func makeDir(d string) { + fi, err := os.Stat(d) + if err == nil && fi.IsDir() { + return + } + verb(1, "creating %s", d) + if err := os.Mkdir(d, 0777); err != nil { + log.Fatal(err) + } +} + +func (s *genstate) callerPkg(which int) string { + return s.Tag + CallerName + strconv.Itoa(which) +} + +func (s *genstate) callerFile(which int) string { + cp := s.callerPkg(which) + return filepath.Join(s.OutDir, cp, cp+".go") +} + +func (s *genstate) checkerPkg(which int) string { + return s.Tag + CheckerName + strconv.Itoa(which) +} + +func (s *genstate) checkerFile(which int) string { + cp := s.checkerPkg(which) + return filepath.Join(s.OutDir, cp, cp+".go") +} + +func (s *genstate) utilsPkg() string { + return s.Tag + "Utils" +} + +func (s *genstate) beginPackage(pkidx int) { + s.pkidx = pkidx + s.derefFuncs = make(map[string]string) + s.assignFuncs = make(map[string]string) + s.allocFuncs = make(map[string]string) + s.globVars = make(map[string]string) + s.genvalFuncs = make(map[string]string) +} + +func runImports(files []string) { + verb(1, "... running goimports") + args := make([]string, 0, len(files)+1) + args = append(args, "-w") + args = append(args, files...) + cmd := exec.Command("goimports", args...) + coutput, cerr := cmd.CombinedOutput() + if cerr != nil { + log.Fatalf("goimports command failed: %s", string(coutput)) + } + verb(1, "... goimports run complete") +} + +// shouldEmitFP returns true if we should actually emit code for the function +// with the specified package + fcn indices. For "regular" runs, fcnmask and pkmask +// will be empty, meaning we want to emit every function in every package. The +// fuzz-runner program also tries to do testcase "minimization", which means that it +// will try to whittle down the set of packages and functions (by running the generator +// using the fcnmask and pkmask options) to emit only specific packages or functions. +func shouldEmitFP(fn int, pk int, fcnmask map[int]int, pkmask map[int]int) bool { + emitpk := true + emitfn := true + if len(pkmask) != 0 { + emitpk = false + if _, ok := pkmask[pk]; ok { + emitpk = true + } + } + if len(fcnmask) != 0 { + emitfn = false + if _, ok := fcnmask[fn]; ok { + emitfn = true + } + } + doemit := emitpk && emitfn + verb(2, "shouldEmitFP(F=%d,P=%d) returns %v", fn, pk, doemit) + return doemit +} + +// Generate is the top level code generation hook for this package. +// Emits code according to the schema in config object 'c'. +func Generate(c GenConfig) int { + mainpkg := c.Tag + "Main" + + var ipref string + if len(c.PkgPath) > 0 { + ipref = c.PkgPath + } + + s := genstate{ + GenConfig: c, + ipref: ipref, + } + + if s.OutDir != "." { + verb(1, "creating %s", s.OutDir) + makeDir(s.OutDir) + } + + mainimports := []string{} + for i := 0; i < s.NumTestPackages; i++ { + if shouldEmitFP(-1, i, nil, s.PkgMask) { + makeDir(s.OutDir + "/" + s.callerPkg(i)) + makeDir(s.OutDir + "/" + s.checkerPkg(i)) + makeDir(s.OutDir + "/" + s.utilsPkg()) + mainimports = append(mainimports, s.callerPkg(i)) + } + } + mainimports = append(mainimports, s.utilsPkg()) + + // Emit utils package. + verb(1, "emit utils") + utilsfile := s.OutDir + "/" + s.utilsPkg() + "/" + s.utilsPkg() + ".go" + utilsoutfile := s.openOutputFile(utilsfile, s.utilsPkg(), []string{}, "") + s.emitUtils(utilsoutfile, s.MaxFail, s.NumTestPackages) + utilsoutfile.Close() + + mainfile := s.OutDir + "/" + mainpkg + ".go" + mainoutfile := s.openOutputFile(mainfile, "main", mainimports, ipref) + + allfiles := []string{mainfile, utilsfile} + for k := 0; k < s.NumTestPackages; k++ { + callerImports := []string{s.checkerPkg(k), s.utilsPkg()} + checkerImports := []string{s.utilsPkg()} + if tunables.doReflectCall { + callerImports = append(callerImports, "reflect") + } + if s.ForceStackGrowth { + callerImports = append(callerImports, "unsafe") + checkerImports = append(checkerImports, "unsafe") + } + var calleroutfile, checkeroutfile *os.File + if shouldEmitFP(-1, k, nil, s.PkgMask) { + calleroutfile = s.openOutputFile(s.callerFile(k), s.callerPkg(k), + callerImports, ipref) + checkeroutfile = s.openOutputFile(s.checkerFile(k), s.checkerPkg(k), + checkerImports, ipref) + allfiles = append(allfiles, s.callerFile(k), s.checkerFile(k)) + } + + s.beginPackage(k) + + var b bytes.Buffer + for i := 0; i < s.NumTestFunctions; i++ { + doemit := shouldEmitFP(i, k, s.FcnMask, s.PkgMask) + s.Seed = s.GenPair(calleroutfile, checkeroutfile, i, k, + &b, s.Seed, doemit) + } + + // When minimization is in effect, we sometimes wind + // up eliminating all refs to the utils package. Add a + // dummy to help with this. + fmt.Fprintf(calleroutfile, "\n// dummy\nvar Dummy UtilsType\n") + fmt.Fprintf(checkeroutfile, "\n// dummy\nvar Dummy UtilsType\n") + calleroutfile.Close() + checkeroutfile.Close() + } + s.emitMain(mainoutfile, s.NumTestFunctions, s.FcnMask, s.PkgMask) + + // emit go.mod + verb(1, "opening go.mod") + fn := s.OutDir + "/go.mod" + outf, err := os.OpenFile(fn, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666) + if err != nil { + log.Fatal(err) + } + outf.WriteString(fmt.Sprintf("module %s\n\ngo 1.17\n", s.PkgPath)) + outf.Close() + + verb(1, "closing files") + mainoutfile.Close() + + if s.errs == 0 && s.RunGoImports { + runImports(allfiles) + } + + return s.errs +} diff --git a/cmd/signature-fuzzer/internal/fuzz-generator/mapparm.go b/cmd/signature-fuzzer/internal/fuzz-generator/mapparm.go new file mode 100644 index 0000000000..96264750e8 --- /dev/null +++ b/cmd/signature-fuzzer/internal/fuzz-generator/mapparm.go @@ -0,0 +1,91 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package generator + +import ( + "bytes" + "fmt" +) + +// mapparm describes a parameter of map type; it implements the +// "parm" interface. +type mapparm struct { + aname string + qname string + keytype parm + valtype parm + keytmp string + isBlank + addrTakenHow + isGenValFunc + skipCompare +} + +func (p mapparm) IsControl() bool { + return false +} + +func (p mapparm) TypeName() string { + return p.aname +} + +func (p mapparm) QualName() string { + return p.qname +} + +func (p mapparm) Declare(b *bytes.Buffer, prefix string, suffix string, caller bool) { + n := p.aname + if caller { + n = p.qname + } + b.WriteString(fmt.Sprintf("%s %s%s", prefix, n, suffix)) +} + +func (p mapparm) String() string { + return fmt.Sprintf("%s map[%s]%s", p.aname, + p.keytype.String(), p.valtype.String()) +} + +func (p mapparm) GenValue(s *genstate, f *funcdef, value int, caller bool) (string, int) { + var buf bytes.Buffer + + verb(5, "mapparm.GenValue(%d)", value) + + n := p.aname + if caller { + n = p.qname + } + buf.WriteString(fmt.Sprintf("%s{", n)) + buf.WriteString(p.keytmp + ": ") + + var valstr string + valstr, value = s.GenValue(f, p.valtype, value, caller) + buf.WriteString(valstr + "}") + return buf.String(), value +} + +func (p mapparm) GenElemRef(elidx int, path string) (string, parm) { + vne := p.valtype.NumElements() + verb(4, "begin GenElemRef(%d,%s) on %s %d", elidx, path, p.String(), vne) + + ppath := fmt.Sprintf("%s[mkt.%s]", path, p.keytmp) + + // otherwise dig into the value + verb(4, "recur GenElemRef(%d,...)", elidx) + + // Otherwise our victim is somewhere inside the value + if p.IsBlank() { + ppath = "_" + } + return p.valtype.GenElemRef(elidx, ppath) +} + +func (p mapparm) NumElements() int { + return p.valtype.NumElements() +} + +func (p mapparm) HasPointer() bool { + return true +} diff --git a/cmd/signature-fuzzer/internal/fuzz-generator/numparm.go b/cmd/signature-fuzzer/internal/fuzz-generator/numparm.go new file mode 100644 index 0000000000..6be0d912a1 --- /dev/null +++ b/cmd/signature-fuzzer/internal/fuzz-generator/numparm.go @@ -0,0 +1,144 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package generator + +import ( + "bytes" + "fmt" + "math" +) + +// numparm describes a numeric parameter type; it implements the +// "parm" interface. +type numparm struct { + tag string + widthInBits uint32 + ctl bool + isBlank + addrTakenHow + isGenValFunc + skipCompare +} + +var f32parm *numparm = &numparm{ + tag: "float", + widthInBits: uint32(32), + ctl: false, +} +var f64parm *numparm = &numparm{ + tag: "float", + widthInBits: uint32(64), + ctl: false, +} + +func (p numparm) TypeName() string { + if p.tag == "byte" { + return "byte" + } + return fmt.Sprintf("%s%d", p.tag, p.widthInBits) +} + +func (p numparm) QualName() string { + return p.TypeName() +} + +func (p numparm) String() string { + if p.tag == "byte" { + return "byte" + } + ctl := "" + if p.ctl { + ctl = " [ctl=yes]" + } + return fmt.Sprintf("%s%s", p.TypeName(), ctl) +} + +func (p numparm) NumElements() int { + return 1 +} + +func (p numparm) IsControl() bool { + return p.ctl +} + +func (p numparm) GenElemRef(elidx int, path string) (string, parm) { + return path, &p +} + +func (p numparm) Declare(b *bytes.Buffer, prefix string, suffix string, caller bool) { + t := fmt.Sprintf("%s%d%s", p.tag, p.widthInBits, suffix) + if p.tag == "byte" { + t = fmt.Sprintf("%s%s", p.tag, suffix) + } + b.WriteString(prefix + " " + t) +} + +func (p numparm) genRandNum(s *genstate, value int) (string, int) { + which := uint8(s.wr.Intn(int64(100))) + if p.tag == "int" { + var v int64 + if which < 3 { + // max + v = (1 << (p.widthInBits - 1)) - 1 + + } else if which < 5 { + // min + v = (-1 << (p.widthInBits - 1)) + } else { + nrange := int64(1 << (p.widthInBits - 2)) + v = s.wr.Intn(nrange) + if value%2 != 0 { + v = -v + } + } + return fmt.Sprintf("%s%d(%d)", p.tag, p.widthInBits, v), value + 1 + } + if p.tag == "uint" || p.tag == "byte" { + nrange := int64(1 << (p.widthInBits - 2)) + v := s.wr.Intn(nrange) + if p.tag == "byte" { + return fmt.Sprintf("%s(%d)", p.tag, v), value + 1 + } + return fmt.Sprintf("%s%d(0x%x)", p.tag, p.widthInBits, v), value + 1 + } + if p.tag == "float" { + if p.widthInBits == 32 { + rf := s.wr.Float32() * (math.MaxFloat32 / 4) + if value%2 != 0 { + rf = -rf + } + return fmt.Sprintf("%s%d(%v)", p.tag, p.widthInBits, rf), value + 1 + } + if p.widthInBits == 64 { + return fmt.Sprintf("%s%d(%v)", p.tag, p.widthInBits, + s.wr.NormFloat64()), value + 1 + } + panic("unknown float type") + } + if p.tag == "complex" { + if p.widthInBits == 64 { + f1, v2 := f32parm.genRandNum(s, value) + f2, v3 := f32parm.genRandNum(s, v2) + return fmt.Sprintf("complex(%s,%s)", f1, f2), v3 + } + if p.widthInBits == 128 { + f1, v2 := f64parm.genRandNum(s, value) + f2, v3 := f64parm.genRandNum(s, v2) + return fmt.Sprintf("complex(%v,%v)", f1, f2), v3 + } + panic("unknown complex type") + } + panic("unknown numeric type") +} + +func (p numparm) GenValue(s *genstate, f *funcdef, value int, caller bool) (string, int) { + r, nv := p.genRandNum(s, value) + verb(5, "numparm.GenValue(%d) = %s", value, r) + return r, nv +} + +func (p numparm) HasPointer() bool { + return false +} diff --git a/cmd/signature-fuzzer/internal/fuzz-generator/parm.go b/cmd/signature-fuzzer/internal/fuzz-generator/parm.go new file mode 100644 index 0000000000..7ee2224fad --- /dev/null +++ b/cmd/signature-fuzzer/internal/fuzz-generator/parm.go @@ -0,0 +1,216 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package generator + +import ( + "bytes" + "fmt" + "os" + "sort" +) + +// parm is an interface describing an abstract parameter var or return +// var; there will be concrete types of various sorts that implement +// this interface. +type parm interface { + + // Declare emits text containing a declaration of this param + // or return var into the specified buffer. Prefix is a tag to + // prepend before the declaration (for example a variable + // name) followed by a space; suffix is an arbitrary string to + // tack onto the end of the param's type text. Here 'caller' + // is set to true if we're emitting the caller part of a test + // pair as opposed to the checker. + Declare(b *bytes.Buffer, prefix string, suffix string, caller bool) + + // GenElemRef returns a pair [X,Y] corresponding to a + // component piece of some composite parm, where X is a string + // forming the reference (ex: ".field" if we're picking out a + // struct field) and Y is a parm object corresponding to the + // type of the element. + GenElemRef(elidx int, path string) (string, parm) + + // GenValue constructs a new concrete random value appropriate + // for the type in question and returns it, along with a + // sequence number indicating how many random decisions we had + // to make. Here "s" is the current generator state, "f" is + // the current function we're emitting, value is a sequence + // number indicating how many random decisions have been made + // up until this point, and 'caller' is set to true if we're + // emitting the caller part of a test pair as opposed to the + // checker. Return value is a pair [V,I] where V is the text + // if the value, and I is a new sequence number reflecting any + // additional random choices we had to make. For example, if + // the parm is something like "type Foo struct { f1 int32; f2 + // float64 }" then we might expect GenValue to emit something + // like "Foo{int32(-9), float64(123.123)}". + GenValue(s *genstate, f *funcdef, value int, caller bool) (string, int) + + // IsControl returns true if this specific param has been marked + // as the single param that controls recursion for a recursive + // checker function. The test code doesn't check this param for a specific + // value, but instead returns early if it has value 0 or decrements it + // on a recursive call. + IsControl() bool + + // NumElements returns the total number of discrete elements contained + // in this parm. For non-composite types, this will always be 1. + NumElements() int + + // String returns a descriptive string for this parm. + String() string + + // TypeName returns the non-qualified type name for this parm. + TypeName() string + + // QualName returns a package-qualified type name for this parm. + QualName() string + + // HasPointer returns true if this parm is of pointer type, or + // if it is a composite that has a pointer element somewhere inside. + // Strings and slices return true for this hook. + HasPointer() bool + + // IsBlank() returns true if the name of this parm is "_" (that is, + // if we randomly chose to make it a blank). SetBlank() is used + // to set the 'blank' property for this parm. + IsBlank() bool + SetBlank(v bool) + + // AddrTaken() return a token indicating whether this parm should + // be address taken or not, the nature of the address-taken-ness (see + // below at the def of addrTakenHow). SetAddrTaken is used to set + // the address taken property of the parm. + AddrTaken() addrTakenHow + SetAddrTaken(val addrTakenHow) + + // IsGenVal() returns true if the values of this type should + // be obtained by calling a helper func, as opposed to + // emitting code inline (as one would for things like numeric + // types). SetIsGenVal is used to set the gen-val property of + // the parm. + IsGenVal() bool + SetIsGenVal(val bool) + + // SkipCompare() returns true if we've randomly decided that + // we don't want to compare the value for this param or + // return. SetSkipCompare is used to set the skip-compare + // property of the parm. + SkipCompare() skipCompare + SetSkipCompare(val skipCompare) +} + +type addrTakenHow uint8 + +const ( + // Param not address taken. + notAddrTaken addrTakenHow = 0 + + // Param address is taken and used for simple reads/writes. + addrTakenSimple addrTakenHow = 1 + + // Param address is taken and passed to a well-behaved function. + addrTakenPassed addrTakenHow = 2 + + // Param address is taken and stored to a global var. + addrTakenHeap addrTakenHow = 3 +) + +func (a *addrTakenHow) AddrTaken() addrTakenHow { + return *a +} + +func (a *addrTakenHow) SetAddrTaken(val addrTakenHow) { + *a = val +} + +type isBlank bool + +func (b *isBlank) IsBlank() bool { + return bool(*b) +} + +func (b *isBlank) SetBlank(val bool) { + *b = isBlank(val) +} + +type isGenValFunc bool + +func (g *isGenValFunc) IsGenVal() bool { + return bool(*g) +} + +func (g *isGenValFunc) SetIsGenVal(val bool) { + *g = isGenValFunc(val) +} + +type skipCompare int + +const ( + // Param not address taken. + SkipAll = -1 + SkipNone = 0 + SkipPayload = 1 +) + +func (s *skipCompare) SkipCompare() skipCompare { + return skipCompare(*s) +} + +func (s *skipCompare) SetSkipCompare(val skipCompare) { + *s = skipCompare(val) +} + +// containedParms takes an arbitrary param 'p' and returns a slice +// with 'p' itself plus any component parms contained within 'p'. +func containedParms(p parm) []parm { + visited := make(map[string]parm) + worklist := []parm{p} + + addToWork := func(p parm) { + if p == nil { + panic("not expected") + } + if _, ok := visited[p.TypeName()]; !ok { + worklist = append(worklist, p) + } + } + + for len(worklist) != 0 { + cp := worklist[0] + worklist = worklist[1:] + if _, ok := visited[cp.TypeName()]; ok { + continue + } + visited[cp.TypeName()] = cp + switch x := cp.(type) { + case *mapparm: + addToWork(x.keytype) + addToWork(x.valtype) + case *structparm: + for _, fld := range x.fields { + addToWork(fld) + } + case *arrayparm: + addToWork(x.eltype) + case *pointerparm: + addToWork(x.totype) + case *typedefparm: + addToWork(x.target) + } + } + rv := []parm{} + for _, v := range visited { + rv = append(rv, v) + } + sort.Slice(rv, func(i, j int) bool { + if rv[i].TypeName() == rv[j].TypeName() { + fmt.Fprintf(os.Stderr, "%d %d %+v %+v %s %s\n", i, j, rv[i], rv[i].String(), rv[j], rv[j].String()) + panic("unexpected") + } + return rv[i].TypeName() < rv[j].TypeName() + }) + return rv +} diff --git a/cmd/signature-fuzzer/internal/fuzz-generator/pointerparm.go b/cmd/signature-fuzzer/internal/fuzz-generator/pointerparm.go new file mode 100644 index 0000000000..1ec61e51fc --- /dev/null +++ b/cmd/signature-fuzzer/internal/fuzz-generator/pointerparm.go @@ -0,0 +1,75 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package generator + +import ( + "bytes" + "fmt" +) + +// pointerparm describes a parameter of pointer type; it implements the +// "parm" interface. +type pointerparm struct { + tag string + totype parm + isBlank + addrTakenHow + isGenValFunc + skipCompare +} + +func (p pointerparm) Declare(b *bytes.Buffer, prefix string, suffix string, caller bool) { + n := p.totype.TypeName() + if caller { + n = p.totype.QualName() + } + b.WriteString(fmt.Sprintf("%s *%s%s", prefix, n, suffix)) +} + +func (p pointerparm) GenElemRef(elidx int, path string) (string, parm) { + return path, &p +} + +func (p pointerparm) GenValue(s *genstate, f *funcdef, value int, caller bool) (string, int) { + pref := "" + if caller { + pref = s.checkerPkg(s.pkidx) + "." + } + var valstr string + valstr, value = s.GenValue(f, p.totype, value, caller) + fname := s.genAllocFunc(p.totype) + return fmt.Sprintf("%s%s(%s)", pref, fname, valstr), value +} + +func (p pointerparm) IsControl() bool { + return false +} + +func (p pointerparm) NumElements() int { + return 1 +} + +func (p pointerparm) String() string { + return fmt.Sprintf("*%s", p.totype) +} + +func (p pointerparm) TypeName() string { + return fmt.Sprintf("*%s", p.totype.TypeName()) +} + +func (p pointerparm) QualName() string { + return fmt.Sprintf("*%s", p.totype.QualName()) +} + +func mkPointerParm(to parm) pointerparm { + var pp pointerparm + pp.tag = "pointer" + pp.totype = to + return pp +} + +func (p pointerparm) HasPointer() bool { + return true +} diff --git a/cmd/signature-fuzzer/internal/fuzz-generator/stringparm.go b/cmd/signature-fuzzer/internal/fuzz-generator/stringparm.go new file mode 100644 index 0000000000..2da541d4ac --- /dev/null +++ b/cmd/signature-fuzzer/internal/fuzz-generator/stringparm.go @@ -0,0 +1,64 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package generator + +import ( + "bytes" +) + +// stringparm describes a parameter of string type; it implements the +// "parm" interface +type stringparm struct { + tag string + isBlank + addrTakenHow + isGenValFunc + skipCompare +} + +func (p stringparm) Declare(b *bytes.Buffer, prefix string, suffix string, caller bool) { + b.WriteString(prefix + " string" + suffix) +} + +func (p stringparm) GenElemRef(elidx int, path string) (string, parm) { + return path, &p +} + +var letters = []rune("�꿦3򂨃f6ꂅ8ˋ<􂊇񊶿(z̽|ϣᇊ񁗇򟄼q񧲥筁{ЂƜĽ") + +func (p stringparm) GenValue(s *genstate, f *funcdef, value int, caller bool) (string, int) { + ns := len(letters) - 9 + nel := int(s.wr.Intn(8)) + st := int(s.wr.Intn(int64(ns))) + en := st + nel + if en > ns { + en = ns + } + return "\"" + string(letters[st:en]) + "\"", value + 1 +} + +func (p stringparm) IsControl() bool { + return false +} + +func (p stringparm) NumElements() int { + return 1 +} + +func (p stringparm) String() string { + return "string" +} + +func (p stringparm) TypeName() string { + return "string" +} + +func (p stringparm) QualName() string { + return "string" +} + +func (p stringparm) HasPointer() bool { + return false +} diff --git a/cmd/signature-fuzzer/internal/fuzz-generator/structparm.go b/cmd/signature-fuzzer/internal/fuzz-generator/structparm.go new file mode 100644 index 0000000000..df90107837 --- /dev/null +++ b/cmd/signature-fuzzer/internal/fuzz-generator/structparm.go @@ -0,0 +1,163 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package generator + +import ( + "bytes" + "fmt" + "strings" +) + +// structparm describes a parameter of struct type; it implements the +// "parm" interface. +type structparm struct { + sname string + qname string + fields []parm + isBlank + addrTakenHow + isGenValFunc + skipCompare +} + +func (p structparm) TypeName() string { + return p.sname +} + +func (p structparm) QualName() string { + return p.qname +} + +func (p structparm) Declare(b *bytes.Buffer, prefix string, suffix string, caller bool) { + n := p.sname + if caller { + n = p.qname + } + b.WriteString(fmt.Sprintf("%s %s%s", prefix, n, suffix)) +} + +func (p structparm) FieldName(i int) string { + if p.fields[i].IsBlank() { + return "_" + } + return fmt.Sprintf("F%d", i) +} + +func (p structparm) String() string { + var buf bytes.Buffer + + buf.WriteString(fmt.Sprintf("struct %s {\n", p.sname)) + for fi, f := range p.fields { + buf.WriteString(fmt.Sprintf("%s %s\n", p.FieldName(fi), f.String())) + } + buf.WriteString("}") + return buf.String() +} + +func (p structparm) GenValue(s *genstate, f *funcdef, value int, caller bool) (string, int) { + var buf bytes.Buffer + + verb(5, "structparm.GenValue(%d)", value) + + n := p.sname + if caller { + n = p.qname + } + buf.WriteString(fmt.Sprintf("%s{", n)) + nbfi := 0 + for fi, fld := range p.fields { + var valstr string + valstr, value = s.GenValue(f, fld, value, caller) + if p.fields[fi].IsBlank() { + buf.WriteString("/* ") + valstr = strings.ReplaceAll(valstr, "/*", "[[") + valstr = strings.ReplaceAll(valstr, "*/", "]]") + } else { + writeCom(&buf, nbfi) + } + buf.WriteString(p.FieldName(fi) + ": ") + buf.WriteString(valstr) + if p.fields[fi].IsBlank() { + buf.WriteString(" */") + } else { + nbfi++ + } + } + buf.WriteString("}") + return buf.String(), value +} + +func (p structparm) IsControl() bool { + return false +} + +func (p structparm) NumElements() int { + ne := 0 + for _, f := range p.fields { + ne += f.NumElements() + } + return ne +} + +func (p structparm) GenElemRef(elidx int, path string) (string, parm) { + ct := 0 + verb(4, "begin GenElemRef(%d,%s) on %s", elidx, path, p.String()) + + for fi, f := range p.fields { + fne := f.NumElements() + + //verb(4, "+ examining field %d fne %d ct %d", fi, fne, ct) + + // Empty field. Continue on. + if elidx == ct && fne == 0 { + continue + } + + // Is this field the element we're interested in? + if fne == 1 && elidx == ct { + + // The field in question may be a composite that has only + // multiple elements but a single non-zero-sized element. + // If this is the case, keep going. + if sp, ok := f.(*structparm); ok { + if len(sp.fields) > 1 { + ppath := fmt.Sprintf("%s.F%d", path, fi) + if p.fields[fi].IsBlank() || path == "_" { + ppath = "_" + } + return f.GenElemRef(elidx-ct, ppath) + } + } + + verb(4, "found field %d type %s in GenElemRef(%d,%s)", fi, f.TypeName(), elidx, path) + ppath := fmt.Sprintf("%s.F%d", path, fi) + if p.fields[fi].IsBlank() || path == "_" { + ppath = "_" + } + return ppath, f + } + + // Is the element we want somewhere inside this field? + if fne > 1 && elidx >= ct && elidx < ct+fne { + ppath := fmt.Sprintf("%s.F%d", path, fi) + if p.fields[fi].IsBlank() || path == "_" { + ppath = "_" + } + return f.GenElemRef(elidx-ct, ppath) + } + + ct += fne + } + panic(fmt.Sprintf("GenElemRef failed for struct %s elidx %d", p.TypeName(), elidx)) +} + +func (p structparm) HasPointer() bool { + for _, f := range p.fields { + if f.HasPointer() { + return true + } + } + return false +} diff --git a/cmd/signature-fuzzer/internal/fuzz-generator/typedefparm.go b/cmd/signature-fuzzer/internal/fuzz-generator/typedefparm.go new file mode 100644 index 0000000000..27cea64f4b --- /dev/null +++ b/cmd/signature-fuzzer/internal/fuzz-generator/typedefparm.go @@ -0,0 +1,90 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package generator + +import ( + "bytes" + "fmt" +) + +// typedefparm describes a parameter that is a typedef of some other +// type; it implements the "parm" interface +type typedefparm struct { + aname string + qname string + target parm + isBlank + addrTakenHow + isGenValFunc + skipCompare +} + +func (p typedefparm) Declare(b *bytes.Buffer, prefix string, suffix string, caller bool) { + n := p.aname + if caller { + n = p.qname + } + b.WriteString(fmt.Sprintf("%s %s%s", prefix, n, suffix)) +} + +func (p typedefparm) GenElemRef(elidx int, path string) (string, parm) { + _, isarr := p.target.(*arrayparm) + _, isstruct := p.target.(*structparm) + _, ismap := p.target.(*mapparm) + rv, rp := p.target.GenElemRef(elidx, path) + // this is hacky, but I don't see a nicer way to do this + if isarr || isstruct || ismap { + return rv, rp + } + rp = &p + return rv, rp +} + +func (p typedefparm) GenValue(s *genstate, f *funcdef, value int, caller bool) (string, int) { + n := p.aname + if caller { + n = p.qname + } + rv, v := s.GenValue(f, p.target, value, caller) + rv = n + "(" + rv + ")" + return rv, v +} + +func (p typedefparm) IsControl() bool { + return false +} + +func (p typedefparm) NumElements() int { + return p.target.NumElements() +} + +func (p typedefparm) String() string { + return fmt.Sprintf("%s typedef of %s", p.aname, p.target.String()) + +} + +func (p typedefparm) TypeName() string { + return p.aname + +} + +func (p typedefparm) QualName() string { + return p.qname +} + +func (p typedefparm) HasPointer() bool { + return p.target.HasPointer() +} + +func (s *genstate) makeTypedefParm(f *funcdef, target parm, pidx int) parm { + var tdp typedefparm + ns := len(f.typedefs) + tdp.aname = fmt.Sprintf("MyTypeF%dS%d", f.idx, ns) + tdp.qname = fmt.Sprintf("%s.MyTypeF%dS%d", s.checkerPkg(pidx), f.idx, ns) + tdp.target = target + tdp.SetBlank(uint8(s.wr.Intn(100)) < tunables.blankPerc) + f.typedefs = append(f.typedefs, tdp) + return &tdp +} diff --git a/cmd/signature-fuzzer/internal/fuzz-generator/wraprand.go b/cmd/signature-fuzzer/internal/fuzz-generator/wraprand.go new file mode 100644 index 0000000000..bba178dc31 --- /dev/null +++ b/cmd/signature-fuzzer/internal/fuzz-generator/wraprand.go @@ -0,0 +1,136 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package generator + +import ( + "fmt" + "math/rand" + "os" + "runtime" + "strings" +) + +const ( + RandCtlNochecks = 0 + RandCtlChecks = 1 << iota + RandCtlCapture + RandCtlPanic +) + +func NewWrapRand(seed int64, ctl int) *wraprand { + rand.Seed(seed) + return &wraprand{seed: seed, ctl: ctl} +} + +type wraprand struct { + f32calls int + f64calls int + intncalls int + seed int64 + tag string + calls []string + ctl int +} + +func (w *wraprand) captureCall(tag string, val string) { + call := tag + ": " + val + "\n" + pc := make([]uintptr, 10) + n := runtime.Callers(1, pc) + if n == 0 { + panic("why?") + } + pc = pc[:n] // pass only valid pcs to runtime.CallersFrames + frames := runtime.CallersFrames(pc) + for { + frame, more := frames.Next() + if strings.Contains(frame.File, "testing.") { + break + } + call += fmt.Sprintf("%s %s:%d\n", frame.Function, frame.File, frame.Line) + if !more { + break + } + + } + w.calls = append(w.calls, call) +} + +func (w *wraprand) Intn(n int64) int64 { + w.intncalls++ + rv := rand.Int63n(n) + if w.ctl&RandCtlCapture != 0 { + w.captureCall("Intn", fmt.Sprintf("%d", rv)) + } + return rv +} + +func (w *wraprand) Float32() float32 { + w.f32calls++ + rv := rand.Float32() + if w.ctl&RandCtlCapture != 0 { + w.captureCall("Float32", fmt.Sprintf("%f", rv)) + } + return rv +} + +func (w *wraprand) NormFloat64() float64 { + w.f64calls++ + rv := rand.NormFloat64() + if w.ctl&RandCtlCapture != 0 { + w.captureCall("NormFloat64", fmt.Sprintf("%f", rv)) + } + return rv +} + +func (w *wraprand) emitCalls(fn string) { + outf, err := os.OpenFile(fn, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666) + if err != nil { + panic(err) + } + for _, c := range w.calls { + fmt.Fprint(outf, c) + } + outf.Close() +} + +func (w *wraprand) Equal(w2 *wraprand) bool { + return w.f32calls == w2.f32calls && + w.f64calls == w2.f64calls && + w.intncalls == w2.intncalls +} + +func (w *wraprand) Check(w2 *wraprand) { + if w.ctl != 0 && !w.Equal(w2) { + fmt.Fprintf(os.Stderr, "wraprand consistency check failed:\n") + t := "w" + if w.tag != "" { + t = w.tag + } + t2 := "w2" + if w2.tag != "" { + t2 = w2.tag + } + fmt.Fprintf(os.Stderr, " %s: {f32:%d f64:%d i:%d}\n", t, + w.f32calls, w.f64calls, w.intncalls) + fmt.Fprintf(os.Stderr, " %s: {f32:%d f64:%d i:%d}\n", t2, + w2.f32calls, w2.f64calls, w2.intncalls) + if w.ctl&RandCtlCapture != 0 { + f := fmt.Sprintf("/tmp/%s.txt", t) + f2 := fmt.Sprintf("/tmp/%s.txt", t2) + w.emitCalls(f) + w2.emitCalls(f2) + fmt.Fprintf(os.Stderr, "=-= emitted calls to %s, %s\n", f, f2) + } + if w.ctl&RandCtlPanic != 0 { + panic("bad") + } + } +} + +func (w *wraprand) Checkpoint(tag string) { + if w.ctl&RandCtlCapture != 0 { + w.calls = append(w.calls, "=-=\n"+tag+"\n=-=\n") + } +}