Skip to content

Commit 287eabf

Browse files
committed
all: merge master (49d48a0) into gopls-release-branch.0.8
Also update gopls/go.mod with a replace directive. For golang/go#51659 Merge List: + 2022-03-14 49d48a0 go/analysis/passes/composite: allow InternalFuzzTarget + 2022-03-11 198cae3 go/ssa: split pkg() into different cases for *Package and *types.Package + 2022-03-10 ee31f70 internal/lsp: add completion for use directives + 2022-03-10 622cf7b internal/lsp/cache: copy workFile when invalidating workspace + 2022-03-10 e7a12a3 go/ssa: add type substitution + 2022-03-10 c773560 internal/lsp/cache: set GOWORK=off when mutating modfiles + 2022-03-10 613589d internal/lsp: more precise completions for *testing.F fuzz methods + 2022-03-10 9f99e95 go/analysis: add analyzer for f.Add + 2022-03-10 7b442e3 go/callgraph/vta: fix package doc + 2022-03-09 1670aad go/analysis: fix bug with finding field type + 2022-03-09 fd72fd6 go/ssa/interp: adding external functions for tests + 2022-03-09 b105aac internal/lsp: use regexp to add go mod edit -go quick fix + 2022-03-09 03d333a internal/lsp: add snippet completion for function type parameters + 2022-03-09 94322c4 go/internal/gcimporter: pass -p to compiler in test + 2022-03-07 b59c441 internal/lsp/cache: always consider go.work files for ws expansion + 2022-03-04 e155b03 cmd/getgo: exec main from TestMain instead of running 'go build' in tests + 2022-03-04 e562276 internal/lsp: add hover for go.work use statements + 2022-03-04 121d1e4 internal/lsp: report diagnostics on go.work files + 2022-03-04 0eabed7 cmd/file2fuzz: exec main from TestMain instead of running 'go build' in tests + 2022-03-04 19fe2d7 gopls: update xurls dependency, remove \b workaround + 2022-03-03 3ce7728 internal/lsp/source: support stubbing concrete type params + 2022-03-03 3286927 internal/lsp/cache: construct workspace even when go.work has error + 2022-03-02 09e0201 go/ssa: determine whether a type is parameterized. + 2022-03-02 e43402d go/ssa: changes Package.values to objects. + 2022-03-02 a972457 go/ssa: adds *types.Info field to ssa.Function. + 2022-03-02 fc47946 go/ssa: Move BasicBlock operations into block.go + 2022-03-02 7103138 gopls: add regtest for edit go directive quick fix + 2022-03-02 4a5e7f0 internal/lsp: correctly apply file edits for edit go directive + 2022-03-02 6a6eb59 go/ssa: Put type canonicalization on own mutex. + 2022-03-02 afc5fce gopls/doc: address additional comments on workspace.md + 2022-03-02 abbbcaf gopls/doc: update the documentation for workspaces to suggest go.work + 2022-03-02 72442fe gopls: update neovim documentation for imitating goimports + 2022-03-01 ffa170d internal/jsonrpc2_v2: fix a racy map assignment in readIncoming + 2022-03-01 625c871 gopls: update neovim documentation for using go.work + 2022-03-01 fb3622a signature-generator: add Go func signature fuzzing tools + 2022-03-01 5d35a75 internal/jsonrpc2_v2: clarify documentation Change-Id: Iff6ca42c407f6e63b3f99453d523dec4bcb44b1f
2 parents 86f5b03 + 49d48a0 commit 287eabf

File tree

95 files changed

+7292
-496
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

95 files changed

+7292
-496
lines changed

cmd/file2fuzz/main_test.go

Lines changed: 17 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -5,67 +5,41 @@
55
package main
66

77
import (
8-
"fmt"
98
"io/ioutil"
109
"os"
1110
"os/exec"
1211
"path/filepath"
13-
"runtime"
1412
"strings"
1513
"sync"
1614
"testing"
1715
)
1816

19-
// The setup for this test is mostly cribbed from x/exp/txtar.
17+
func TestMain(m *testing.M) {
18+
if os.Getenv("GO_FILE2FUZZ_TEST_IS_FILE2FUZZ") != "" {
19+
main()
20+
os.Exit(0)
21+
}
22+
23+
os.Exit(m.Run())
24+
}
2025

