Skip to content

Commit 93f1aac

Browse files
committed
cmd/vet: in rangeloop check, inspect for loop variables too
+ Test. Change-Id: I42eaea1c716217f7945c008ff4bde6de14df5687 Reviewed-on: https://go-review.googlesource.com/34619 Reviewed-by: Robert Griesemer <[email protected]>
1 parent 66fa4fc commit 93f1aac

File tree

3 files changed

+79
-23
lines changed

3 files changed

+79
-23
lines changed

src/cmd/vet/main.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ var (
136136
callExpr *ast.CallExpr
137137
compositeLit *ast.CompositeLit
138138
exprStmt *ast.ExprStmt
139+
forStmt *ast.ForStmt
139140
funcDecl *ast.FuncDecl
140141
funcLit *ast.FuncLit
141142
genDecl *ast.GenDecl
@@ -495,6 +496,8 @@ func (f *File) Visit(node ast.Node) ast.Visitor {
495496
key = compositeLit
496497
case *ast.ExprStmt:
497498
key = exprStmt
499+
case *ast.ForStmt:
500+
key = forStmt
498501
case *ast.FuncDecl:
499502
key = funcDecl
500503
case *ast.FuncLit:

src/cmd/vet/rangeloop.go

Lines changed: 46 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -25,27 +25,55 @@ import "go/ast"
2525

2626
func init() {
2727
register("rangeloops",
28-
"check that range loop variables are used correctly",
29-
checkRangeLoop,
30-
rangeStmt)
28+
"check that loop variables are used correctly",
29+
checkLoop,
30+
rangeStmt, forStmt)
3131
}
3232

33-
// checkRangeLoop walks the body of the provided range statement, checking if
33+
// checkLoop walks the body of the provided loop statement, checking whether
3434
// its index or value variables are used unsafely inside goroutines or deferred
3535
// function literals.
36-
func checkRangeLoop(f *File, node ast.Node) {
37-
n := node.(*ast.RangeStmt)
38-
key, _ := n.Key.(*ast.Ident)
39-
val, _ := n.Value.(*ast.Ident)
40-
if key == nil && val == nil {
36+
func checkLoop(f *File, node ast.Node) {
37+
// Find the variables updated by the loop statement.
38+
var vars []*ast.Ident
39+
addVar := func(expr ast.Expr) {
40+
if id, ok := expr.(*ast.Ident); ok {
41+
vars = append(vars, id)
42+
}
43+
}
44+
var body *ast.BlockStmt
45+
switch n := node.(type) {
46+
case *ast.RangeStmt:
47+
body = n.Body
48+
addVar(n.Key)
49+
addVar(n.Value)
50+
case *ast.ForStmt:
51+
body = n.Body
52+
switch post := n.Post.(type) {
53+
case *ast.AssignStmt:
54+
// e.g. for p = head; p != nil; p = p.next
55+
for _, lhs := range post.Lhs {
56+
addVar(lhs)
57+
}
58+
case *ast.IncDecStmt:
59+
// e.g. for i := 0; i < n; i++
60+
addVar(post.X)
61+
}
62+
}
63+
if vars == nil {
4164
return
4265
}
43-
sl := n.Body.List
44-
if len(sl) == 0 {
66+
67+
// Inspect a go or defer statement
68+
// if it's the last one in the loop body.
69+
// (We give up if there are following statements,
70+
// because it's hard to prove go isn't followed by wait,
71+
// or defer by return.)
72+
if len(body.List) == 0 {
4573
return
4674
}
4775
var last *ast.CallExpr
48-
switch s := sl[len(sl)-1].(type) {
76+
switch s := body.List[len(body.List)-1].(type) {
4977
case *ast.GoStmt:
5078
last = s.Call
5179
case *ast.DeferStmt:
@@ -63,11 +91,14 @@ func checkRangeLoop(f *File, node ast.Node) {
6391
return true
6492
}
6593
if f.pkg.types[id].Type == nil {
66-
// Not referring to a variable
94+
// Not referring to a variable (e.g. struct field name)
6795
return true
6896
}
69-
if key != nil && id.Obj == key.Obj || val != nil && id.Obj == val.Obj {
70-
f.Bad(id.Pos(), "range variable", id.Name, "captured by func literal")
97+
for _, v := range vars {
98+
if v.Obj == id.Obj {
99+
f.Badf(id.Pos(), "loop variable %s captured by func literal",
100+
id.Name)
101+
}
71102
}
72103
return true
73104
})

src/cmd/vet/testdata/rangeloop.go

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,24 +10,24 @@ func RangeLoopTests() {
1010
var s []int
1111
for i, v := range s {
1212
go func() {
13-
println(i) // ERROR "range variable i captured by func literal"
14-
println(v) // ERROR "range variable v captured by func literal"
13+
println(i) // ERROR "loop variable i captured by func literal"
14+
println(v) // ERROR "loop variable v captured by func literal"
1515
}()
1616
}
1717
for i, v := range s {
1818
defer func() {
19-
println(i) // ERROR "range variable i captured by func literal"
20-
println(v) // ERROR "range variable v captured by func literal"
19+
println(i) // ERROR "loop variable i captured by func literal"
20+
println(v) // ERROR "loop variable v captured by func literal"
2121
}()
2222
}
2323
for i := range s {
2424
go func() {
25-
println(i) // ERROR "range variable i captured by func literal"
25+
println(i) // ERROR "loop variable i captured by func literal"
2626
}()
2727
}
2828
for _, v := range s {
2929
go func() {
30-
println(v) // ERROR "range variable v captured by func literal"
30+
println(v) // ERROR "loop variable v captured by func literal"
3131
}()
3232
}
3333
for i, v := range s {
@@ -53,7 +53,7 @@ func RangeLoopTests() {
5353
var f int
5454
for x[0], f = range s {
5555
go func() {
56-
_ = f // ERROR "range variable f captured by func literal"
56+
_ = f // ERROR "loop variable f captured by func literal"
5757
}()
5858
}
5959
type T struct {
@@ -62,7 +62,29 @@ func RangeLoopTests() {
6262
for _, v := range s {
6363
go func() {
6464
_ = T{v: 1}
65-
_ = []int{v: 1} // ERROR "range variable v captured by func literal"
65+
_ = []int{v: 1} // ERROR "loop variable v captured by func literal"
66+
}()
67+
}
68+
69+
// ordinary for-loops
70+
for i := 0; i < 10; i++ {
71+
go func() {
72+
print(i) // ERROR "loop variable i captured by func literal"
73+
}()
74+
}
75+
for i, j := 0, 1; i < 100; i, j = j, i+j {
76+
go func() {
77+
print(j) // ERROR "loop variable j captured by func literal"
78+
}()
79+
}
80+
type cons struct {
81+
car int
82+
cdr *cons
83+
}
84+
var head *cons
85+
for p := head; p != nil; p = p.next {
86+
go func() {
87+
print(p.car) // ERROR "loop variable p captured by func literal"
6688
}()
6789
}
6890
}

0 commit comments

Comments
 (0)