Unsafe Control Flow Inside Finally

ID

python.unsafe_finally

Severity

high

Remediation Complexity

trivial

Remediation Risk

low

Remediation Effort

low

Resource

Reliability

Language

Python

Tags

exception-handling, reliability

Description

Reports break, continue, or return statements inside a finally block when they exit the surrounding try. Such control transfers swallow any pending exception that the try body raised, turning a hard error into a silent success.

for item in items:
    try:
        process(item)
    finally:
        break          # FLAW — drops the exception silently

def lookup(key):
    try:
        return load(key)
    finally:
        return None    # FLAW — overrides the original return / raise

Rationale

The finally block is meant for cleanup that runs whether or not the try body raised. If the cleanup transfers control out of the surrounding scope, the exception that was being propagated is lost — the program continues as if nothing had happened, and the failure surfaces far away from its real cause.

Remediation

Move the early-exit logic out of finally. Cleanup goes in finally; control flow stays in the try/except/else blocks:

for item in items:
    try:
        process(item)
    except SomeError:
        break              # OK — explicit, no pending exception
    finally:
        cleanup(item)

A break / continue inside a loop that is itself nested in finally is fine — only the jumps that exit the outer loop or function are flagged.