21-
var buildBin struct {
26+
var f2f struct {
2227
once sync.Once
23-
name string
28+
path string
2429
err error
2530
}
2631

27-
func binPath(t *testing.T) string {
28-
t.Helper()
29-
if _, err := exec.LookPath("go"); err != nil {
30-
t.Skipf("cannot build file2fuzz binary: %v", err)
31-
}
32-
33-
buildBin.once.Do(func() {
34-
exe, err := ioutil.TempFile("", "file2fuzz-*.exe")
35-
if err != nil {
36-
buildBin.err = err
37-
return
38-
}
39-
exe.Close()
40-
buildBin.name = exe.Name()
41-
42-
cmd := exec.Command("go", "build", "-o", buildBin.name, ".")
43-
out, err := cmd.CombinedOutput()
44-
if err != nil {
45-
buildBin.err = fmt.Errorf("%s: %v\n%s", strings.Join(cmd.Args, " "), err, out)
46-
}
32+
func file2fuzz(t *testing.T, dir string, args []string, stdin string) (string, bool) {
33+
f2f.once.Do(func() {
34+
f2f.path, f2f.err = os.Executable()
4735
})
48-
49-
if buildBin.err != nil {
50-
if runtime.GOOS == "android" {
51-
t.Skipf("skipping test after failing to build file2fuzz binary: go_android_exec may have failed to copy needed dependencies (see https://golang.org/issue/37088)")
52-
}
53-
t.Fatal(buildBin.err)
54-
}
55-
return buildBin.name
56-
}
57-
58-
func TestMain(m *testing.M) {
59-
os.Exit(m.Run())
60-
if buildBin.name != "" {
61-
os.Remove(buildBin.name)
36+
if f2f.err != nil {
37+
t.Fatal(f2f.err)
6238
}
63-
}
6439

65-
func file2fuzz(t *testing.T, dir string, args []string, stdin string) (string, bool) {
66-
t.Helper()
67-
cmd := exec.Command(binPath(t), args...)
40+
cmd := exec.Command(f2f.path, args...)
6841
cmd.Dir = dir
42+
cmd.Env = append(os.Environ(), "PWD="+dir, "GO_FILE2FUZZ_TEST_IS_FILE2FUZZ=1")
6943
if stdin != "" {
7044
cmd.Stdin = strings.NewReader(stdin)
7145
}

cmd/getgo/.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
11
build
2-
testgetgo
32
getgo

cmd/getgo/main_test.go

Lines changed: 14 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -13,50 +13,27 @@ import (
1313
"io/ioutil"
1414
"os"
1515
"os/exec"
16-
"runtime"
1716
"testing"
1817
)
1918

20-
const (
21-
testbin = "testgetgo"
22-
)
23-
24-
var (
25-
exeSuffix string // ".exe" on Windows
26-
)
27-
28-
func init() {
29-
if runtime.GOOS == "windows" {
30-
exeSuffix = ".exe"
19+
func TestMain(m *testing.M) {
20+
if os.Getenv("GO_GETGO_TEST_IS_GETGO") != "" {
21+
main()
22+
os.Exit(0)
3123
}
32-
}
3324

34-
// TestMain creates a getgo command for testing purposes and
35-
// deletes it after the tests have been run.
36-
func TestMain(m *testing.M) {
3725
if os.Getenv("GOGET_INTEGRATION") == "" {
3826
fmt.Fprintln(os.Stderr, "main_test: Skipping integration tests with GOGET_INTEGRATION unset")
3927
return
4028
}
4129

42-
args := []string{"build", "-tags", testbin, "-o", testbin + exeSuffix}
43-
out, err := exec.Command("go", args...).CombinedOutput()
44-
if err != nil {
45-
fmt.Fprintf(os.Stderr, "building %s failed: %v\n%s", testbin, err, out)
46-
os.Exit(2)
47-
}
48-
4930
// Don't let these environment variables confuse the test.
5031
os.Unsetenv("GOBIN")
5132
os.Unsetenv("GOPATH")
5233
os.Unsetenv("GIT_ALLOW_PROTOCOL")
5334
os.Unsetenv("PATH")
5435

55-
r := m.Run()
56-
57-
os.Remove(testbin + exeSuffix)
58-
59-
os.Exit(r)
36+
os.Exit(m.Run())
6037
}
6138

6239
func createTmpHome(t *testing.T) string {
@@ -72,12 +49,18 @@ func createTmpHome(t *testing.T) string {
7249
// doRun runs the test getgo command, recording stdout and stderr and
7350
// returning exit status.
7451
func doRun(t *testing.T, args ...string) error {
52+
exe, err := os.Executable()
53+
if err != nil {
54+
t.Fatal(err)
55+
}
56+
t.Helper()
57+
58+
t.Logf("running getgo %v", args)
7559
var stdout, stderr bytes.Buffer
76-
t.Logf("running %s %v", testbin, args)
77-
cmd := exec.Command("./"+testbin+exeSuffix, args...)
60+
cmd := exec.Command(exe, args...)
7861
cmd.Stdout = &stdout
7962
cmd.Stderr = &stderr
80-
cmd.Env = os.Environ()
63+
cmd.Env = append(os.Environ(), "GO_GETGO_TEST_IS_GETGO=1")
8164
status := cmd.Run()
8265
if stdout.Len() > 0 {
8366
t.Log("standard output:")

cmd/signature-fuzzer/README.md

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

0 commit comments

Comments
 (0)