package sa4008 import ( "go/ast" "honnef.co/go/tools/analysis/lint" "honnef.co/go/tools/analysis/report" "honnef.co/go/tools/go/ir" "honnef.co/go/tools/internal/passes/buildir" "golang.org/x/tools/go/analysis" ) var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ Analyzer: &analysis.Analyzer{ Name: "SA4008", Run: run, Requires: []*analysis.Analyzer{buildir.Analyzer}, }, Doc: &lint.RawDocumentation{ Title: `The variable in the loop condition never changes, are you incrementing the wrong variable?`, Text: `For example: for i := 0; i < 10; j++ { ... } This may also occur when a loop can only execute once because of unconditional control flow that terminates the loop. For example, when a loop body contains an unconditional break, return, or panic: func f() { panic("oops") } func g() { for i := 0; i < 10; i++ { // f unconditionally calls panic, which means "i" is // never incremented. f() } }`, Since: "2017.1", Severity: lint.SeverityWarning, MergeIf: lint.MergeIfAll, }, }) var Analyzer = SCAnalyzer.Analyzer func run(pass *analysis.Pass) (interface{}, error) { for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs { cb := func(node ast.Node) bool { loop, ok := node.(*ast.ForStmt) if !ok { return true } if loop.Init == nil || loop.Cond == nil || loop.Post == nil { return true } init, ok := loop.Init.(*ast.AssignStmt) if !ok || len(init.Lhs) != 1 || len(init.Rhs) != 1 { return true } cond, ok := loop.Cond.(*ast.BinaryExpr) if !ok { return true } x, ok := cond.X.(*ast.Ident) if !ok { return true } lhs, ok := init.Lhs[0].(*ast.Ident) if !ok { return true } if pass.TypesInfo.ObjectOf(x) != pass.TypesInfo.ObjectOf(lhs) { return true } if _, ok := loop.Post.(*ast.IncDecStmt); !ok { return true } v, isAddr := fn.ValueForExpr(cond.X) if v == nil || isAddr { return true } switch v := v.(type) { case *ir.Phi: ops := v.Operands(nil) if len(ops) != 2 { return true } _, ok := (*ops[0]).(*ir.Const) if !ok { return true } sigma, ok := (*ops[1]).(*ir.Sigma) if !ok { return true } if sigma.X != v { return true } case *ir.Load: return true } report.Report(pass, cond, "variable in loop condition never changes") return true } if source := fn.Source(); source != nil { ast.Inspect(source, cb) } } return nil, nil }