@@ -119,6 +119,55 @@ extension Backtrace: Codable {
119
119
// MARK: - Backtraces for thrown errors
120
120
121
121
extension 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
+
122
171
/// An entry in the error-mapping cache.
123
172
private struct _ErrorMappingCacheEntry : Sendable {
124
173
/// The error object (`SwiftError` or `NSError`) that was thrown.
@@ -133,9 +182,9 @@ extension Backtrace {
133
182
/// object (abandoning memory until the process exits.)
134
183
/// ([swift-#62985](https://github.com/swiftlang/swift/issues/62985))
135
184
#if os(Windows)
136
- var errorObject : ( any AnyObject & Sendable ) ?
185
+ nonisolated ( unsafe ) var errorObject: AnyObject ?
137
186
#else
138
- weak var errorObject : ( any AnyObject & Sendable ) ?
187
+ nonisolated ( unsafe ) weak var errorObject : AnyObject ?
139
188
#endif
140
189
141
190
/// The backtrace captured when `errorObject` was thrown.
@@ -158,11 +207,34 @@ extension Backtrace {
158
207
/// same location.)
159
208
///
160
209
/// 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 ] > ( )
162
211
163
212
/// The previous `swift_willThrow` handler, if any.
164
213
private static let _oldWillThrowHandler = Locked < SWTWillThrowHandler ? > ( )
165
214
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
+
166
238
/// Handle a thrown error.
167
239
///
168
240
/// - Parameters:
@@ -173,17 +245,81 @@ extension Backtrace {
173
245
private static func _willThrow( _ errorAddress: UnsafeMutableRawPointer , from backtrace: Backtrace ) {
174
246
_oldWillThrowHandler. rawValue ? ( errorAddress)
175
247
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
+ }
179
251
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)
185
320
}
186
321
}
322
+ forward ( errorType)
187
323
}
188
324
189
325
/// The implementation of ``Backtrace/startCachingForThrownErrors()``, run
@@ -198,6 +334,14 @@ extension Backtrace {
198
334
_willThrow ( errorAddress, from: backtrace)
199
335
}
200
336
}
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
+ }
201
345
} ( )
202
346
203
347
/// Configure the Swift runtime to allow capturing backtraces when errors are
@@ -236,9 +380,8 @@ extension Backtrace {
236
380
/// existential containers with different addresses.
237
381
@inline ( never)
238
382
init ? ( forFirstThrowOf error: any Error ) {
239
- let errorID = ObjectIdentifier ( unsafeBitCast ( error as any Error , to: AnyObject . self) )
240
383
let entry = Self . _errorMappingCache. withLock { cache in
241
- cache [ errorID ]
384
+ cache [ . init ( error ) ]
242
385
}
243
386
if let entry, entry. errorObject != nil {
244
387
// There was an entry and its weak reference is still valid.
0 commit comments