mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
An error type can conform to one or more of these new protocols to customize its behavior and representation. From an implementation standpoint, the protocol conformances are used to fill in the user-info dictionary in NSError to interoperate with the Cocoa error-handling system. There are a few outstanding problems with this implementation, although it is fully functional: * Population of the userInfo dictionary is currently eager; we should use user info providers on platforms where they are available. * At present, the Swift dynamic casting machinery is unable to unbox a _SwiftNativeNSError when trying to cast from it to (e.g.) an existential, which makes it impossible to retrieve the RecoverableError from the NSError. Instead, just capture the original error---hey, they're supposed to be value types anyway!---and use that to implement the entry points for the informal NSErrorRecoveryAttempting protocol. This is part (1) of the proposal solution.
488 lines
18 KiB
Plaintext
488 lines
18 KiB
Plaintext
//===--- 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 <dlfcn.h>
|
|
#include <objc/NSObject.h>
|
|
#include <objc/runtime.h>
|
|
#include <objc/message.h>
|
|
#include <objc/objc.h>
|
|
#include <Foundation/Foundation.h>
|
|
|
|
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<char*>(instance) + valueOffset;
|
|
auto valuePtr = reinterpret_cast<OpaqueValue*>(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<HeapObject*>(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<const WitnessTable *>(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<T : ErrorProtocol>
|
|
// (x: UnsafePointer<T>) -> 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<T : ErrorProtocol>(x: UnsafePointer<T>) -> 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<T : ErrorProtocol>(_ x: UnsafePointer<T>) -> 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<T : ErrorProtocol>(_ x: UnsafePointer<T>) -> 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<GetDefaultFn*>
|
|
(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<NSError *>(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<T>) -> Bool {
|
|
typedef SWIFT_CC(swift)
|
|
bool BridgeFn(NSError *, OpaqueValue*, const Metadata *,
|
|
const WitnessTable *);
|
|
auto bridgeNSErrorToErrorProtocol = SWIFT_LAZY_CONSTANT(
|
|
reinterpret_cast<BridgeFn*>
|
|
(dlsym(RTLD_DEFAULT, "swift_stdlib_bridgeNSErrorToErrorProtocol")));
|
|
// protocol _ObjectiveCBridgeableErrorProtocol
|
|
auto TheObjectiveCBridgeableErrorProtocolProtocol = SWIFT_LAZY_CONSTANT(
|
|
reinterpret_cast<const ProtocolDescriptor *>(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<CFTypeRef *>(src);
|
|
if (CFGetTypeID(srcInstance) != CFErrorTypeID)
|
|
return false;
|
|
break;
|
|
}
|
|
case MetadataKind::ObjCClassWrapper: {
|
|
// ObjC class should be an NSError subclass.
|
|
auto srcWrapper = static_cast<const ObjCClassWrapperMetadata *>(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<NSError * const*>(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);
|
|
}
|