github.com/karrick/go@v0.0.0-20170817181416-d5b0ec858b37/src/cmd/vet/rangeloop.go (about)

     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 f.pkg.types[id].Type == nil {
    66  			// Not referring to a variable
    67  			return true
    68  		}
    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")
    71  		}
    72  		return true
    73  	})
    74  }