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.