sa5007.go raw

   1  package sa5007
   2  
   3  import (
   4  	"honnef.co/go/tools/analysis/lint"
   5  	"honnef.co/go/tools/analysis/report"
   6  	"honnef.co/go/tools/go/ir"
   7  	"honnef.co/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.RawDocumentation{
  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  }
  78