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  }