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