Crash Xcode 14 with Swift -> Objective-C framework; AutoreleasingUnsafeMutablePointer; -O Optimize for Speed

I am having a crash with a Swift -> Objective-C interoperability under Xcode 14 / Swift 5.7 but only when Optimize for Speed is part of the build. There was no issue with this under Xcode 13.4.1 / Swift 5.6 under the same conditions. Typically, this compiler option is disabled for Debug builds so this was only detected once we provided release builds to beta users using Test Flight. 

The interoperability is with using a proprietary, third-party framework. I am not privy to the Objective-C code that makes up the public API for this framework. As the framework had worked with Xcode 13.4.1 (with and without the speed optimization) and also works in Debug mode with Xcode 14.x (I have tried 14.1 as well) but crashes with the speed optimization turned on, I suspect the optimization may be at fault. But it is hard to say. I am hoping someone with better knowledge than I of this can guide me as to why this is happening…and only now.

Here is an example of the code. Note: there are multiple instances of this same pattern in the API. There are vendor supplied structures (as well as NSArray as in this example) that cause similar failure. The vendor has supplied only Objective-C sample code and documentation.

    func listItems() {
        var availableItems: NSMutableArray? = NSMutableArray()
        let result = itemSDK.getAvailableItemList(&availableItems)
	     // ...
    }

The header file provides:

- (ITEM_RESULT) getAvailableItemList:(NSMutableArray**)getAvailableItemList;

The derived Swift interface for this is:

func getAvailableItemList(_ availableItemsList: AutoreleasingUnsafeMutablePointer<NSMutableArray?>!) -> ITEM_RESULT

The crash occurs when calling the API and/or attempting to use the value associated with the AutoreleasingUnsafeMutablePointer.

I have turned on all memory management diagnostics for the run phase of the scheme. When I do so, I see the following in the console when I get a fatal Thread 1: EXC_BREAKPOINT (code=1, subcode=0x1d66e8a88) presented in the caller to the listItems() method.

*** -[__NSArrayM retain]: message sent to deallocated instance 0x28bd15200

My assumption is that this indicates the memory was released (aggressively?) and so this causes the crash. Again, only with the -O (Optimize for Speed) option is enabled.

I have been able to code a potential solution to this issue. Maybe this should have been the solution for prior Swift versions…but I had no reason (as it had worked without issue up until Xcode 14).

    func listItems() {
        let itemsArray = NSMutableArray() 
        var availableItems: NSMutableArray? = itemsArray
        let result = itemSDK.getAvailableItemList(&availableItems)
	     // ...
    }

This solution assigns the array to an immutable variable and then assigns that reference to an optional that is passed for the AutoreleasingUnsafeMutablePointer. My assumption is that adds an additional retain to keep the optimization from removing the memory for the array until the itemsArray is no longer referenced.

I am reaching out to see if this is a known issue by others and/or expected (but new) behavior as I do not have much experience with AutoreleasingUnsafeMutablePointer. Also, if this is not a bug but is the new normal, is the potential solution the proper way to use the AutoreleasingUnsafeMutablePointer with Objective-C framework APIs? Or should I be doing something different?

Thanks for any insight or pointers (even if unsafe).

Replies

I suspect the optimization may be at fault.

It’s certainly possible, but it’s also possible that something is wonky in the closed source code and the optimiser changes have exposed that.

func listItems() {
    var availableItems: NSMutableArray? = NSMutableArray()
    let result = itemSDK.getAvailableItemList(&availableItems)
   // ...
}

The standard pattern for this is would be to initialise availableItems to nil. Does that help?

The derived Swift interface for this is:

Please post the original C interface for this method.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Thanks, eskimo.

I notice another post with a similar issue:.

The header file for the framework had the following protocol...

@protocol itemSDK <NSObject>
- (SDK_RESULT) getAvailableItemList:(NSMutableArray**)itemList;
// ...
@end

I tested this specific API with the "standard pattern" and it does not fail. I will give it a try with the many other SDK interfaces for this framework and see if this can be the best solution. BTW, where is the standard pattern documented as the proper way to pass a value for an AutoreleasingUnsafeMutablePointer?. I found very few examples and most assign an initial value. I reviewed many of the videos from WWDC on unsafe pointers but none cover this particular pointer. Maybe I missed something.

I tested this specific API with the "standard pattern" and it does not fail.

Cool.

BTW, where is the standard pattern documented as the proper way to pass a value for an AutoreleasingUnsafeMutablePointer?.

That’s a good question. I’ve never gone looking for this because the approach I use is ‘obvious’. Most Objective-C code uses this ‘double star’ pattern for out parameters and, if you’re dealing with an out parameter, there’s no point setting up an initial value for it to be overwritten by the callee.

The header file for the framework had the following protocol...

Are there any doc comments, or just docs, that explain the expected calling conventions for this? It certainly looks like itemList is an out parameter but there’s nothing in that prototype that guarantees that [1].

btw I tried reproducing your crash with some test code that replicates the -getAvailableItemList: method but I never got it to crash. That suggests that the problem is somehow tied to the implementation of -getAvailableItemList:, which means it’ll be hard to make progress without cooperation of that code’s author [2].

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] Indeed, the use of NSMutableArray, rather than NSArray, suggests that something else is going on here, but that could just be ignorance on the part of the library’s author. Type names like SDK_RESULT do not inspire me with confidence )-:

[2] Well, in your shoes I’d probably disassemble -getAvailableItemList: to see what it’s actually doing, but that’s definitely above and beyond the call of duty (-:

Again, thanks eskimo.

I tried several other of the APIs using the standard pattern as a solution. This does eliminate crashes but the results returned are not always the same as before when a value was provided instead of nil. In a different, but very similar API call to the getAvailableItemList, an NSMutableArray is to be returned but instead I get a SDK_RESULT value indicating that the invalid parameters have been supplied. If I use the workaround that I have instead (that does crash and provides a non-nil value), then I get a success SDK_RESULT along with the values in the array parameter. So I think each will need to be tested. I just wish I had worked as before without crashing.

I have also tried replicating the same crash with sample Object-C code with no success. Do you know if there might be some interoperbility differences with a framework compiled with an older Xcode but used with Swift code under Xcode 14 (with -O)?. I can try and ask the vendor what Xcode they use to build their frameowrk. Based on the warning in the usage sample code they provide...it is not a recent version.

In a different, but very similar API call to the getAvailableItemList, an NSMutableArray is to be returned but instead I get a SDK_RESULT value indicating that the invalid parameters have been supplied.

Well, that doesn’t exactly inspire me with confidence )-:

Do you know if there might be some interoperbility differences with a framework compiled with an older Xcode but used with Swift code under Xcode 14 (with -O)?

I’m not aware of any systematic problem in this space.

Keep in mind that Swift interacts with a lot of different libraries via the Objective-C ABI. This obviously works in general, otherwise apps would be failing left, right, and centre.

I can try and ask the vendor what Xcode they use to build their frameowrk.

If you do manage to engage with the vendor I recommend that you spend that time clarifying the expected calling conventions for these methods. Don’t muddy the waters by mentioning Swift; just ask ’em how you’re supposed to call these methods from Objective-C.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"