Objective-C Throwing Methods Inconsistent in Xcode 14

After moving to Xcode 14 I'm getting inconsistent behavior in my throwing functions. This call works sometimes, and other times it behaves completely differently despite all the inputs and runtime conditions being the same.

And it always worked in previous versions of Swift/Xcode 13.

The method is properly translated to a throwing method in Swift. When the ObjC code runs, Swift is indeed generating an NSError** and passsing it into the ObjC method. On inspection I can see it properly sets the NSError*. Stepping through instructions it never hits the return SecureFile statement as expected.

And yet the Swift code receives a SecureFile object that is not initialized and proceeds to crash.

If I return NULL in the error path, the error is successfully caught. Someone pointed out that it seems to be behavior like swift_error(null_result) instead of swift_error(nonnull_error)

SomeAPI.h

- (nonnull SecureFile *)getSecureFile:(nonnull NSString *)fileName
                                     :(NSError **)error
__attribute__((swift_error(nonnull_error)));

SomeAPI.m

- (nonnull SecureFile *)getSecureFile:(nonnull NSString *)fileName
                                     :(NSError **)error {
    // Some code up here that fails
    if (secureFile != NULL) {
        return secureFile;
    } else {
        *error = [NSError errorWithDomain: MyErrorDomain
                                     code: NotLoggedIn
                                 userInfo:@{
            NSLocalizedDescriptionKey : @"User is not logged in!"
        }];
    }
}

SomeClass.swift

do {
    let secureFile = try someAPI.getSecureFile("test")
    return secureFile.decode()
} catch {
    // Some recovery code here
}

Another interesting behavior I just noticed - if I turn on ASAN for the scheme, it properly catches the error every time.

I think it's failing because the synthesized NSError* variable that Swift provided a pointer to isn't initialized with anything meaningful. If your call succeeds, the Swift call-site is going to check for a non-nil error, and that's sometimes going to think there's a valid error when there's not.

If that's what is going on, you could perhaps make an argument that this is a Swift bug, because your own Swift code certainly doesn't have the opportunity to set the error to nil before the call. I suggest you file a bug report to have the compiler team dig into this a bit, or you could ask the question over on https://forums.swift.org.

However, your method uses a non-standard Obj-C pattern. The usual pattern when providing a NSError** parameter, is to use the return value to say whether an error actually occurred, so that the NSError* pointer is only meaningful when you've actually returned an error, regardless of how it was initialized before the call.

IOW, it'd be more usual for your method to return nil on failure, as well as the NSError object (if the NSError** pointer isn't nil).

Objective-C Throwing Methods Inconsistent in Xcode 14
 
 
Q