SIGN IN SIGN UP

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.
A
Anthony Shoumikhin committed
b669184cbc3bf8d2d3d819165eda51bbc1a97602
Parent: f3e8972
Committed by GitHub <noreply@github.com> on 4/25/2025, 7:09:21 PM