github.com/amarpal/go-tools@v0.0.0-20240422043104-40142f59f616/staticcheck/sa4008/sa4008.go (about) 1 package sa4008 2 3 import ( 4 "go/ast" 5 6 "github.com/amarpal/go-tools/analysis/lint" 7 "github.com/amarpal/go-tools/analysis/report" 8 "github.com/amarpal/go-tools/go/ir" 9 "github.com/amarpal/go-tools/internal/passes/buildir" 10 11 "golang.org/x/tools/go/analysis" 12 ) 13 14 var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ 15 Analyzer: &analysis.Analyzer{ 16 Name: "SA4008", 17 Run: run, 18 Requires: []*analysis.Analyzer{buildir.Analyzer}, 19 }, 20 Doc: &lint.Documentation{ 21 Title: `The variable in the loop condition never changes, are you incrementing the wrong variable?`, 22 Text: `For example: 23 24 for i := 0; i < 10; j++ { ... } 25 26 This may also occur when a loop can only execute once because of unconditional 27 control flow that terminates the loop. For example, when a loop body contains an 28 unconditional break, return, or panic: 29 30 func f() { 31 panic("oops") 32 } 33 func g() { 34 for i := 0; i < 10; i++ { 35 // f unconditionally calls panic, which means "i" is 36 // never incremented. 37 f() 38 } 39 }`, 40 Since: "2017.1", 41 Severity: lint.SeverityWarning, 42 MergeIf: lint.MergeIfAll, 43 }, 44 }) 45 46 var Analyzer = SCAnalyzer.Analyzer 47 48 func run(pass *analysis.Pass) (interface{}, error) { 49 for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs { 50 cb := func(node ast.Node) bool { 51 loop, ok := node.(*ast.ForStmt) 52 if !ok { 53 return true 54 } 55 if loop.Init == nil || loop.Cond == nil || loop.Post == nil { 56 return true 57 } 58 init, ok := loop.Init.(*ast.AssignStmt) 59 if !ok || len(init.Lhs) != 1 || len(init.Rhs) != 1 { 60 return true 61 } 62 cond, ok := loop.Cond.(*ast.BinaryExpr) 63 if !ok { 64 return true 65 } 66 x, ok := cond.X.(*ast.Ident) 67 if !ok { 68 return true 69 } 70 lhs, ok := init.Lhs[0].(*ast.Ident) 71 if !ok { 72 return true 73 } 74 if pass.TypesInfo.ObjectOf(x) != pass.TypesInfo.ObjectOf(lhs) { 75 return true 76 } 77 if _, ok := loop.Post.(*ast.IncDecStmt); !ok { 78 return true 79 } 80 81 v, isAddr := fn.ValueForExpr(cond.X) 82 if v == nil || isAddr { 83 return true 84 } 85 switch v := v.(type) { 86 case *ir.Phi: 87 ops := v.Operands(nil) 88 if len(ops) != 2 { 89 return true 90 } 91 _, ok := (*ops[0]).(*ir.Const) 92 if !ok { 93 return true 94 } 95 sigma, ok := (*ops[1]).(*ir.Sigma) 96 if !ok { 97 return true 98 } 99 if sigma.X != v { 100 return true 101 } 102 case *ir.Load: 103 return true 104 } 105 report.Report(pass, cond, "variable in loop condition never changes") 106 107 return true 108 } 109 if source := fn.Source(); source != nil { 110 ast.Inspect(source, cb) 111 } 112 } 113 return nil, nil 114 }