blob: d5303b3a2e1a1e92244863a1bb4797433b5d37b4 [file] [log] [blame]
//===--- ErrorObject.mm - Cocoa-interoperable recoverable error object ----===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
// This implements the object representation of the standard Error
// 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
// Error 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/Config.h"
#if SWIFT_OBJC_INTEROP
#include "swift/Runtime/Casting.h"
#include "swift/Runtime/Debug.h"
#include "swift/Runtime/ObjCBridge.h"
#include "swift/Basic/Lazy.h"
#include "swift/Demangling/ManglingMacros.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;
using namespace swift::hashable_support;
/// 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)allocWithZone:(NSZone *)zone {
(void)zone;
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
&& "Error box used as NSError before initialization");
// Don't need to .retain.autorelease since it's immutable.
return cf_const_cast<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);
assert(userInfo
&& "Error box used as NSError before initialization");
// Don't need to .retain.autorelease since it's immutable.
return cf_const_cast<NSDictionary*>(userInfo);
}
- (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();
}
// Note: We support comparing cases of `@objc` enums defined in Swift to
// pure `NSError`s. They should compare equal as long as the domain and
// code match. Equal values should have equal hash values. Thus, we can't
// use the Swift hash value computation that comes from the `Hashable`
// conformance if one exists, and we must use the `NSError` hashing
// algorithm.
//
// So we are not overriding the `hash` method, even though we are
// overriding `isEqual:`.
- (BOOL)isEqual:(id)other {
auto self_ = (const SwiftError *)self;
auto other_ = (const SwiftError *)other;
assert(!self_->isPureNSError());
if (self == other) {
return YES;
}
if (!other) {
return NO;
}
if (other_->isPureNSError()) {
return [super isEqual:other];
}
auto hashableBaseType = self_->getHashableBaseType();
if (!hashableBaseType || other_->getHashableBaseType() != hashableBaseType) {
return [super isEqual:other];
}
auto hashableConformance = self_->getHashableConformance();
if (!hashableConformance) {
return [super isEqual:other];
}
return _swift_stdlib_Hashable_isEqual_indirect(
self_->getValue(), other_->getValue(), hashableBaseType,
hashableConformance);
}
@end
Class swift::getNSErrorClass() {
return SWIFT_LAZY_CONSTANT([NSError class]);
}
const Metadata *swift::getNSErrorMetadata() {
return SWIFT_LAZY_CONSTANT(
swift_getObjCClassMetadata((const ClassMetadata *)getNSErrorClass()));
}
static Class getSwiftNativeNSErrorClass() {
return SWIFT_LAZY_CONSTANT([_SwiftNativeNSError class]);
}
/// Allocate a catchable error object.
BoxPair
swift::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;
instance->hashableBaseType = nullptr;
instance->hashableConformance = nullptr;
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};
}
/// Deallocate an error object whose contained object has already been
/// destroyed.
void
swift::swift_deallocError(SwiftError *error, const Metadata *type) {
object_dispose((id)error);
}
static const WitnessTable *getNSErrorConformanceToError() {
// 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,
MANGLE_AS_STRING(MANGLE_SYM(So10CFErrorRefas5Error10FoundationWP))));
assert(TheWitnessTable &&
"Foundation overlay not loaded, or 'CFError : Error' conformance "
"not available");
return reinterpret_cast<const WitnessTable *>(TheWitnessTable);
}
static const HashableWitnessTable *getNSErrorConformanceToHashable() {
auto TheWitnessTable = SWIFT_LAZY_CONSTANT(dlsym(RTLD_DEFAULT,
MANGLE_AS_STRING(MANGLE_SYM(So8NSObjectCs8Hashable10ObjectiveCWP))));
assert(TheWitnessTable &&
"ObjectiveC overlay not loaded, or 'NSObject : Hashable' conformance "
"not available");
return reinterpret_cast<const HashableWitnessTable *>(TheWitnessTable);
}
bool SwiftError::isPureNSError() const {
// We can do an exact type check; _SwiftNativeNSError shouldn't be subclassed
// or proxied.
return _swift_getClass(this) != (ClassMetadata *)getSwiftNativeNSErrorClass();
}
const Metadata *SwiftError::getType() const {
if (isPureNSError()) {
auto asError = reinterpret_cast<NSError *>(const_cast<SwiftError *>(this));
return swift_getObjCClassMetadata((ClassMetadata*)[asError class]);
}
return type;
}
const WitnessTable *SwiftError::getErrorConformance() const {
if (isPureNSError()) {
return getNSErrorConformanceToError();
}
return errorConformance;
}
const Metadata *SwiftError::getHashableBaseType() const {
if (isPureNSError()) {
return getNSErrorMetadata();
}
if (auto type = hashableBaseType.load(std::memory_order_acquire)) {
if (reinterpret_cast<uintptr_t>(type) == 1) {
return nullptr;
}
return type;
}
const Metadata *expectedType = nullptr;
const Metadata *hashableBaseType = findHashableBaseType(type);
this->hashableBaseType.compare_exchange_strong(
expectedType, hashableBaseType ? hashableBaseType
: reinterpret_cast<const Metadata *>(1),
std::memory_order_acq_rel);
return type;
}
const HashableWitnessTable *SwiftError::getHashableConformance() const {
if (isPureNSError()) {
return getNSErrorConformanceToHashable();
}
if (auto wt = hashableConformance.load(std::memory_order_acquire)) {
if (reinterpret_cast<uintptr_t>(wt) == 1) {
return nullptr;
}
return wt;
}
const HashableWitnessTable *expectedWT = nullptr;
const HashableWitnessTable *wt =
reinterpret_cast<const HashableWitnessTable *>(
swift_conformsToProtocol(type, &HashableProtocolDescriptor));
hashableConformance.compare_exchange_strong(
expectedWT, wt ? wt : reinterpret_cast<const HashableWitnessTable *>(1),
std::memory_order_acq_rel);
return wt;
}
/// Extract a pointer to the value, the type metadata, and the Error
/// 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.
///
/// This function is called by compiler-generated code.
void
swift::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.
out->type = errorObject->getType();
// Check for a bridged Cocoa NSError.
if (errorObject->isPureNSError()) {
// Return a pointer to the scratch buffer.
*scratch = (void*)errorObject;
out->value = (const OpaqueValue *)scratch;
out->errorConformance = getNSErrorConformanceToError();
} else {
out->value = errorObject->getValue();
out->errorConformance = errorObject->errorConformance;
}
}
// internal func _getErrorDomainNSString<T : Error>
// (_ x: UnsafePointer<T>) -> AnyObject
#define getErrorDomainNSString \
MANGLE_SYM(s23_getErrorDomainNSStringyyXlSPyxGs0B0RzlF)
SWIFT_CC(swift) SWIFT_RUNTIME_STDLIB_INTERNAL
NSString *getErrorDomainNSString(const OpaqueValue *error,
const Metadata *T,
const WitnessTable *Error);
// internal func _getErrorCode<T : Error>(_ x: UnsafePointer<T>) -> Int
#define getErrorCode \
MANGLE_SYM(s13_getErrorCodeySiSPyxGs0B0RzlF)
SWIFT_CC(swift) SWIFT_RUNTIME_STDLIB_INTERNAL
NSInteger getErrorCode(const OpaqueValue *error,
const Metadata *T,
const WitnessTable *Error);
// internal func _getErrorUserInfoNSDictionary<T : Error>(_ x: UnsafePointer<T>) -> AnyObject
#define getErrorUserInfoNSDictionary \
MANGLE_SYM(s29_getErrorUserInfoNSDictionaryyyXlSgSPyxGs0B0RzlF)
SWIFT_CC(swift) SWIFT_RUNTIME_STDLIB_INTERNAL
NSDictionary *getErrorUserInfoNSDictionary(
const OpaqueValue *error,
const Metadata *T,
const WitnessTable *Error);
// @_silgen_name("_swift_stdlib_getErrorDefaultUserInfo")
// internal func _getErrorDefaultUserInfo<T : Error>(_ x: T) -> AnyObject
SWIFT_CC(swift) SWIFT_RUNTIME_STDLIB_INTERNAL
NSDictionary *_swift_stdlib_getErrorDefaultUserInfo(OpaqueValue *error,
const Metadata *T,
const WitnessTable *Error) {
typedef SWIFT_CC(swift)
NSDictionary *GetDefaultFn(const OpaqueValue *error,
const Metadata *T,
const WitnessTable *Error);
// public func Foundation._getErrorDefaultUserInfo<T: Error>(_ error: T)
// -> AnyObject?
auto foundationGetDefaultUserInfo = SWIFT_LAZY_CONSTANT(
reinterpret_cast<GetDefaultFn*> (dlsym(RTLD_DEFAULT,
MANGLE_AS_STRING(MANGLE_SYM(10Foundation24_getErrorDefaultUserInfoyyXlSgxs0C0RzlF)))));
if (!foundationGetDefaultUserInfo) {
SWIFT_CC_PLUSONE_GUARD(T->vw_destroy(error));
return nullptr;
}
// +0 Convention: In the case where we have the +1 convention, this will
// destroy the error for us, otherwise, it will take the value guaranteed. The
// conclusion is that we can leave this alone.
return foundationGetDefaultUserInfo(error, T, Error);
}
/// Take an Error box and turn it into a valid NSError instance.
id
swift::_swift_stdlib_bridgeErrorToNSError(SwiftError *errorObject) {
auto ns = reinterpret_cast<NSError *>(errorObject);
// If we already have a domain set, then we've already initialized.
// If this is a real NSError, then Cocoa and Core Foundation's initializers
// guarantee that the domain is never nil, so if this test fails, we can
// assume we're working with a bridged error. (Note that Cocoa and CF
// **will** allow the userInfo storage to be initialized to nil.)
//
// If this is a bridged error, then the domain, code, and user info are
// lazily computed, and the domain will be nil if they haven't been computed
// yet. The initialization is ordered in such a way that all other lazy
// initialization of the object happens-before the domain initialization so
// that the domain can be used alone as a flag for the initialization of the
// object.
if (errorObject->domain.load(std::memory_order_acquire))
return ns;
// Otherwise, calculate the domain, code, and user info, and
// initialize the NSError.
auto value = SwiftError::getIndirectValue(&errorObject);
auto type = errorObject->getType();
auto witness = errorObject->getErrorConformance();
NSString *domain = getErrorDomainNSString(value, type, witness);
NSInteger code = getErrorCode(value, type, witness);
NSDictionary *userInfo = getErrorUserInfoNSDictionary(value, type, witness);
// Never produce an empty userInfo dictionary.
if (!userInfo)
userInfo = SWIFT_LAZY_CONSTANT(@{});
// 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
// also the flag 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;
}
bool
swift::tryDynamicCastNSErrorToValue(OpaqueValue *dest,
OpaqueValue *src,
const Metadata *srcType,
const Metadata *destType,
DynamicCastFlags flags) {
Class NSErrorClass = getNSErrorClass();
auto CFErrorTypeID = SWIFT_LAZY_CONSTANT(CFErrorGetTypeID());
// public func Foundation._bridgeNSErrorToError<
// T : _ObjectiveCBridgeableError
// >(error: NSError, out: UnsafeMutablePointer<T>) -> Bool {
typedef SWIFT_CC(swift)
bool BridgeFn(NSError *, OpaqueValue*, const Metadata *,
const WitnessTable *);
auto bridgeNSErrorToError = SWIFT_LAZY_CONSTANT(
reinterpret_cast<BridgeFn*>(dlsym(RTLD_DEFAULT,
MANGLE_AS_STRING(MANGLE_SYM(10Foundation21_bridgeNSErrorToError_3outSbSo0C0C_SpyxGtAA021_ObjectiveCBridgeableE0RzlF)))));
// protocol _ObjectiveCBridgeableError
auto TheObjectiveCBridgeableError = SWIFT_LAZY_CONSTANT(
reinterpret_cast<const ProtocolDescriptor *>(dlsym(RTLD_DEFAULT,
MANGLE_AS_STRING(MANGLE_SYM(10Foundation26_ObjectiveCBridgeableErrorMp)))));
// If the Foundation overlay isn't loaded, then NSErrors can't be bridged.
if (!bridgeNSErrorToError || !TheObjectiveCBridgeableError)
return false;
// Is the input type an NSError?
switch (srcType->getKind()) {
case MetadataKind::Class:
case MetadataKind::ObjCClassWrapper:
// Native class or ObjC class should be an NSError subclass.
if (![srcType->getObjCClassObject() 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;
}
// 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,
TheObjectiveCBridgeableError);
if (!witness)
return false;
// If so, attempt the bridge.
NSError *srcInstance = *reinterpret_cast<NSError * const*>(src);
SWIFT_CC_PLUSONE_GUARD(objc_retain(srcInstance));
if (bridgeNSErrorToError(srcInstance, dest, destType, witness)) {
if (flags & DynamicCastFlags::TakeOnSuccess)
objc_release(srcInstance);
return true;
}
return false;
}
SwiftError *
swift::swift_errorRetain(SwiftError *error) {
// For now, SwiftError is always objc-refcounted.
return (SwiftError*)objc_retain((id)error);
}
void
swift::swift_errorRelease(SwiftError *error) {
// For now, SwiftError is always objc-refcounted.
return objc_release((id)error);
}
/// Breakpoint hook for debuggers.
void
swift::swift_willThrow(SwiftError *error) {
// empty
}
#endif