"pointer being freed was not allocated", unless I have an empty `deinit`

Hello,

I recently helped a colleague with an unexpected crash, which I have been able to reduce it to the code below.

Starting with a new iOS project, default settings, add these two files:

// Thing.swift
import Foundation

protocol Helper {
    func doStuff()
}

class StandardHelper: Helper {
    func doStuff() { }

    // deinit { }
}

struct OtherHelper: Helper {
    func doStuff() { }
}

@MainActor
class ThingWithInitWithDefault {
    private let helper: Helper

    // ⚠️ Call to main actor-isolated initializer 'init()' in a synchronous nonisolated context
    init(helper: Helper = StandardHelper()) {
        self.helper = helper
    }

    func perform() {
        helper.doStuff()
    }
}

@MainActor
class ThingWithInitWithInternalDefault {
    private let helper: Helper

    init(helper: Helper? = nil) {
        // Brings the evaluation of the "default" *inside* our init
        self.helper = helper ?? StandardHelper()
    }

    func perform() {
        helper.doStuff()
    }
}

And:

// ThingsTests.swift
import Foundation
import XCTest
@testable import FreeingNeverAllocated

@MainActor
final class ThingTests: XCTestCase {
    // Crashes without empty deinit
    func testThingWithInitWithDefault() {
        let sut = ThingWithInitWithDefault()
        sut.perform()
    }

    // Crashes without empty deinit
    func testThingWithInitWithInternalDefault() {
        let sut = ThingWithInitWithInternalDefault()
        sut.perform()
    }

    // Always fine, because struct
    func testThingPassedHelperAtInit() {
        let sut = ThingWithInitWithDefault(helper: OtherHelper())
        sut.perform()
    }
}

The two ThingWithInit* implementations were to explore whether the issue was being caused by the need to evaluate a default value for an argument before entering the init - it made no difference, but did clear the warning. Is there an annotation to address the warning in the first implementation?

With the code as-supplied, both of the first two tests will crash:

malloc: *** error for object 0x262c4ff30: pointer being freed was not allocated

Uncommenting the empty deinit in StandardHelper stops the crash from happening.

(Confirmed on Xcode 26.1.2 and 26.0)

Following the recommendation to put a break point on malloc_error_break yields the following stack trace:

#0	0x000000018022ec64 in malloc_error_break ()
#1	0x000000018023c118 in malloc_vreport ()
#2	0x000000018023c36c in malloc_report ()
#3	0x0000000180208dc4 in ___BUG_IN_CLIENT_OF_LIBMALLOC_POINTER_BEING_FREED_WAS_NOT_ALLOCATED ()
#4	0x0000000257d38fcc in swift::TaskLocal::StopLookupScope::~StopLookupScope ()
#5	0x0000000257d2d3d8 in swift_task_deinitOnExecutorImpl ()
#6	0x000000010447ef5c in StandardHelper.__deallocating_deinit ()
#7	0x00000001972245d0 in _swift_release_dealloc ()
#8	0x0000000197225094 in swift::RefCounts<swift::RefCountBitsT<(swift::RefCountInlinedness)1>>::doDecrementSlow<(swift::PerformDeinit)1> ()
#9	0x000000010447f220 in __swift_destroy_boxed_opaque_existential_1 ()
#10	0x000000010447f3b0 in ThingWithInitWithDefault.deinit ()
#11	0x000000010447f3e8 in ThingWithInitWithDefault.__isolated_deallocating_deinit ()
#12	0x0000000257d2d3d0 in swift_task_deinitOnExecutorImpl ()
#13	0x000000010447f488 in ThingWithInitWithDefault.__deallocating_deinit ()
#14	0x00000001972245d0 in _swift_release_dealloc ()
#15	0x0000000197225094 in swift::RefCounts<swift::RefCountBitsT<(swift::RefCountInlinedness)1>>::doDecrementSlow<(swift::PerformDeinit)1> ()
#16	0x0000000105035310 in ThingTests.testThingWithInitWithDefault() at /Users/.../FreeingNeverAllocated/FreeingNeverAllocatedTests/ThingTests.swift:10

What are we doing wrong? The code isn’t doing anything complicated or obscure.

It looks like a bug in isolated deinit (@Nickolas_Pohilets), perhaps in combination with the mainactor-by-default feature. But I certainly don't understand why this code in particular causes a problem…

For me testThingPassedHelperAtInit crashes as well.

Were you able to reproduce it on macOS? I would like to debug it using a debug build of standard library, but as far as I know it is not possible to run iOS apps using custom standard library.

Not yet, but I will try.

I was able to reproduce on macOS. For both macOS and iOS issue reproduces if Thing.swift is inside a SwiftUI app, but does not reproduce if Thing.swift is inside a static library.

I'll make a debug build of the standard library and will try to debug it on macOS.

1 Like

It might be more fruitful to try a swift.org toolchain built with asserts enabled instead (or build one yourself of course), because the SIL verifier and other assertions often catch issues with mismatched retain/release operations and other invariant violations.

Looks like it was already fixed in [Concurrency] Fix MarkerItem::create to use malloc when there's no task. · swiftlang/swift@29245e4 · GitHub

1 Like

That sounds promising. I guess it will be a month or two before we see it in an Xcode release.

For now, do you have any recommendation for an approach to avoid it? (i.e. in the sample code)

Issues happens when deallocating in the presence of the task-local values. Wrapping code into await Task.detached should suppress it.