//===--- ErrorObject.mm - Cocoa-interoperable recoverable error object ----===// // // This source file is part of the Swift.org open source project // // Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See http://swift.org/LICENSE.txt for license information // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// // // This implements the object representation of the standard ErrorProtocol // type, which represents recoverable errors in the language. This // implementation is designed to interoperate efficiently with Cocoa libraries // by: // - allowing for NSError and CFError objects to "toll-free bridge" to // ErrorProtocol existentials, which allows for cheap Cocoa to Swift interop // - allowing a native Swift error to lazily "become" an NSError when // passed into Cocoa, allowing for cheap Swift to Cocoa interop // //===----------------------------------------------------------------------===// #include "swift/Runtime/Debug.h" #include "swift/Runtime/ObjCBridge.h" #include "swift/Basic/Lazy.h" #include "ErrorObject.h" #include "Private.h" #include #include #include #include #include #include using namespace swift; /// A subclass of NSError used to represent bridged native Swift errors. /// This type cannot be subclassed, and should not ever be instantiated /// except by the Swift runtime. @interface _SwiftNativeNSError : NSError @end @implementation _SwiftNativeNSError + (instancetype)alloc { swift::crash("_SwiftNativeNSError cannot be instantiated"); } - (void)dealloc { // We must destroy the contained Swift value. auto error = (SwiftError*)self; error->getType()->vw_destroy(error->getValue()); [super dealloc]; } // Override the domain/code/userInfo accessors to follow our idea of NSError's // layout. This gives us a buffer in case NSError decides to change its stored // property order. - (NSString*)domain { auto error = (const SwiftError*)self; // The domain string should not be nil; if it is, then this error box hasn't // been initialized yet as an NSError. auto domain = error->domain.load(SWIFT_MEMORY_ORDER_CONSUME); assert(domain && "ErrorProtocol box used as NSError before initialization"); // Don't need to .retain.autorelease since it's immutable. return (NSString*)domain; } - (NSInteger)code { auto error = (const SwiftError*)self; return error->code.load(SWIFT_MEMORY_ORDER_CONSUME); } - (NSDictionary*)userInfo { auto error = (const SwiftError*)self; auto userInfo = error->userInfo.load(SWIFT_MEMORY_ORDER_CONSUME); if (userInfo) { // Don't need to .retain.autorelease since it's immutable. return (NSDictionary*)userInfo; } else { // -[NSError userInfo] never returns nil on OS X 10.8 or later. NSDictionary *emptyDict = SWIFT_LAZY_CONSTANT(@{}); return emptyDict; } } - (id)copyWithZone:(NSZone *)zone { (void)zone; // _SwiftNativeNSError is immutable, so we can return the same instance back. return [self retain]; } - (Class)classForCoder { // This is a runtime-private subclass. When archiving or unarchiving, do so // as an NSError. return getNSErrorClass(); } @end Class swift::getNSErrorClass() { return SWIFT_LAZY_CONSTANT([NSError class]); } static Class getSwiftNativeNSErrorClass() { return SWIFT_LAZY_CONSTANT([_SwiftNativeNSError class]); } /// Allocate a catchable error object. static BoxPair::Return _swift_allocError_(const Metadata *type, const WitnessTable *errorConformance, OpaqueValue *initialValue, bool isTake) { auto TheSwiftNativeNSError = getSwiftNativeNSErrorClass(); assert(class_getInstanceSize(TheSwiftNativeNSError) == sizeof(SwiftErrorHeader) && "NSError layout changed!"); // Determine the extra allocated space necessary to carry the value. // TODO: If the error type is a simple enum with no associated values, we // could emplace it in the "code" slot of the NSError and save ourselves // some work. unsigned size = type->getValueWitnesses()->getSize(); unsigned alignMask = type->getValueWitnesses()->getAlignmentMask(); size_t alignmentPadding = -sizeof(SwiftError) & alignMask; size_t totalExtraSize = sizeof(SwiftError) - sizeof(SwiftErrorHeader) + alignmentPadding + size; size_t valueOffset = alignmentPadding + sizeof(SwiftError); // Allocate the instance as if it were a CFError. We won't really initialize // the CFError parts until forced to though. auto instance = (SwiftError *)class_createInstance(TheSwiftNativeNSError, totalExtraSize); // Leave the NSError bits zero-initialized. We'll lazily instantiate them when // needed. // Initialize the Swift type metadata. instance->type = type; instance->errorConformance = errorConformance; auto valueBytePtr = reinterpret_cast(instance) + valueOffset; auto valuePtr = reinterpret_cast(valueBytePtr); // If an initial value was given, copy or take it in. if (initialValue) { if (isTake) type->vw_initializeWithTake(valuePtr, initialValue); else type->vw_initializeWithCopy(valuePtr, initialValue); } // Return the SwiftError reference and a pointer to the uninitialized value // inside. return BoxPair{reinterpret_cast(instance), valuePtr}; } SWIFT_RUNTIME_EXPORT extern "C" auto *_swift_allocError = _swift_allocError_; BoxPair::Return swift::swift_allocError(const Metadata *type, const WitnessTable *errorConformance, OpaqueValue *value, bool isTake) { return _swift_allocError(type, errorConformance, value, isTake); } /// Deallocate an error object whose contained object has already been /// destroyed. static void _swift_deallocError_(SwiftError *error, const Metadata *type) { object_dispose((id)error); } SWIFT_RUNTIME_EXPORT extern "C" auto *_swift_deallocError = _swift_deallocError_; void swift::swift_deallocError(SwiftError *error, const Metadata *type) { return _swift_deallocError(error, type); } static const WitnessTable *getNSErrorConformanceToErrorProtocol() { // CFError and NSError are toll-free-bridged, so we can use either type's // witness table interchangeably. CFError's is potentially slightly more // efficient since it doesn't need to dispatch for an unsubclassed NSCFError. // The witness table lives in the Foundation overlay, but it should be safe // to assume that that's been linked in if a user is using NSError in their // Swift source. auto TheWitnessTable = SWIFT_LAZY_CONSTANT(dlsym(RTLD_DEFAULT, "_TWPCSo7CFErrors13ErrorProtocol10Foundation")); assert(TheWitnessTable && "Foundation overlay not loaded, or CFError: ErrorProtocol conformance " "not available"); return reinterpret_cast(TheWitnessTable); } bool SwiftError::isPureNSError() const { auto TheSwiftNativeNSError = getSwiftNativeNSErrorClass(); // We can do an exact type check; _SwiftNativeNSError shouldn't be subclassed // or proxied. return (Class)_swift_getClass(this) != TheSwiftNativeNSError; } const Metadata *SwiftError::getType() const { if (isPureNSError()) { auto asError = (NSError*)this; return swift_getObjCClassMetadata((ClassMetadata*)[asError class]); } return type; } const WitnessTable *SwiftError::getErrorConformance() const { if (isPureNSError()) { return getNSErrorConformanceToErrorProtocol(); } return errorConformance; } /// Extract a pointer to the value, the type metadata, and the ErrorProtocol /// protocol witness from an error object. /// /// The "scratch" pointer should point to an uninitialized word-sized /// temporary buffer. The implementation may write a reference to itself to /// that buffer if the error object is a toll-free-bridged NSError instead of /// a native Swift error, in which case the object itself is the "boxed" value. static void _swift_getErrorValue_(const SwiftError *errorObject, void **scratch, ErrorValueResult *out) { // TODO: Would be great if Clang had a return-three convention so we didn't // need the out parameter here. // Check for a bridged Cocoa NSError. if (errorObject->isPureNSError()) { // Return a pointer to the scratch buffer. auto asError = (NSError*)errorObject; *scratch = (void*)errorObject; out->value = (const OpaqueValue *)scratch; out->type = swift_getObjCClassMetadata((ClassMetadata*)[asError class]); out->errorConformance = getNSErrorConformanceToErrorProtocol(); return; } out->value = errorObject->getValue(); out->type = errorObject->type; out->errorConformance = errorObject->errorConformance; return; } SWIFT_RUNTIME_EXPORT extern "C" auto *_swift_getErrorValue = _swift_getErrorValue_; void swift::swift_getErrorValue(const SwiftError *errorObject, void **scratch, ErrorValueResult *out) { return _swift_getErrorValue(errorObject, scratch, out); } // @_silgen_name("swift_stdlib_getErrorDomainNSString") // public func _stdlib_getErrorDomainNSString // (x: UnsafePointer) -> AnyObject SWIFT_CC(swift) extern "C" NSString *swift_stdlib_getErrorDomainNSString( const OpaqueValue *error, const Metadata *T, const WitnessTable *ErrorProtocol); // @_silgen_name("swift_stdlib_getErrorCode") // public func _stdlib_getErrorCode(x: UnsafePointer) -> Int SWIFT_CC(swift) extern "C" NSInteger swift_stdlib_getErrorCode(const OpaqueValue *error, const Metadata *T, const WitnessTable *ErrorProtocol); //@_silgen_name("swift_stdlib_getErrorUserInfoNSDictionary") //public func _stdlib_getErrorUserInfoNSDictionary(_ x: UnsafePointer) -> AnyObject SWIFT_CC(swift) extern "C" NSDictionary *swift_stdlib_getErrorUserInfoNSDictionary( const OpaqueValue *error, const Metadata *T, const WitnessTable *ErrorProtocol); //@_silgen_name("swift_stdlib_getErrorDefaultUserInfo") //public func _stdlib_getErrorDefaultUserInfo(_ x: UnsafePointer) -> AnyObject SWIFT_CC(swift) SWIFT_RT_ENTRY_VISIBILITY extern "C" NSDictionary *swift_stdlib_getErrorDefaultUserInfo( const OpaqueValue *error, const Metadata *T, const WitnessTable *ErrorProtocol) { typedef SWIFT_CC(swift) NSDictionary *GetDefaultFn(const OpaqueValue *error, const Metadata *T, const WitnessTable *ErrorProtocol); auto foundationGetDefaultUserInfo = SWIFT_LAZY_CONSTANT( reinterpret_cast (dlsym(RTLD_DEFAULT, "swift_Foundation_getErrorDefaultUserInfo"))); if (!foundationGetDefaultUserInfo) { return nullptr; } return foundationGetDefaultUserInfo(error, T, ErrorProtocol); } /// Take an ErrorProtocol box and turn it into a valid NSError instance. SWIFT_CC(swift) static id _swift_bridgeErrorProtocolToNSError_(SwiftError *errorObject) { auto ns = reinterpret_cast(errorObject); // If we already have a domain set, then we've already initialized. if (errorObject->domain.load(SWIFT_MEMORY_ORDER_CONSUME)) return ns; // Otherwise, calculate the domain and code (TODO: and user info), and // initialize the NSError. auto value = SwiftError::getIndirectValue(&errorObject); auto type = errorObject->getType(); auto witness = errorObject->getErrorConformance(); NSString *domain = swift_stdlib_getErrorDomainNSString(value, type, witness); NSInteger code = swift_stdlib_getErrorCode(value, type, witness); NSDictionary *userInfo = swift_stdlib_getErrorUserInfoNSDictionary(value, type, witness); // The error code shouldn't change, so we can store it blindly, even if // somebody beat us to it. The store can be relaxed, since we'll do a // store(release) of the domain last thing to publish the initialized // NSError. errorObject->code.store(code, std::memory_order_relaxed); // However, we need to cmpxchg the userInfo; if somebody beat us to it, // we need to release. CFDictionaryRef expectedUserInfo = nullptr; if (!errorObject->userInfo.compare_exchange_strong(expectedUserInfo, (CFDictionaryRef)userInfo, std::memory_order_acq_rel)) objc_release(userInfo); // We also need to cmpxchg in the domain; if somebody beat us to it, // we need to release. // // Storing the domain must be the LAST THING we do, since it's // the signal that the NSError has been initialized. CFStringRef expectedDomain = nullptr; if (!errorObject->domain.compare_exchange_strong(expectedDomain, (CFStringRef)domain, std::memory_order_acq_rel)) objc_release(domain); return ns; } SWIFT_RUNTIME_EXPORT extern "C" auto *_swift_bridgeErrorProtocolToNSError = _swift_bridgeErrorProtocolToNSError_; id swift::swift_bridgeErrorProtocolToNSError(SwiftError *errorObject) { return _swift_bridgeErrorProtocolToNSError(errorObject); } bool swift::tryDynamicCastNSErrorToValue(OpaqueValue *dest, OpaqueValue *src, const Metadata *srcType, const Metadata *destType, DynamicCastFlags flags) { Class NSErrorClass = getNSErrorClass(); auto CFErrorTypeID = SWIFT_LAZY_CONSTANT(CFErrorGetTypeID()); // @_silgen_name("swift_stdlib_bridgeNSErrorToErrorProtocol") // public func _stdlib_bridgeNSErrorToErrorProtocol< // T : _ObjectiveCBridgeableErrorProtocol // >(error: NSError, out: UnsafeMutablePointer) -> Bool { typedef SWIFT_CC(swift) bool BridgeFn(NSError *, OpaqueValue*, const Metadata *, const WitnessTable *); auto bridgeNSErrorToErrorProtocol = SWIFT_LAZY_CONSTANT( reinterpret_cast (dlsym(RTLD_DEFAULT, "swift_stdlib_bridgeNSErrorToErrorProtocol"))); // protocol _ObjectiveCBridgeableErrorProtocol auto TheObjectiveCBridgeableErrorProtocolProtocol = SWIFT_LAZY_CONSTANT( reinterpret_cast(dlsym(RTLD_DEFAULT, "_TMp10Foundation34_ObjectiveCBridgeableErrorProtocol"))); // If the Foundation overlay isn't loaded, then NSErrors can't be bridged. if (!bridgeNSErrorToErrorProtocol || !TheObjectiveCBridgeableErrorProtocolProtocol) return false; // Is the input type an NSError? switch (srcType->getKind()) { case MetadataKind::Class: // Native class should be an NSError subclass. if (![(Class)srcType isSubclassOfClass: NSErrorClass]) return false; break; case MetadataKind::ForeignClass: { // Foreign class should be CFError. CFTypeRef srcInstance = *reinterpret_cast(src); if (CFGetTypeID(srcInstance) != CFErrorTypeID) return false; break; } case MetadataKind::ObjCClassWrapper: { // ObjC class should be an NSError subclass. auto srcWrapper = static_cast(srcType); if (![(Class)srcWrapper->getClassObject() isSubclassOfClass: NSErrorClass]) return false; break; } // Not a class. case MetadataKind::Enum: case MetadataKind::Optional: case MetadataKind::Existential: case MetadataKind::ExistentialMetatype: case MetadataKind::Function: case MetadataKind::HeapLocalVariable: case MetadataKind::HeapGenericLocalVariable: case MetadataKind::ErrorObject: case MetadataKind::Metatype: case MetadataKind::Opaque: case MetadataKind::Struct: case MetadataKind::Tuple: return false; } // Is the target type a bridgeable error? auto witness = swift_conformsToProtocol(destType, TheObjectiveCBridgeableErrorProtocolProtocol); if (!witness) return false; // If so, attempt the bridge. NSError *srcInstance = *reinterpret_cast(src); objc_retain(srcInstance); if (bridgeNSErrorToErrorProtocol(srcInstance, dest, destType, witness)) { if (flags & DynamicCastFlags::TakeOnSuccess) objc_release(srcInstance); return true; } return false; } static SwiftError *_swift_errorRetain_(SwiftError *error) { // For now, SwiftError is always objc-refcounted. return (SwiftError*)objc_retain((id)error); } SWIFT_RUNTIME_EXPORT extern "C" auto *_swift_errorRetain = _swift_errorRetain_; SwiftError *swift::swift_errorRetain(SwiftError *error) { return _swift_errorRetain(error); } static void _swift_errorRelease_(SwiftError *error) { // For now, SwiftError is always objc-refcounted. return objc_release((id)error); } SWIFT_RUNTIME_EXPORT extern "C" auto *_swift_errorRelease = _swift_errorRelease_; void swift::swift_errorRelease(SwiftError *error) { return _swift_errorRelease(error); } static void _swift_willThrow_(SwiftError *error) { } SWIFT_RUNTIME_EXPORT extern "C" auto *_swift_willThrow = _swift_willThrow_; void swift::swift_willThrow(SwiftError *error) { return _swift_willThrow(error); }