UnsafeMutableRawPointer with XCode 15

After update XCode to 15, I encountered a crash with UnsafeMutableRawPointer. To recreate the problem I write this simple test code.

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        test()
    }

    private func test() {
        var abl = AudioBufferList()
        let capacity = 4096
        let lp1 = UnsafeMutableAudioBufferListPointer(&abl)
        let outputBuffer1 = UnsafeMutablePointer<Int8>.allocate(capacity: capacity)
        let outputBuffer2 = UnsafeMutablePointer<Int8>.allocate(capacity: capacity)

        // It crashed here
        lp1[0].mData = UnsafeMutableRawPointer(outputBuffer1)
        lp1[0].mNumberChannels = 1
        lp1[0].mDataByteSize = UInt32(capacity)
        lp1[1].mData = UnsafeMutableRawPointer(outputBuffer2)
        lp1[1].mNumberChannels = 1
        lp1[1].mDataByteSize = UInt32(capacity)

        let lp2 = UnsafeMutableAudioBufferListPointer(&abl)

        let data = (
            UnsafeMutablePointer<Int16>.allocate(capacity: 4096),
            packet: 1
        )

        lp2[0].mData = UnsafeMutableRawPointer(data.0)
    }
}

I checked the XCode 15 Release Notes and found out they did something with the pointer default initialization

P1020R1 - Smart pointer creation with default initialization

Is this causing the problem or I'm doing it wrong? Because it work perfectly fine with XCode 14.3.1 and below

P/s: I can't provide the full crash logs cause it's company property but I can provide these:

Exception Type:  EXC_CRASH (SIGABRT)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Exception Note:  EXC_CORPSE_NOTIFY
Triggered by Thread:  5

Application Specific Information:
stack buffer overflow
Thread 5 name:   Dispatch queue: com.apple.NSXPCConnection.user.endpoint
Thread 5 Crashed:
0   libsystem_kernel.dylib        	       0x20419ab78 __pthread_kill + 8
1   libsystem_pthread.dylib       	       0x23de0c3bc pthread_kill + 268
2   libsystem_c.dylib             	       0x1d780c44c __abort + 128
3   libsystem_c.dylib             	       0x1d77f7868 __stack_chk_fail + 96

Clearly there are something wrong with the memory address after init UnsafeMutableRawPointer from the UnsafeMutablePointer<Int8>

Accepted Reply

I checked the Xcode 15 Release Notes and found out they did something with the pointer default initialization

That’s a C++ thing which has nothing to do with Swift.

Your problem is almost certainly this:

var abl = AudioBufferList() … let lp1 = UnsafeMutableAudioBufferListPointer(&amp;abl)

The UnsafeMutableAudioBufferListPointer type expects that the pointer you pass to its initialiser live for as long as the UnsafeMutableAudioBufferListPointer value itself. You’re passing in a temporary pointer and thus the UnsafeMutableAudioBufferListPointer value ends up ‘dangling’.

To learn more about this, real The Peril of the Ampersand.

Share and Enjoy

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

Replies

I checked the Xcode 15 Release Notes and found out they did something with the pointer default initialization

That’s a C++ thing which has nothing to do with Swift.

Your problem is almost certainly this:

var abl = AudioBufferList() … let lp1 = UnsafeMutableAudioBufferListPointer(&amp;abl)

The UnsafeMutableAudioBufferListPointer type expects that the pointer you pass to its initialiser live for as long as the UnsafeMutableAudioBufferListPointer value itself. You’re passing in a temporary pointer and thus the UnsafeMutableAudioBufferListPointer value ends up ‘dangling’.

To learn more about this, real The Peril of the Ampersand.

Share and Enjoy

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

@eskimo Thank you for your replied. After reading your mention thread and watching briefly the WWDC videos, I can understand thats pointer's life time and it's value's lifetime is a different things, plus pointer type and it's pointing object type can be sometime really messed up. So I can asume that until XCode 15, Pointer is being easy on me ?

Base on the informations you provide me I'm able to resolved the 'dangling' problem, but now when passing AudioBufferList to AudioConverterFillComplexBuffer it always return -50, so it's kinda unrelated to the thread but can you show me the proper way to create an UnsafeMutablePointer<AudioBufferList >? The way I'm doing it now when I updated the mData, the corresponding value in AudioBufferList not get update

        let capacity = 4096
        var abl = AudioBufferList()
        abl.mNumberOfBuffers = 2
        var uPABL = UnsafeMutablePointer<AudioBufferList>.allocate(capacity: capacity * 2)
        let lp1 = UnsafeMutableAudioBufferListPointer(uPABL)

        let outputBuffer1 = UnsafeMutablePointer<Int8>.allocate(capacity: capacity)
        lp1[0].mData = UnsafeMutableRawPointer(outputBuffer1)

Really sorry for multiple reply but I did init the UnsafeMutablePointer<AudioBufferList>

        let capacity = 4096
        var abl = AudioBufferList()
        abl.mNumberOfBuffers = 2
        var uPABL = UnsafeMutablePointer<AudioBufferList>.allocate(capacity: capacity * 2)
        uPABL.initialize(to: abl)
        let lp1 = UnsafeMutableAudioBufferListPointer(uPABL)

        let outputBuffer1 = UnsafeMutablePointer<Int8>.allocate(capacity: capacity)
        lp1[0].mData = UnsafeMutableRawPointer(outputBuffer1)

@eskimo Nevermind, my bad I pass the temporary pointer to the AudioConverterFillComplexBuffer method, that why It won't work, now it's work perpectly fine. Thank you, but if you had some spare time, I would really happy if you can educate me why until XCode, it decide to crash. ^^!

The variable abl contains an empty AudioBufferList, and the value of lp1.first above is nil. Since the collection is empty, trying to get its first element via lp1[0] results in a crash.

You can use AVAudioPCMBuffer to allocate an AudioBufferList in Swift. For a non-interleaved stereo AudioBufferList with format Int16 and a total capacity of 2048 samples, you can put:

guard let format = AVAudioFormat(commonFormat: .pcmFormatInt16, sampleRate: 48000, channels: 2, interleaved: false),
      let buffer = AVAudioPCMBuffer(pcmFormat: format, frameCapacity: 2048) else { return }

Then access the underlying AudioBufferList with:

var abl = UnsafeMutableAudioBufferListPointer(buffer.mutableAudioBufferList)

In addition to UnsafeMutableAudioBufferListPointer, you can use the int16ChannelData property of AVAudioPCMBuffer to modify the audio buffers in this case, since buffer has common format pcmFormatInt16.

I’m glad that Cayley stepped in to answer your audio questions because, while I know a lot about Swift pointers, I know very little about audio (-;

So I can asume that until Xcode 15, Pointer is being easy on me ?

Not specifically, it’s just how things pan out. When you rely on undefined behaviour you can see all sorts of weird things. For example, you might see the behaviour change:

  • In Debug vs Release builds

  • When you make an unrelated change to your code

  • When you update the compiler

  • When you update the OS version

  • Between different architectures (Intel vs Apple silicon, for example)

Pointers are labelled Unsafe for a reason! When programming for Apple platforms you have to use a lot of them because of our Objective-C heritage. Hopefully that’ll improve over time, but audio is one of the more challenging cases.

Share and Enjoy

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