@@ -17,21 +17,15 @@ var Analyzer = &analysis.Analyzer{
1717 Run : run ,
1818 RunDespiteErrors : true ,
1919 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")
2620}
2721
2822func run (pass * analysis.Pass ) (interface {}, error ) {
2923 inspect := pass .ResultOf [inspect .Analyzer ].(* inspector.Inspector )
3024
3125 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 ,
3529 }
3630
3731 nodeFilter := []ast.Node {
@@ -42,50 +36,60 @@ func run(pass *analysis.Pass) (interface{}, error) {
4236 (* ast .UnaryExpr )(nil ),
4337 }
4438
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 )
7140
7241 return nil , nil
7342}
7443
7544type 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.
7846 // - 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
8654}
8755
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 ) {
8993 switch typed := n .(type ) {
9094 case * ast.RangeStmt :
9195 s .parseRangeStmt (typed )
@@ -102,72 +106,92 @@ func (s *Searcher) Check(n ast.Node, stack []ast.Node) (*ast.Ident, token.Pos, b
102106 return nil , token .NoPos , true
103107}
104108
109+ // parseRangeStmt will check range statement (i.e. `for <KEY>, <VALUE> := range ...`),
110+ // and collect positions of <KEY> and <VALUE>.
105111func (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 )
108114}
109115
116+ // parseForStmt will check for statement (i.e. `for <VALUE> := <INIT>; <CONDITION>; <INCREMENT>`),
117+ // and collect positions of <VALUE>.
110118func (s * Searcher ) parseForStmt (n * ast.ForStmt ) {
111119 switch post := n .Post .(type ) {
112120 case * ast.AssignStmt :
113121 // e.g. for p = head; p != nil; p = p.next
114122 for _ , lhs := range post .Lhs {
115- s .addStat (lhs )
123+ s .storeLoopVars (lhs )
116124 }
117125 case * ast.IncDecStmt :
118126 // e.g. for i := 0; i < n; i++
119- s .addStat (post .X )
127+ s .storeLoopVars (post .X )
120128 }
121129}
122130
123- func (s * Searcher ) addStat (expr ast.Expr ) {
131+ func (s * Searcher ) storeLoopVars (expr ast.Expr ) {
124132 if id , ok := expr .(* ast.Ident ); ok {
125- s .Stats [id .Pos ()] = struct {}{}
133+ s .LoopVars [id .Pos ()] = struct {}{}
126134 }
127135}
128136
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.
129139func (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+
130151 loop , _ := s .innermostLoop (stack )
131152 if loop == nil {
132153 return
133154 }
134155
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 )
141160 }
142161 }
143162}
144163
164+ // parseDeclStmt will parse assignment statement (i.e. `<VAR> = <VALUE>`),
165+ // and store the position if it is .
145166func (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+
146172 loop , _ := s .innermostLoop (stack )
147173 if loop == nil {
148174 return
149175 }
150176
151177 // 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 )
156180 }
157181}
158182
159- func (s * Searcher ) addVar (loop ast.Node , expr ast.Expr ) {
183+ func (s * Searcher ) storeLocalVar (loop ast.Node , expr ast.Expr ) {
160184 loopPos := loop .Pos ()
161185 id , ok := expr .(* ast.Ident )
162186 if ! ok {
163187 return
164188 }
165- vars , ok := s .Vars [loopPos ]
189+ vars , ok := s .LocalVars [loopPos ]
166190 if ! ok {
167191 vars = map [token.Pos ]struct {}{}
168192 }
169193 vars [id .Obj .Pos ()] = struct {}{}
170- s .Vars [loopPos ] = vars
194+ s .LocalVars [loopPos ] = vars
171195}
172196
173197func insertionPosition (block * ast.BlockStmt ) token.Pos {
@@ -189,13 +213,15 @@ func (s *Searcher) innermostLoop(stack []ast.Node) (ast.Node, token.Pos) {
189213 return nil , token .NoPos
190214}
191215
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.
192218func (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 {
195220 return nil , token .NoPos , true
196221 }
197222
198- if n .Op != token .AND {
223+ loop , insert := s .innermostLoop (stack )
224+ if loop == nil {
199225 return nil , token .NoPos , true
200226 }
201227
@@ -207,7 +233,7 @@ func (s *Searcher) checkUnaryExpr(n *ast.UnaryExpr, stack []ast.Node) (*ast.Iden
207233
208234 // If the identity is not the loop statement variable,
209235 // it will not be reported.
210- if _ , isStat := s .Stats [id .Obj .Pos ()]; ! isStat {
236+ if _ , isDecl := s .LoopVars [id .Obj .Pos ()]; ! isDecl {
211237 return nil , token .NoPos , true
212238 }
213239
@@ -266,7 +292,7 @@ func (s *Searcher) checkUnaryExpr(n *ast.UnaryExpr, stack []ast.Node) (*ast.Iden
266292}
267293
268294func (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{}
270296 if vars == nil {
271297 return false
272298 }
@@ -287,7 +313,7 @@ func (s *Searcher) getIdentity(expr ast.Expr) *ast.Ident {
287313 switch typed := expr .(type ) {
288314 case * ast.SelectorExpr :
289315 // 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 {
291317 return nil
292318 }
293319
0 commit comments