@@ -18,6 +18,7 @@ import (
18
18
"golang.org/x/tools/go/analysis/passes/inspect"
19
19
"golang.org/x/tools/go/ast/inspector"
20
20
"golang.org/x/tools/go/types/typeutil"
21
+ "golang.org/x/tools/internal/analysisinternal"
21
22
)
22
23
23
24
const Doc = `check references to loop variables from within nested functions
@@ -87,6 +88,108 @@ var Analyzer = &analysis.Analyzer{
87
88
}
88
89
89
90
func run (pass * analysis.Pass ) (interface {}, error ) {
91
+ // Check if we are enabling additional experimental logic.
92
+ if ! analysisinternal .LoopclosureGo121 {
93
+ return runGo120 (pass )
94
+ }
95
+ return runGo121 (pass )
96
+ }
97
+
98
+ // runGo120 runs the analyzer with logic intended for Go 1.20 cmd/vet.
99
+ // TODO: delete this once the Go 1.21 dev cycle has started.
100
+ func runGo120 (pass * analysis.Pass ) (interface {}, error ) {
101
+ inspect := pass .ResultOf [inspect .Analyzer ].(* inspector.Inspector )
102
+
103
+ nodeFilter := []ast.Node {
104
+ (* ast .RangeStmt )(nil ),
105
+ (* ast .ForStmt )(nil ),
106
+ }
107
+ inspect .Preorder (nodeFilter , func (n ast.Node ) {
108
+ // Find the variables updated by the loop statement.
109
+ vars := make (map [types.Object ]int )
110
+ addVar := func (expr ast.Expr ) {
111
+ if id , _ := expr .(* ast.Ident ); id != nil {
112
+ if obj := pass .TypesInfo .ObjectOf (id ); obj != nil {
113
+ // For runGo121, we use a count to track when to remove
114
+ // elements from the vars map.
115
+ // For runGo120, we do not, but set the proper count anyway.
116
+ vars [obj ] = 1
117
+ }
118
+ }
119
+ }
120
+ var body * ast.BlockStmt
121
+ switch n := n .(type ) {
122
+ case * ast.RangeStmt :
123
+ body = n .Body
124
+ addVar (n .Key )
125
+ addVar (n .Value )
126
+ case * ast.ForStmt :
127
+ body = n .Body
128
+ switch post := n .Post .(type ) {
129
+ case * ast.AssignStmt :
130
+ // e.g. for p = head; p != nil; p = p.next
131
+ for _ , lhs := range post .Lhs {
132
+ addVar (lhs )
133
+ }
134
+ case * ast.IncDecStmt :
135
+ // e.g. for i := 0; i < n; i++
136
+ addVar (post .X )
137
+ }
138
+ }
139
+ if vars == nil {
140
+ return
141
+ }
142
+
143
+ // Inspect statements to find function literals that may be run outside of
144
+ // the current loop iteration.
145
+ //
146
+ // For go, defer, and errgroup.Group.Go, we ignore all but the last
147
+ // statement, where "last" is defined recursively.
148
+ // See runGo120 for an alternative approach.
149
+ v := visitor {last : func (v visitor , last ast.Stmt ) {
150
+ var stmts []ast.Stmt
151
+ switch s := last .(type ) {
152
+ case * ast.GoStmt :
153
+ stmts = litStmts (s .Call .Fun )
154
+ case * ast.DeferStmt :
155
+ stmts = litStmts (s .Call .Fun )
156
+ case * ast.ExprStmt : // check for errgroup.Group.Go
157
+ if call , ok := s .X .(* ast.CallExpr ); ok {
158
+ stmts = litStmts (goInvoke (pass .TypesInfo , call ))
159
+ }
160
+ }
161
+ for _ , stmt := range stmts {
162
+ reportCaptured (pass , vars , stmt )
163
+ }
164
+ }}
165
+ v .visit (body .List )
166
+
167
+ // Also check for testing.T.Run (with T.Parallel).
168
+ // We consider every t.Run statement in the loop body, because there is
169
+ // no commonly used mechanism for synchronizing parallel subtests.
170
+ // It is of course theoretically possible to synchronize parallel subtests,
171
+ // though such a pattern is likely to be exceedingly rare as it would be
172
+ // fighting against the test runner.
173
+ for _ , s := range body .List {
174
+ switch s := s .(type ) {
175
+ case * ast.ExprStmt :
176
+ if call , ok := s .X .(* ast.CallExpr ); ok {
177
+ for _ , stmt := range parallelSubtest (pass .TypesInfo , call ) {
178
+ reportCaptured (pass , vars , stmt )
179
+ }
180
+ }
181
+ }
182
+ }
183
+ })
184
+ return nil , nil
185
+ }
186
+
187
+ // runGo121 runs the analyzer with additional experimental logic
188
+ // that is not intended for Go 1.20 cmd/vet, including examining
189
+ // statements following a go, defer or errgroup.Group.Go statement
190
+ // to determine if they cannot delay start of execution of the
191
+ // go or defer.
192
+ func runGo121 (pass * analysis.Pass ) (interface {}, error ) {
90
193
// We do two passes with inspect: first, a simpler walk
91
194
// looking for problems with testing.T.Run with T.Parallel, and
92
195
// second, a more involved walk to examine last statements
0 commit comments