Method calls itself with no conditional in its body
ID |
vbnet.correctness.unconditional_recursion |
Severity |
high |
Remediation Complexity |
medium |
Remediation Risk |
low |
Remediation Effort |
low |
Resource |
Control Flow |
Language |
VB.NET |
Description
Flags a Sub or Function that calls itself (directly or through Me.) passing its own parameters straight through, in a body that contains no conditional or looping construct at all - no If, Select Case, While, Do, For, For Each, Try, inline If(…) operator, and no AndAlso / OrElse short-circuit. With nothing that can stop the descent, the call recurses on every execution. A call that passes anything other than the parameter itself - a different local, or a member read off the parameter - is not reported, because in Visual Basic that is how overload dispatch is written (ContainContact(user) calling ContainContact(user.Username)) and telling the two apart needs the argument types, which this rule does not have. The cost is that recursion on a derived argument (Depth(node.Parent)) is not reported either.
Rationale
Recursion needs a base case: some condition on which the method returns without calling itself. When the body holds no conditional at all, the base case is missing and the recursion never terminates. In .NET this ends in a StackOverflowException, which since .NET 2.0 cannot be caught by a Try/Catch and cannot be handled by an unhandled-exception filter - the whole process is torn down immediately, losing in-flight work, buffered writes and any chance to log the failure. The usual causes are a property or wrapper that reads itself instead of its backing field (Return Total where Return _total was meant), a rename that turned a call to a helper into a call to the enclosing method, or a guard clause deleted during a refactor.
The following code illustrates the pattern detected by this rule:
Public Sub Flush()
' FLAGGED: Method calls itself with no conditional in its body
Flush()
End Sub
Remediation
Add the missing base case: an If (or Select Case) at the top of the method that returns, throws or exits before the recursive call whenever the terminating condition holds - typically an empty collection, Nothing, or a counter reaching zero. If the self-call was never intended, point it at what it should reach: the backing field (_total rather than Total), the base implementation (MyBase.Method(…)), or the helper that was meant to be called. Where the recursion depth is driven by input you do not control, prefer an explicit iterative loop with a Stack(Of T) over recursion, so depth is bounded by available heap rather than by stack size.