[Swift 5.7] Using AutoreleasingUnsafeMutablePointer leads to EXC_BAD_ACCESS

Hello everyone!
I've got this simple Swift/Objective-C app:

Test.h

#import <Foundation/Foundation.h>

@interface TestStructObj: NSObject

@property (nonnull) NSString* str;

@end

Test.m

@implementation TestStructObj

- (instancetype)init
{
    self = [super init];
    self.str = @"init";
    return self;
}

@end

ContentView.swift

import SwiftUI

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

struct ContentView: View {
    var body: some View {
        VStack {
            Text(testObjAutorelease()) // here is "Thread 1: EXC_BAD_ACCESS (code=1, address=...)" only in Release runtime
        }
        .padding()
    }
}

func testObjAutorelease() -> String {
    var t = TestStructObj()
    fillObjAutorelease(&t)
    return t.str
}

func fillObjAutorelease(_ t: AutoreleasingUnsafeMutablePointer<TestStructObj>) {
    t.pointee.str = "fillObjAutorelease"
    //print("test") -> uncomment leads to EXC_BAD_ACCESS
    //print("\(t.pointee.str)") -> uncomment leads to EXC_BAD_ACCESS
    //let i = t.pointee.str.count -> uncomment leads to EXC_BAD_ACCESS
}

I've faced this issue: with Release configuration app crashes with EXC_BAD_ACCESS if any of commented lines in fillObjAutorelease body are uncommented...

Screenshot with exception:

App crashes only with Release configuration and XCode >= 14.0
With Debug or XCode 13.4.1 app works just fine...

I've also noticed that using UnsafeMutablePointer instead of AutoreleasingUnsafeMutablePointer solves this problem.

So,

  1. Any ideas why using AutoreleasingUnsafeMutablePointer with XCode >= 14 causes EXC_BAD_ACCESS?
  2. Which MutablePointer is more correct to use in this situation?

P.S. I didn't use inout because I need MutablePointer for objc compatibility in more complicated project

The Xcode 14 compiler has an upgrade for smaller message send and optimized ARC. I think it leads to this issue because of the asm optimization.

When you set optimization level to none, everything back to normal.

So there is some autoreleasing related asm instructions is not correct leads to a over-releasing issue.

Improve app size and runtime performance - WWDC22 - Videos - Apple Developer

I think there is no need to use AutoreleasingUnsafeMutablePointer here. As you said, replace it with UnsafeMutablePointer is good.

2 Likes

Thanx for the answer @blastmann!

So, it seems like XCode 14 bug?

Yeah, I think I'll use UnsafeMutablePointer at least as long as this bug exists...

A bit of explanation.

I used AutoreleasingUnsafeMutablePointer, because I need my swift class to conform to this objc protocol:

@protocol TestProtocol
+ (void)test:(TestStructObj * _Nonnull * _Nonnull)t;
@end

But swift auto representation (made by XCode) is

protocol TestProtocol {
    static func test(_ t: AutoreleasingUnsafeMutablePointer<TestStructObj>)
}

So it seems to be more correct to use AutoreleasingUnsafeMutablePointer.
But I found out, that

@protocol TestProtocol
+ (void)test:(TestStructObj * _Nonnull __weak * _Nonnull)t;
@end

is equal to swift

protocol TestProtocol {
    static func test(_ t: UnsafeMutablePointer<TestStructObj>)
}

and it's working fine for all XCode versions and configurations :slight_smile:

Double-check that you haven’t introduced a leak making this change. Still, UMP and AUMP should only differ when calling a setter on the Swift side, not the ObjC side, so I’m surprised you’re seeing different behavior. Running the AUMP version under Address Sanitizer may reveal where the object is getting prematurely released.

EDIT: the actual ObjC equivalent to UMP would use strong, not weak. But Swift may be handling that for you.

1 Like