Skip to content

Commit 4bd6fba

Browse files
authored
fix: unconditional-recursion false positive when the function is called right after its declaration (#1212) (#1214)
1 parent 5f01efa commit 4bd6fba

File tree

2 files changed

+37
-15
lines changed

2 files changed

+37
-15
lines changed

rule/unconditional_recursion.go

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,35 @@ func (*UnconditionalRecursionRule) Apply(file *lint.File, _ lint.Arguments) []li
1717
failures = append(failures, failure)
1818
}
1919

20-
w := &lintUnconditionalRecursionRule{onFailure: onFailure}
21-
ast.Walk(w, file.AST)
20+
// Range over global declarations of the file to detect func/method declarations and analyze them
21+
for _, decl := range file.AST.Decls {
22+
n, ok := decl.(*ast.FuncDecl)
23+
if !ok {
24+
continue // not a func/method declaration
25+
}
26+
27+
if n.Body == nil {
28+
continue // func/method with empty body => it can not be recursive
29+
}
30+
31+
var rec *ast.Ident
32+
switch {
33+
case n.Recv == nil:
34+
rec = nil
35+
case n.Recv.NumFields() < 1 || len(n.Recv.List[0].Names) < 1:
36+
rec = &ast.Ident{Name: "_"}
37+
default:
38+
rec = n.Recv.List[0].Names[0]
39+
}
40+
41+
w := &lintUnconditionalRecursionRule{
42+
onFailure: onFailure,
43+
currentFunc: &funcStatus{&funcDesc{rec, n.Name}, false},
44+
}
45+
46+
ast.Walk(w, n.Body)
47+
}
48+
2249
return failures
2350
}
2451

@@ -50,26 +77,14 @@ type lintUnconditionalRecursionRule struct {
5077
inGoStatement bool
5178
}
5279

53-
// Visit will traverse the file AST.
54-
// The rule is based in the following algorithm: inside each function body we search for calls to the function itself.
80+
// Visit will traverse function's body we search for calls to the function itself.
5581
// We do not search inside conditional control structures (if, for, switch, ...) because any recursive call inside them is conditioned
5682
// We do search inside conditional control structures are statements that will take the control out of the function (return, exit, panic)
5783
// If we find conditional control exits, it means the function is NOT unconditionally-recursive
5884
// If we find a recursive call before finding any conditional exit, a failure is generated
5985
// In resume: if we found a recursive call control-dependent from the entry point of the function then we raise a failure.
6086
func (w *lintUnconditionalRecursionRule) Visit(node ast.Node) ast.Visitor {
6187
switch n := node.(type) {
62-
case *ast.FuncDecl:
63-
var rec *ast.Ident
64-
switch {
65-
case n.Recv == nil:
66-
rec = nil
67-
case n.Recv.NumFields() < 1 || len(n.Recv.List[0].Names) < 1:
68-
rec = &ast.Ident{Name: "_"}
69-
default:
70-
rec = n.Recv.List[0].Names[0]
71-
}
72-
w.currentFunc = &funcStatus{&funcDesc{rec, n.Name}, false}
7388
case *ast.CallExpr:
7489
// check if call arguments has a recursive call
7590
for _, arg := range n.Args {

testdata/unconditional_recursion.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,3 +199,10 @@ func nr902() {
199199
nr902() // MATCH /unconditional recursive call/
200200
}()
201201
}
202+
203+
// Test for issue #1212
204+
func NewFactory() int {
205+
return 0
206+
}
207+
208+
var defaultFactory = NewFactory()

0 commit comments

Comments
 (0)