Skip to content

Commit eb19bc4

Browse files
authored
ruleguard/typematch: use external matcher state (#374)
This is the same trick we did with gogrep matcher. The caller is supposed to pass the state that is not shared between different threads. For the most use cases, ruleguard has worker/runner based concurrency, so it's easy to pass this state from the worker that owns that state and doesn't share it with other workers. Also added some E2E tests that compile a ruleguard binary with `-race` and run it using all test rules over the ruleguard own source code. If any of these rules cause a data race, this test fails. To avoid the slower test times, I removed the `-race` from the basic test as they do not involve any concurrent behavior anyway, so it was just a waste of time. Fixes #372 Fixes #368
1 parent a4ee291 commit eb19bc4

File tree

10 files changed

+135
-63
lines changed

10 files changed

+135
-63
lines changed

.gitignore

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
.idea
22
.vscode
3-
test-ruleguard
4-
test-ruleguard-ir
3+
test-ruleguard.exe
4+
test-ruleguard-ir.exe
55
coverage.txt

Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ build-release:
1414
./cmd/ruleguard
1515

1616
test:
17-
go test -count 3 -coverpkg=./... -coverprofile=coverage.txt -covermode=atomic -race ./...
17+
go test -timeout=10m -count=1 -race -coverpkg=./... -coverprofile=coverage.txt -covermode=atomic ./...
18+
go test -count=3 -run=TestE2E ./analyzer
1819
cd rules && go test -v .
1920
@echo "everything is OK"
2021

analyzer/analyzer_test.go

Lines changed: 57 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,15 @@ var tests = []struct {
5151
{name: "quickfix", quickfixes: true},
5252
}
5353

54+
var ruleguardExe string
55+
56+
func TestMain(m *testing.M) {
57+
ruleguardExe = buildRuleguard()
58+
59+
exitCode := m.Run()
60+
os.Exit(exitCode)
61+
}
62+
5463
func TestDirectiveComments(t *testing.T) {
5564
testdata := analysistest.TestData()
5665
badDirectiveRe := regexp.MustCompile("// want `[^\\\\][^Q].*")
@@ -114,6 +123,31 @@ func TestAnalyzer(t *testing.T) {
114123
}
115124
}
116125

126+
func TestE2E(t *testing.T) {
127+
wd, err := os.Getwd()
128+
if err != nil {
129+
t.Fatal(err)
130+
}
131+
rootPath := filepath.Join(wd, "..")
132+
133+
for i := range tests {
134+
test := tests[i]
135+
t.Run(test.name, func(t *testing.T) {
136+
rulesFilename := fmt.Sprintf("./analyzer/testdata/src/%s/rules.go", test.name)
137+
cmd := exec.Command(ruleguardExe, "-rules", rulesFilename, "./...") // nolint:gosec
138+
cmd.Dir = rootPath
139+
cmd.Env = append([]string{}, os.Environ()...)
140+
cmd.Env = append(cmd.Env, "GORACE=halt_on_error=1 exitcode=39")
141+
out, err := cmd.CombinedOutput()
142+
if exitError, ok := err.(*exec.ExitError); ok {
143+
if exitError.ExitCode() == 39 {
144+
t.Fatalf("found a data race!\n%s", out)
145+
}
146+
}
147+
})
148+
}
149+
}
150+
117151
func TestPrintIR(t *testing.T) {
118152
analyzerTemplate := `
119153
package main
@@ -137,18 +171,6 @@ var rulesFile = %s
137171
t.Fatal(err)
138172
}
139173

140-
{
141-
args := []string{
142-
"build",
143-
"-o", "test-ruleguard",
144-
filepath.Join(wd, "..", "cmd", "ruleguard"),
145-
}
146-
out, err := exec.Command("go", args...).CombinedOutput()
147-
if err != nil {
148-
t.Fatalf("build go-ruleguard: %v: %s", err, out)
149-
}
150-
}
151-
152174
for i := range tests {
153175
test := tests[i]
154176
if test.flags != nil {
@@ -194,14 +216,14 @@ var rulesFile = %s
194216
t.Fatal(err)
195217
}
196218

197-
srcRulesCmd := exec.Command(filepath.Join(wd, "test-ruleguard"), "-rules", rulesFilename, "./...") // nolint:gosec
219+
srcRulesCmd := exec.Command(ruleguardExe, "-rules", rulesFilename, "./...") // nolint:gosec
198220
srcRulesCmd.Dir = filepath.Join(wd, "testdata", "src", test.name)
199221
srcOut, _ := srcRulesCmd.CombinedOutput()
200222

201223
{
202224
args := []string{
203225
"build",
204-
"-o", "test-ruleguard-ir",
226+
"-o", "test-ruleguard-ir.exe",
205227
mainFile.Name(),
206228
}
207229
out, err := exec.Command("go", args...).CombinedOutput()
@@ -210,7 +232,7 @@ var rulesFile = %s
210232
}
211233
}
212234

213-
irRulesCmd := exec.Command(filepath.Join(wd, "test-ruleguard-ir"), "./...") // nolint:gosec
235+
irRulesCmd := exec.Command(filepath.Join(wd, "test-ruleguard-ir.exe"), "./...") // nolint:gosec
214236
irRulesCmd.Dir = filepath.Join(wd, "testdata", "src", test.name)
215237
irOut, _ := irRulesCmd.CombinedOutput()
216238

@@ -221,3 +243,23 @@ var rulesFile = %s
221243
})
222244
}
223245
}
246+
247+
func buildRuleguard() string {
248+
wd, err := os.Getwd()
249+
if err != nil {
250+
panic(err)
251+
}
252+
253+
args := []string{
254+
"build",
255+
"-o", "test-ruleguard.exe",
256+
"-race",
257+
filepath.Join(wd, "..", "cmd", "ruleguard"),
258+
}
259+
out, err := exec.Command("go", args...).CombinedOutput()
260+
if err != nil {
261+
panic(fmt.Sprintf("build go-ruleguard: %v: %s", err, out))
262+
}
263+
264+
return filepath.Join(wd, "test-ruleguard.exe")
265+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package regression
2+
3+
func _() {
4+
_ = map[string]int{} // want `\Qcreating a map`
5+
_ = make(map[int][]string) // want `\Qcreating a map`
6+
}

analyzer/testdata/src/regression/rules.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,9 @@ func issue360(m dsl.Matcher) {
7171
Report(`don't use strings.Compare`).
7272
At(m["s1"])
7373
}
74+
75+
func issue372(m dsl.Matcher) {
76+
m.Match("$x{}", "make($x)").
77+
Where(m["x"].Type.Is("map[$k]$v")).
78+
Report("creating a map")
79+
}

ruleguard/filters.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -292,11 +292,11 @@ func makeTypeIsFilter(src, varname string, underlying bool, pat *typematch.Patte
292292
return func(params *filterParams) matchFilterResult {
293293
if list, ok := params.subNode(varname).(gogrep.ExprSlice); ok {
294294
return exprListFilterApply(src, list, func(x ast.Expr) bool {
295-
return pat.MatchIdentical(params.typeofNode(x).Underlying())
295+
return pat.MatchIdentical(params.typematchState, params.typeofNode(x).Underlying())
296296
})
297297
}
298298
typ := params.typeofNode(params.subNode(varname)).Underlying()
299-
if pat.MatchIdentical(typ) {
299+
if pat.MatchIdentical(params.typematchState, typ) {
300300
return filterSuccess
301301
}
302302
return filterFailure(src)
@@ -306,11 +306,11 @@ func makeTypeIsFilter(src, varname string, underlying bool, pat *typematch.Patte
306306
return func(params *filterParams) matchFilterResult {
307307
if list, ok := params.subNode(varname).(gogrep.ExprSlice); ok {
308308
return exprListFilterApply(src, list, func(x ast.Expr) bool {
309-
return pat.MatchIdentical(params.typeofNode(x))
309+
return pat.MatchIdentical(params.typematchState, params.typeofNode(x))
310310
})
311311
}
312312
typ := params.typeofNode(params.subNode(varname))
313-
if pat.MatchIdentical(typ) {
313+
if pat.MatchIdentical(params.typematchState, typ) {
314314
return filterSuccess
315315
}
316316
return filterFailure(src)

ruleguard/gorule.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"regexp"
88

99
"github.com/quasilyte/go-ruleguard/ruleguard/quasigo"
10+
"github.com/quasilyte/go-ruleguard/ruleguard/typematch"
1011
"github.com/quasilyte/gogrep"
1112
"github.com/quasilyte/gogrep/nodetag"
1213
)
@@ -60,6 +61,7 @@ type filterParams struct {
6061

6162
importer *goImporter
6263
gogrepSubState *gogrep.MatcherState
64+
typematchState *typematch.MatcherState
6365

6466
match matchData
6567
nodePath *nodePath

ruleguard/runner.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717

1818
"github.com/quasilyte/go-ruleguard/ruleguard/goutil"
1919
"github.com/quasilyte/go-ruleguard/ruleguard/profiling"
20+
"github.com/quasilyte/go-ruleguard/ruleguard/typematch"
2021
"github.com/quasilyte/gogrep"
2122
"github.com/quasilyte/gogrep/nodetag"
2223
)
@@ -80,9 +81,10 @@ func newRulesRunner(ctx *RunContext, buildContext *build.Context, state *engineS
8081
nodePath: newNodePath(),
8182
truncateLen: ctx.TruncateLen,
8283
filterParams: filterParams{
83-
env: state.env.GetEvalEnv(),
84-
importer: importer,
85-
ctx: ctx,
84+
typematchState: typematch.NewMatcherState(),
85+
env: state.env.GetEvalEnv(),
86+
importer: importer,
87+
ctx: ctx,
8688
},
8789
}
8890
if ctx.TruncateLen == 0 {

0 commit comments

Comments
 (0)