Help~ NSMutableArray conditional bridge crash in iOS 15.4 beta

We found our app crashes in iOS 15.4 beta only. The summary:

  • Try to conditionally cast to swift array from an Objective-C NSMutableArray property of Objective-C class
  • The NSMutableArray property is declared nonnull by the macro pair NS_ASSUME_NONNULL_BEGIN , but not initialized properly then actually it is nil
  • The casting fails and return nil in iOS 15.3 and earlier, but crashes in the new iOS 15.4 beta
  • Crash log shows EXC_BAD_ACCESS with null at swift_getObjectType function

(It is iOS 15.4 beta 1 when creating this topic)

Tested by add a symbol breakpoint, found that ‘swift_getObjectType’ is not called in iOS 15.3 and earlier.

the stack of the crashed demo

#0	0x00000001b24cbbe0 in swift_getObjectType ()
#1	0x00000001b243190c in specialized _bridgeCocoaArray<τ_0_0>(_:) ()
#2	0x00000001b211f6d8 in _bridgeCocoaArray<τ_0_0>(_:) ()
#3	0x00000001b1daf4a8 in static Array._conditionallyBridgeFromObjectiveC(_:result:) ()
#4	0x00000001b213b168 in _conditionallyBridgeFromObjectiveC_bridgeable<τ_0_0>(_:_:) ()
#5	0x0000000102e5e60c in ViewController.testCastingMutableArray() at /Users/dinglan/Downloads/example/brook/open/ObjectiveCMutableArrayBridging/ObjectiveCMutableArrayBridging/ObjectiveCMutableArrayBridging/ViewController.swift:23

It’s not safe to use a nonnull property which is actually nil, we have corrected by initializing the object properly
Still no idea the crash reason. Do you guys know, What changed about Swift in iOS 15.4 beta, The question is why the casting behaves different in iOS 15.3 and iOS 15.4
, is it a Swift 5.6 issue?

The demo code to reproduce the crash.

// the Objective-C class
NS_ASSUME_NONNULL_BEGIN

@interface MLCParseResult : NSObject

/// assume nonnull, but not initialized properly
@property (nonatomic, strong) NSMutableArray <NSObject *> *mutableArray;

@end

NS_ASSUME_NONNULL_END

@implementation MLCParseResult

// mutableArray is not initialized

@end
// cast in swift file
func testCastingMutableArray() {
    let result = MLCParseResult()
    
    // cast mutableArray will crash here in iOS 15.4 beta
    let array = result.mutableArray as? [NSObject]
    
    print(array as Any)
}

Also push the demo at GitHub

Thanks in advance!:innocent:

I may be missing something (genuinely, please correct me if I am!), but this seems like correct behavior. If you say something can't be nil, and it is nil, then you've violated your API contract. You can probably work around it by doing result.mutableArray as NSMutableArray? as? [NSObject].

5 Likes

Thanks for your reply. Yeah, we have correct the object by initialize the array in the initializer.

- (instancetype)init {
    self = [super init]
    if (self) {
        _mutableArray = [NSMutableArray array];
    }
}

The question is why the casting behaves different in iOS 15.3 and iOS 15.4

In iOS 15.4 beta2(19E5219e), we found this crash disappeared, It's a bug of iOS 15.4?

Returning null when you've promised something is non-null is what's called undefined behavior. When you write code that has undefined behavior it can do literally anything. It can crash, or not crash, or corrupt memory, or email your enemy the nuclear launch codes. What causes it to start or stop crashing could be anything – a change in the OS, the compiler, the weather.

In this case, a kindly compiler engineer might have spotted people were relying on this not crashing and put in a workaround in a later beta. But you should never rely on that happening.

10 Likes

Great, Got it!. Thanks a lot, we have fixed the incorrect code! :pray::pray::pray:

It does not crash in the latest beta on Feb 14.