Skip to content

Commit ac0fbda

Browse files
committed
Adopt _CFErrorCopyCallStackReturnAddresses().
This PR adopts a Core Foundation function, new in macOS 15.4/etc. and added for us to use, that captures backtraces for `NSError` and `CFError` instances at initialization time and stores them in a sidecar location for consumption by Swift Testing and/or XCTest. Upstreaming from Apple's fork (rdar://139841808, 508230a)
1 parent 992cd27 commit ac0fbda

File tree

1 file changed

+33
-6
lines changed

1 file changed

+33
-6
lines changed

Sources/Testing/SourceAttribution/Backtrace.swift

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,25 @@ extension Backtrace {
324324
}
325325
#endif
326326

327+
#if !hasFeature(Embedded) && SWT_TARGET_OS_APPLE && !SWT_NO_DYNAMIC_LINKING
328+
/// A function provided by Core Foundation that copies the captured backtrace
329+
/// from storage inside `CFError` or `NSError`.
330+
///
331+
/// - Parameters:
332+
/// - error: The error whose backtrace is desired.
333+
///
334+
/// - Returns: The backtrace (as an instance of `NSArray`) captured by `error`
335+
/// when it was created, or `nil` if none was captured. The caller is
336+
/// responsible for releasing this object when done.
337+
///
338+
/// This function was added in an internal Foundation PR
339+
/// ([#2678](https://github.pie.apple.com/Cocoa/Foundation/pull/2678)) and is
340+
/// not available on older systems.
341+
private static let _CFErrorCopyCallStackReturnAddresses = symbol(named: "_CFErrorCopyCallStackReturnAddresses").map {
342+
castCFunction(at: $0, to: (@convention(c) (_ error: any Error) -> Unmanaged<AnyObject>?).self)
343+
}
344+
#endif
345+
327346
/// Whether or not Foundation provides a function that triggers the capture of
328347
/// backtaces when instances of `NSError` or `CFError` are created.
329348
///
@@ -339,7 +358,11 @@ extension Backtrace {
339358
/// first time the value of this property is read.
340359
static let isFoundationCaptureEnabled = {
341360
#if !hasFeature(Embedded) && _runtime(_ObjC) && !SWT_NO_DYNAMIC_LINKING
342-
if Environment.flag(named: "SWT_FOUNDATION_ERROR_BACKTRACING_ENABLED") == true {
361+
// Check the environment variable; if it isn't set, enable if and only if
362+
// the Core Foundation getter function is implemented.
363+
let foundationBacktracesEnabled = Environment.flag(named: "SWT_FOUNDATION_ERROR_BACKTRACING_ENABLED")
364+
?? (_CFErrorCopyCallStackReturnAddresses != nil)
365+
if foundationBacktracesEnabled {
343366
let _CFErrorSetCallStackCaptureEnabled = symbol(named: "_CFErrorSetCallStackCaptureEnabled").map {
344367
castCFunction(at: $0, to: (@convention(c) (DarwinBoolean) -> DarwinBoolean).self)
345368
}
@@ -422,11 +445,15 @@ extension Backtrace {
422445
#if !hasFeature(Embedded)
423446
@inline(never)
424447
init?(forFirstThrowOf error: any Error, checkFoundation: Bool = true) {
425-
if checkFoundation && Self.isFoundationCaptureEnabled,
426-
let userInfo = error._userInfo as? [String: Any],
427-
let addresses = userInfo["NSCallStackReturnAddresses"] as? [Address], !addresses.isEmpty {
428-
self.init(addresses: addresses)
429-
return
448+
if checkFoundation && Self.isFoundationCaptureEnabled {
449+
if let addresses = Self._CFErrorCopyCallStackReturnAddresses?(error)?.takeRetainedValue() as? [Address] {
450+
self.init(addresses: addresses)
451+
return
452+
} else if let userInfo = error._userInfo as? [String: Any],
453+
let addresses = userInfo["NSCallStackReturnAddresses"] as? [Address], !addresses.isEmpty {
454+
self.init(addresses: addresses)
455+
return
456+
}
430457
}
431458

432459
let entry = Self._errorMappingCache.withLock { cache in

0 commit comments

Comments
 (0)