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.

Configuration

This detector does not need any configuration.