src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

sa4008.go (2267B)


      1 package sa4008
      2 
      3 import (
      4 	"go/ast"
      5 
      6 	"honnef.co/go/tools/analysis/lint"
      7 	"honnef.co/go/tools/analysis/report"
      8 	"honnef.co/go/tools/go/ir"
      9 	"honnef.co/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.RawDocumentation{
     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) (any, 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.(type) {
     86 			case *ir.Phi:
     87 				return true
     88 			case *ir.Load:
     89 				return true
     90 			}
     91 			report.Report(pass, cond, "variable in loop condition never changes")
     92 
     93 			return true
     94 		}
     95 		if source := fn.Source(); source != nil {
     96 			ast.Inspect(source, cb)
     97 		}
     98 	}
     99 	return nil, nil
    100 }