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