Files
swift-mirror/stdlib/public/runtime/ErrorObject.mm
Doug Gregor b0f9317765 [SE-0112] Add error protocols LocalizedError, RecoverableError, CustomNSError
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.
2016-07-12 10:53:52 -07:00

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);
}