//===--- GenericMetadataBuilder.cpp - Code to build generic metadata. -----===// // // This source file is part of the Swift.org open source project // // Copyright (c) 2014 - 2024 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 // //===----------------------------------------------------------------------===// // // Builder for generic metadata, in-process and out-of-process. // //===----------------------------------------------------------------------===// #include "swift/Runtime/GenericMetadataBuilder.h" #include "MetadataCache.h" #include "Private.h" #include "swift/ABI/Metadata.h" #include "swift/ABI/MetadataValues.h" #include "swift/ABI/TargetLayout.h" #include "swift/Runtime/EnvironmentVariables.h" #include "swift/Runtime/Metadata.h" #include #include #if SWIFT_STDLIB_HAS_DLADDR && __has_include() #include #define USE_DLADDR 1 #endif using namespace swift; #define LOG(fmt, ...) \ log(METADATA_BUILDER_LOG_FILE_NAME, __LINE__, __func__, fmt, __VA_ARGS__) /// A ReaderWriter (as used by GenericMetadataBuilder) that works in-process. /// Pointer writing and pointer resolution are just raw pointer operations. Type /// lookup is done by asking the runtime. Symbol lookup uses `dlsym`. class InProcessReaderWriter { public: using Runtime = InProcess; using Size = typename Runtime::StoredSize; using StoredPointer = typename Runtime::StoredPointer; using GenericArgument = const void *; /// A typed buffer which wraps a value, or values, of type T. template class Buffer { public: Buffer() : ptr(nullptr) {} Buffer(T *ptr) : ptr(ptr) {} /// Construct an arbitrarily typed buffer from a Buffer, using /// const char as an "untyped" buffer type. Buffer(const Buffer &buffer) : ptr(reinterpret_cast(buffer.ptr)) {} /// The pointer to the buffer's underlying storage. T *ptr; template Buffer cast() { return Buffer(reinterpret_cast(ptr)); } bool isNull() const { return !ptr; } /// The various resolvePointer functions take a pointer to a pointer within /// the buffer, and dereference it. In-process, this is a simple operation, /// basically just wrapping the * operator or get() function. This /// abstraction is needed for out-of-process operations. BuilderErrorOr> resolvePointer(uintptr_t *ptr) { return Buffer{reinterpret_cast(*ptr)}; } template BuilderErrorOr> resolvePointer(const RelativeDirectPointer *ptr) { return Buffer{ptr->get()}; } template BuilderErrorOr> resolvePointer(const RelativeIndirectablePointer *ptr) { return {ptr->get()}; } template BuilderErrorOr> resolvePointer(const RelativeIndirectablePointer *ptr) { return Buffer{ptr->get()}; } template auto resolvePointer(const U *ptr) -> BuilderErrorOr>> { return Buffer>{*ptr}; } template BuilderErrorOr> resolveFunctionPointer(const U *ptr) { return Buffer{reinterpret_cast(*ptr)}; } template BuilderErrorOr> resolveFunctionPointer( TargetCompactFunctionPointer *ptr) { return Buffer{reinterpret_cast(ptr->get())}; } /// Get an address value for the buffer, for logging purposes. uint64_t getAddress() { return (uint64_t)ptr; } }; /// WritableData is a mutable Buffer subclass. template class WritableData : public Buffer { /// Check that the given pointer lies within memory of this data object. void checkPtr(void *toCheck) { assert((uintptr_t)toCheck - (uintptr_t)this->ptr < size); } public: WritableData(T *ptr, size_t size) : Buffer(ptr), size(size) {} size_t size; /// The various writePointer functions take a pointer to a pointer within /// the data, and a target, and set the pointer to the target. When done /// in-process, this is just a wrapper around the * and = operators. This /// abstracted is needed for out-of-process work. template BuilderErrorOr writePointer(StoredPointer *to, Buffer target) { checkPtr(to); *to = reinterpret_cast(target.ptr); return {{}}; } template BuilderErrorOr writePointer(U **to, Buffer target) { checkPtr(to); *to = target.ptr; return {{}}; } template BuilderErrorOr writePointer(const U **to, Buffer target) { checkPtr(to); *to = target.ptr; return {{}}; } BuilderErrorOr writePointer(const Metadata **to, GenericArgument target) { checkPtr(to); *to = reinterpret_cast(target); return {{}}; } template BuilderErrorOr writePointer(To *to, Buffer target) { checkPtr((void *)to); *to = target.ptr; return {{}}; } template BuilderErrorOr writeFunctionPointer(U *to, Buffer target) { checkPtr((void *)to); // This weird double cast handles the case where the function pointer // type has a custom __ptrauth attribute, which the compiler doesn't like // casting to. auto castTarget = (const decltype(&**to))(void *)target.ptr; *to = castTarget; return {{}}; } }; /// Basic info about a symbol. struct SymbolInfo { std::string symbolName; std::string libraryName; uint64_t pointerOffset; }; /// Get info about the symbol corresponding to the given buffer. If no /// information can be retrieved, the result is filled with "" /// strings and a 0 offset. template SymbolInfo getSymbolInfo(Buffer buffer) { #if USE_DLADDR Dl_info info; int result = dladdr(buffer.ptr, &info); if (result == 0) return {"", "", 0}; if (info.dli_fname == nullptr) info.dli_fname = ""; if (info.dli_sname == nullptr) info.dli_sname = ""; const char *libName = info.dli_fname; if (auto slash = strrchr(libName, '/')) libName = slash + 1; return {info.dli_sname, libName, buffer.getAddress() - (uintptr_t)info.dli_fbase}; #else return {"", "", 0}; #endif } /// Given a symbol name, retrieve a buffer pointing to the symbol's data. template BuilderErrorOr> getSymbolPointer(const char *name) { #if USE_DLADDR #ifdef RTLD_SELF // Use RTLD_SELF for performance where it's available. void *ptr = dlsym(RTLD_SELF, name); #else // Otherwise use RTLD_DEFAULT to search everything. void *ptr = dlsym(RTLD_DEFAULT, name); #endif LOG("getSymbolPointer(\"%s\") -> %p", name, ptr); if (!ptr) return BuilderError("dlsym could not find symbol '%s'", name); return Buffer{reinterpret_cast(ptr)}; #else return BuilderError("getSymbolPointer is not implemented on this platform"); #endif } /// Look up a type with a given mangled name, in the context of the given /// metadata. The metadata's generic arguments must already be installed. Used /// for retrieving metadata for field records. BuilderErrorOr> getTypeByMangledName( WritableData> containingMetadataBuffer, NodePointer metadataMangleNode, llvm::StringRef mangledTypeName) { auto metadata = static_cast(containingMetadataBuffer.ptr); SubstGenericParametersFromMetadata substitutions(metadata); auto result = swift_getTypeByMangledName( MetadataState::LayoutComplete, mangledTypeName, substitutions.getGenericArgs(), [&substitutions, this](unsigned depth, unsigned index) { auto result = substitutions.getMetadata(depth, index).Ptr; LOG("substitutions.getMetadata(%u, %u).Ptr = %p", depth, index, result); return result; }, [&substitutions, this](const Metadata *type, unsigned index) { auto result = substitutions.getWitnessTable(type, index); LOG("substitutions.getWitnessTable(%p, %u) = %p", type, index, result); return result; }); if (result.isError()) { return *result.getError(); } return Buffer{result.getType().getMetadata()}; } /// Allocate a WritableData with the given size. template WritableData allocate(size_t size) { auto bytes = reinterpret_cast( MetadataAllocator(MetadataAllocatorTags::GenericValueMetadataTag) .Allocate(size, alignof(void *))); return WritableData{bytes, size}; } bool isLoggingEnabled() { return true; } SWIFT_FORMAT(5, 6) void log(const char *filename, unsigned line, const char *function, const char *fmt, ...) { va_list args; va_start(args, fmt); fprintf(stderr, "%s:%u:%s: ", filename, line, function); vfprintf(stderr, fmt, args); fputs("\n", stderr); va_end(args); } }; SWIFT_FORMAT(2, 3) static void validationLog(bool isValidationFailure, const char *fmt, ...) { if (!isValidationFailure) return; FILE *output = stderr; va_list args; va_start(args, fmt); fputs("GenericMetadataBuilder validation: ", output); vfprintf(output, fmt, args); fputs("\n", output); va_end(args); } SWIFT_FORMAT(1, 2) static void printToStderr(const char *fmt, ...) { va_list args; va_start(args, fmt); vfprintf(stderr, fmt, args); va_end(args); } static BuilderErrorOr dumpMetadata(const Metadata *metadata) { GenericMetadataBuilder::Dumper dumper(printToStderr); return dumper.dumpMetadata({metadata}); } template static const T &unwrapVWTField(const T &field) { return field; } static uint32_t unwrapVWTField(const ValueWitnessFlags &field) { return field.getOpaqueValue(); } static bool equalVWTs(const ValueWitnessTable *a, const ValueWitnessTable *b) { #define WANT_ONLY_REQUIRED_VALUE_WITNESSES #define FUNCTION_VALUE_WITNESS(LOWER_ID, UPPER_ID, RET, PARAMS) \ if (a->LOWER_ID != b->LOWER_ID) \ return false; #define VALUE_WITNESS(LOWER_ID, UPPER_ID) \ if (unwrapVWTField(a->LOWER_ID) != unwrapVWTField(b->LOWER_ID)) \ return false; #include "swift/ABI/ValueWitness.def" auto *enumA = dyn_cast(a); auto *enumB = dyn_cast(b); if (enumA == nullptr && enumB == nullptr) { return true; } if (enumA != nullptr && enumB != nullptr) { #define WANT_ONLY_ENUM_VALUE_WITNESSES #define VALUE_WITNESS(LOWER_ID, UPPER_ID) \ if (unwrapVWTField(enumA->LOWER_ID) != unwrapVWTField(enumB->LOWER_ID)) \ return false; #include "swift/ABI/ValueWitness.def" return true; } // Only one of a and b is an enum table. return false; } bool swift::compareGenericMetadata(const Metadata *original, const Metadata *newMetadata) { if (original == newMetadata) return true; bool equal = true; if (original->getKind() != newMetadata->getKind()) { validationLog(true, "Kinds do not match"); equal = false; } else { auto originalDescriptor = original->getTypeContextDescriptor(); auto newDescriptor = newMetadata->getTypeContextDescriptor(); if (originalDescriptor != newDescriptor) { validationLog(true, "Descriptors do not match"); equal = false; } else if (!originalDescriptor->isGeneric()) { validationLog(true, "Descriptor is not generic and pointers are not identical"); equal = false; } else { auto origVWT = asFullMetadata(original)->ValueWitnesses; auto newVWT = asFullMetadata(newMetadata)->ValueWitnesses; if (!equalVWTs(origVWT, newVWT)) { validationLog(true, "VWTs do not match"); equal = false; } size_t originalSize = 0; size_t newSize = 0; // Find the range of the generic arguments. They can't be compared with // bytewise equality. auto &genericContextHeader = originalDescriptor->getGenericContextHeader(); auto *genericArgumentsPtr = originalDescriptor->getGenericArguments(original); uintptr_t genericArgumentsStart = (uintptr_t)genericArgumentsPtr - (uintptr_t)original; uintptr_t genericArgumentsEnd = genericArgumentsStart + genericContextHeader.NumKeyArguments * sizeof(void *); if (original->getKind() == MetadataKind::Class) { originalSize = reinterpret_cast(original)->getSizeInWords(); newSize = reinterpret_cast(newMetadata) ->getSizeInWords(); } else { // Sizes are at least equal to genericArgumentsEnd. originalSize = newSize = genericArgumentsEnd; if (original->getKind() == MetadataKind::Struct) { // If they're structs, try the trailing flags or field offsets. auto getSize = [](const Metadata *metadata, size_t previous) { auto *structMetadata = reinterpret_cast(metadata); const void *end; if (auto *flags = structMetadata->getTrailingFlags()) end = &flags[1]; else if (auto *fieldOffsets = structMetadata->getFieldOffsets()) end = &fieldOffsets[structMetadata->getDescription()->NumFields]; else return previous; return (uintptr_t)end - (uintptr_t)metadata; }; originalSize = getSize(original, originalSize); newSize = getSize(newMetadata, newSize); } else if (original->getKind() == MetadataKind::Enum) { // If they're enums, try the trailing flags. auto getSize = [](const Metadata *metadata, size_t previous) { auto *enumMetadata = reinterpret_cast(metadata); if (auto *flags = enumMetadata->getTrailingFlags()) return (uintptr_t)&flags[1] - (uintptr_t)metadata; return previous; }; originalSize = getSize(original, originalSize); newSize = getSize(newMetadata, newSize); } } if (originalSize != newSize) { validationLog(true, "Sizes do not match"); equal = false; } for (unsigned i = 0; i < genericContextHeader.NumKeyArguments; i++) { auto *originalArg = originalDescriptor->getGenericArguments(original)[i]; auto *newArg = newDescriptor->getGenericArguments(newMetadata)[i]; if (compareGenericMetadata(originalArg, newArg)) continue; validationLog(true, "Generic argument %u does not match", i); equal = false; } if (genericArgumentsEnd < originalSize) { if (memcmp((const char *)original + genericArgumentsEnd, (const char *)newMetadata + genericArgumentsEnd, originalSize - genericArgumentsEnd)) { validationLog( true, "Metadatas do not match in the part after generic arguments"); } } } } if (!equal) { validationLog(true, "Error: original and new metadata do not match!"); validationLog(true, "Original metadata:"); if (auto *error = dumpMetadata(original).getError()) validationLog(true, "error dumping original metadata: %s", error->cStr()); validationLog(true, "New metadata builder:"); if (auto *error = dumpMetadata(newMetadata).getError()) validationLog(true, "error dumping new metadata: %s", error->cStr()); } return equal; }