@@ -17,21 +17,15 @@ var Analyzer = &analysis.Analyzer{
17
17
Run : run ,
18
18
RunDespiteErrors : true ,
19
19
Requires : []* analysis.Analyzer {inspect .Analyzer },
20
- // ResultType reflect.Type
21
- // FactTypes []Fact
22
- }
23
-
24
- func init () {
25
- // Analyzer.Flags.StringVar(&v, "name", "default", "description")
26
20
}
27
21
28
22
func run (pass * analysis.Pass ) (interface {}, error ) {
29
23
inspect := pass .ResultOf [inspect .Analyzer ].(* inspector.Inspector )
30
24
31
25
search := & Searcher {
32
- Stats : map [token.Pos ]struct {}{},
33
- Vars : map [token.Pos ]map [token.Pos ]struct {}{},
34
- Types : pass . TypesInfo . Types ,
26
+ LoopVars : map [token.Pos ]struct {}{},
27
+ LocalVars : map [token.Pos ]map [token.Pos ]struct {}{},
28
+ Pass : pass ,
35
29
}
36
30
37
31
nodeFilter := []ast.Node {
@@ -42,50 +36,60 @@ func run(pass *analysis.Pass) (interface{}, error) {
42
36
(* ast .UnaryExpr )(nil ),
43
37
}
44
38
45
- inspect .WithStack (nodeFilter , func (n ast.Node , push bool , stack []ast.Node ) bool {
46
- id , insert , digg := search .Check (n , stack )
47
- if id != nil {
48
- dMsg := fmt .Sprintf ("exporting a pointer for the loop variable %s" , id .Name )
49
- fMsg := fmt .Sprintf ("loop variable %s should be pinned" , id .Name )
50
- var suggest []analysis.SuggestedFix
51
- if insert != token .NoPos {
52
- suggest = []analysis.SuggestedFix {{
53
- Message : fMsg ,
54
- TextEdits : []analysis.TextEdit {{
55
- Pos : insert ,
56
- End : insert ,
57
- NewText : []byte (fmt .Sprintf ("%[1]s := %[1]s\n " , id .Name )),
58
- }},
59
- }}
60
- }
61
- d := analysis.Diagnostic {Pos : id .Pos (),
62
- End : id .End (),
63
- Message : dMsg ,
64
- Category : "exportloopref" ,
65
- SuggestedFixes : suggest ,
66
- }
67
- pass .Report (d )
68
- }
69
- return digg
70
- })
39
+ inspect .WithStack (nodeFilter , search .CheckAndReport )
71
40
72
41
return nil , nil
73
42
}
74
43
75
44
type Searcher struct {
76
- // Statement variables : map to collect positions that
77
- // variables are declared like below.
45
+ // LoopVars is positions that loop-variables are declared like below.
78
46
// - for <KEY>, <VALUE> := range ...
79
- // - var <X> int
80
- // - D := ...
81
- Stats map [token. Pos ] struct {}
82
- // Local variables maps loop-position, decl-location to ignore
83
- // safe pointers for variable which declared in the loop.
84
- Vars map [token. Pos ] map [token. Pos ] struct {}
85
- Types map [ast. Expr ]types. TypeAndValue
47
+ // - for <VALUE> := <INIT>; <CONDITION>; <INCREMENT>
48
+ LoopVars map [token. Pos ] struct {}
49
+ // LocalVars is positions of loops and the variables declared in them.
50
+ // Use this to determine if a point assignment is an export outside the loop.
51
+ LocalVars map [token. Pos ] map [token. Pos ] struct {}
52
+
53
+ Pass * analysis. Pass
86
54
}
87
55
88
- func (s * Searcher ) Check (n ast.Node , stack []ast.Node ) (* ast.Ident , token.Pos , bool ) {
56
+ // CheckAndReport inspects each node with stack.
57
+ // It is implemented as the I/F of the "golang.org/x/tools/go/analysis/passes/inspect".Analysis.WithStack.
58
+ func (s * Searcher ) CheckAndReport (n ast.Node , push bool , stack []ast.Node ) bool {
59
+ id , insert , digg := s .Check (n , stack )
60
+ if id == nil {
61
+ // no prob.
62
+ return digg
63
+ }
64
+
65
+ // suggests fix
66
+ var suggest []analysis.SuggestedFix
67
+ if insert != token .NoPos {
68
+ suggest = []analysis.SuggestedFix {{
69
+ Message : fmt .Sprintf ("loop variable %s should be pinned" , id .Name ),
70
+ TextEdits : []analysis.TextEdit {{
71
+ Pos : insert ,
72
+ End : insert ,
73
+ NewText : []byte (fmt .Sprintf ("%[1]s := %[1]s\n " , id .Name )),
74
+ }},
75
+ }}
76
+ }
77
+
78
+ // report a diagnostic
79
+ d := analysis.Diagnostic {Pos : id .Pos (),
80
+ End : id .End (),
81
+ Message : fmt .Sprintf ("exporting a pointer for the loop variable %s" , id .Name ),
82
+ Category : "exportloopref" ,
83
+ SuggestedFixes : suggest ,
84
+ }
85
+ s .Pass .Report (d )
86
+ return digg
87
+ }
88
+
89
+ // Check each node and stack, whether it exports loop variables or not.
90
+ // Finding export, report the *ast.Ident of exported loop variable,
91
+ // and token.Pos to insert assignment to fix the diagnostic.
92
+ func (s * Searcher ) Check (n ast.Node , stack []ast.Node ) (loopVar * ast.Ident , insertPos token.Pos , digg bool ) {
89
93
switch typed := n .(type ) {
90
94
case * ast.RangeStmt :
91
95
s .parseRangeStmt (typed )
@@ -102,72 +106,92 @@ func (s *Searcher) Check(n ast.Node, stack []ast.Node) (*ast.Ident, token.Pos, b
102
106
return nil , token .NoPos , true
103
107
}
104
108
109
+ // parseRangeStmt will check range statement (i.e. `for <KEY>, <VALUE> := range ...`),
110
+ // and collect positions of <KEY> and <VALUE>.
105
111
func (s * Searcher ) parseRangeStmt (n * ast.RangeStmt ) {
106
- s .addStat (n .Key )
107
- s .addStat (n .Value )
112
+ s .storeLoopVars (n .Key )
113
+ s .storeLoopVars (n .Value )
108
114
}
109
115
116
+ // parseForStmt will check for statement (i.e. `for <VALUE> := <INIT>; <CONDITION>; <INCREMENT>`),
117
+ // and collect positions of <VALUE>.
110
118
func (s * Searcher ) parseForStmt (n * ast.ForStmt ) {
111
119
switch post := n .Post .(type ) {
112
120
case * ast.AssignStmt :
113
121
// e.g. for p = head; p != nil; p = p.next
114
122
for _ , lhs := range post .Lhs {
115
- s .addStat (lhs )
123
+ s .storeLoopVars (lhs )
116
124
}
117
125
case * ast.IncDecStmt :
118
126
// e.g. for i := 0; i < n; i++
119
- s .addStat (post .X )
127
+ s .storeLoopVars (post .X )
120
128
}
121
129
}
122
130
123
- func (s * Searcher ) addStat (expr ast.Expr ) {
131
+ func (s * Searcher ) storeLoopVars (expr ast.Expr ) {
124
132
if id , ok := expr .(* ast.Ident ); ok {
125
- s .Stats [id .Pos ()] = struct {}{}
133
+ s .LoopVars [id .Pos ()] = struct {}{}
126
134
}
127
135
}
128
136
137
+ // parseDeclStmt will parse declaring statement (i.e. `var`, `type`, `const`),
138
+ // and store the position if it is "var" declaration and is in any loop.
129
139
func (s * Searcher ) parseDeclStmt (n * ast.DeclStmt , stack []ast.Node ) {
140
+ genDecl , ok := n .Decl .(* ast.GenDecl )
141
+ if ! ok {
142
+ // (dead branch)
143
+ // if the Decl is not GenDecl (i.e. `var`, `type` or `const` statement), it is ignored
144
+ return
145
+ }
146
+ if genDecl .Tok != token .VAR {
147
+ // if the Decl is not `var` (may be `type` or `const`), it is ignored
148
+ return
149
+ }
150
+
130
151
loop , _ := s .innermostLoop (stack )
131
152
if loop == nil {
132
153
return
133
154
}
134
155
135
- // Register declaring variables
136
- if genDecl , ok := n .Decl .(* ast.GenDecl ); ok && genDecl .Tok == token .VAR {
137
- for _ , spec := range genDecl .Specs {
138
- for _ , name := range spec .(* ast.ValueSpec ).Names {
139
- s .addVar (loop , name )
140
- }
156
+ // Register declared variables
157
+ for _ , spec := range genDecl .Specs {
158
+ for _ , name := range spec .(* ast.ValueSpec ).Names {
159
+ s .storeLocalVar (loop , name )
141
160
}
142
161
}
143
162
}
144
163
164
+ // parseDeclStmt will parse assignment statement (i.e. `<VAR> = <VALUE>`),
165
+ // and store the position if it is .
145
166
func (s * Searcher ) parseAssignStmt (n * ast.AssignStmt , stack []ast.Node ) {
167
+ if n .Tok != token .DEFINE {
168
+ // if the statement is simple assignment (without definement), it is ignored
169
+ return
170
+ }
171
+
146
172
loop , _ := s .innermostLoop (stack )
147
173
if loop == nil {
148
174
return
149
175
}
150
176
151
177
// Find statements declaring local variable
152
- if n .Tok == token .DEFINE {
153
- for _ , h := range n .Lhs {
154
- s .addVar (loop , h )
155
- }
178
+ for _ , h := range n .Lhs {
179
+ s .storeLocalVar (loop , h )
156
180
}
157
181
}
158
182
159
- func (s * Searcher ) addVar (loop ast.Node , expr ast.Expr ) {
183
+ func (s * Searcher ) storeLocalVar (loop ast.Node , expr ast.Expr ) {
160
184
loopPos := loop .Pos ()
161
185
id , ok := expr .(* ast.Ident )
162
186
if ! ok {
163
187
return
164
188
}
165
- vars , ok := s .Vars [loopPos ]
189
+ vars , ok := s .LocalVars [loopPos ]
166
190
if ! ok {
167
191
vars = map [token.Pos ]struct {}{}
168
192
}
169
193
vars [id .Obj .Pos ()] = struct {}{}
170
- s .Vars [loopPos ] = vars
194
+ s .LocalVars [loopPos ] = vars
171
195
}
172
196
173
197
func insertionPosition (block * ast.BlockStmt ) token.Pos {
@@ -189,13 +213,15 @@ func (s *Searcher) innermostLoop(stack []ast.Node) (ast.Node, token.Pos) {
189
213
return nil , token .NoPos
190
214
}
191
215
216
+ // checkUnaryExpr check unary expression (i.e. <OPERATOR><VAR> like `-x`, `*p` or `&v`) and stack.
217
+ // THIS IS THE ESSENTIAL PART OF THIS PARSER.
192
218
func (s * Searcher ) checkUnaryExpr (n * ast.UnaryExpr , stack []ast.Node ) (* ast.Ident , token.Pos , bool ) {
193
- loop , insert := s .innermostLoop (stack )
194
- if loop == nil {
219
+ if n .Op != token .AND {
195
220
return nil , token .NoPos , true
196
221
}
197
222
198
- if n .Op != token .AND {
223
+ loop , insert := s .innermostLoop (stack )
224
+ if loop == nil {
199
225
return nil , token .NoPos , true
200
226
}
201
227
@@ -207,7 +233,7 @@ func (s *Searcher) checkUnaryExpr(n *ast.UnaryExpr, stack []ast.Node) (*ast.Iden
207
233
208
234
// If the identity is not the loop statement variable,
209
235
// it will not be reported.
210
- if _ , isStat := s .Stats [id .Obj .Pos ()]; ! isStat {
236
+ if _ , isDecl := s .LoopVars [id .Obj .Pos ()]; ! isDecl {
211
237
return nil , token .NoPos , true
212
238
}
213
239
@@ -266,7 +292,7 @@ func (s *Searcher) checkUnaryExpr(n *ast.UnaryExpr, stack []ast.Node) (*ast.Iden
266
292
}
267
293
268
294
func (s * Searcher ) isVar (loop ast.Node , expr ast.Expr ) bool {
269
- vars := s .Vars [loop .Pos ()] // map[token.Pos]struct{}
295
+ vars := s .LocalVars [loop .Pos ()] // map[token.Pos]struct{}
270
296
if vars == nil {
271
297
return false
272
298
}
@@ -287,7 +313,7 @@ func (s *Searcher) getIdentity(expr ast.Expr) *ast.Ident {
287
313
switch typed := expr .(type ) {
288
314
case * ast.SelectorExpr :
289
315
// Ignore if the parent is pointer ref (fix for #2)
290
- if _ , ok := s .Types [typed .X ].Type .(* types.Pointer ); ok {
316
+ if _ , ok := s .Pass . TypesInfo . Types [typed .X ].Type .(* types.Pointer ); ok {
291
317
return nil
292
318
}
293
319
0 commit comments