Skip to content

Commit d219531

Browse files
committed
[NSError bridging] Use embedded NSError when erasing types to Error existentials.
Imported Cocoa error types are represented by structs wrapping an NSError. The conversion from these structs to Error would end up boxing the structs in _SwiftNativeNSError, losing identity and leading to a wrapping loop. Instead, extract the embedded NSError if there is one. In the Swift runtime, do this as part of the dynamic cast to NSError, using a (new, defaulted) requirement in the Error type so we can avoid an extra runtime lookup of the protocol. In SILGEn, do this by looking for the _BridgedStoredNSError protocol conformance when erasing to an Error type. Fixes SR-1562 / rdar://problem/26370984.
1 parent 177ad2c commit d219531

File tree

8 files changed

+236
-2
lines changed

8 files changed

+236
-2
lines changed

lib/SILGen/SILGen.cpp

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,83 @@ SILGenModule::getConformanceToObjectiveCBridgeable(SILLocation loc, Type type) {
283283
return nullptr;
284284
}
285285

286+
ProtocolDecl *SILGenModule::getBridgedStoredNSError(SILLocation loc) {
287+
if (BridgedStoredNSError)
288+
return *BridgedStoredNSError;
289+
290+
// Find the _BridgedStoredNSError protocol.
291+
auto &ctx = getASTContext();
292+
auto proto = ctx.getProtocol(KnownProtocolKind::BridgedStoredNSError);
293+
BridgedStoredNSError = proto;
294+
return proto;
295+
}
296+
297+
VarDecl *SILGenModule::getNSErrorRequirement(SILLocation loc) {
298+
if (NSErrorRequirement)
299+
return *NSErrorRequirement;
300+
301+
// Find the _BridgedStoredNSError protocol.
302+
auto proto = getBridgedStoredNSError(loc);
303+
if (!proto) {
304+
NSErrorRequirement = nullptr;
305+
return nullptr;
306+
}
307+
308+
// Look for _nsError.
309+
auto &ctx = getASTContext();
310+
VarDecl *found = nullptr;
311+
for (auto member : proto->lookupDirect(ctx.Id_nsError, true)) {
312+
if (auto var = dyn_cast<VarDecl>(member)) {
313+
found = var;
314+
break;
315+
}
316+
}
317+
318+
NSErrorRequirement = found;
319+
return found;
320+
}
321+
322+
ProtocolConformance *
323+
SILGenModule::getConformanceToBridgedStoredNSError(SILLocation loc, Type type) {
324+
auto proto = getBridgedStoredNSError(loc);
325+
if (!proto) return nullptr;
326+
327+
// Find the conformance to _BridgedStoredNSError.
328+
auto result = SwiftModule->lookupConformance(type, proto, nullptr);
329+
if (result) return result->getConcrete();
330+
return nullptr;
331+
}
332+
333+
ProtocolConformance *SILGenModule::getNSErrorConformanceToError() {
334+
if (NSErrorConformanceToError)
335+
return *NSErrorConformanceToError;
336+
337+
auto &ctx = getASTContext();
338+
auto nsError = ctx.getNSErrorDecl();
339+
if (!nsError) {
340+
NSErrorConformanceToError = nullptr;
341+
return nullptr;
342+
}
343+
344+
auto error = ctx.getErrorDecl();
345+
if (!error) {
346+
NSErrorConformanceToError = nullptr;
347+
return nullptr;
348+
}
349+
350+
auto conformance =
351+
SwiftModule->lookupConformance(nsError->getDeclaredInterfaceType(),
352+
cast<ProtocolDecl>(error),
353+
nullptr);
354+
355+
if (conformance && conformance->isConcrete())
356+
NSErrorConformanceToError = conformance->getConcrete();
357+
else
358+
NSErrorConformanceToError = nullptr;
359+
return *NSErrorConformanceToError;
360+
}
361+
362+
286363
SILFunction *SILGenModule::emitTopLevelFunction(SILLocation Loc) {
287364
ASTContext &C = M.getASTContext();
288365
auto extInfo = SILFunctionType::ExtInfo()

lib/SILGen/SILGen.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,11 @@ class LLVM_LIBRARY_VISIBILITY SILGenModule : public ASTVisitor<SILGenModule> {
129129
Optional<FuncDecl*> UnconditionallyBridgeFromObjectiveCRequirement;
130130
Optional<AssociatedTypeDecl*> BridgedObjectiveCType;
131131

132+
Optional<ProtocolDecl*> BridgedStoredNSError;
133+
Optional<VarDecl*> NSErrorRequirement;
134+
135+
Optional<ProtocolConformance *> NSErrorConformanceToError;
136+
132137
public:
133138
SILGenModule(SILModule &M, Module *SM, bool makeModuleFragile);
134139
~SILGenModule();
@@ -380,6 +385,20 @@ class LLVM_LIBRARY_VISIBILITY SILGenModule : public ASTVisitor<SILGenModule> {
380385
ProtocolConformance *getConformanceToObjectiveCBridgeable(SILLocation loc,
381386
Type type);
382387

388+
/// Retrieve the _BridgedStoredNSError protocol definition.
389+
ProtocolDecl *getBridgedStoredNSError(SILLocation loc);
390+
391+
/// Retrieve the _BridgedStoredNSError._nsError requirement.
392+
VarDecl *getNSErrorRequirement(SILLocation loc);
393+
394+
/// Find the conformance of the given Swift type to the
395+
/// _BridgedStoredNSError protocol.
396+
ProtocolConformance *getConformanceToBridgedStoredNSError(SILLocation loc,
397+
Type type);
398+
399+
/// Retrieve the conformance of NSError to the Error protocol.
400+
ProtocolConformance *getNSErrorConformanceToError();
401+
383402
/// Report a diagnostic.
384403
template<typename...T, typename...U>
385404
InFlightDiagnostic diagnose(SourceLoc loc, Diag<T...> diag,

lib/SILGen/SILGenConvert.cpp

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,57 @@ ManagedValue SILGenFunction::emitExistentialErasure(
455455
for (auto conformance : conformances)
456456
SGM.useConformance(conformance);
457457

458+
// If we're erasing to the 'Error' type, and the concrete type conforms to the
459+
// _BridgedStoredNSError protocol, call the _nsError witness getter to extract
460+
// the NSError directly.
461+
auto &ctx = getASTContext();
462+
if (existentialTL.getSemanticType().getSwiftRValueType()->getAnyNominal() ==
463+
ctx.getErrorDecl()) {
464+
auto conformance =
465+
SGM.getConformanceToBridgedStoredNSError(loc, concreteFormalType);
466+
auto nsError = ctx.getNSErrorDecl();
467+
if (conformance && nsError && SGM.getNSErrorConformanceToError()) {
468+
CanType nsErrorType =
469+
nsError->getDeclaredInterfaceType()->getCanonicalType();
470+
if (auto witness =
471+
conformance->getWitness(SGM.getNSErrorRequirement(loc), nullptr)) {
472+
// Create a reference to the getter witness.
473+
SILDeclRef getter =
474+
getGetterDeclRef(cast<VarDecl>(witness.getDecl()),
475+
/*isDirectAccessorUse=*/true);
476+
477+
// Compute the substitutions.
478+
ArrayRef<Substitution> substitutions =
479+
concreteFormalType->gatherAllSubstitutions(
480+
SGM.SwiftModule, nullptr);
481+
482+
// Emit the erasure, through the getter to _nsError.
483+
ProtocolConformanceRef nsErrorConformances[1] = {
484+
ProtocolConformanceRef(SGM.getNSErrorConformanceToError())
485+
};
486+
487+
return emitExistentialErasure(
488+
loc, nsErrorType,
489+
getTypeLowering(nsErrorType),
490+
existentialTL,
491+
ctx.AllocateCopy(nsErrorConformances),
492+
C,
493+
[&](SGFContext innerC) -> ManagedValue {
494+
// Call the getter.
495+
return emitGetAccessor(loc, getter, substitutions,
496+
ArgumentSource(loc,
497+
RValue(*this, loc,
498+
concreteFormalType,
499+
F(SGFContext()))),
500+
/*isSuper=*/false,
501+
/*isDirectAccessorUse=*/true,
502+
RValue(), innerC)
503+
.getAsSingleValue(*this, loc);
504+
});
505+
}
506+
}
507+
}
508+
458509
switch (existentialTL.getLoweredType().getObjectType()
459510
.getPreferredExistentialRepresentation(SGM.M, concreteFormalType)) {
460511
case ExistentialRepresentation::None:

stdlib/public/SDK/Foundation/NSError.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,11 @@ extension _ErrorCodeProtocol where Self._ErrorType: _BridgedStoredNSError {
496496
}
497497

498498
extension _BridgedStoredNSError {
499+
/// Retrieve the embedded NSError from a bridged, stored NSError.
500+
public func _getEmbeddedNSError() -> AnyObject? {
501+
return _nsError
502+
}
503+
499504
public static func == (lhs: Self, rhs: Self) -> Bool {
500505
return lhs._nsError.isEqual(rhs._nsError)
501506
}

stdlib/public/core/ErrorType.swift

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,11 +114,22 @@ public protocol Error {
114114
var _domain: String { get }
115115
var _code: Int { get }
116116
var _userInfo: Any? { get }
117+
118+
#if _runtime(_ObjC)
119+
func _getEmbeddedNSError() -> AnyObject?
120+
#endif
117121
}
118122

119123
#if _runtime(_ObjC)
120-
// Helper functions for the C++ runtime to have easy access to domain,
121-
// code, and userInfo as Objective-C values.
124+
extension Error {
125+
/// Default implementation: there is no embedded NSError.
126+
public func _getEmbeddedNSError() -> AnyObject? { return nil }
127+
}
128+
#endif
129+
130+
#if _runtime(_ObjC)
131+
// Helper functions for the C++ runtime to have easy access to embedded error,
132+
// domain, code, and userInfo as Objective-C values.
122133
@_silgen_name("swift_stdlib_getErrorDomainNSString")
123134
public func _stdlib_getErrorDomainNSString<T : Error>(_ x: UnsafePointer<T>)
124135
-> AnyObject {
@@ -138,6 +149,12 @@ public func _stdlib_getErrorUserInfoNSDictionary<T : Error>(_ x: UnsafePointer<T
138149
return x.pointee._userInfo.map { $0 as AnyObject }
139150
}
140151

152+
@_silgen_name("swift_stdlib_getErrorEmbeddedNSError")
153+
public func _stdlib_getErrorEmbeddedNSError<T : Error>(_ x: UnsafePointer<T>)
154+
-> AnyObject? {
155+
return x.pointee._getEmbeddedNSError()
156+
}
157+
141158
@_silgen_name("swift_stdlib_getErrorDefaultUserInfo")
142159
public func _stdlib_getErrorDefaultUserInfo(_ error: Error) -> AnyObject?
143160

stdlib/public/runtime/Casting.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2006,14 +2006,32 @@ static bool _dynamicCastToFunction(OpaqueValue *dest,
20062006
}
20072007

20082008
#if SWIFT_OBJC_INTEROP
2009+
// @_silgen_name("swift_stdlib_getErrorEmbeddedNSError")
2010+
// public func _stdlib_getErrorEmbeddedNSError<T : Error>(_ x: UnsafePointer<T>)
2011+
// -> AnyObject?
2012+
SWIFT_CC(swift)
2013+
extern "C" id swift_stdlib_getErrorEmbeddedNSError(const OpaqueValue *error,
2014+
const Metadata *T,
2015+
const WitnessTable *Error);
2016+
20092017
static id dynamicCastValueToNSError(OpaqueValue *src,
20102018
const Metadata *srcType,
20112019
const WitnessTable *srcErrorWitness,
20122020
DynamicCastFlags flags) {
2021+
// Check whether there is an embedded error.
2022+
if (auto embedded = swift_stdlib_getErrorEmbeddedNSError(src, srcType,
2023+
srcErrorWitness)) {
2024+
if (flags & DynamicCastFlags::TakeOnSuccess)
2025+
srcType->vw_destroy(src);
2026+
2027+
return embedded;
2028+
}
2029+
20132030
BoxPair errorBox = swift_allocError(srcType, srcErrorWitness, src,
20142031
/*isTake*/ flags & DynamicCastFlags::TakeOnSuccess);
20152032
return swift_bridgeErrorToNSError((SwiftError*)errorBox.first);
20162033
}
2034+
20172035
#endif
20182036

20192037
namespace {

test/1_stdlib/ErrorBridged.swift

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -590,4 +590,41 @@ ErrorBridgingTests.test("NSError subclass identity") {
590590
expectTrue(type(of: nsError) == MyNSError.self)
591591
}
592592

593+
ErrorBridgingTests.test("Wrapped NSError identity") {
594+
let nsError = NSError(domain: NSCocoaErrorDomain,
595+
code: NSFileNoSuchFileError,
596+
userInfo: [
597+
AnyHashable(NSFilePathErrorKey) : "/dev/null",
598+
AnyHashable(NSStringEncodingErrorKey): /*ASCII=*/1,
599+
])
600+
601+
let error: Error = nsError
602+
let nsError2: NSError = error as NSError
603+
expectTrue(nsError === nsError2)
604+
605+
// Extracting the NSError via the runtime.
606+
let cocoaErrorAny: Any = error as! CocoaError
607+
let nsError3: NSError = cocoaErrorAny as! NSError
608+
expectTrue(nsError === nsError3)
609+
610+
if let cocoaErrorAny2: Any = error as? CocoaError {
611+
let nsError4: NSError = cocoaErrorAny2 as! NSError
612+
expectTrue(nsError === nsError4)
613+
} else {
614+
expectUnreachable()
615+
}
616+
617+
// Extracting the NSError via direct call.
618+
let cocoaError = error as! CocoaError
619+
let nsError5: NSError = cocoaError as NSError
620+
expectTrue(nsError === nsError5)
621+
622+
if let cocoaError2 = error as? CocoaError {
623+
let nsError6: NSError = cocoaError as NSError
624+
expectTrue(nsError === nsError6)
625+
} else {
626+
expectUnreachable()
627+
}
628+
}
629+
593630
runAllTests()

test/SILGen/objc_error.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,3 +108,13 @@ class MyNSError : NSError {
108108
func eraseMyNSError() -> Error {
109109
return MyNSError()
110110
}
111+
112+
// CHECK-LABEL: sil hidden @_TF10objc_error25eraseFictionalServerErrorFT_Ps5Error_
113+
func eraseFictionalServerError() -> Error {
114+
// CHECK-NOT: return
115+
// CHECK: [[NSERROR_GETTER:%[0-9]+]] = function_ref @_TFVSC20FictionalServerErrorg8_nsErrorCSo7NSError
116+
// CHECK: [[NSERROR:%[0-9]+]] = apply [[NSERROR_GETTER]]
117+
// CHECK: [[ERROR:%[0-9]+]] = init_existential_ref [[NSERROR]]
118+
// CHECK: return [[ERROR]]
119+
return FictionalServerError(.meltedDown)
120+
}

0 commit comments

Comments
 (0)