Exception thrown from a Finally block

ID

vbnet.correctness.throw_from_finally_block

Severity

high

Remediation Complexity

medium

Remediation Risk

medium

Remediation Effort

low

Resource

Error Handling

Language

VB.NET

Description

Reports a Throw statement inside the Finally clause of a Try statement, whether it is the first statement of the clause or follows the cleanup code, and points the finding at the thrown expression. Throwing from the Try body or from a Catch clause is not reported: those are the normal ways to signal a failure, because the exception can still be caught by an enclosing handler and carries the original failure with it.

Rationale

A Finally clause runs on both paths out of the Try statement, including the path where an exception is already travelling up the stack. Throwing there abandons that exception: the new one takes its place, the original object and the stack trace that lead back to the statement which actually failed are discarded, and no handler ever sees them. The caller is told that cleanup went wrong and is given no way to find out what went wrong first, so the log entry names the symptom of the recovery instead of the fault - the class of failure that can only be diagnosed by reproducing it. On the path where the Try body succeeded, the same Throw turns a completed operation into a failure reported from cleanup, at a point where the caller has every reason to believe the work is done.

The following code illustrates the pattern detected by this rule:

Public Sub Import(ByVal path As String)
    Dim reader As StreamReader = Nothing
    Try
        reader = New StreamReader(path)
        Apply(reader.ReadToEnd())
    Finally
        ' FLAGGED: Exception thrown from a Finally block
        Throw New InvalidOperationException("import finished in an unknown state")
    End Try
End Sub

Remediation

Keep the Finally clause to cleanup that cannot fail - guard the state you rely on rather than asserting it, as in If reader IsNot Nothing Then reader.Dispose(). When a cleanup step can genuinely fail, wrap that step in its own Try …​ Catch inside the Finally clause and record the failure without propagating it, so the in-flight exception survives; if the caller must learn about it, attach it to the original exception as an inner exception or aggregate the two rather than replacing one with the other. Validation and state checks that were placed in the Finally clause belong in the Try body, or after the whole Try statement, where an enclosing handler can still act on them.

Configuration

This detector does not need any configuration.