//===--- MetadataReader.h - Abstract access to remote metadata --*- C++ -*-===// // // 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 file defines operations for reading metadata from a remote process. // //===----------------------------------------------------------------------===// #ifndef SWIFT_REMOTE_METADATAREADER_H #define SWIFT_REMOTE_METADATAREADER_H #include "swift/Runtime/Metadata.h" #include "swift/Remote/MemoryReader.h" #include "swift/Demangling/Demangler.h" #include "swift/Demangling/TypeDecoder.h" #include "swift/Basic/Range.h" #include "swift/Basic/LLVM.h" #include "swift/Runtime/Unreachable.h" #include #include namespace swift { namespace remote { template using FunctionParam = swift::Demangle::FunctionParam; template using TypeDecoder = swift::Demangle::TypeDecoder; /// A pointer to the local buffer of an object that also remembers the /// address at which it was stored remotely. template class RemoteRef { public: using StoredPointer = typename Runtime::StoredPointer; private: StoredPointer Address; const T *LocalBuffer; public: /*implicit*/ RemoteRef(std::nullptr_t _) : Address(0), LocalBuffer(nullptr) {} explicit RemoteRef(StoredPointer address, const T *localBuffer) : Address(address), LocalBuffer(localBuffer) {} StoredPointer getAddress() const { return Address; } const T *getLocalBuffer() const { return LocalBuffer; } explicit operator bool() const { return LocalBuffer != nullptr; } const T *operator->() const { assert(LocalBuffer); return LocalBuffer; } }; /// A structure, designed for use with std::unique_ptr, which destroys /// a pointer by calling free on it (and not trying to call a destructor). struct delete_with_free { void operator()(const void *memory) { free(const_cast(memory)); } }; /// A generic reader of metadata. /// /// BuilderType must implement a particular interface which is currently /// too fluid to allow useful documentation; consult the actual /// implementations. The chief thing is that it provides several member /// types which should obey the following constraints: /// - T() yields a value which is false when contextually converted to bool /// - a false value signals that an error occurred when building a value template class MetadataReader { public: using BuiltType = typename BuilderType::BuiltType; using BuiltNominalTypeDecl = typename BuilderType::BuiltNominalTypeDecl; using BuiltProtocolDecl = typename BuilderType::BuiltProtocolDecl; using StoredPointer = typename Runtime::StoredPointer; using StoredSize = typename Runtime::StoredSize; private: /// A cache of built types, keyed by the address of the type. std::unordered_map TypeCache; using MetadataRef = RemoteRef>; using OwnedMetadataRef = std::unique_ptr, delete_with_free>; /// A cache of read type metadata, keyed by the address of the metadata. std::unordered_map MetadataCache; using ContextDescriptorRef = RemoteRef>; using OwnedContextDescriptorRef = std::unique_ptr, delete_with_free>; /// A cache of read nominal type descriptors, keyed by the address of the /// nominal type descriptor. std::unordered_map ContextDescriptorCache; using OwnedProtocolDescriptorRef = std::unique_ptr, delete_with_free>; enum class IsaEncodingKind { /// We haven't checked yet. Unknown, /// There was an error trying to find out the isa encoding. Error, /// There's no special isa encoding. None, /// There's an unconditional mask to apply to the isa pointer. /// - IsaMask stores the mask. Masked, /// Isa pointers are indexed. If applying a mask yields a magic value, /// applying a different mask and shifting yields an index into a global /// array of class pointers. Otherwise, the isa pointer is just a raw /// class pointer. /// - IsaIndexMask stores the index mask. /// - IsaIndexShift stores the index shift. /// - IsaMagicMask stores the magic value mask. /// - IsaMagicValue stores the magic value. /// - IndexedClassesPointer stores the pointer to the start of the /// indexed classes array; this is constant throughout the program. /// - IndexedClassesCountPointer stores a pointer to the number /// of elements in the indexed classes array. Indexed }; IsaEncodingKind IsaEncoding = IsaEncodingKind::Unknown; union { StoredPointer IsaMask; StoredPointer IsaIndexMask; }; StoredPointer IsaIndexShift; StoredPointer IsaMagicMask; StoredPointer IsaMagicValue; StoredPointer IndexedClassesPointer; StoredPointer IndexedClassesCountPointer; StoredPointer LastIndexedClassesCount = 0; Demangle::NodeFactory Factory; Demangle::NodeFactory &getNodeFactory() { return Factory; } public: BuilderType Builder; BuilderType &getBuilder() { return this->Builder; } std::shared_ptr Reader; template MetadataReader(std::shared_ptr reader, T &&... args) : Builder(std::forward(args)...), Reader(std::move(reader)) { } MetadataReader(const MetadataReader &other) = delete; MetadataReader &operator=(const MetadataReader &other) = delete; /// Clear all of the caches in this reader. void clear() { TypeCache.clear(); MetadataCache.clear(); ContextDescriptorCache.clear(); } /// Given a demangle tree, attempt to turn it into a type. BuiltType decodeMangledType(const Demangle::NodePointer &Node) { return swift::Demangle::decodeMangledType(Builder, Node); } /// Get the remote process's swift_isaMask. llvm::Optional readIsaMask() { auto encoding = getIsaEncoding(); if (encoding != IsaEncodingKind::Masked) { // Still return success if there's no isa encoding at all. if (encoding == IsaEncodingKind::None) return 0; else return llvm::None; } return IsaMask; } /// Given a remote pointer to metadata, attempt to discover its MetadataKind. llvm::Optional readKindFromMetadata(StoredPointer MetadataAddress) { auto meta = readMetadata(MetadataAddress); if (!meta) return llvm::None; return meta->getKind(); } /// Given a remote pointer to class metadata, attempt to read its superclass. StoredPointer readSuperClassFromClassMetadata(StoredPointer MetadataAddress) { auto meta = readMetadata(MetadataAddress); if (!meta || meta->getKind() != MetadataKind::Class) return StoredPointer(); auto classMeta = cast>(meta); return classMeta->SuperClass; } /// Given a remote pointer to class metadata, attempt to discover its class /// instance size and whether fields should use the resilient layout strategy. llvm::Optional readInstanceStartAndAlignmentFromClassMetadata(StoredPointer MetadataAddress) { auto meta = readMetadata(MetadataAddress); if (!meta || meta->getKind() != MetadataKind::Class) return llvm::None; // The following algorithm only works on the non-fragile Apple runtime. // Grab the RO-data pointer. This part is not ABI. StoredPointer roDataPtr = readObjCRODataPtr(MetadataAddress); if (!roDataPtr) return llvm::None; // Get the address of the InstanceStart field. auto address = roDataPtr + sizeof(uint32_t) * 1; unsigned start; if (!Reader->readInteger(RemoteAddress(address), &start)) return llvm::None; return start; } /// Given a remote pointer to metadata, attempt to turn it into a type. BuiltType readTypeFromMetadata(StoredPointer MetadataAddress, bool skipArtificialSubclasses = false) { auto Cached = TypeCache.find(MetadataAddress); if (Cached != TypeCache.end()) return Cached->second; // If we see garbage data in the process of building a BuiltType, and get // the same metadata address again, we will hit an infinite loop. // Insert a negative result into the cache now so that, if we recur with // the same address, we will return the negative result with the check // just above. TypeCache.insert({MetadataAddress, BuiltType()}); auto Meta = readMetadata(MetadataAddress); if (!Meta) return BuiltType(); switch (Meta->getKind()) { case MetadataKind::Class: if (!cast>(Meta)->isTypeMetadata()) return BuiltType(); return readNominalTypeFromMetadata(Meta, skipArtificialSubclasses); case MetadataKind::Struct: return readNominalTypeFromMetadata(Meta); case MetadataKind::Enum: case MetadataKind::Optional: return readNominalTypeFromMetadata(Meta); case MetadataKind::Tuple: { auto tupleMeta = cast>(Meta); std::vector elementTypes; elementTypes.reserve(tupleMeta->NumElements); for (unsigned i = 0, n = tupleMeta->NumElements; i != n; ++i) { auto &element = tupleMeta->getElement(i); if (auto elementType = readTypeFromMetadata(element.Type)) elementTypes.push_back(elementType); else return BuiltType(); } // Read the labels string. std::string labels; if (tupleMeta->Labels && !Reader->readString(RemoteAddress(tupleMeta->Labels), labels)) return BuiltType(); auto BuiltTuple = Builder.createTupleType(elementTypes, std::move(labels), /*variadic*/ false); TypeCache[MetadataAddress] = BuiltTuple; return BuiltTuple; } case MetadataKind::Function: { auto Function = cast>(Meta); std::vector> Parameters; for (unsigned i = 0, n = Function->getNumParameters(); i != n; ++i) { auto ParamTypeRef = readTypeFromMetadata(Function->getParameter(i)); if (!ParamTypeRef) return BuiltType(); FunctionParam Param; Param.setType(ParamTypeRef); Param.setFlags(Function->getParameterFlags(i)); Parameters.push_back(std::move(Param)); } auto Result = readTypeFromMetadata(Function->ResultType); if (!Result) return BuiltType(); auto flags = FunctionTypeFlags() .withConvention(Function->getConvention()) .withThrows(Function->throws()) .withParameterFlags(Function->hasParameterFlags()) .withEscaping(Function->isEscaping()); auto BuiltFunction = Builder.createFunctionType(Parameters, Result, flags); TypeCache[MetadataAddress] = BuiltFunction; return BuiltFunction; } case MetadataKind::Existential: { auto Exist = cast>(Meta); bool HasExplicitAnyObject = false; if (Exist->isClassBounded()) HasExplicitAnyObject = true; BuiltType SuperclassType = BuiltType(); if (Exist->Flags.hasSuperclassConstraint()) { // The superclass is stored after the list of protocols. SuperclassType = readTypeFromMetadata( Exist->Protocols[Exist->Protocols.NumProtocols]); if (!SuperclassType) return BuiltType(); HasExplicitAnyObject = true; } std::vector Protocols; for (size_t i = 0; i < Exist->Protocols.NumProtocols; ++i) { auto ProtocolAddress = Exist->Protocols[i]; auto ProtocolDescriptor = readProtocolDescriptor(ProtocolAddress); if (!ProtocolDescriptor) return BuiltType(); std::string MangledNameStr; if (!Reader->readString(RemoteAddress(ProtocolDescriptor->Name), MangledNameStr)) return BuiltType(); StringRef MangledName = Demangle::dropSwiftManglingPrefix(MangledNameStr); Demangle::Context DCtx; auto Demangled = DCtx.demangleTypeAsNode(MangledName); if (!Demangled) return BuiltType(); auto Protocol = Builder.createProtocolDecl(Demangled); if (!Protocol) return BuiltType(); Protocols.push_back(Protocol); } auto BuiltExist = Builder.createProtocolCompositionType( Protocols, SuperclassType, HasExplicitAnyObject); TypeCache[MetadataAddress] = BuiltExist; return BuiltExist; } case MetadataKind::Metatype: { auto Metatype = cast>(Meta); auto Instance = readTypeFromMetadata(Metatype->InstanceType); if (!Instance) return BuiltType(); auto BuiltMetatype = Builder.createMetatypeType(Instance); TypeCache[MetadataAddress] = BuiltMetatype; return BuiltMetatype; } case MetadataKind::ObjCClassWrapper: { auto objcWrapper = cast>(Meta); auto classAddress = objcWrapper->Class; std::string className; if (!readObjCClassName(classAddress, className)) return BuiltType(); auto BuiltObjCClass = Builder.createObjCClassType(std::move(className)); TypeCache[MetadataAddress] = BuiltObjCClass; return BuiltObjCClass; } case MetadataKind::ExistentialMetatype: { auto Exist = cast>(Meta); auto Instance = readTypeFromMetadata(Exist->InstanceType); if (!Instance) return BuiltType(); auto BuiltExist = Builder.createExistentialMetatypeType(Instance); TypeCache[MetadataAddress] = BuiltExist; return BuiltExist; } case MetadataKind::ForeignClass: { auto Foreign = cast>(Meta); StoredPointer namePtr = resolveRelativeField(Meta, asFullMetadata(Foreign)->Name); if (namePtr == 0) return BuiltType(); std::string name; if (!Reader->readString(RemoteAddress(namePtr), name)) return BuiltType(); auto BuiltForeign = Builder.createForeignClassType(std::move(name)); TypeCache[MetadataAddress] = BuiltForeign; return BuiltForeign; } case MetadataKind::HeapLocalVariable: case MetadataKind::HeapGenericLocalVariable: case MetadataKind::ErrorObject: // Treat these all as Builtin.NativeObject for type lowering purposes. return Builder.createBuiltinType("Bo"); case MetadataKind::Opaque: { auto BuiltOpaque = Builder.getOpaqueType(); TypeCache[MetadataAddress] = BuiltOpaque; return BuiltOpaque; } } swift_runtime_unreachable("Unhandled MetadataKind in switch"); } BuiltType readTypeFromMangledName(const char *MangledTypeName, size_t Length) { Demangle::Demangler Dem; Demangle::NodePointer Demangled = Dem.demangleSymbol(StringRef(MangledTypeName, Length)); return decodeMangledType(Demangled); } /// Read a context descriptor from the given address and build a mangling /// tree representing it. Demangle::NodePointer readDemanglingForContextDescriptor(StoredPointer contextAddress, Demangler &Dem) { auto context = readContextDescriptor(contextAddress); if (!context) return nullptr; return buildNominalTypeMangling(context, Dem); } /// Read the isa pointer of a class or closure context instance and apply /// the isa mask. llvm::Optional readMetadataFromInstance(StoredPointer objectAddress) { StoredPointer isa; if (!Reader->readInteger(RemoteAddress(objectAddress), &isa)) return llvm::None; switch (getIsaEncoding()) { case IsaEncodingKind::Unknown: case IsaEncodingKind::Error: return llvm::None; case IsaEncodingKind::None: return isa; case IsaEncodingKind::Masked: return isa & IsaMask; case IsaEncodingKind::Indexed: { // If applying the magic mask doesn't give us the magic value, // it's not an indexed isa. if ((isa & IsaMagicMask) != IsaMagicValue) return isa; // Extract the index. auto classIndex = (isa & IsaIndexMask) >> IsaIndexShift; // 0 is never a valid index. if (classIndex == 0) { return llvm::None; // If the index is out of range, it's an error; but check for an // update first. (This will also trigger the first time because // we initialize LastIndexedClassesCount to 0). } else if (classIndex >= LastIndexedClassesCount) { StoredPointer count; if (!Reader->readInteger(RemoteAddress(IndexedClassesCountPointer), &count)) { return llvm::None; } LastIndexedClassesCount = count; if (classIndex >= count) { return llvm::None; } } // Find the address of the appropriate array element. RemoteAddress eltPointer = RemoteAddress(IndexedClassesPointer + classIndex * sizeof(StoredPointer)); StoredPointer metadataPointer; if (!Reader->readInteger(eltPointer, &metadataPointer)) { return llvm::None; } return metadataPointer; } } swift_runtime_unreachable("Unhandled IsaEncodingKind in switch."); } /// Read the offset of the generic parameters of a class from the nominal /// type descriptor. If the class has a resilient superclass, we also /// have to read the superclass size and add that to the offset. /// /// The offset is in units of words, from the start of the class's /// metadata. llvm::Optional readGenericArgsOffset(MetadataRef metadata, ContextDescriptorRef descriptor) { switch (descriptor->getKind()) { case ContextDescriptorKind::Class: { auto type = cast>(descriptor); auto *classMetadata = dyn_cast>(metadata); if (!classMetadata) return llvm::None; if (!classMetadata->SuperClass) return type->getGenericArgumentOffset(nullptr, nullptr); auto superMetadata = readMetadata(classMetadata->SuperClass); if (!superMetadata) return llvm::None; auto superClassMetadata = dyn_cast>(superMetadata); if (!superClassMetadata) return llvm::None; auto result = type->getGenericArgumentOffset(classMetadata, superClassMetadata); return result; } case ContextDescriptorKind::Enum: { auto type = cast>(descriptor); return type->getGenericArgumentOffset(); } case ContextDescriptorKind::Struct: { auto type = cast>(descriptor); return type->getGenericArgumentOffset(); } default: return llvm::None; } } /// Read a single generic type argument from a bound generic type /// metadata. llvm::Optional readGenericArgFromMetadata(StoredPointer metadata, unsigned index) { auto Meta = readMetadata(metadata); if (!Meta) return llvm::None; auto descriptorAddress = readAddressOfNominalTypeDescriptor(Meta); if (!descriptorAddress) return llvm::None; // Read the nominal type descriptor. auto descriptor = readContextDescriptor(descriptorAddress); if (!descriptor) return llvm::None; auto generics = descriptor->getGenericContext(); if (!generics) return llvm::None; auto offsetToGenericArgs = readGenericArgsOffset(Meta, descriptor); if (!offsetToGenericArgs) return llvm::None; auto addressOfGenericArgAddress = (Meta.getAddress() + *offsetToGenericArgs * sizeof(StoredPointer) + index * sizeof(StoredPointer)); if (index >= generics->getGenericContextHeader().getNumArguments()) return llvm::None; StoredPointer genericArgAddress; if (!Reader->readInteger(RemoteAddress(addressOfGenericArgAddress), &genericArgAddress)) return llvm::None; return genericArgAddress; } /// Given the address of a nominal type descriptor, attempt to resolve /// its nominal type declaration. BuiltNominalTypeDecl readNominalTypeFromDescriptor(StoredPointer address) { auto descriptor = readContextDescriptor(address); if (!descriptor) return BuiltNominalTypeDecl(); return buildNominalTypeDecl(descriptor); } /// Try to read the offset of a tuple element from a tuple metadata. bool readTupleElementOffset(StoredPointer metadataAddress, unsigned eltIndex, StoredSize *offset) { // Read the metadata. auto metadata = readMetadata(metadataAddress); if (!metadata) return false; // Ensure that the metadata actually is tuple metadata. auto tupleMetadata = dyn_cast>(metadata); if (!tupleMetadata) return false; // Ensure that the element is in-bounds. if (eltIndex >= tupleMetadata->NumElements) return false; // Read the offset. const auto &element = tupleMetadata->getElement(eltIndex); *offset = element.Offset; return true; } /// Given a remote pointer to class metadata, attempt to read its superclass. llvm::Optional readOffsetToFirstCaptureFromMetadata(StoredPointer MetadataAddress) { auto meta = readMetadata(MetadataAddress); if (!meta || meta->getKind() != MetadataKind::HeapLocalVariable) return llvm::None; auto heapMeta = cast>(meta); return heapMeta->OffsetToFirstCapture; } /// Given a remote pointer to class metadata, attempt to read its superclass. llvm::Optional readCaptureDescriptorFromMetadata(StoredPointer MetadataAddress) { auto meta = readMetadata(MetadataAddress); if (!meta || meta->getKind() != MetadataKind::HeapLocalVariable) return llvm::None; auto heapMeta = cast>(meta); return heapMeta->CaptureDescription; } protected: template StoredPointer resolveRelativeOffset(StoredPointer targetAddress) { Offset relative; if (!Reader->readInteger(RemoteAddress(targetAddress), &relative)) return 0; using SignedOffset = typename std::make_signed::type; using SignedPointer = typename std::make_signed::type; auto signext = (SignedPointer)(SignedOffset)relative; return targetAddress + signext; } template llvm::Optional resolveNullableRelativeOffset(StoredPointer targetAddress) { Offset relative; if (!Reader->readInteger(RemoteAddress(targetAddress), &relative)) return llvm::None; if (relative == 0) return 0; using SignedOffset = typename std::make_signed::type; using SignedPointer = typename std::make_signed::type; auto signext = (SignedPointer)(SignedOffset)relative; return targetAddress + signext; } template llvm::Optional resolveNullableRelativeIndirectableOffset(StoredPointer targetAddress) { Offset relative; if (!Reader->readInteger(RemoteAddress(targetAddress), &relative)) return llvm::None; if (relative == 0) return 0; bool indirect = relative & 1; relative &= ~1u; using SignedOffset = typename std::make_signed::type; using SignedPointer = typename std::make_signed::type; auto signext = (SignedPointer)(SignedOffset)relative; StoredPointer resultAddress = targetAddress + signext; // Low bit set in the offset indicates that the offset leads to the absolute // address in memory. if (indirect) { if (!Reader->readBytes(RemoteAddress(resultAddress), (uint8_t *)&resultAddress, sizeof(StoredPointer))) return llvm::None; } return resultAddress; } template StoredPointer resolveRelativeField( RemoteRef base, const Field &field) { // Map the offset from within our local buffer to the remote address. auto distance = (intptr_t)&field - (intptr_t)base.getLocalBuffer(); return resolveRelativeOffset(base.getAddress() + distance); } template llvm::Optional resolveNullableRelativeField( RemoteRef base, const Field &field) { // Map the offset from within our local buffer to the remote address. auto distance = (intptr_t)&field - (intptr_t)base.getLocalBuffer(); return resolveNullableRelativeOffset(base.getAddress() + distance); } template llvm::Optional resolveNullableRelativeIndirectableField( RemoteRef base, const Field &field) { // Map the offset from within our local buffer to the remote address. auto distance = (intptr_t)&field - (intptr_t)base.getLocalBuffer(); return resolveNullableRelativeIndirectableOffset( base.getAddress() + distance); } /// Given a pointer to an Objective-C class, try to read its class name. bool readObjCClassName(StoredPointer classAddress, std::string &className) { // The following algorithm only works on the non-fragile Apple runtime. // Grab the RO-data pointer. This part is not ABI. StoredPointer roDataPtr = readObjCRODataPtr(classAddress); if (!roDataPtr) return false; // This is ABI. static constexpr auto OffsetToName = roundUpToAlignment(size_t(12), sizeof(StoredPointer)) + sizeof(StoredPointer); // Read the name pointer. StoredPointer namePtr; if (!Reader->readInteger(RemoteAddress(roDataPtr + OffsetToName), &namePtr)) return false; // If the name pointer is null, treat that as an error. if (!namePtr) return false; return Reader->readString(RemoteAddress(namePtr), className); } MetadataRef readMetadata(StoredPointer address) { auto cached = MetadataCache.find(address); if (cached != MetadataCache.end()) return MetadataRef(address, cached->second.get()); StoredPointer KindValue = 0; if (!Reader->readInteger(RemoteAddress(address), &KindValue)) return nullptr; switch (getEnumeratedMetadataKind(KindValue)) { case MetadataKind::Class: return _readMetadata(address); case MetadataKind::Enum: return _readMetadata(address); case MetadataKind::ErrorObject: return _readMetadata(address); case MetadataKind::Existential: { StoredPointer flagsAddress = address + sizeof(StoredPointer); StoredPointer flags; if (!Reader->readInteger(RemoteAddress(flagsAddress), &flags)) return nullptr; StoredPointer numProtocolsAddress = address + TargetExistentialTypeMetadata::OffsetToNumProtocols; StoredPointer numProtocols; if (!Reader->readInteger(RemoteAddress(numProtocolsAddress), &numProtocols)) return nullptr; // Make sure the number of protocols is reasonable if (numProtocols >= 256) return nullptr; auto totalSize = sizeof(TargetExistentialTypeMetadata) + numProtocols * sizeof(ConstTargetMetadataPointer); if (ExistentialTypeFlags(flags).hasSuperclassConstraint()) totalSize += sizeof(StoredPointer); return _readMetadata(address, totalSize); } case MetadataKind::ExistentialMetatype: return _readMetadata(address); case MetadataKind::ForeignClass: return _readMetadata(address); case MetadataKind::Function: { StoredSize flagsValue; auto flagsAddr = address + TargetFunctionTypeMetadata::OffsetToFlags; if (!Reader->readInteger(RemoteAddress(flagsAddr), &flagsValue)) return nullptr; auto flags = TargetFunctionTypeFlags::fromIntValue(flagsValue); auto totalSize = sizeof(TargetFunctionTypeMetadata) + flags.getNumParameters() * sizeof(FunctionTypeMetadata::Parameter); if (flags.hasParameterFlags()) totalSize += flags.getNumParameters() * sizeof(uint32_t); return _readMetadata(address, roundUpToAlignment(totalSize, sizeof(void *))); } case MetadataKind::HeapGenericLocalVariable: return _readMetadata(address); case MetadataKind::HeapLocalVariable: return _readMetadata(address); case MetadataKind::Metatype: return _readMetadata(address); case MetadataKind::ObjCClassWrapper: return _readMetadata(address); case MetadataKind::Opaque: return _readMetadata(address); case MetadataKind::Optional: return _readMetadata(address); case MetadataKind::Struct: return _readMetadata(address); case MetadataKind::Tuple: { auto numElementsAddress = address + TargetTupleTypeMetadata::OffsetToNumElements; StoredSize numElements; if (!Reader->readInteger(RemoteAddress(numElementsAddress), &numElements)) return nullptr; auto totalSize = sizeof(TargetTupleTypeMetadata) + numElements * sizeof(TupleTypeMetadata::Element); // Make sure the number of elements is reasonable if (numElements >= 256) return nullptr; return _readMetadata(address, totalSize); } } // We can fall out here if the value wasn't actually a valid // MetadataKind. return nullptr; } private: template