1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
|
package gocapturedrefrace
import (
"go/ast"
"go/types"
"golang.org/x/tools/go/analysis"
)
var Analyzer = &analysis.Analyzer{
Name: "gocapturedrefrace",
Doc: "reports captured references in goroutine closures",
Run: run,
}
func run(pass *analysis.Pass) (interface{}, error) {
for _, file := range pass.Files {
ast.Inspect(
file,
func(node ast.Node) bool {
// Find `go` statements.
goStmt, ok := node.(*ast.GoStmt)
if !ok {
return true
}
// Look for a function literal after the `go` statement.
funcLit, ok := goStmt.Call.Fun.(*ast.FuncLit)
if !ok {
return true
}
// Inspect closure argument list.
for _, arg := range funcLit.Type.Params.List {
// Report reference arguments.
_, ok := arg.Type.(*ast.StarExpr)
if !ok {
continue
}
pass.Reportf(
arg.Pos(),
"reference %s in goroutine closure",
arg.Names[0],
)
}
// Get the closure's scope.
funcScope := pass.TypesInfo.Scopes[funcLit.Type]
checkClosure(pass, funcLit, funcScope)
return true
},
)
}
return nil, nil
}
func checkClosure(
pass *analysis.Pass,
funcLit *ast.FuncLit,
funcScope *types.Scope,
) {
ast.Inspect(
funcLit,
func(node ast.Node) bool {
ident, ok := node.(*ast.Ident)
if !ok {
return true
}
if ident.Obj == nil {
return true
}
// Find out whether `ident` was defined in an outer scope.
scope, scopeObj := funcScope.LookupParent(ident.Name, ident.NamePos)
// Identifier is local to the closure.
if scope == nil && scopeObj == nil {
return true
}
// Ignore non-variable identifiers.
_, ok = scopeObj.(*types.Var)
if !ok {
return true
}
// Identifier was defined in a different scope.
if funcScope != scope {
pass.Reportf(
ident.Pos(),
"captured reference %s in goroutine closure",
ident,
)
}
return true
},
)
}
|