Skip to content

Commit 7e3e7c0

Browse files
authored
Merge pull request #7983 from jckarter/error-bridging-early-exit-fix
Runtime: Bridging an Error to NSError requires only checking its domain.
2 parents 99ea154 + 294913e commit 7e3e7c0

File tree

6 files changed

+65
-12
lines changed

6 files changed

+65
-12
lines changed

stdlib/public/SDK/Foundation/NSError.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ internal let _errorDomainUserInfoProviderQueue = DispatchQueue(
202202

203203
/// Retrieve the default userInfo dictionary for a given error.
204204
@_silgen_name("swift_Foundation_getErrorDefaultUserInfo")
205-
public func _swift_Foundation_getErrorDefaultUserInfo(_ error: Error)
205+
public func _swift_Foundation_getErrorDefaultUserInfo<T: Error>(_ error: T)
206206
-> AnyObject? {
207207
let hasUserInfoValueProvider: Bool
208208

stdlib/public/core/ErrorType.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
//===----------------------------------------------------------------------===//
1212
import SwiftShims
1313

14-
// TODO: API review
1514
/// A type representing an error value that can be thrown.
1615
///
1716
/// Any type that declares conformance to the `Error` protocol can be used to
@@ -167,7 +166,7 @@ public func _stdlib_getErrorEmbeddedNSError<T : Error>(_ x: T)
167166
}
168167

169168
@_silgen_name("swift_stdlib_getErrorDefaultUserInfo")
170-
public func _stdlib_getErrorDefaultUserInfo(_ error: Error) -> AnyObject?
169+
public func _stdlib_getErrorDefaultUserInfo<T: Error>(_ error: T) -> AnyObject?
171170

172171
// Known function for the compiler to use to coerce `Error` instances
173172
// to `NSError`.

stdlib/public/runtime/ErrorObject.mm

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -397,7 +397,7 @@ static Class getSwiftNativeNSErrorClass() {
397397
const WitnessTable *Error);
398398

399399
//@_silgen_name("swift_stdlib_getErrorDefaultUserInfo")
400-
//public func _stdlib_getErrorDefaultUserInfo<T : Error>(_ x: UnsafePointer<T>) -> AnyObject
400+
//public func _stdlib_getErrorDefaultUserInfo<T : Error>(_ x: T) -> AnyObject
401401
SWIFT_CC(swift) SWIFT_RT_ENTRY_VISIBILITY
402402
NSDictionary *swift_stdlib_getErrorDefaultUserInfo(
403403
OpaqueValue *error,
@@ -424,14 +424,22 @@ typedef SWIFT_CC(swift)
424424
static id _swift_bridgeErrorToNSError_(SwiftError *errorObject) {
425425
auto ns = reinterpret_cast<NSError *>(errorObject);
426426

427-
// If we already have a domain and userInfo set, then we've already
428-
// initialized.
429-
// FIXME: This might be overly strict; can we look only at the domain?
430-
if (errorObject->domain.load(std::memory_order_acquire) &&
431-
errorObject->userInfo.load(std::memory_order_acquire))
427+
// If we already have a domain set, then we've already initialized.
428+
// If this is a real NSError, then Cocoa and Core Foundation's initializers
429+
// guarantee that the domain is never nil, so if this test fails, we can
430+
// assume we're working with a bridged error. (Note that Cocoa and CF
431+
// **will** allow the userInfo storage to be initialized to nil.)
432+
//
433+
// If this is a bridged error, then the domain, code, and user info are
434+
// lazily computed, and the domain will be nil if they haven't been computed
435+
// yet. The initialization is ordered in such a way that all other lazy
436+
// initialization of the object happens-before the domain initialization so
437+
// that the domain can be used alone as a flag for the initialization of the
438+
// object.
439+
if (errorObject->domain.load(std::memory_order_acquire))
432440
return ns;
433441

434-
// Otherwise, calculate the domain and code (TODO: and user info), and
442+
// Otherwise, calculate the domain, code, and user info, and
435443
// initialize the NSError.
436444
auto value = SwiftError::getIndirectValue(&errorObject);
437445
auto type = errorObject->getType();
@@ -463,8 +471,8 @@ static id _swift_bridgeErrorToNSError_(SwiftError *errorObject) {
463471
// We also need to cmpxchg in the domain; if somebody beat us to it,
464472
// we need to release.
465473
//
466-
// Storing the domain must be the LAST THING we do, since it's
467-
// the signal that the NSError has been initialized.
474+
// Storing the domain must be the **LAST THING** we do, since it's
475+
// also the flag that the NSError has been initialized.
468476
CFStringRef expectedDomain = nullptr;
469477
if (!errorObject->domain.compare_exchange_strong(expectedDomain,
470478
(CFStringRef)domain,

test/stdlib/ErrorBridgedStatic.swift

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// RUN: rm -rf %t
2+
// RUN: mkdir -p %t
3+
// RUN: %target-clang -fmodules -c -o %t/ErrorBridgedStaticImpl.o %S/Inputs/ErrorBridgedStaticImpl.m
4+
// RUN: %target-build-swift -static-stdlib -o %t/ErrorBridgedStatic %t/ErrorBridgedStaticImpl.o %s -import-objc-header %S/Inputs/ErrorBridgedStaticImpl.h
5+
// RUN: strip %t/ErrorBridgedStatic
6+
// RUN: %t/ErrorBridgedStatic
7+
8+
// REQUIRES: executable_test
9+
// REQUIRES: objc_interop
10+
// REQUIRES: static_stdlib
11+
12+
import StdlibUnittest
13+
14+
class Bar: Foo {
15+
override func foo(_ x: Int32) throws {
16+
try super.foo(5)
17+
}
18+
}
19+
20+
var ErrorBridgingStaticTests = TestSuite("ErrorBridging with static libs")
21+
22+
ErrorBridgingStaticTests.test("round-trip Swift override of ObjC method") {
23+
do {
24+
try (Bar() as Foo).foo(5)
25+
} catch { }
26+
}
27+
28+
runAllTests()
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
@import Foundation;
2+
3+
@interface Foo: NSObject
4+
5+
- (BOOL)foo:(int)x error:(NSError**)error;
6+
7+
@end
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#include "ErrorBridgedStaticImpl.h"
2+
3+
@implementation Foo
4+
5+
- (BOOL)foo:(int)x error:(NSError**)error {
6+
*error = nil;
7+
return NO;
8+
}
9+
10+
@end
11+

0 commit comments

Comments
 (0)