From ac0fbda13f4933dffdc531c77657483adb0d53c7 Mon Sep 17 00:00:00 2001 From: Jonathan Grynspan Date: Tue, 30 Sep 2025 12:34:27 -0400 Subject: [PATCH 1/3] 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) --- .../Testing/SourceAttribution/Backtrace.swift | 39 ++++++++++++++++--- 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/Sources/Testing/SourceAttribution/Backtrace.swift b/Sources/Testing/SourceAttribution/Backtrace.swift index 60d08fede..62f3cd227 100644 --- a/Sources/Testing/SourceAttribution/Backtrace.swift +++ b/Sources/Testing/SourceAttribution/Backtrace.swift @@ -324,6 +324,25 @@ extension Backtrace { } #endif +#if !hasFeature(Embedded) && SWT_TARGET_OS_APPLE && !SWT_NO_DYNAMIC_LINKING + /// A function provided by Core Foundation that copies the captured backtrace + /// from storage inside `CFError` or `NSError`. + /// + /// - Parameters: + /// - error: The error whose backtrace is desired. + /// + /// - Returns: The backtrace (as an instance of `NSArray`) captured by `error` + /// when it was created, or `nil` if none was captured. The caller is + /// responsible for releasing this object when done. + /// + /// This function was added in an internal Foundation PR + /// ([#2678](https://github.pie.apple.com/Cocoa/Foundation/pull/2678)) and is + /// not available on older systems. + private static let _CFErrorCopyCallStackReturnAddresses = symbol(named: "_CFErrorCopyCallStackReturnAddresses").map { + castCFunction(at: $0, to: (@convention(c) (_ error: any Error) -> Unmanaged?).self) + } +#endif + /// Whether or not Foundation provides a function that triggers the capture of /// backtaces when instances of `NSError` or `CFError` are created. /// @@ -339,7 +358,11 @@ extension Backtrace { /// first time the value of this property is read. static let isFoundationCaptureEnabled = { #if !hasFeature(Embedded) && _runtime(_ObjC) && !SWT_NO_DYNAMIC_LINKING - if Environment.flag(named: "SWT_FOUNDATION_ERROR_BACKTRACING_ENABLED") == true { + // Check the environment variable; if it isn't set, enable if and only if + // the Core Foundation getter function is implemented. + let foundationBacktracesEnabled = Environment.flag(named: "SWT_FOUNDATION_ERROR_BACKTRACING_ENABLED") + ?? (_CFErrorCopyCallStackReturnAddresses != nil) + if foundationBacktracesEnabled { let _CFErrorSetCallStackCaptureEnabled = symbol(named: "_CFErrorSetCallStackCaptureEnabled").map { castCFunction(at: $0, to: (@convention(c) (DarwinBoolean) -> DarwinBoolean).self) } @@ -422,11 +445,15 @@ extension Backtrace { #if !hasFeature(Embedded) @inline(never) init?(forFirstThrowOf error: any Error, checkFoundation: Bool = true) { - if checkFoundation && Self.isFoundationCaptureEnabled, - let userInfo = error._userInfo as? [String: Any], - let addresses = userInfo["NSCallStackReturnAddresses"] as? [Address], !addresses.isEmpty { - self.init(addresses: addresses) - return + if checkFoundation && Self.isFoundationCaptureEnabled { + if let addresses = Self._CFErrorCopyCallStackReturnAddresses?(error)?.takeRetainedValue() as? [Address] { + self.init(addresses: addresses) + return + } else if let userInfo = error._userInfo as? [String: Any], + let addresses = userInfo["NSCallStackReturnAddresses"] as? [Address], !addresses.isEmpty { + self.init(addresses: addresses) + return + } } let entry = Self._errorMappingCache.withLock { cache in From b596601ca94cd70b76817bfc61ee8ea19e40a72e Mon Sep 17 00:00:00 2001 From: Jonathan Grynspan Date: Tue, 30 Sep 2025 12:43:20 -0400 Subject: [PATCH 2/3] Oh right Linux --- Sources/Testing/SourceAttribution/Backtrace.swift | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Sources/Testing/SourceAttribution/Backtrace.swift b/Sources/Testing/SourceAttribution/Backtrace.swift index 62f3cd227..5e23113fd 100644 --- a/Sources/Testing/SourceAttribution/Backtrace.swift +++ b/Sources/Testing/SourceAttribution/Backtrace.swift @@ -446,11 +446,15 @@ extension Backtrace { @inline(never) init?(forFirstThrowOf error: any Error, checkFoundation: Bool = true) { if checkFoundation && Self.isFoundationCaptureEnabled { +#if !hasFeature(Embedded) && SWT_TARGET_OS_APPLE && !SWT_NO_DYNAMIC_LINKING if let addresses = Self._CFErrorCopyCallStackReturnAddresses?(error)?.takeRetainedValue() as? [Address] { self.init(addresses: addresses) return - } else if let userInfo = error._userInfo as? [String: Any], - let addresses = userInfo["NSCallStackReturnAddresses"] as? [Address], !addresses.isEmpty { + } +#endif + + if let userInfo = error._userInfo as? [String: Any], + let addresses = userInfo["NSCallStackReturnAddresses"] as? [Address], !addresses.isEmpty { self.init(addresses: addresses) return } From 9dd9970e686b52c6ede5ae2a377754943b0ee0e2 Mon Sep 17 00:00:00 2001 From: Jonathan Grynspan Date: Tue, 30 Sep 2025 12:45:26 -0400 Subject: [PATCH 3/3] Fix comment --- Sources/Testing/SourceAttribution/Backtrace.swift | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Sources/Testing/SourceAttribution/Backtrace.swift b/Sources/Testing/SourceAttribution/Backtrace.swift index 5e23113fd..fd7972cc4 100644 --- a/Sources/Testing/SourceAttribution/Backtrace.swift +++ b/Sources/Testing/SourceAttribution/Backtrace.swift @@ -335,9 +335,8 @@ extension Backtrace { /// when it was created, or `nil` if none was captured. The caller is /// responsible for releasing this object when done. /// - /// This function was added in an internal Foundation PR - /// ([#2678](https://github.pie.apple.com/Cocoa/Foundation/pull/2678)) and is - /// not available on older systems. + /// This function was added in an internal Foundation PR and is not available + /// on older systems. private static let _CFErrorCopyCallStackReturnAddresses = symbol(named: "_CFErrorCopyCallStackReturnAddresses").map { castCFunction(at: $0, to: (@convention(c) (_ error: any Error) -> Unmanaged?).self) }