sa9001.go raw

   1  package sa9001
   2  
   3  import (
   4  	"go/ast"
   5  	"go/token"
   6  	"go/types"
   7  
   8  	"honnef.co/go/tools/analysis/code"
   9  	"honnef.co/go/tools/analysis/lint"
  10  	"honnef.co/go/tools/analysis/report"
  11  	"honnef.co/go/tools/go/types/typeutil"
  12  
  13  	"golang.org/x/tools/go/analysis"
  14  	"golang.org/x/tools/go/analysis/passes/inspect"
  15  )
  16  
  17  var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
  18  	Analyzer: &analysis.Analyzer{
  19  		Name:     "SA9001",
  20  		Run:      run,
  21  		Requires: []*analysis.Analyzer{inspect.Analyzer},
  22  	},
  23  	Doc: &lint.RawDocumentation{
  24  		Title:    `Defers in range loops may not run when you expect them to`,
  25  		Since:    "2017.1",
  26  		Severity: lint.SeverityWarning,
  27  		MergeIf:  lint.MergeIfAny,
  28  	},
  29  })
  30  
  31  var Analyzer = SCAnalyzer.Analyzer
  32  
  33  func run(pass *analysis.Pass) (interface{}, error) {
  34  	fn := func(node ast.Node) {
  35  		loop := node.(*ast.RangeStmt)
  36  		typ := pass.TypesInfo.TypeOf(loop.X)
  37  		_, ok := typeutil.CoreType(typ).(*types.Chan)
  38  		if !ok {
  39  			return
  40  		}
  41  
  42  		stmts := []*ast.DeferStmt{}
  43  		exits := false
  44  		fn2 := func(node ast.Node) bool {
  45  			switch stmt := node.(type) {
  46  			case *ast.DeferStmt:
  47  				stmts = append(stmts, stmt)
  48  			case *ast.FuncLit:
  49  				// Don't look into function bodies
  50  				return false
  51  			case *ast.ReturnStmt:
  52  				exits = true
  53  			case *ast.BranchStmt:
  54  				exits = node.(*ast.BranchStmt).Tok == token.BREAK
  55  			}
  56  			return true
  57  		}
  58  		ast.Inspect(loop.Body, fn2)
  59  
  60  		if exits {
  61  			return
  62  		}
  63  		for _, stmt := range stmts {
  64  			report.Report(pass, stmt, "defers in this range loop won't run unless the channel gets closed")
  65  		}
  66  	}
  67  	code.Preorder(pass, fn, (*ast.RangeStmt)(nil))
  68  	return nil, nil
  69  }
  70