1
1
package pattern
2
2
3
3
import (
4
+ "fmt"
5
+ "go/ast"
6
+ goparser "go/parser"
7
+ "go/token"
8
+ "os"
9
+ "path/filepath"
10
+ "reflect"
11
+ "runtime"
12
+ "strings"
4
13
"testing"
5
14
)
6
15
@@ -18,3 +27,88 @@ func TestParse(t *testing.T) {
18
27
}
19
28
}
20
29
}
30
+
31
+ func FuzzParse (f * testing.F ) {
32
+ var files []* ast.File
33
+ fset := token .NewFileSet ()
34
+
35
+ // Ideally we'd check against as much source code as possible, but that's fairly slow, on the order of 500ms per
36
+ // pattern when checking against the whole standard library.
37
+ //
38
+ // We pick the runtime package in the hopes that it contains the most diverse, and weird, code.
39
+ filepath .Walk (runtime .GOROOT ()+ "/src/runtime" , func (path string , info os.FileInfo , err error ) error {
40
+ if err != nil {
41
+ // XXX error handling
42
+ panic (err )
43
+ }
44
+ if ! strings .HasSuffix (path , ".go" ) {
45
+ return nil
46
+ }
47
+ f , err := goparser .ParseFile (fset , path , nil , goparser .SkipObjectResolution )
48
+ if err != nil {
49
+ return nil
50
+ }
51
+ files = append (files , f )
52
+ return nil
53
+ })
54
+
55
+ parse := func (in string , allowTypeInfo bool ) (Pattern , bool ) {
56
+ p := Parser {
57
+ AllowTypeInfo : allowTypeInfo ,
58
+ }
59
+ pat , err := p .Parse (string (in ))
60
+ if err != nil {
61
+ if strings .Contains (err .Error (), "internal error" ) {
62
+ panic (err )
63
+ }
64
+ return Pattern {}, false
65
+ }
66
+ return pat , true
67
+ }
68
+
69
+ f .Fuzz (func (t * testing.T , in []byte ) {
70
+ defer func () {
71
+ if err := recover (); err != nil {
72
+ str := fmt .Sprint (err )
73
+ if strings .Contains (str , "binding already created:" ) {
74
+ // This is an invalid pattern, not a real failure
75
+ } else {
76
+ // Re-panic the original panic
77
+ panic (err )
78
+ }
79
+ }
80
+ }()
81
+ // Parse twice, once with AllowTypeInfo set to true to exercise the parser, and once with it set to false so we
82
+ // can actually use it in Match, as we don't have type information available.
83
+
84
+ pat , ok := parse (string (in ), true )
85
+ if ! ok {
86
+ return
87
+ }
88
+ // Make sure we can turn it back into a string
89
+ _ = pat .Root .String ()
90
+
91
+ pat , ok = parse (string (in ), false )
92
+ if ! ok {
93
+ return
94
+ }
95
+ // Make sure we can turn it back into a string
96
+ _ = pat .Root .String ()
97
+
98
+ // Don't check patterns with too many relevant nodes; it's too expensive
99
+ if len (pat .Relevant ) < 20 {
100
+ // Make sure trying to match nodes doesn't panic
101
+ for _ , f := range files {
102
+ ast .Inspect (f , func (node ast.Node ) bool {
103
+ rt := reflect .TypeOf (node )
104
+ // We'd prefer calling Match on all nodes, not just those the pattern deems relevant, to find more bugs.
105
+ // However, doing so has a 10x cost in execution time.
106
+ if _ , ok := pat .Relevant [rt ]; ok {
107
+ Match (pat , node )
108
+ }
109
+ return true
110
+ })
111
+ }
112
+ }
113
+ })
114
+ }
0 commit comments