@throw inside an @finally block
ID |
objectivec.correctness.throw_from_finally |
Severity |
low |
Remediation Complexity |
medium |
Remediation Risk |
medium |
Remediation Effort |
medium |
Resource |
Error Handling |
Language |
Objective-C |
Description
Reports a @throw statement inside an @finally block, including one nested in a condition or loop within that block. The region runs from the @finally header to the closing brace at the header’s own indentation, so a @throw in the @try or @catch block, or after the whole statement, is not reported. A return in the same position belongs to the companion rule for returning from a finally block.
Rationale
The @finally block runs while the original exception is still propagating, and throwing from it replaces that exception with this one: the failure that actually happened is lost and the caller diagnoses a cleanup problem instead. Since the cleanup path usually fails only when the original operation did, the exception that surfaces is the least informative of the two.
The following code illustrates the pattern detected by this rule:
@implementation Ledger
- (NSInteger)postAmount:(NSInteger)amount {
@try {
return [self commit:amount];
} @finally {
// FLAGGED: @throw inside an @finally block
@throw [NSException exceptionWithName:@"Cleanup" reason:@"unlock failed" userInfo:nil];
}
}
- (NSInteger)commit:(NSInteger)amount {
return amount;
}
- (BOOL)unlock {
return YES;
}
- (void)log:(NSException *)e {
}
@end
Remediation
Handle the cleanup failure inside the @finally block - log it, or record it on the object - rather than throwing out of it, so the exception already in flight reaches the caller. Where the cleanup failure really is the more important one, catch the original exception explicitly and attach it to the new one before throwing.