github.com/amarpal/go-tools@v0.0.0-20240422043104-40142f59f616/staticcheck/sa5002/sa5002.go (about) 1 package sa5002 2 3 import ( 4 "go/ast" 5 "go/constant" 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 12 "golang.org/x/tools/go/analysis" 13 "golang.org/x/tools/go/analysis/passes/inspect" 14 ) 15 16 var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ 17 Analyzer: &analysis.Analyzer{ 18 Name: "SA5002", 19 Run: run, 20 Requires: []*analysis.Analyzer{inspect.Analyzer}, 21 }, 22 Doc: &lint.Documentation{ 23 Title: `The empty for loop (\"for {}\") spins and can block the scheduler`, 24 Since: "2017.1", 25 Severity: lint.SeverityWarning, 26 MergeIf: lint.MergeIfAny, 27 }, 28 }) 29 30 var Analyzer = SCAnalyzer.Analyzer 31 32 func run(pass *analysis.Pass) (interface{}, error) { 33 fn := func(node ast.Node) { 34 loop := node.(*ast.ForStmt) 35 if len(loop.Body.List) != 0 || loop.Post != nil { 36 return 37 } 38 39 if loop.Init != nil { 40 // TODO(dh): this isn't strictly necessary, it just makes 41 // the check easier. 42 return 43 } 44 // An empty loop is bad news in two cases: 1) The loop has no 45 // condition. In that case, it's just a loop that spins 46 // forever and as fast as it can, keeping a core busy. 2) The 47 // loop condition only consists of variable or field reads and 48 // operators on those. The only way those could change their 49 // value is with unsynchronised access, which constitutes a 50 // data race. 51 // 52 // If the condition contains any function calls, its behaviour 53 // is dynamic and the loop might terminate. Similarly for 54 // channel receives. 55 56 if loop.Cond != nil { 57 if code.MayHaveSideEffects(pass, loop.Cond, nil) { 58 return 59 } 60 if ident, ok := loop.Cond.(*ast.Ident); ok { 61 if k, ok := pass.TypesInfo.ObjectOf(ident).(*types.Const); ok { 62 if !constant.BoolVal(k.Val()) { 63 // don't flag `for false {}` loops. They're a debug aid. 64 return 65 } 66 } 67 } 68 report.Report(pass, loop, "loop condition never changes or has a race condition") 69 } 70 report.Report(pass, loop, "this loop will spin, using 100% CPU", report.ShortRange()) 71 } 72 code.Preorder(pass, fn, (*ast.ForStmt)(nil)) 73 return nil, nil 74 }