blob: 09bd46ea0270070f5b9cc362c15d000753d099db [file] [log] [blame]
//===--- ProtocolConformance.cpp - Swift conformance checking backport ----===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2020 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
//
//===----------------------------------------------------------------------===//
//
// Checking and caching of Swift protocol conformances.
//
// This is a version of the Swift 5.2 protocol conformance cache implementation
// adapted for backporting to Swift 5.1 with the following fixes applied:
//
// - rdar://problem/59460603, fixing a problem where the conformance cache would
// eagerly instantiate metadata for types when not necessary, causing crashes
// if instantiating the type relied on weak-linked symbols that aren't
// available on the client OS
//
//===----------------------------------------------------------------------===//
#include "../../public/runtime/Private.h"
#include "Concurrent.h"
#include "Overrides.h"
#include "swift/Basic/Lazy.h"
#include <assert.h>
#include <dlfcn.h>
#include <mach-o/dyld.h>
#include <mach-o/getsect.h>
#include <objc/runtime.h>
using namespace swift;
using swift::overrides::ConcurrentMap;
using swift::overrides::ConcurrentReadableArray;
// Look up Swift runtime entry points dynamically. This handles the case
// where the main executable can't link against libswiftCore.dylib because
// it will be loaded dynamically from a location that isn't known at build
// time.
static const Metadata *getObjCClassMetadata(const ClassMetadata *c) {
using FPtr = const Metadata *(*)(const ClassMetadata *);
FPtr func = SWIFT_LAZY_CONSTANT(
reinterpret_cast<FPtr>(dlsym(RTLD_DEFAULT, "swift_getObjCClassMetadata")));
return func(c);
}
static const ExistentialTypeMetadata *getExistentialTypeMetadata(
ProtocolClassConstraint classConstraint,
const Metadata *superclassConstraint,
size_t numProtocols,
const ProtocolDescriptorRef *protocols) {
auto func = SWIFT_LAZY_CONSTANT(
reinterpret_cast<const ExistentialTypeMetadata *(*)(ProtocolClassConstraint classConstraint,
const Metadata *superclassConstraint,
size_t numProtocols,
const ProtocolDescriptorRef *protocols)>(
dlsym(RTLD_DEFAULT, "swift_getExistentialTypeMetadata")));
return func(classConstraint, superclassConstraint, numProtocols, protocols);
}
static const TypeContextDescriptor *getTypeContextDescriptor(const Metadata *type) {
auto func = SWIFT_LAZY_CONSTANT(
reinterpret_cast<const TypeContextDescriptor *(*)(const Metadata *)>(
dlsym(RTLD_DEFAULT, "swift_getTypeContextDescriptor")));
return func(type);
}
// Clone of private helper swift::_swift_class_getSuperclass
// for use in the override implementation.
//
// This also gets used from the Compatibility50 library.
const Metadata *_swiftoverride_class_getSuperclass(
const Metadata *theClass) {
if (const ClassMetadata *classType = theClass->getClassObject()) {
if (classHasSuperclass(classType))
return getObjCClassMetadata(classType->Superclass);
}
if (const ForeignClassMetadata *foreignClassType
= dyn_cast<ForeignClassMetadata>(theClass)) {
if (const Metadata *superclass = foreignClassType->Superclass)
return superclass;
}
return nullptr;
}
// Clone of private function getRootSuperclass. This returns the SwiftObject
// class in the ABI-stable dylib, regardless of what the local runtime build
// does, since we're always patching an ABI-stable dylib.
__attribute__((visibility("hidden"), weak))
const ClassMetadata *swift::getRootSuperclass() {
auto theClass = SWIFT_LAZY_CONSTANT(objc_getClass("_TtCs12_SwiftObject"));
return (const ClassMetadata *)theClass;
}
namespace {
StringRef getTypeContextIdentity(const TypeContextDescriptor *type) {
// The first component is the user-facing name and (unless overridden)
// the ABI name.
StringRef component = type->Name.get();
// If we don't have import info, we're done.
if (!type->getTypeContextDescriptorFlags().hasImportInfo()) {
return component;
}
// The identity starts with the user-facing name.
const char *startOfIdentity = component.begin();
const char *endOfIdentity = component.end();
enum class TypeImportComponent : char {
ABIName = 'N',
SymbolNamespace = 'S',
RelatedEntityName = 'R',
};
while (true) {
// Parse the next component. If it's empty, we're done.
component = StringRef(component.end() + 1);
if (component.empty()) break;
// Update the identity bounds and assert that the identity
// components are in the right order.
auto kind = TypeImportComponent(component[0]);
if (kind == TypeImportComponent::ABIName) {
startOfIdentity = component.begin() + 1;
endOfIdentity = component.end();
} else if (kind == TypeImportComponent::SymbolNamespace) {
endOfIdentity = component.end();
} else if (kind == TypeImportComponent::RelatedEntityName) {
endOfIdentity = component.end();
}
}
return StringRef(startOfIdentity, endOfIdentity - startOfIdentity);
}
// Reimplementation of the runtime-private function `swift::equalContexts`
static bool override_equalContexts(const ContextDescriptor *a,
const ContextDescriptor *b)
{
// Fast path: pointer equality.
if (a == b) return true;
// If either context is null, we're done.
if (a == nullptr || b == nullptr)
return false;
// If either descriptor is known to be unique, we're done.
if (a->isUnique() || b->isUnique()) return false;
// Do the kinds match?
if (a->getKind() != b->getKind()) return false;
// Do the parents match?
if (!override_equalContexts(a->Parent.get(), b->Parent.get()))
return false;
// Compare kind-specific details.
switch (auto kind = a->getKind()) {
case ContextDescriptorKind::Module: {
// Modules with the same name are equivalent.
auto moduleA = cast<ModuleContextDescriptor>(a);
auto moduleB = cast<ModuleContextDescriptor>(b);
return strcmp(moduleA->Name.get(), moduleB->Name.get()) == 0;
}
case ContextDescriptorKind::Extension:
case ContextDescriptorKind::Anonymous:
// These context kinds are always unique.
return false;
default:
// Types in the same context with the same name are equivalent.
if (kind >= ContextDescriptorKind::Type_First
&& kind <= ContextDescriptorKind::Type_Last) {
auto typeA = cast<TypeContextDescriptor>(a);
auto typeB = cast<TypeContextDescriptor>(b);
return getTypeContextIdentity(typeA) == getTypeContextIdentity(typeB);
}
// Otherwise, this runtime doesn't know anything about this context kind.
// Conservatively return false.
return false;
}
}
// Reimplementation of the runtime-private function
// `ProtocolConformanceDescriptor::getCanonicalTypeMetadata`.
static const Metadata *
override_getCanonicalTypeMetadata(const ProtocolConformanceDescriptor *conf) {
switch (conf->getTypeKind()) {
// The class may be ObjC, in which case we need to instantiate its Swift
// metadata. The class additionally may be weak-linked, so we have to check
// for null.
case TypeReferenceKind::IndirectObjCClass:
if (auto cls = *conf->getIndirectObjCClass())
return getObjCClassMetadata(cls);
return nullptr;
case TypeReferenceKind::DirectObjCClassName:
if (auto cls = reinterpret_cast<const ClassMetadata *>(
objc_lookUpClass(conf->getDirectObjCClassName())))
return getObjCClassMetadata(cls);
return nullptr;
case TypeReferenceKind::DirectTypeDescriptor:
case TypeReferenceKind::IndirectTypeDescriptor: {
if (auto anyType = conf->getTypeDescriptor()) {
if (auto type = dyn_cast<TypeContextDescriptor>(anyType)) {
if (!type->isGeneric()) {
if (auto accessFn = type->getAccessFunction())
return accessFn(MetadataState::Abstract).Value;
}
} else if (auto protocol = dyn_cast<ProtocolDescriptor>(anyType)) {
auto protocolRef = ProtocolDescriptorRef::forSwift(protocol);
auto constraint =
protocol->getProtocolContextDescriptorFlags().getClassConstraint();
return getExistentialTypeMetadata(constraint,
/*superclass bound*/ nullptr,
/*num protocols*/ 1,
&protocolRef);
}
}
return nullptr;
}
}
swift_unreachable("Unhandled TypeReferenceKind in switch.");
}
class ConformanceCandidate {
const void *candidate;
bool candidateIsMetadata;
public:
ConformanceCandidate() : candidate(0), candidateIsMetadata(false) { }
ConformanceCandidate(const ProtocolConformanceDescriptor &conformance)
: ConformanceCandidate()
{
if (auto description = conformance.getTypeDescriptor()) {
candidate = description;
candidateIsMetadata = false;
return;
}
if (auto metadata = override_getCanonicalTypeMetadata(&conformance)) {
candidate = metadata;
candidateIsMetadata = true;
return;
}
}
/// Retrieve the conforming type as metadata, or NULL if the candidate's
/// conforming type is described in another way (e.g., a nominal type
/// descriptor).
const Metadata *getConformingTypeAsMetadata() const {
return candidateIsMetadata ? static_cast<const Metadata *>(candidate)
: nullptr;
}
const ContextDescriptor *
getContextDescriptor(const Metadata *conformingType) const {
const auto *description = getTypeContextDescriptor(conformingType);
if (description)
return description;
// Handle single-protocol existential types for self-conformance.
auto *existentialType = dyn_cast<ExistentialTypeMetadata>(conformingType);
if (existentialType == nullptr ||
existentialType->getProtocols().size() != 1 ||
existentialType->getSuperclassConstraint() != nullptr)
return nullptr;
auto proto = existentialType->getProtocols()[0];
if (proto.isObjC())
return nullptr;
return proto.getSwiftProtocol();
}
/// Whether the conforming type exactly matches the conformance candidate.
bool matches(const Metadata *conformingType) const {
// Check whether the types match.
if (candidateIsMetadata && conformingType == candidate)
return true;
// Check whether the nominal type descriptors match.
if (!candidateIsMetadata) {
const auto *description = getContextDescriptor(conformingType);
auto candidateDescription =
static_cast<const ContextDescriptor *>(candidate);
if (description && override_equalContexts(description, candidateDescription))
return true;
}
return false;
}
/// Retrieve the type that matches the conformance candidate, which may
/// be a superclass of the given type. Returns null if this type does not
/// match this conformance.
const Metadata *getMatchingType(const Metadata *conformingType) const {
while (conformingType) {
// Check for a match.
if (matches(conformingType))
return conformingType;
// Look for a superclass.
conformingType = _swiftoverride_class_getSuperclass(conformingType);
}
return nullptr;
}
};
struct ConformanceSection {
const ProtocolConformanceRecord *Begin, *End;
const ProtocolConformanceRecord *begin() const {
return Begin;
}
const ProtocolConformanceRecord *end() const {
return End;
}
};
struct ConformanceCacheKey {
/// Either a Metadata* or a NominalTypeDescriptor*.
const void *Type;
const ProtocolDescriptor *Proto;
ConformanceCacheKey(const void *type, const ProtocolDescriptor *proto)
: Type(type), Proto(proto) {
assert(type);
}
};
struct ConformanceCacheEntry {
private:
const void *Type;
const ProtocolDescriptor *Proto;
std::atomic<const ProtocolConformanceDescriptor *> Description;
std::atomic<size_t> FailureGeneration;
public:
ConformanceCacheEntry(ConformanceCacheKey key,
const ProtocolConformanceDescriptor *description,
size_t failureGeneration)
: Type(key.Type), Proto(key.Proto), Description(description),
FailureGeneration(failureGeneration) {
}
int compareWithKey(const ConformanceCacheKey &key) const {
if (key.Type != Type) {
return (uintptr_t(key.Type) < uintptr_t(Type) ? -1 : 1);
} else if (key.Proto != Proto) {
return (uintptr_t(key.Proto) < uintptr_t(Proto) ? -1 : 1);
} else {
return 0;
}
}
template <class... Args>
static size_t getExtraAllocationSize(Args &&... ignored) {
return 0;
}
bool isSuccessful() const {
return Description.load(std::memory_order_relaxed) != nullptr;
}
void makeSuccessful(const ProtocolConformanceDescriptor *description) {
Description.store(description, std::memory_order_release);
}
void updateFailureGeneration(size_t failureGeneration) {
assert(!isSuccessful());
FailureGeneration.store(failureGeneration, std::memory_order_relaxed);
}
/// Get the cached conformance descriptor, if successful.
const ProtocolConformanceDescriptor *getDescription() const {
assert(isSuccessful());
return Description.load(std::memory_order_acquire);
}
/// Get the generation in which this lookup failed.
size_t getFailureGeneration() const {
assert(!isSuccessful());
return FailureGeneration.load(std::memory_order_relaxed);
}
};
#if __POINTER_WIDTH__ == 64
using mach_header_platform = mach_header_64;
#else
using mach_header_platform = mach_header;
#endif
// Conformance Cache.
struct ConformanceState {
ConcurrentMap<ConformanceCacheEntry> Cache;
ConcurrentReadableArray<ConformanceSection> SectionsToScan;
ConformanceState();
void cacheSuccess(const void *type, const ProtocolDescriptor *proto,
const ProtocolConformanceDescriptor *description) {
auto result = Cache.getOrInsert(ConformanceCacheKey(type, proto),
description, 0);
// If the entry was already present, we may need to update it.
if (!result.second) {
result.first->makeSuccessful(description);
}
}
void cacheFailure(const void *type, const ProtocolDescriptor *proto,
size_t failureGeneration) {
auto result =
Cache.getOrInsert(ConformanceCacheKey(type, proto),
(const ProtocolConformanceDescriptor *) nullptr,
failureGeneration);
// If the entry was already present, we may need to update it.
if (!result.second) {
result.first->updateFailureGeneration(failureGeneration);
}
}
ConformanceCacheEntry *findCached(const void *type,
const ProtocolDescriptor *proto) {
return Cache.find(ConformanceCacheKey(type, proto));
}
};
static Lazy<ConformanceState> Conformances;
// The Swift runtime in the OS installs this callback to populate its original
// version of the conformance cache, but since we have our own implementation,
// we must install our own callback to populate our copy as well.
static void addImageCallback(const mach_header *mh) {
// Look for a __swift5_proto section.
unsigned long conformancesSize;
const uint8_t *conformances =
getsectiondata(reinterpret_cast<const mach_header_platform *>(mh),
SEG_TEXT, "__swift5_proto",
&conformancesSize);
if (!conformances)
return;
assert(conformancesSize % sizeof(ProtocolConformanceRecord) == 0 &&
"conformances section not a multiple of ProtocolConformanceRecord");
// If we have a section, enqueue the conformances for lookup.
auto conformanceBytes = reinterpret_cast<const char *>(conformances);
auto recordsBegin
= reinterpret_cast<const ProtocolConformanceRecord*>(conformances);
auto recordsEnd
= reinterpret_cast<const ProtocolConformanceRecord*>
(conformanceBytes + conformancesSize);
// Conformance cache should always be sufficiently initialized by this point.
Conformances.unsafeGetAlreadyInitialized()
.SectionsToScan
.push_back(ConformanceSection{recordsBegin, recordsEnd});
};
static void addImageCallback(const mach_header *mh, intptr_t vmaddr_slide) {
addImageCallback(mh);
}
static void initializeProtocolConformanceLookup() {
// If `objc_addLoadImageFunc` is available on this OS, use it.
// We don't use `__builtin_available` because that requires libraries that may
// not be linked into the binary carrying this compatibility shim.
auto objc_addLoadImageFunc = reinterpret_cast<void(*)(objc_func_loadImage)>(
dlsym(RTLD_DEFAULT, "objc_addLoadImageFunc"));
if (objc_addLoadImageFunc) {
objc_addLoadImageFunc(addImageCallback);
} else {
_dyld_register_func_for_add_image(addImageCallback);
}
}
ConformanceState::ConformanceState() {
initializeProtocolConformanceLookup();
}
struct ConformanceCacheResult {
// true if description is an authoritative result as-is.
// false if more searching is required (for example, because a cached
// failure was returned in failureEntry but it is out-of-date.
bool isAuthoritative;
// The matching conformance descriptor, or null if no cached conformance
// was found.
const ProtocolConformanceDescriptor *description;
// If the search fails, this may be the negative cache entry for the
// queried type itself. This entry may be null or out-of-date.
ConformanceCacheEntry *failureEntry;
static ConformanceCacheResult
cachedSuccess(const ProtocolConformanceDescriptor *description) {
return ConformanceCacheResult { true, description, nullptr };
}
static ConformanceCacheResult
cachedFailure(ConformanceCacheEntry *entry, bool auth) {
return ConformanceCacheResult { auth, nullptr, entry };
}
static ConformanceCacheResult
cacheMiss() {
return ConformanceCacheResult { false, nullptr, nullptr };
}
};
/// Retrieve the type key from the given metadata, to be used when looking
/// into the conformance cache.
static const void *getConformanceCacheTypeKey(const Metadata *type) {
if (auto description = getTypeContextDescriptor(type))
return description;
return type;
}
/// Search for a conformance descriptor in the ConformanceCache.
static ConformanceCacheResult
searchInConformanceCache(const Metadata *type,
const ProtocolDescriptor *protocol) {
auto &C = Conformances.get();
auto origType = type;
ConformanceCacheEntry *failureEntry = nullptr;
recur:
{
// Try the specific type first.
if (auto *Value = C.findCached(type, protocol)) {
if (Value->isSuccessful()) {
// Found a conformance on the type or some superclass. Return it.
return ConformanceCacheResult::cachedSuccess(Value->getDescription());
}
// Found a negative cache entry.
bool isAuthoritative;
if (type == origType) {
// This negative cache entry is for the original query type.
// Remember it so it can be returned later.
failureEntry = Value;
// An up-to-date entry for the original type is authoritative.
isAuthoritative = true;
} else {
// An up-to-date cached failure for a superclass of the type is not
// authoritative: there may be a still-undiscovered conformance
// for the original query type.
isAuthoritative = false;
}
// Check if the negative cache entry is up-to-date.
if (Value->getFailureGeneration() == C.SectionsToScan.snapshot().count()) {
// Negative cache entry is up-to-date. Return failure along with
// the original query type's own cache entry, if we found one.
// (That entry may be out of date but the caller still has use for it.)
return ConformanceCacheResult::cachedFailure(failureEntry,
isAuthoritative);
}
// Negative cache entry is out-of-date.
// Continue searching for a better result.
}
}
{
// For generic and resilient types, nondependent conformances
// are keyed by the nominal type descriptor rather than the
// metadata, so try that.
auto typeKey = getConformanceCacheTypeKey(type);
// Hash and lookup the type-protocol pair in the cache.
if (auto *Value = C.findCached(typeKey, protocol)) {
if (Value->isSuccessful())
return ConformanceCacheResult::cachedSuccess(Value->getDescription());
// We don't try to cache negative responses for generic
// patterns.
}
}
// If there is a superclass, look there.
if (auto superclass = _swiftoverride_class_getSuperclass(type)) {
type = superclass;
goto recur;
}
// We did not find an up-to-date cache entry.
// If we found an out-of-date entry for the original query type then
// return it (non-authoritatively). Otherwise return a cache miss.
if (failureEntry)
return ConformanceCacheResult::cachedFailure(failureEntry, false);
else
return ConformanceCacheResult::cacheMiss();
}
} // end anonymous namespace
const ProtocolConformanceDescriptor *
swift::swift51override_conformsToSwiftProtocol(const Metadata * const type,
const ProtocolDescriptor *protocol,
StringRef module,
ConformsToSwiftProtocol_t *orig) {
auto &C = Conformances.get();
// See if we have a cached conformance. The ConcurrentMap data structure
// allows us to insert and search the map concurrently without locking.
auto FoundConformance = searchInConformanceCache(type, protocol);
// If the result (positive or negative) is authoritative, return it.
if (FoundConformance.isAuthoritative)
return FoundConformance.description;
auto failureEntry = FoundConformance.failureEntry;
// Prepare to scan conformance records.
auto snapshot = C.SectionsToScan.snapshot();
// Scan only sections that were not scanned yet.
// If we found an out-of-date negative cache entry,
// we need not to re-scan the sections that it covers.
auto startIndex = failureEntry ? failureEntry->getFailureGeneration() : 0;
auto endIndex = snapshot.count();
// If there are no unscanned sections outstanding
// then we can cache failure and give up now.
if (startIndex == endIndex) {
C.cacheFailure(type, protocol, snapshot.count());
return nullptr;
}
// Really scan conformance records.
for (size_t i = startIndex; i < endIndex; ++i) {
auto &section = snapshot.Start[i];
// Eagerly pull records for nondependent witnesses into our cache.
for (const auto &record : section) {
auto &descriptor = *record.get();
// We only care about conformances for this protocol.
if (descriptor.getProtocol() != protocol)
continue;
// If there's a matching type, record the positive result.
ConformanceCandidate candidate(descriptor);
if (candidate.getMatchingType(type)) {
const Metadata *matchingType = candidate.getConformingTypeAsMetadata();
if (!matchingType)
matchingType = type;
C.cacheSuccess(matchingType, protocol, &descriptor);
}
}
}
// Conformance scan is complete.
// Search the cache once more, and this time update the cache if necessary.
FoundConformance = searchInConformanceCache(type, protocol);
if (FoundConformance.isAuthoritative) {
return FoundConformance.description;
} else {
C.cacheFailure(type, protocol, snapshot.count());
return nullptr;
}
}