Files
swift-mirror/stdlib/public/runtime/ErrorObject.mm
Andrew Trick a98de1ba1c Add an Optional metadata kind for runtime casts.
Reuses the enum metadata layout and builder because most of the logic is
also required for Optional (generic arg and payload). We may want to
optimize this at some point (Optional doesn't have a Parent), but I
don't see much opportunity.

Note that with this approach there will be no change in metadata layout.
Changing the kind still breaks the ABI of course.

Also leaves the MirrorData summary string as "(Enum Value)". We should
consider changing it.
2015-12-09 15:01:33 -08:00

444 lines
16 KiB
Plaintext

//===--- ErrorObject.mm - Cocoa-interoperable recoverable error object ----===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2015 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 ErrorType protocol
// 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
// ErrorType 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
&& "ErrorType 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;
}
}
@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};
}
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);
}
extern "C" auto *_swift_deallocError = _swift_deallocError_;
void
swift::swift_deallocError(SwiftError *error, const Metadata *type) {
return _swift_deallocError(error, type);
}
static const WitnessTable *getNSErrorConformanceToErrorType() {
// 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,
"_TWPCSo7CFErrors9ErrorType10Foundation"));
assert(TheWitnessTable &&
"Foundation overlay not loaded, or CFError: ErrorType 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 getNSErrorConformanceToErrorType();
}
return errorConformance;
}
/// Extract a pointer to the value, the type metadata, and the ErrorType
/// 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 = getNSErrorConformanceToErrorType();
return;
}
out->value = errorObject->getValue();
out->type = errorObject->type;
out->errorConformance = errorObject->errorConformance;
return;
}
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 : ErrorType>
// (x: UnsafePointer<T>) -> AnyObject
extern "C" NSString *swift_stdlib_getErrorDomainNSString(
const OpaqueValue *error,
const Metadata *T,
const WitnessTable *ErrorType);
// @_silgen_name("swift_stdlib_getErrorCode")
// public func _stdlib_getErrorCode<T : ErrorType>(x: UnsafePointer<T>) -> Int
extern "C" NSInteger swift_stdlib_getErrorCode(const OpaqueValue *error,
const Metadata *T,
const WitnessTable *ErrorType);
/// Take an ErrorType box and turn it into a valid NSError instance.
static id _swift_bridgeErrorTypeToNSError_(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);
// TODO: user info?
// 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 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 expected = nullptr;
if (!errorObject->domain.compare_exchange_strong(expected,
(CFStringRef)domain,
std::memory_order_acq_rel))
objc_release(domain);
return ns;
}
extern "C" auto *_swift_bridgeErrorTypeToNSError = _swift_bridgeErrorTypeToNSError_;
id
swift::swift_bridgeErrorTypeToNSError(SwiftError *errorObject) {
return _swift_bridgeErrorTypeToNSError(errorObject);
}
SwiftError *
swift::swift_convertNSErrorToErrorType(id errorObject) {
// The fast path is that we have a real error object.
if (errorObject) return reinterpret_cast<SwiftError*>(errorObject);
// Unlike Objective-C, we can't just propagate nil errors around.
auto allocNilError =
(SwiftError*(*)()) dlsym(RTLD_DEFAULT, "swift_allocNilObjCError");
assert(allocNilError && "didn't link Foundation overlay?");
return allocNilError();
}
id swift::swift_convertErrorTypeToNSError(SwiftError *errorObject) {
assert(errorObject && "bridging a nil error!");
return swift_bridgeErrorTypeToNSError(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_bridgeNSErrorToErrorType")
// public func _stdlib_bridgeNSErrorToErrorType<
// T : _ObjectiveCBridgeableErrorType
// >(error: NSError, out: UnsafeMutablePointer<T>) -> Bool {
auto bridgeNSErrorToErrorType = SWIFT_LAZY_CONSTANT(
reinterpret_cast<bool (*)(NSError *, OpaqueValue*, const Metadata *,
const WitnessTable *)>
(dlsym(RTLD_DEFAULT, "swift_stdlib_bridgeNSErrorToErrorType")));
// protocol _ObjectiveCBridgeableErrorType
auto TheObjectiveCBridgeableErrorTypeProtocol = SWIFT_LAZY_CONSTANT(
reinterpret_cast<const ProtocolDescriptor *>(dlsym(RTLD_DEFAULT,
"_TMp10Foundation30_ObjectiveCBridgeableErrorType")));
// If the Foundation overlay isn't loaded, then NSErrors can't be bridged.
if (!bridgeNSErrorToErrorType || !TheObjectiveCBridgeableErrorTypeProtocol)
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,
TheObjectiveCBridgeableErrorTypeProtocol);
if (!witness)
return false;
// If so, attempt the bridge.
NSError *srcInstance = *reinterpret_cast<NSError * const*>(src);
objc_retain(srcInstance);
if (bridgeNSErrorToErrorType(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);
}
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);
}
extern "C" auto *_swift_errorRelease = _swift_errorRelease_;
void swift::swift_errorRelease(SwiftError *error) {
return _swift_errorRelease(error);
}
static void _swift_willThrow_(SwiftError *error) { }
extern "C" auto *_swift_willThrow = _swift_willThrow_;
void swift::swift_willThrow(SwiftError *error) {
return _swift_willThrow(error);
}