You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Use a local strong NSError for methods with nested autorelease pool (#10465)
Otherwise, the out `NSError` arg get assigned an autoreleasing `NSError`
object that gets deallocated when the nested autorelease pool is drained
and no error is reported:
```objectivec
- (BOOL)foo:(NSError **)error {
if (error) {
// Creates an autoreleased error object.
*error = [NSError errorWithDomain:@"DemoDomain" code:42 userInfo:nil];
}
return NO;
}
- (BOOL)bar:(NSError **)error {
@autoreleasepool {
// foo: returns NO, sets *error to an autoreleased NSError.
// But as soon as we leave this block, that NSError is released.
return [self foo:error];
}
}
- (void)run {
NSError *error = nil;
if (![self bar:&error]) { // Crash here even before entering the if-statement below due to `objc_storeStrong` call on the autoreleased error out arg.
// Even if we manage to get here by marking the error var as __autoreleasing above,
// Error now points at freed memory, so we get another crash or garbage.
NSLog(@"❌ Faulty: error code = %ld", (long)error.code);
}
}
```
We introduce a local error var to hold onto the autoreleased `NSError`
created by some other API like `foo` and use it to initialize the out
`NSError` arg:
```objectivec
- (BOOL)bar:(NSError **)error {
NSError *localError = nil;
BOOL ok = NO;
@autoreleasepool {
ok = [self foo:&localError];
// ARC retains localError here when assigning the autoreleased object.
}
if (!ok && error) {
*error = localError; // Safe - localError survived the pool drain.
}
return ok;
}
- (void)run {
NSError *error = nil;
if (![self bar:&error]) {
if (error) { // Don't forget to still check the error var.
// Now this reliably logs “42”
NSLog(@"✅ Fixed: error code = %ld", (long)error.code);
}
}
}
```
Another important observation is that generally it's required to check
if the out `NSError` has been set despite the returned status.
And a more trickier crash happens even before the control reaches error
handling. The compiler assumes the error local var in the `run` method
is `__strong`, so it generates `objc_storeStrong` call. Whereas in fact
it points to an `__autoreleasing` object (that gets deallocated when the
pool drains as we clarified), and thus such a call on it will lead to a
crash regardless, we won't even reach the code that tries to access
`code` on it. Unless we indeed introduce a local strong var inside `bar`
to be assigned to the out `NSError` arg and so make the generated
`objc_storeStrong` call a legit one. Although, that issue can
technically be resolved differently - by marking `error` var in `run` as
`__autoreleasing` to skip the `objc_storeStrong` call. But that still
doesn't help reporting the actual error, since it's gonna be nil after
the nested autorelease pool drainage inside `bar`. And apparently having
the nested autorelease pool is important enough, so we can't avoid it
and have to use such workarounds.
0 commit comments