@@ -119,6 +119,55 @@ extension Backtrace: Codable {
119119// MARK: - Backtraces for thrown errors
120120
121121extension Backtrace {
122+ // MARK: - Error cache keys
123+
124+ /// A type used as a cache key that uniquely identifies error existential
125+ /// boxes.
126+ private struct _ErrorMappingCacheKey : Sendable , Equatable , Hashable {
127+ private nonisolated ( unsafe) var _rawValue : UnsafeMutableRawPointer ?
128+
129+ /// Initialize an instance of this type from a pointer to an error
130+ /// existential box.
131+ ///
132+ /// - Parameters:
133+ /// - errorAddress: The address of the error existential box.
134+ init ( _ errorAddress: UnsafeMutableRawPointer ) {
135+ _rawValue = errorAddress
136+ #if SWT_TARGET_OS_APPLE
137+ let error = Unmanaged < AnyObject > . fromOpaque ( errorAddress) . takeUnretainedValue ( ) as! any Error
138+ if type ( of: error) is AnyObject . Type {
139+ _rawValue = Unmanaged . passUnretained ( error as AnyObject ) . toOpaque ( )
140+ }
141+ #else
142+ withUnsafeTemporaryAllocation ( of: SWTErrorValueResult . self, capacity: 1 ) { buffer in
143+ var scratch : UnsafeMutableRawPointer ?
144+ return withExtendedLifetime ( scratch) {
145+ swift_getErrorValue ( errorAddress, & scratch, buffer. baseAddress!)
146+ let result = buffer. baseAddress!. move ( )
147+
148+ if unsafeBitCast ( result. type, to: Any . Type. self) is AnyObject . Type {
149+ let errorObject = result. value. load ( as: AnyObject . self)
150+ _rawValue = Unmanaged . passUnretained ( errorObject) . toOpaque ( )
151+ }
152+ }
153+ }
154+ #endif
155+ }
156+
157+ /// Initialize an instance of this type from an error existential box.
158+ ///
159+ /// - Parameters:
160+ /// - error: The error existential box.
161+ ///
162+ /// - Note: Care must be taken to avoid unboxing and re-boxing `error`. This
163+ /// initializer cannot be made an instance method or property of `Error`
164+ /// because doing so will cause Swift-native errors to be unboxed into
165+ /// existential containers with different addresses.
166+ init ( _ error: any Error ) {
167+ self . init ( unsafeBitCast ( error as any Error , to: UnsafeMutableRawPointer . self) )
168+ }
169+ }
170+
122171 /// An entry in the error-mapping cache.
123172 private struct _ErrorMappingCacheEntry : Sendable {
124173 /// The error object (`SwiftError` or `NSError`) that was thrown.
@@ -133,9 +182,9 @@ extension Backtrace {
133182 /// object (abandoning memory until the process exits.)
134183 /// ([swift-#62985](https://github.com/swiftlang/swift/issues/62985))
135184#if os(Windows)
136- var errorObject : ( any AnyObject & Sendable ) ?
185+ nonisolated ( unsafe ) var errorObject: AnyObject ?
137186#else
138- weak var errorObject : ( any AnyObject & Sendable ) ?
187+ nonisolated ( unsafe ) weak var errorObject : AnyObject ?
139188#endif
140189
141190 /// The backtrace captured when `errorObject` was thrown.
@@ -158,11 +207,34 @@ extension Backtrace {
158207 /// same location.)
159208 ///
160209 /// Access to this dictionary is guarded by a lock.
161- private static let _errorMappingCache = Locked < [ ObjectIdentifier : _ErrorMappingCacheEntry ] > ( )
210+ private static let _errorMappingCache = Locked < [ _ErrorMappingCacheKey : _ErrorMappingCacheEntry ] > ( )
162211
163212 /// The previous `swift_willThrow` handler, if any.
164213 private static let _oldWillThrowHandler = Locked < SWTWillThrowHandler ? > ( )
165214
215+ /// The previous `swift_willThrowTyped` handler, if any.
216+ private static let _oldWillThrowTypedHandler = Locked < SWTWillThrowTypedHandler ? > ( )
217+
218+ /// Handle a thrown error.
219+ ///
220+ /// - Parameters:
221+ /// - errorObject: The error that is about to be thrown.
222+ /// - backtrace: The backtrace from where the error was thrown.
223+ /// - errorID: The ID under which the thrown error should be tracked.
224+ ///
225+ /// This function serves as the bottleneck for the various callbacks below.
226+ private static func _willThrow( _ errorObject: AnyObject , from backtrace: Backtrace , forKey errorKey: _ErrorMappingCacheKey ) {
227+ let newEntry = _ErrorMappingCacheEntry ( errorObject: errorObject, backtrace: backtrace)
228+
229+ _errorMappingCache. withLock { cache in
230+ let oldEntry = cache [ errorKey]
231+ if oldEntry? . errorObject == nil {
232+ // Either no entry yet, or its weak reference was zeroed.
233+ cache [ errorKey] = newEntry
234+ }
235+ }
236+ }
237+
166238 /// Handle a thrown error.
167239 ///
168240 /// - Parameters:
@@ -173,17 +245,81 @@ extension Backtrace {
173245 private static func _willThrow( _ errorAddress: UnsafeMutableRawPointer , from backtrace: Backtrace ) {
174246 _oldWillThrowHandler. rawValue ? ( errorAddress)
175247
176- let errorObject = unsafeBitCast ( errorAddress, to : ( any AnyObject & Sendable ) . self )
177- let errorID = ObjectIdentifier ( errorObject )
178- let newEntry = _ErrorMappingCacheEntry ( errorObject : errorObject , backtrace : backtrace )
248+ let errorObject = Unmanaged < AnyObject > . fromOpaque ( errorAddress) . takeUnretainedValue ( )
249+ _willThrow ( errorObject , from : backtrace , forKey : . init ( errorAddress ) )
250+ }
179251
180- _errorMappingCache. withLock { cache in
181- let oldEntry = cache [ errorID]
182- if oldEntry? . errorObject == nil {
183- // Either no entry yet, or its weak reference was zeroed.
184- cache [ errorID] = newEntry
252+ /// Handle a typed thrown error.
253+ ///
254+ /// - Parameters:
255+ /// - error: The error that is about to be thrown. If the error is of
256+ /// reference type, it is forwarded to `_willThrow()`. Otherwise, it is
257+ /// (currently) discarded because its identity cannot be tracked.
258+ /// - backtrace: The backtrace from where the error was thrown.
259+ @available ( _typedThrowsAPI, * )
260+ private static func _willThrowTyped< E> ( _ error: borrowing E , from backtrace: Backtrace ) where E: Error {
261+ if E . self is AnyObject . Type {
262+ // The error has a stable address and can be tracked as an object.
263+ let error = copy error
264+ _willThrow ( error as AnyObject , from: backtrace, forKey: . init( error) )
265+ } else if E . self == ( any Error ) . self {
266+ // The thrown error has non-specific type (any Error). In this case,
267+ // the runtime produces a temporary existential box to contain the
268+ // error, but discards the box immediately after we return so there's
269+ // no stability provided by the error's address. Unbox the error and
270+ // recursively call this function in case it contains an instance of a
271+ // reference-counted error type.
272+ //
273+ // This dance through Any lets us unbox the error's existential box
274+ // correctly. Skipping it and calling _willThrowTyped() will fail to open
275+ // the existential and will result in an infinite recursion. The copy is
276+ // unfortunate but necessary due to casting being a consuming operation.
277+ let error = ( ( copy error) as Any ) as! any Error
278+ _willThrowTyped ( error, from: backtrace)
279+ } else {
280+ // The error does _not_ have a stable address. The Swift runtime does
281+ // not give us an opportunity to insert additional information into
282+ // arbitrary error values. Thus, we won't attempt to capture any
283+ // backtrace for such an error.
284+ //
285+ // We could, in the future, attempt to track such errors if they conform
286+ // to Identifiable, Equatable, etc., but that would still be imperfect.
287+ // Perhaps the compiler or runtime could assign a unique ID to each error
288+ // at throw time that could be looked up later. SEE: rdar://122824443.
289+ }
290+ }
291+
292+ /// Handle a typed thrown error.
293+ ///
294+ /// - Parameters:
295+ /// - error: The error that is about to be thrown. This pointer points
296+ /// directly to the unboxed error in memory. For errors of reference type,
297+ /// the pointer points to the object and is not the object's address
298+ /// itself.
299+ /// - errorType: The metatype of `error`.
300+ /// - errorConformance: The witness table for `error`'s conformance to the
301+ /// `Error` protocol.
302+ /// - backtrace: The backtrace from where the error was thrown.
303+ @available ( _typedThrowsAPI, * )
304+ private static func _willThrowTyped( _ errorAddress: UnsafeMutableRawPointer , _ errorType: UnsafeRawPointer , _ errorConformance: UnsafeRawPointer , from backtrace: Backtrace ) {
305+ _oldWillThrowTypedHandler. rawValue ? ( errorAddress, errorType, errorConformance)
306+
307+ // Get a thick protocol type back from the C pointer arguments. Ideally we
308+ // would specify this function as generic, but then the Swift calling
309+ // convention would force us to specialize it immediately in order to pass
310+ // it to the C++ thunk that sets the runtime's function pointer.
311+ let errorType = unsafeBitCast ( ( errorType, errorConformance) , to: ( any Error . Type ) . self)
312+
313+ // Open `errorType` as an existential. Rebind the memory at `errorAddress`
314+ // to the correct type and then pass the error to the fully Swiftified
315+ // handler function. Don't call load(as:) to avoid copying the error
316+ // (ideally this is a zero-copy operation.) The callee borrows its argument.
317+ func forward< E> ( _ errorType: E . Type ) where E: Error {
318+ errorAddress. withMemoryRebound ( to: E . self, capacity: 1 ) { errorAddress in
319+ _willThrowTyped ( errorAddress. pointee, from: backtrace)
185320 }
186321 }
322+ forward ( errorType)
187323 }
188324
189325 /// The implementation of ``Backtrace/startCachingForThrownErrors()``, run
@@ -198,6 +334,14 @@ extension Backtrace {
198334 _willThrow ( errorAddress, from: backtrace)
199335 }
200336 }
337+ if #available( _typedThrowsAPI, * ) {
338+ _oldWillThrowTypedHandler. withLock { oldWillThrowTypedHandler in
339+ oldWillThrowTypedHandler = swt_setWillThrowTypedHandler { errorAddress, errorType, errorConformance in
340+ let backtrace = Backtrace . current ( )
341+ _willThrowTyped ( errorAddress, errorType, errorConformance, from: backtrace)
342+ }
343+ }
344+ }
201345 } ( )
202346
203347 /// Configure the Swift runtime to allow capturing backtraces when errors are
@@ -236,9 +380,8 @@ extension Backtrace {
236380 /// existential containers with different addresses.
237381 @inline ( never)
238382 init ? ( forFirstThrowOf error: any Error ) {
239- let errorID = ObjectIdentifier ( unsafeBitCast ( error as any Error , to: AnyObject . self) )
240383 let entry = Self . _errorMappingCache. withLock { cache in
241- cache [ errorID ]
384+ cache [ . init ( error ) ]
242385 }
243386 if let entry, entry. errorObject != nil {
244387 // There was an entry and its weak reference is still valid.
0 commit comments