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.