Skip to content

Commit 3d5dd84

Browse files
authored
Adopt _CFErrorCopyCallStackReturnAddresses(). (#1344)
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 + dd7bae2) ### Checklist: - [x] Code and documentation should follow the style of the [Style Guide](https://github.com/apple/swift-testing/blob/main/Documentation/StyleGuide.md). - [x] If public symbols are renamed or modified, DocC references should be updated.
1 parent 992cd27 commit 3d5dd84

File tree

1 file changed

+36
-6
lines changed

1 file changed

+36
-6
lines changed

Sources/Testing/SourceAttribution/Backtrace.swift

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,24 @@ 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 and is not available
339+
/// on older systems.
340+
private static let _CFErrorCopyCallStackReturnAddresses = symbol(named: "_CFErrorCopyCallStackReturnAddresses").map {
341+
castCFunction(at: $0, to: (@convention(c) (_ error: any Error) -> Unmanaged<AnyObject>?).self)
342+
}
343+
#endif
344+
327345
/// Whether or not Foundation provides a function that triggers the capture of
328346
/// backtaces when instances of `NSError` or `CFError` are created.
329347
///
@@ -339,7 +357,11 @@ extension Backtrace {
339357
/// first time the value of this property is read.
340358
static let isFoundationCaptureEnabled = {
341359
#if !hasFeature(Embedded) && _runtime(_ObjC) && !SWT_NO_DYNAMIC_LINKING
342-
if Environment.flag(named: "SWT_FOUNDATION_ERROR_BACKTRACING_ENABLED") == true {
360+
// Check the environment variable; if it isn't set, enable if and only if
361+
// the Core Foundation getter function is implemented.
362+
let foundationBacktracesEnabled = Environment.flag(named: "SWT_FOUNDATION_ERROR_BACKTRACING_ENABLED")
363+
?? (_CFErrorCopyCallStackReturnAddresses != nil)
364+
if foundationBacktracesEnabled {
343365
let _CFErrorSetCallStackCaptureEnabled = symbol(named: "_CFErrorSetCallStackCaptureEnabled").map {
344366
castCFunction(at: $0, to: (@convention(c) (DarwinBoolean) -> DarwinBoolean).self)
345367
}
@@ -422,11 +444,19 @@ extension Backtrace {
422444
#if !hasFeature(Embedded)
423445
@inline(never)
424446
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
447+
if checkFoundation && Self.isFoundationCaptureEnabled {
448+
#if !hasFeature(Embedded) && SWT_TARGET_OS_APPLE && !SWT_NO_DYNAMIC_LINKING
449+
if let addresses = Self._CFErrorCopyCallStackReturnAddresses?(error)?.takeRetainedValue() as? [Address] {
450+
self.init(addresses: addresses)
451+
return
452+
}
453+
#endif
454+
455+
if let userInfo = error._userInfo as? [String: Any],
456+
let addresses = userInfo["NSCallStackReturnAddresses"] as? [Address], !addresses.isEmpty {
457+
self.init(addresses: addresses)
458+
return
459+
}
430460
}
431461

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

0 commit comments

Comments
 (0)