Home | History | Annotate | Download | only in vet
      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