1 // Copyright 2012 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 /* 6 This file contains the code to check range loop variables bound inside function 7 literals that are deferred or launched in new goroutines. We only check 8 instances where the defer or go statement is the last statement in the loop 9 body, as otherwise we would need whole program analysis. 10 11 For example: 12 13 for i, v := range s { 14 go func() { 15 println(i, v) // not what you might expect 16 }() 17 } 18 19 See: https://golang.org/doc/go_faq.html#closures_and_goroutines 20 */ 21 22 package main 23 24 import "go/ast" 25 26 func init() { 27 register("rangeloops", 28 "check that range loop variables are used correctly", 29 checkRangeLoop, 30 rangeStmt) 31 } 32 33 // checkRangeLoop walks the body of the provided range statement, checking if 34 // its index or value variables are used unsafely inside goroutines or deferred 35 // 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 { 41 return 42 } 43 sl := n.Body.List 44 if len(sl) == 0 { 45 return 46 } 47 var last *ast.CallExpr 48 switch s := sl[len(sl)-1].(type) { 49 case *ast.GoStmt: 50 last = s.Call 51 case *ast.DeferStmt: 52 last = s.Call 53 default: 54 return 55 } 56 lit, ok := last.Fun.(*ast.FuncLit) 57 if !ok { 58 return 59 } 60 ast.Inspect(lit.Body, func(n ast.Node) bool { 61 id, ok := n.(*ast.Ident) 62 if !ok || id.Obj == nil { 63 return true 64 } 65 if key != nil && id.Obj == key.Obj || val != nil && id.Obj == val.Obj { 66 f.Bad(id.Pos(), "range variable", id.Name, "captured by func literal") 67 } 68 return true 69 }) 70 } 71