github.com/amarpal/go-tools@v0.0.0-20240422043104-40142f59f616/staticcheck/sa5007/sa5007.go (about)

     1  package sa5007
     2  
     3  import (
     4  	"github.com/amarpal/go-tools/analysis/lint"
     5  	"github.com/amarpal/go-tools/analysis/report"
     6  	"github.com/amarpal/go-tools/go/ir"
     7  	"github.com/amarpal/go-tools/internal/passes/buildir"
     8  
     9  	"golang.org/x/tools/go/analysis"
    10  )
    11  
    12  var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
    13  	Analyzer: &analysis.Analyzer{
    14  		Name:     "SA5007",
    15  		Run:      run,
    16  		Requires: []*analysis.Analyzer{buildir.Analyzer},
    17  	},
    18  	Doc: &lint.Documentation{
    19  		Title: `Infinite recursive call`,
    20  		Text: `A function that calls itself recursively needs to have an exit
    21  condition. Otherwise it will recurse forever, until the system runs
    22  out of memory.
    23  
    24  This issue can be caused by simple bugs such as forgetting to add an
    25  exit condition. It can also happen "on purpose". Some languages have
    26  tail call optimization which makes certain infinite recursive calls
    27  safe to use. Go, however, does not implement TCO, and as such a loop
    28  should be used instead.`,
    29  		Since:    "2017.1",
    30  		Severity: lint.SeverityWarning,
    31  		MergeIf:  lint.MergeIfAny,
    32  	},
    33  })
    34  
    35  var Analyzer = SCAnalyzer.Analyzer
    36  
    37  func run(pass *analysis.Pass) (interface{}, error) {
    38  	for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
    39  		eachCall(fn, func(caller *ir.Function, site ir.CallInstruction, callee *ir.Function) {
    40  			if callee != fn {
    41  				return
    42  			}
    43  			if _, ok := site.(*ir.Go); ok {
    44  				// Recursively spawning goroutines doesn't consume
    45  				// stack space infinitely, so don't flag it.
    46  				return
    47  			}
    48  
    49  			block := site.Block()
    50  			for _, b := range fn.Blocks {
    51  				if block.Dominates(b) {
    52  					continue
    53  				}
    54  				if len(b.Instrs) == 0 {
    55  					continue
    56  				}
    57  				if _, ok := b.Control().(*ir.Return); ok {
    58  					return
    59  				}
    60  			}
    61  			report.Report(pass, site, "infinite recursive call")
    62  		})
    63  	}
    64  	return nil, nil
    65  }
    66  
    67  func eachCall(fn *ir.Function, cb func(caller *ir.Function, site ir.CallInstruction, callee *ir.Function)) {
    68  	for _, b := range fn.Blocks {
    69  		for _, instr := range b.Instrs {
    70  			if site, ok := instr.(ir.CallInstruction); ok {
    71  				if g := site.Common().StaticCallee(); g != nil {
    72  					cb(fn, site, g)
    73  				}
    74  			}
    75  		}
    76  	}
    77  }