github.com/amarpal/go-tools@v0.0.0-20240422043104-40142f59f616/staticcheck/sa9001/sa9001.go (about) 1 package sa9001 2 3 import ( 4 "go/ast" 5 "go/token" 6 "go/types" 7 8 "github.com/amarpal/go-tools/analysis/code" 9 "github.com/amarpal/go-tools/analysis/lint" 10 "github.com/amarpal/go-tools/analysis/report" 11 "github.com/amarpal/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.Documentation{ 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 }