Loading...
common/ObjCVisitor.cpp /dev/null dyld-1335
--- /dev/null
+++ dyld/dyld-1335/common/ObjCVisitor.cpp
@@ -0,0 +1,2107 @@
+/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil -*-
+ *
+ * Copyright (c) 2014 Apple Inc. All rights reserved.
+ *
+ * @APPLE_LICENSE_HEADER_START@
+ *
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this
+ * file.
+ *
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ *
+ * @APPLE_LICENSE_HEADER_END@
+ */
+
+#include <TargetConditionals.h>
+
+#if !TARGET_OS_EXCLAVEKIT
+
+#include "ObjCVisitor.h"
+
+#if SUPPORT_VM_LAYOUT
+#include "MachOAnalyzer.h"
+#endif
+
+#if BUILDING_CACHE_BUILDER || BUILDING_CACHE_BUILDER_UNIT_TESTS
+#include "ASLRTracker.h"
+#include <unordered_set>
+#endif
+
+using namespace objc_visitor;
+using ResolvedValue = metadata_visitor::ResolvedValue;
+using mach_o::Header;
+
+#if !SUPPORT_VM_LAYOUT
+using metadata_visitor::Segment;
+#endif
+
+//
+// MARK: --- Class methods ---
+//
+
+const void* Class::getFieldPos(const Visitor& objcVisitor, Field field) const
+{
+    if ( objcVisitor.pointerSize == 4 ) {
+        const class32_t* class32 = (const class32_t*)this->classPos.value();
+        switch ( field ) {
+            case Field::isa:
+                return &class32->isaVMAddr;
+            case Field::superclass:
+                return &class32->superclassVMAddr;
+            case Field::methodCacheBuckets:
+                return &class32->methodCacheBuckets;
+            case Field::methodCacheProperties:
+                return &class32->methodCacheProperties;
+            case Field::data:
+                return &class32->dataVMAddrAndFastFlags;
+            case Field::swiftClassFlags:
+                return &class32->swiftClassFlags;
+        }
+    } else {
+        const class64_t* class64 = (const class64_t*)this->classPos.value();
+        switch ( field ) {
+            case Field::isa:
+                return &class64->isaVMAddr;
+            case Field::superclass:
+                return &class64->superclassVMAddr;
+            case Field::methodCacheBuckets:
+                return &class64->methodCacheBuckets;
+            case Field::methodCacheProperties:
+                return &class64->methodCacheProperties;
+            case Field::data:
+                return &class64->dataVMAddrAndFastFlags;
+            case Field::swiftClassFlags:
+                return &class64->swiftClassFlags;
+        }
+    }
+}
+
+ResolvedValue Class::getISA(const Visitor& objcVisitor, bool& isPatchableClass) const
+{
+    ResolvedValue field = objcVisitor.getField(this->classPos, this->getFieldPos(objcVisitor, Field::isa));
+    return objcVisitor.resolveBindOrRebase(field, isPatchableClass);
+}
+
+#if BUILDING_CACHE_BUILDER || BUILDING_CACHE_BUILDER_UNIT_TESTS || BUILDING_SHARED_CACHE_UTIL
+std::optional<ResolvedValue> Class::getSuperclass(const Visitor& objcVisitor) const
+{
+    ResolvedValue field = objcVisitor.getField(this->classPos, this->getFieldPos(objcVisitor, Field::superclass));
+    return objcVisitor.resolveOptionalRebase(field);
+}
+
+std::optional<VMAddress> Class::getSuperclassVMAddr(const Visitor& objcVisitor) const
+{
+    ResolvedValue field = objcVisitor.getField(this->classPos, this->getFieldPos(objcVisitor, Field::superclass));
+    std::optional<VMAddress> targetAddress = objcVisitor.resolveOptionalRebaseToVMAddress(field);
+
+    if ( !targetAddress.has_value() )
+        return std::nullopt;
+
+    return targetAddress;
+}
+#endif
+
+ResolvedValue Class::getSuperclassField(const Visitor& objcVisitor) const
+{
+    return objcVisitor.getField(this->classPos, this->getFieldPos(objcVisitor, Field::superclass));
+}
+
+ResolvedValue Class::getMethodCache(const Visitor& objcVisitor) const
+{
+    ResolvedValue field = objcVisitor.getField(this->classPos, this->getFieldPos(objcVisitor, Field::methodCacheBuckets));
+
+    bool unusedIsPatchableClass;
+    return objcVisitor.resolveBindOrRebase(field, unusedIsPatchableClass);
+}
+
+std::optional<ResolvedValue> Class::getMethodCacheProperties(const Visitor& objcVisitor) const
+{
+    ResolvedValue field = objcVisitor.getField(this->classPos, this->getFieldPos(objcVisitor, Field::methodCacheProperties));
+    return objcVisitor.resolveOptionalRebase(field);
+}
+
+ResolvedValue Class::getMethodCachePropertiesField(const Visitor& objcVisitor) const
+{
+    return objcVisitor.getField(this->classPos, this->getFieldPos(objcVisitor, Field::methodCacheProperties));
+}
+
+std::optional<VMAddress> Class::getMethodCachePropertiesVMAddr(const Visitor& objcVisitor) const
+{
+    ResolvedValue field = objcVisitor.getField(this->classPos, this->getFieldPos(objcVisitor, Field::methodCacheProperties));
+
+    // The field might be null.  Get the target value, then see if it has a value
+    std::optional<ResolvedValue> targetValue = objcVisitor.resolveOptionalRebase(field);
+    if ( targetValue.has_value() )
+        return targetValue->vmAddress();
+
+    return { };
+}
+
+#if BUILDING_CACHE_BUILDER || BUILDING_CACHE_BUILDER_UNIT_TESTS
+void Class::setMethodCachePropertiesVMAddr(const Visitor& objcVisitor, VMAddress vmAddr,
+                                           const dyld3::MachOFile::PointerMetaData& PMD)
+{
+    ResolvedValue field = objcVisitor.getField(this->classPos, this->getFieldPos(objcVisitor, Field::methodCacheProperties));
+    objcVisitor.setTargetVMAddress(field, CacheVMAddress(vmAddr.rawValue()), PMD);
+}
+#endif
+
+#if BUILDING_CACHE_BUILDER || BUILDING_CACHE_BUILDER_UNIT_TESTS || BUILDING_SHARED_CACHE_UTIL
+void Class::withSuperclass(const Visitor& objcVisitor,
+                           void (^handler)(const dyld3::MachOFile::ChainedFixupPointerOnDisk* fixup, uint16_t pointerFormat)) const
+{
+    // HACK: The visitor classes need to be refactored to handle cache util. For now just force the caller of this method in
+    // cache util to have the chain format
+#if BUILDING_SHARED_CACHE_UTIL
+    uint16_t chainedPointerFormat = 0;
+#else
+    uint16_t chainedPointerFormat = this->classPos.chainedPointerFormat().value();
+#endif
+
+    const void* fieldPos = this->getFieldPos(objcVisitor, Field::superclass);
+    dyld3::MachOFile::ChainedFixupPointerOnDisk* fieldFixup = (dyld3::MachOFile::ChainedFixupPointerOnDisk*)fieldPos;
+    handler(fieldFixup, chainedPointerFormat);
+}
+#endif
+
+bool Class::isUnfixedBackwardDeployingStableSwift(const Visitor& objcVisitor) const
+{
+    // Only classes marked as Swift legacy need apply.
+    if ( !this->isSwiftLegacy(objcVisitor) )
+        return false;
+
+    std::optional<uint32_t> swiftClassFlags = this->swiftClassFlags(objcVisitor);
+    if ( swiftClassFlags.has_value() ) {
+        // Check the true legacy vs stable distinguisher.
+        // The low bit of Swift's ClassFlags is SET for true legacy
+        // and UNSET for stable pretending to be legacy.
+        bool isActuallySwiftLegacy = (swiftClassFlags.value() & isSwiftPreStableABI) != 0;
+        return !isActuallySwiftLegacy;
+    } else {
+        // Is this possible?  We were a legacy class but had no flags?
+        return false;
+    }
+}
+
+bool Class::isSwiftLegacy(const Visitor& objcVisitor) const
+{
+    ResolvedValue field = objcVisitor.getField(this->classPos, this->getFieldPos(objcVisitor, Field::data));
+    ResolvedValue fieldValue = objcVisitor.resolveRebase(field);
+
+    // Mask out the flags from the data value
+    uint64_t rawDataVMAddr = fieldValue.vmAddress().rawValue();
+    return (rawDataVMAddr & FAST_IS_SWIFT_LEGACY) != 0;
+}
+
+bool Class::isSwiftStable(const Visitor& objcVisitor) const
+{
+    ResolvedValue field = objcVisitor.getField(this->classPos, this->getFieldPos(objcVisitor, Field::data));
+    ResolvedValue fieldValue = objcVisitor.resolveRebase(field);
+
+    // Mask out the flags from the data value
+    uint64_t rawDataVMAddr = fieldValue.vmAddress().rawValue();
+    return (rawDataVMAddr & FAST_IS_SWIFT_STABLE) != 0;
+}
+
+bool Class::isSwift(const Visitor& objcVisitor) const
+{
+    return this->isSwiftStable(objcVisitor) || this->isSwiftLegacy(objcVisitor);
+}
+
+std::optional<uint32_t> Class::swiftClassFlags(const Visitor& objcVisitor) const
+{
+    if ( !this->isSwift(objcVisitor) )
+        return { };
+
+    return *(uint32_t*)this->getFieldPos(objcVisitor, Field::swiftClassFlags);
+}
+
+ResolvedValue Class::getDataField(const Visitor& objcVisitor) const
+{
+    return objcVisitor.getField(this->classPos, this->getFieldPos(objcVisitor, Field::data));
+}
+
+ClassData Class::getClassData(const Visitor& objcVisitor) const
+{
+    ResolvedValue field = this->getDataField(objcVisitor);
+    ResolvedValue targetValue = objcVisitor.resolveRebase(field);
+    // Mask out the low bits, if they are set
+    VMAddress vmAddr = targetValue.vmAddress();
+
+    uint64_t mask = (objcVisitor.pointerSize == 4) ? (uint64_t)FAST_DATA_MASK32 : FAST_DATA_MASK64;
+    uint64_t rawVMAddr = vmAddr.rawValue();
+    uint64_t maskedVMAddr = rawVMAddr & mask;
+    if ( maskedVMAddr != rawVMAddr ) {
+        // Adjust the pointer as we have bits to remove
+        uint64_t adjust = rawVMAddr - maskedVMAddr;
+        const uint8_t* unadjustedValue = (const uint8_t*)targetValue.value();
+        const uint8_t* adjustedValue = unadjustedValue - adjust;
+        //return ClassData(ResolvedValue(targetValue, adjustedValue));
+
+        // We can't just construct a new ResolvedValue here with the adjusted value as we don't
+        // have the right constructors when building for dyld.  Instead we'll pretend we are
+        // just accessing a field of a struct, where the value we have is the struct and we are
+        // resolving to a value before it.  Eg, "struct foo ... return &foo - 2"
+        ResolvedValue adjustedField = objcVisitor.getField(targetValue, adjustedValue);
+        return ClassData(adjustedField);
+    }
+    return ClassData(targetValue);
+}
+
+VMAddress Class::getClassDataVMAddr(const Visitor& objcVisitor) const
+{
+    return getClassData(objcVisitor).getVMAddress();
+}
+
+bool Class::isRootClass(const Visitor& objcVisitor) const
+{
+    ClassData data = getClassData(objcVisitor);
+    uint32_t flags = *(uint32_t*)data.getFieldPos(objcVisitor, ClassData::Field::flags);
+
+    const uint32_t RO_ROOT = (1 << 1);
+    return (flags & RO_ROOT) != 0;
+}
+
+const char* Class::getName(const Visitor& objcVisitor) const
+{
+    ClassData classData = getClassData(objcVisitor);
+    ResolvedValue field = classData.getField(objcVisitor, ClassData::Field::name);
+    return (const char*)objcVisitor.resolveRebase(field).value();
+}
+
+VMAddress Class::getNameVMAddr(const Visitor& objcVisitor) const
+{
+    ClassData classData = getClassData(objcVisitor);
+    ResolvedValue field = classData.getField(objcVisitor, ClassData::Field::name);
+    return objcVisitor.resolveRebase(field).vmAddress();
+}
+
+MethodList Class::getBaseMethods(const Visitor& objcVisitor) const
+{
+    ClassData classData = getClassData(objcVisitor);
+    ResolvedValue field = classData.getField(objcVisitor, ClassData::Field::baseMethods);
+    return objcVisitor.resolveOptionalRebase(field);
+}
+
+#if BUILDING_CACHE_BUILDER || BUILDING_CACHE_BUILDER_UNIT_TESTS
+ResolvedValue Class::setBaseMethodsVMAddr(const Visitor& objcVisitor, VMAddress vmAddr,
+                                 const dyld3::MachOFile::PointerMetaData& PMD)
+{
+    ClassData classData = getClassData(objcVisitor);
+    ResolvedValue field = classData.getField(objcVisitor, ClassData::Field::baseMethods);;
+    objcVisitor.setTargetVMAddress(field, CacheVMAddress(vmAddr.rawValue()), PMD);
+    return field;
+}
+#endif
+
+ProtocolList Class::getBaseProtocols(const Visitor& objcVisitor) const
+{
+    ClassData classData = getClassData(objcVisitor);
+    ResolvedValue field = classData.getField(objcVisitor, ClassData::Field::baseProtocols);
+    return objcVisitor.resolveOptionalRebase(field);
+}
+
+#if BUILDING_CACHE_BUILDER || BUILDING_CACHE_BUILDER_UNIT_TESTS
+ResolvedValue Class::setBaseProtocolsVMAddr(const Visitor& objcVisitor, VMAddress vmAddr)
+{
+    ClassData classData = getClassData(objcVisitor);
+    ResolvedValue field = classData.getField(objcVisitor, ClassData::Field::baseProtocols);
+    objcVisitor.updateTargetVMAddress(field, CacheVMAddress(vmAddr.rawValue()));
+    return field;
+}
+#endif
+
+IVarList Class::getIVars(const Visitor& objcVisitor) const
+{
+    ClassData classData = getClassData(objcVisitor);
+    ResolvedValue field = classData.getField(objcVisitor, ClassData::Field::ivars);
+    return objcVisitor.resolveOptionalRebase(field);
+}
+
+PropertyList Class::getBaseProperties(const Visitor& objcVisitor) const
+{
+    ClassData classData = getClassData(objcVisitor);
+    ResolvedValue field = classData.getField(objcVisitor, ClassData::Field::baseProperties);
+    return objcVisitor.resolveOptionalRebase(field);
+}
+
+#if BUILDING_CACHE_BUILDER || BUILDING_CACHE_BUILDER_UNIT_TESTS
+ResolvedValue Class::setBasePropertiesVMAddr(const Visitor& objcVisitor, VMAddress vmAddr)
+{
+    ClassData classData = getClassData(objcVisitor);
+    ResolvedValue field = classData.getField(objcVisitor, ClassData::Field::baseProperties);
+    objcVisitor.updateTargetVMAddress(field, CacheVMAddress(vmAddr.rawValue()));
+    return field;
+}
+#endif
+
+uint32_t Class::getInstanceStart(const Visitor& objcVisitor) const
+{
+    ClassData data = getClassData(objcVisitor);
+    return *(uint32_t*)data.getFieldPos(objcVisitor, ClassData::Field::instanceStart);
+}
+
+void Class::setInstanceStart(const Visitor& objcVisitor, uint32_t value) const
+{
+    ClassData data = getClassData(objcVisitor);
+    *(uint32_t*)data.getFieldPos(objcVisitor, ClassData::Field::instanceStart) = value;
+}
+
+uint32_t Class::getInstanceSize(const Visitor& objcVisitor) const
+{
+    ClassData data = getClassData(objcVisitor);
+    return *(uint32_t*)data.getFieldPos(objcVisitor, ClassData::Field::instanceSize);
+}
+
+void Class::setInstanceSize(const Visitor& objcVisitor, uint32_t value) const
+{
+    ClassData data = getClassData(objcVisitor);
+    *(uint32_t*)data.getFieldPos(objcVisitor, ClassData::Field::instanceSize) = value;
+}
+
+const void* Class::getLocation() const
+{
+    return this->classPos.value();
+}
+
+VMAddress Class::getVMAddress() const
+{
+    return this->classPos.vmAddress();
+}
+
+//
+// MARK: --- ClassData methods ---
+//
+
+const void* ClassData::getFieldPos(const Visitor& objcVisitor, Field field) const
+{
+    if ( objcVisitor.pointerSize == 4 ) {
+        const data32_t* data32 = (const data32_t*)this->classDataPos.value();
+        switch ( field ) {
+            case Field::flags:
+                return &data32->flags;
+            case Field::instanceStart:
+                return &data32->instanceStart;
+            case Field::instanceSize:
+                return &data32->instanceSize;
+            case Field::ivarLayout:
+                return &data32->ivarLayoutVMAddr;
+            case Field::name:
+                return &data32->nameVMAddr;
+            case Field::baseMethods:
+                return &data32->baseMethodsVMAddr;
+            case Field::baseProtocols:
+                return &data32->baseProtocolsVMAddr;
+            case Field::ivars:
+                return &data32->ivarsVMAddr;
+            case Field::weakIvarLayout:
+                return &data32->weakIvarLayoutVMAddr;
+            case Field::baseProperties:
+                return &data32->basePropertiesVMAddr;
+        }
+    } else {
+        const data64_t* data64 = (const data64_t*)this->classDataPos.value();
+        switch ( field ) {
+            case Field::flags:
+                return &data64->flags;
+            case Field::instanceStart:
+                return &data64->instanceStart;
+            case Field::instanceSize:
+                return &data64->instanceSize;
+            case Field::ivarLayout:
+                return &data64->ivarLayoutVMAddr;
+            case Field::name:
+                return &data64->nameVMAddr;
+            case Field::baseMethods:
+                return &data64->baseMethodsVMAddr;
+            case Field::baseProtocols:
+                return &data64->baseProtocolsVMAddr;
+            case Field::ivars:
+                return &data64->ivarsVMAddr;
+            case Field::weakIvarLayout:
+                return &data64->weakIvarLayoutVMAddr;
+            case Field::baseProperties:
+                return &data64->basePropertiesVMAddr;
+        }
+    }
+}
+
+ResolvedValue ClassData::getField(const Visitor& objcVisitor, Field field) const
+{
+    return objcVisitor.getField(this->classDataPos, this->getFieldPos(objcVisitor, field));
+}
+
+const void* ClassData::getLocation() const
+{
+    return this->classDataPos.value();
+}
+
+VMAddress ClassData::getVMAddress() const
+{
+    return this->classDataPos.vmAddress();
+}
+
+//
+// MARK: --- Category methods ---
+//
+
+const void* Category::getFieldPos(const Visitor& objcVisitor, Field field) const
+{
+    if ( objcVisitor.pointerSize == 4 ) {
+        const category32_t* category32 = (const category32_t*)this->categoryPos.value();
+        switch ( field ) {
+            case Field::name:
+                return &category32->nameVMAddr;
+            case Field::cls:
+                return &category32->clsVMAddr;
+            case Field::instanceMethods:
+                return &category32->instanceMethodsVMAddr;
+            case Field::classMethods:
+                return &category32->classMethodsVMAddr;
+            case Field::protocols:
+                return &category32->protocolsVMAddr;
+            case Field::instanceProperties:
+                return &category32->instancePropertiesVMAddr;
+            case Field::classProperties:
+                return &category32->classPropertiesVMAddr;
+        }
+    } else {
+        const category64_t* category64 = (const category64_t*)this->categoryPos.value();
+        switch ( field ) {
+            case Field::name:
+                return &category64->nameVMAddr;
+            case Field::cls:
+                return &category64->clsVMAddr;
+            case Field::instanceMethods:
+                return &category64->instanceMethodsVMAddr;
+            case Field::classMethods:
+                return &category64->classMethodsVMAddr;
+            case Field::protocols:
+                return &category64->protocolsVMAddr;
+            case Field::instanceProperties:
+                return &category64->instancePropertiesVMAddr;
+            case Field::classProperties:
+                return &category64->classPropertiesVMAddr;
+        }
+    }
+}
+
+const char* Category::getName(const Visitor& objcVisitor) const
+{
+    ResolvedValue field = objcVisitor.getField(this->categoryPos, this->getFieldPos(objcVisitor, Field::name));
+    return (const char*)objcVisitor.resolveRebase(field).value();
+}
+
+VMAddress Category::getVMAddress() const
+{
+    return this->categoryPos.vmAddress();
+}
+
+const void* Category::getLocation() const
+{
+    return this->categoryPos.value();
+}
+
+VMAddress Category::getNameVMAddr(const Visitor& objcVisitor) const
+{
+    ResolvedValue field = objcVisitor.getField(this->categoryPos, this->getFieldPos(objcVisitor, Field::name));
+    return objcVisitor.resolveRebase(field).vmAddress();
+}
+
+MethodList Category::getInstanceMethods(const Visitor& objcVisitor) const
+{
+    ResolvedValue field = objcVisitor.getField(this->categoryPos, this->getFieldPos(objcVisitor, Field::instanceMethods));
+    return objcVisitor.resolveOptionalRebase(field);
+}
+
+MethodList Category::getClassMethods(const Visitor& objcVisitor) const
+{
+    ResolvedValue field = objcVisitor.getField(this->categoryPos, this->getFieldPos(objcVisitor, Field::classMethods));
+    return objcVisitor.resolveOptionalRebase(field);
+}
+
+ProtocolList Category::getProtocols(const Visitor& objcVisitor) const
+{
+    ResolvedValue field = objcVisitor.getField(this->categoryPos, this->getFieldPos(objcVisitor, Field::protocols));
+    return objcVisitor.resolveOptionalRebase(field);
+}
+
+PropertyList Category::getInstanceProperties(const Visitor& objcVisitor) const
+{
+    ResolvedValue field = objcVisitor.getField(this->categoryPos, this->getFieldPos(objcVisitor, Field::instanceProperties));
+    return objcVisitor.resolveOptionalRebase(field);
+}
+
+PropertyList Category::getClassProperties(const Visitor& objcVisitor) const
+{
+    ResolvedValue field = objcVisitor.getField(this->categoryPos, this->getFieldPos(objcVisitor, Field::classProperties));
+    return objcVisitor.resolveOptionalRebase(field);
+}
+
+#if BUILDING_CACHE_BUILDER || BUILDING_CACHE_BUILDER_UNIT_TESTS || BUILDING_SHARED_CACHE_UTIL
+void Category::withClass(const Visitor& objcVisitor,
+                         void (^handler)(const dyld3::MachOFile::ChainedFixupPointerOnDisk* fixup, uint16_t pointerFormat)) const
+{
+    // HACK: The visitor classes need to be refactored to handle cache util. For now just force the caller of this method in
+    // cache util to have the chain format
+#if BUILDING_SHARED_CACHE_UTIL
+    uint16_t chainedPointerFormat = 0;
+#else
+    assert(objcVisitor.isOnDiskBinary());
+    uint16_t chainedPointerFormat = this->categoryPos.chainedPointerFormat().value();
+#endif
+
+    const void* fieldPos = nullptr;
+    if ( objcVisitor.pointerSize == 8 ) {
+        fieldPos = &((const category64_t*)this->categoryPos.value())->clsVMAddr;
+    } else if (objcVisitor.pointerSize == 4 ){
+        fieldPos = &((const category32_t*)this->categoryPos.value())->clsVMAddr;
+    }
+    assert(fieldPos != nullptr);
+
+    dyld3::MachOFile::ChainedFixupPointerOnDisk* fieldFixup = (dyld3::MachOFile::ChainedFixupPointerOnDisk*)fieldPos;
+    handler(fieldFixup, chainedPointerFormat);
+}
+#endif
+
+uint32_t Category::getSize(bool is64)
+{
+    return is64 ? sizeof(category64_t) : sizeof(category32_t);
+}
+
+
+#if BUILDING_SHARED_CACHE_UTIL
+std::optional<VMAddress> Category::getClassVMAddr(const Visitor& objcVisitor) const
+{
+    ResolvedValue field = objcVisitor.getField(this->categoryPos, this->getFieldPos(objcVisitor, Field::cls));
+    std::optional<ResolvedValue> targetValue = objcVisitor.resolveOptionalRebase(field);
+    if ( targetValue )
+        return targetValue->vmAddress();
+
+    return { };
+}
+#endif
+
+//
+// MARK: --- Protocol methods ---
+//
+
+const void* Protocol::getFieldPos(const Visitor& objcVisitor, Field field) const
+{
+    if ( objcVisitor.pointerSize == 4 ) {
+        const protocol32_t* protocol32 = (const protocol32_t*)this->protocolPos.value();
+        switch ( field ) {
+            case Field::isa:
+                return &protocol32->isaVMAddr;
+            case Field::name:
+                return &protocol32->nameVMAddr;
+            case Field::protocols:
+                return &protocol32->protocolsVMAddr;
+            case Field::instanceMethods:
+                return &protocol32->instanceMethodsVMAddr;
+            case Field::classMethods:
+                return &protocol32->classMethodsVMAddr;
+            case Field::optionalInstanceMethods:
+                return &protocol32->optionalInstanceMethodsVMAddr;
+            case Field::optionalClassMethods:
+                return &protocol32->optionalClassMethodsVMAddr;
+            case Field::instanceProperties:
+                return &protocol32->instancePropertiesVMAddr;
+            case Field::size:
+                return &protocol32->size;
+            case Field::flags:
+                return &protocol32->flags;
+            case Field::extendedMethodTypes:
+                return &protocol32->extendedMethodTypesVMAddr;
+            case Field::demangledName:
+                return &protocol32->demangledNameVMAddr;
+            case Field::classProperties:
+                return &protocol32->classPropertiesVMAddr;
+        }
+    } else {
+        const protocol64_t* protocol64 = (const protocol64_t*)this->protocolPos.value();
+        switch ( field ) {
+            case Field::isa:
+                return &protocol64->isaVMAddr;
+            case Field::name:
+                return &protocol64->nameVMAddr;
+            case Field::protocols:
+                return &protocol64->protocolsVMAddr;
+            case Field::instanceMethods:
+                return &protocol64->instanceMethodsVMAddr;
+            case Field::classMethods:
+                return &protocol64->classMethodsVMAddr;
+            case Field::optionalInstanceMethods:
+                return &protocol64->optionalInstanceMethodsVMAddr;
+            case Field::optionalClassMethods:
+                return &protocol64->optionalClassMethodsVMAddr;
+            case Field::instanceProperties:
+                return &protocol64->instancePropertiesVMAddr;
+            case Field::size:
+                return &protocol64->size;
+            case Field::flags:
+                return &protocol64->flags;
+            case Field::extendedMethodTypes:
+                return &protocol64->extendedMethodTypesVMAddr;
+            case Field::demangledName:
+                return &protocol64->demangledNameVMAddr;
+            case Field::classProperties:
+                return &protocol64->classPropertiesVMAddr;
+        }
+    }
+}
+
+std::optional<VMAddress> Protocol::getISAVMAddr(const Visitor& objcVisitor) const
+{
+    ResolvedValue field = objcVisitor.getField(this->protocolPos, this->getFieldPos(objcVisitor, Field::isa));
+    std::optional<ResolvedValue> value = objcVisitor.resolveOptionalRebase(field);
+    if ( value )
+        return value->vmAddress();
+
+    return { };
+}
+
+#if BUILDING_CACHE_BUILDER || BUILDING_CACHE_BUILDER_UNIT_TESTS
+void Protocol::setISA(const Visitor& objcVisitor, VMAddress vmAddr, const dyld3::MachOFile::PointerMetaData& PMD)
+{
+    ResolvedValue field = objcVisitor.getField(this->protocolPos, this->getFieldPos(objcVisitor, Field::isa));
+    objcVisitor.setTargetVMAddress(field, CacheVMAddress(vmAddr.rawValue()), PMD);
+}
+#endif
+
+const char* Protocol::getName(const Visitor& objcVisitor) const
+{
+    ResolvedValue field = objcVisitor.getField(this->protocolPos, this->getFieldPos(objcVisitor, Field::name));
+    return (const char*)objcVisitor.resolveRebase(field).value();
+}
+
+VMAddress Protocol::getNameVMAddr(const Visitor& objcVisitor) const
+{
+    ResolvedValue field = objcVisitor.getField(this->protocolPos, this->getFieldPos(objcVisitor, Field::name));
+    return objcVisitor.resolveRebase(field).vmAddress();
+}
+
+ProtocolList Protocol::getProtocols(const Visitor& objcVisitor) const
+{
+    ResolvedValue field = objcVisitor.getField(this->protocolPos, this->getFieldPos(objcVisitor, Field::protocols));
+    return objcVisitor.resolveOptionalRebase(field);
+}
+
+MethodList Protocol::getInstanceMethods(const Visitor& objcVisitor) const
+{
+    ResolvedValue field = objcVisitor.getField(this->protocolPos, this->getFieldPos(objcVisitor, Field::instanceMethods));
+    return objcVisitor.resolveOptionalRebase(field);
+}
+
+MethodList Protocol::getClassMethods(const Visitor& objcVisitor) const
+{
+    ResolvedValue field = objcVisitor.getField(this->protocolPos, this->getFieldPos(objcVisitor, Field::classMethods));
+    return objcVisitor.resolveOptionalRebase(field);
+}
+
+MethodList Protocol::getOptionalInstanceMethods(const Visitor& objcVisitor) const
+{
+    ResolvedValue field = objcVisitor.getField(this->protocolPos, this->getFieldPos(objcVisitor, Field::optionalInstanceMethods));
+    return objcVisitor.resolveOptionalRebase(field);
+}
+
+MethodList Protocol::getOptionalClassMethods(const Visitor& objcVisitor) const
+{
+    ResolvedValue field = objcVisitor.getField(this->protocolPos, this->getFieldPos(objcVisitor, Field::optionalClassMethods));
+    return objcVisitor.resolveOptionalRebase(field);
+}
+
+uint32_t Protocol::getSize(const Visitor& objcVisitor) const
+{
+    return *(uint32_t*)this->getFieldPos(objcVisitor, Field::size);
+}
+
+void Protocol::setSize(const Visitor& objcVisitor, uint32_t size)
+{
+    *(uint32_t*)this->getFieldPos(objcVisitor, Field::size) = size;
+}
+
+void Protocol::setFixedUp(const Visitor& objcVisitor)
+{
+    uint32_t& flags = *(uint32_t*)this->getFieldPos(objcVisitor, Field::flags);
+
+    assert((flags & (1<<30)) == 0);
+    flags = flags | (1<<30);
+}
+
+void Protocol::setIsCanonical(const Visitor& objcVisitor)
+{
+    uint32_t& flags = *(uint32_t*)this->getFieldPos(objcVisitor, Field::flags);
+
+    assert((flags & (1<<29)) == 0);
+    flags = flags | (1<<29);
+}
+
+std::optional<ResolvedValue> Protocol::getExtendedMethodTypes(const Visitor& objcVisitor) const
+{
+    // extendedMethodTypes is not always present on disk.
+    uint32_t structSize = this->getSize(objcVisitor);
+    if ( objcVisitor.pointerSize == 4 ) {
+        if ( structSize < (offsetof(protocol32_t, extendedMethodTypesVMAddr) + sizeof(protocol32_t::extendedMethodTypesVMAddr)) )
+            return std::nullopt;
+    } else {
+        if ( structSize < (offsetof(protocol64_t, extendedMethodTypesVMAddr) + sizeof(protocol64_t::extendedMethodTypesVMAddr)) )
+            return std::nullopt;
+    }
+
+    ResolvedValue field = objcVisitor.getField(this->protocolPos, this->getFieldPos(objcVisitor, Field::extendedMethodTypes));
+    return objcVisitor.resolveOptionalRebase(field);
+}
+
+std::optional<const char*> Protocol::getDemangledName(const Visitor& objcVisitor) const
+{
+    // demangledName is not always present on disk.
+    uint32_t structSize = this->getSize(objcVisitor);
+    if ( objcVisitor.pointerSize == 4 ) {
+        if ( structSize < (offsetof(protocol32_t, demangledNameVMAddr) + sizeof(protocol32_t::demangledNameVMAddr)) )
+            return std::nullopt;
+    } else {
+        if ( structSize < (offsetof(protocol64_t, demangledNameVMAddr) + sizeof(protocol64_t::demangledNameVMAddr)) )
+            return std::nullopt;
+    }
+
+    ResolvedValue field = objcVisitor.getField(this->protocolPos, this->getFieldPos(objcVisitor, Field::demangledName));
+    std::optional<ResolvedValue> value = objcVisitor.resolveOptionalRebase(field);
+    if ( value.has_value() )
+        return (const char*)value->value();
+
+    return std::nullopt;
+}
+
+#if BUILDING_CACHE_BUILDER || BUILDING_CACHE_BUILDER_UNIT_TESTS
+void Protocol::setDemangledName(const Visitor& objcVisitor, VMAddress vmAddr)
+{
+    uint32_t structSize = this->getSize(objcVisitor);
+    if ( objcVisitor.pointerSize == 4 ) {
+        assert(structSize >= (offsetof(protocol32_t, demangledNameVMAddr) + sizeof(protocol32_t::demangledNameVMAddr)));
+    } else {
+        assert(structSize >= (offsetof(protocol64_t, demangledNameVMAddr) + sizeof(protocol64_t::demangledNameVMAddr)));
+    }
+
+    ResolvedValue field = objcVisitor.getField(this->protocolPos, this->getFieldPos(objcVisitor, Field::demangledName));
+    objcVisitor.updateTargetVMAddress(field, CacheVMAddress(vmAddr.rawValue()));
+}
+#endif
+
+#if BUILDING_CACHE_BUILDER || BUILDING_CACHE_BUILDER_UNIT_TESTS
+void Protocol::addFixups(const Visitor& objcVisitor, std::vector<void*>& fixups) const
+{
+
+    if ( objcVisitor.pointerSize == 4 ) {
+        protocol32_t* protocol = (protocol32_t*)this->protocolPos.value();
+        if ( protocol->isaVMAddr != 0 )
+            fixups.push_back(&protocol->isaVMAddr);
+        if ( protocol->nameVMAddr != 0 )
+            fixups.push_back(&protocol->nameVMAddr);
+        if ( protocol->protocolsVMAddr != 0 )
+            fixups.push_back(&protocol->protocolsVMAddr);
+        if ( protocol->instanceMethodsVMAddr != 0 )
+            fixups.push_back(&protocol->instanceMethodsVMAddr);
+        if ( protocol->classMethodsVMAddr != 0 )
+            fixups.push_back(&protocol->classMethodsVMAddr);
+        if ( protocol->optionalInstanceMethodsVMAddr != 0 )
+            fixups.push_back(&protocol->optionalInstanceMethodsVMAddr);
+        if ( protocol->optionalClassMethodsVMAddr != 0 )
+            fixups.push_back(&protocol->optionalClassMethodsVMAddr);
+        if ( protocol->instancePropertiesVMAddr != 0 )
+            fixups.push_back(&protocol->instancePropertiesVMAddr);
+
+        uint32_t structSize = ((const protocol32_t*)this->protocolPos.value())->size;
+        if ( structSize >= (offsetof(protocol32_t, extendedMethodTypesVMAddr) + sizeof(protocol32_t::extendedMethodTypesVMAddr)) ) {
+            if ( protocol->extendedMethodTypesVMAddr != 0 )
+                fixups.push_back(&protocol->extendedMethodTypesVMAddr);
+        }
+        if ( structSize >= (offsetof(protocol32_t, demangledNameVMAddr) + sizeof(protocol32_t::demangledNameVMAddr)) ) {
+            if ( protocol->demangledNameVMAddr != 0 )
+                fixups.push_back(&protocol->demangledNameVMAddr);
+        }
+        if ( structSize >= (offsetof(protocol32_t, classPropertiesVMAddr) + sizeof(protocol32_t::classPropertiesVMAddr)) ) {
+            if ( protocol->classPropertiesVMAddr != 0 )
+                fixups.push_back(&protocol->classPropertiesVMAddr);
+        }
+    } else {
+        protocol64_t* protocol = (protocol64_t*)this->protocolPos.value();
+        if ( protocol->isaVMAddr != 0 )
+            fixups.push_back(&protocol->isaVMAddr);
+        if ( protocol->nameVMAddr != 0 )
+            fixups.push_back(&protocol->nameVMAddr);
+        if ( protocol->protocolsVMAddr != 0 )
+            fixups.push_back(&protocol->protocolsVMAddr);
+        if ( protocol->instanceMethodsVMAddr != 0 )
+            fixups.push_back(&protocol->instanceMethodsVMAddr);
+        if ( protocol->classMethodsVMAddr != 0 )
+            fixups.push_back(&protocol->classMethodsVMAddr);
+        if ( protocol->optionalInstanceMethodsVMAddr != 0 )
+            fixups.push_back(&protocol->optionalInstanceMethodsVMAddr);
+        if ( protocol->optionalClassMethodsVMAddr != 0 )
+            fixups.push_back(&protocol->optionalClassMethodsVMAddr);
+        if ( protocol->instancePropertiesVMAddr != 0 )
+            fixups.push_back(&protocol->instancePropertiesVMAddr);
+
+        uint32_t structSize = ((const protocol64_t*)this->protocolPos.value())->size;
+        if ( structSize >= (offsetof(protocol64_t, extendedMethodTypesVMAddr) + sizeof(protocol64_t::extendedMethodTypesVMAddr)) ) {
+            if ( protocol->extendedMethodTypesVMAddr != 0 )
+                fixups.push_back(&protocol->extendedMethodTypesVMAddr);
+        }
+        if ( structSize >= (offsetof(protocol64_t, demangledNameVMAddr) + sizeof(protocol64_t::demangledNameVMAddr)) ) {
+            if ( protocol->demangledNameVMAddr != 0 )
+                fixups.push_back(&protocol->demangledNameVMAddr);
+        }
+        if ( structSize >= (offsetof(protocol64_t, classPropertiesVMAddr) + sizeof(protocol64_t::classPropertiesVMAddr)) ) {
+            if ( protocol->classPropertiesVMAddr != 0 )
+                fixups.push_back(&protocol->classPropertiesVMAddr);
+        }
+    }
+}
+
+std::optional<uint16_t> Protocol::chainedPointerFormat() const
+{
+    return this->protocolPos.chainedPointerFormat();
+}
+#endif
+
+const void* Protocol::getLocation() const
+{
+    return this->protocolPos.value();
+}
+
+VMAddress Protocol::getVMAddress() const
+{
+    return this->protocolPos.vmAddress();
+}
+
+uint32_t Protocol::getSize(bool is64)
+{
+    return is64 ? sizeof(protocol64_t) : sizeof(protocol32_t);
+}
+
+//
+// MARK: --- MethodList methods ---
+//
+
+uint32_t MethodList::numMethods() const
+{
+    if ( !methodListPos.has_value() )
+        return 0;
+
+    const ResolvedValue& methodListValue = this->methodListPos.value();
+
+    const method_list_t* methodList = (const method_list_t*)methodListValue.value();
+    assert(methodList != nullptr);
+
+    return methodList->getMethodCount();
+}
+
+uint32_t MethodList::listSize() const
+{
+    if ( !methodListPos.has_value() )
+        return 0;
+
+    const ResolvedValue& methodListValue = this->methodListPos.value();
+
+    const method_list_t* methodList = (const method_list_t*)methodListValue.value();
+    assert(methodList != nullptr);
+
+    uint32_t size = sizeof(uint32_t) * 2;
+    size += methodList->getMethodCount() * methodList->getMethodSize();
+    return size;
+}
+
+uint32_t MethodList::methodSize() const
+{
+    if ( !methodListPos.has_value() )
+        return 0;
+
+    const ResolvedValue& methodListValue = this->methodListPos.value();
+
+    const method_list_t* methodList = (const method_list_t*)methodListValue.value();
+    assert(methodList != nullptr);
+
+    return methodList->getMethodSize();
+}
+
+bool MethodList::usesRelativeOffsets() const
+{
+    if ( !methodListPos.has_value() )
+        return false;
+
+    const ResolvedValue& methodListValue = this->methodListPos.value();
+
+    const method_list_t* methodList = (const method_list_t*)methodListValue.value();
+    assert(methodList != nullptr);
+
+    return methodList->usesRelativeOffsets();
+}
+
+bool MethodList::usesOffsetsFromSelectorBuffer() const
+{
+    if ( !methodListPos.has_value() )
+        return false;
+
+    const ResolvedValue& methodListValue = this->methodListPos.value();
+
+    const method_list_t* methodList = (const method_list_t*)methodListValue.value();
+    assert(methodList != nullptr);
+
+    return methodList->usesOffsetsFromSelectorBuffer();
+}
+
+void MethodList::setIsUniqued()
+{
+    if ( !methodListPos.has_value() )
+        return;
+
+    const ResolvedValue& methodListValue = this->methodListPos.value();
+
+    method_list_t* methodList = (method_list_t*)methodListValue.value();
+    assert(methodList != nullptr);
+
+    methodList->setIsUniqued();
+}
+
+void MethodList::setIsSorted()
+{
+    if ( !methodListPos.has_value() )
+        return;
+
+    const ResolvedValue& methodListValue = this->methodListPos.value();
+
+    method_list_t* methodList = (method_list_t*)methodListValue.value();
+    assert(methodList != nullptr);
+
+    methodList->setIsSorted();
+}
+
+size_t MethodList::makeEmptyMethodList(void* buffer)
+{
+    assert(buffer != nullptr);
+    method_list_t* methodList = (method_list_t*)buffer;
+    bzero(methodList, sizeof(method_list_t));
+
+    methodList->setIsUniqued();
+    methodList->setIsSorted();
+
+    return sizeof(method_list_t);
+}
+
+void MethodList::setUsesOffsetsFromSelectorBuffer()
+{
+    if ( !methodListPos.has_value() )
+        return;
+
+    const ResolvedValue& methodListValue = this->methodListPos.value();
+
+    method_list_t* methodList = (method_list_t*)methodListValue.value();
+    assert(methodList != nullptr);
+
+    methodList->setUsesOffsetsFromSelectorBuffer();
+}
+
+bool MethodList::isListOfLists() const
+{
+    if ( !methodListPos.has_value() )
+        return false;
+
+    const ResolvedValue& methodListValue = this->methodListPos.value();
+    return methodListValue.vmAddress().rawValue() & 1;
+}
+
+const void* MethodList::getLocation() const
+{
+    if ( !this->methodListPos.has_value() )
+        return nullptr;
+    return this->methodListPos->value();
+}
+
+std::optional<VMAddress> MethodList::getVMAddress() const
+{
+    if ( !this->methodListPos.has_value() )
+        return { };
+    return this->methodListPos->vmAddress();
+}
+
+static Method::Kind getKind(const MethodList::method_list_t* methodList)
+{
+    typedef Method::Kind Kind;
+    if ( methodList->usesRelativeOffsets() )
+        return methodList->usesOffsetsFromSelectorBuffer() ? Kind::relativeDirect : Kind::relativeIndirect;
+    else
+        return Kind::pointer;
+}
+
+Method MethodList::getMethod(const Visitor& objcVisitor, uint32_t i) const
+{
+    assert(methodListPos.has_value());
+
+    const ResolvedValue& methodListValue = this->methodListPos.value();
+
+    const method_list_t* methodList = (const method_list_t*)methodListValue.value();
+    assert(methodList != nullptr);
+
+    const uint8_t* methodListBase = methodList->methodBase();
+    const uint8_t* method = methodListBase + (i * methodList->getMethodSize());
+
+    ResolvedValue methodValue = objcVisitor.getField(methodListValue, method);
+
+    return Method(getKind(methodList), methodValue);
+}
+
+//
+// MARK: --- Method methods ---
+//
+
+const void* Method::getFieldPos(const Visitor& objcVisitor, Field field) const
+{
+    if ( objcVisitor.pointerSize == 4 ) {
+        const method32_t* method32 = (const method32_t*)this->methodPos.value();
+        switch ( field ) {
+            case Field::name:
+                return &method32->nameVMAddr;
+            case Field::types:
+                return &method32->typesVMAddr;
+            case Field::imp:
+                return &method32->impVMAddr;
+        }
+    } else {
+        const method64_t* method64 = (const method64_t*)this->methodPos.value();
+        switch ( field ) {
+            case Field::name:
+                return &method64->nameVMAddr;
+            case Field::types:
+                return &method64->typesVMAddr;
+            case Field::imp:
+                return &method64->impVMAddr;
+        }
+    }
+}
+
+ResolvedValue Method::getNameField(const Visitor& objcVisitor) const
+{
+    switch ( this->kind ) {
+        case Kind::relativeIndirect: {
+            assert(0);
+        }
+        case Kind::relativeDirect: {
+            assert(0);
+        }
+        case Kind::pointer: {
+            return objcVisitor.getField(this->methodPos, this->getFieldPos(objcVisitor, Field::name));
+        }
+    }
+}
+
+ResolvedValue Method::getTypesField(const Visitor& objcVisitor) const
+{
+    switch ( this->kind ) {
+        case Kind::relativeIndirect: {
+            assert(0);
+        }
+        case Kind::relativeDirect: {
+            assert(0);
+        }
+        case Kind::pointer: {
+            return objcVisitor.getField(this->methodPos, this->getFieldPos(objcVisitor, Field::types));
+        }
+    }
+}
+
+ResolvedValue Method::getIMPField(const Visitor& objcVisitor) const
+{
+    switch ( this->kind ) {
+        case Kind::relativeIndirect: {
+            assert(0);
+        }
+        case Kind::relativeDirect: {
+            assert(0);
+        }
+        case Kind::pointer: {
+            return objcVisitor.getField(this->methodPos, this->getFieldPos(objcVisitor, Field::imp));
+        }
+    }
+}
+
+const char* Method::getName(const Visitor& objcVisitor) const
+{
+    switch ( this->kind ) {
+        case Kind::relativeIndirect: {
+            // The uint32_t name field is an offset from itself to a selref.  The selref then points to the selector string
+            const uint8_t* fieldPos = (const uint8_t*)&((const relative_method_t*)this->methodPos.value())->nameOffset;
+            int32_t relativeOffsetFromField = *(int32_t*)fieldPos;
+            VMOffset relativeOffsetFromMethod((uint64_t)offsetof(relative_method_t, nameOffset) + relativeOffsetFromField);
+
+            VMAddress methodVMAddr = this->methodPos.vmAddress();
+            VMAddress nameSelRefVMAddr = methodVMAddr + relativeOffsetFromMethod;
+
+            ResolvedValue nameSelRefValue = objcVisitor.getValueFor(nameSelRefVMAddr);
+            return (const char*)objcVisitor.resolveRebase(nameSelRefValue).value();
+        }
+        case Kind::relativeDirect: {
+#if BUILDING_SHARED_CACHE_UTIL
+            const uint8_t* fieldPos = (const uint8_t*)&((const relative_method_t*)this->methodPos.value())->nameOffset;
+            uint32_t nameOffsetInBuffer = *(uint32_t*)fieldPos;
+
+            VMAddress nameVMAddr = objcVisitor.sharedCacheSelectorStringsBaseAddress() + VMOffset((uint64_t)nameOffsetInBuffer);
+            ResolvedValue nameValue = objcVisitor.getValueFor(nameVMAddr);
+            return (const char*)nameValue.value();
+#else
+            // dyld should never walk direct methods as the objc closure optimizations skip cache dylibs
+            assert(0);
+#endif
+        }
+        case Kind::pointer: {
+            ResolvedValue nameField = this->getNameField(objcVisitor);
+            return (const char*)objcVisitor.resolveRebase(nameField).value();
+        }
+    }
+}
+
+const char* Method::getTypes(const Visitor& objcVisitor) const
+{
+    switch ( this->kind ) {
+        case Kind::relativeIndirect: {
+            const uint8_t* fieldPos = (const uint8_t*)&((const relative_method_t*)this->methodPos.value())->typesOffset;
+            int32_t relativeOffsetFromField = *(int32_t*)fieldPos;
+            VMOffset relativeOffsetFromMethod((uint64_t)offsetof(relative_method_t, typesOffset) + relativeOffsetFromField);
+
+            VMAddress methodVMAddr = this->methodPos.vmAddress();
+            VMAddress typeVMAddr = methodVMAddr + relativeOffsetFromMethod;
+
+            ResolvedValue typeValue = objcVisitor.getValueFor(typeVMAddr);
+            return (const char*)typeValue.value();
+        }
+        case Kind::relativeDirect: {
+            assert(0);
+        }
+        case Kind::pointer: {
+            ResolvedValue typesField = this->getTypesField(objcVisitor);
+            return (const char*)objcVisitor.resolveRebase(typesField).value();
+        }
+    }
+}
+
+const void* Method::getIMP(const Visitor& objcVisitor) const
+{
+    switch ( this->kind ) {
+        case Kind::relativeIndirect: {
+            const uint8_t* fieldPos = (const uint8_t*)&((const relative_method_t*)this->methodPos.value())->impOffset;
+            int32_t relativeOffsetFromField = *(int32_t*)fieldPos;
+            VMOffset relativeOffsetFromMethod((uint64_t)offsetof(relative_method_t, impOffset) + relativeOffsetFromField);
+
+            VMAddress methodVMAddr = this->methodPos.vmAddress();
+            VMAddress impVMAddr = methodVMAddr + relativeOffsetFromMethod;
+
+            ResolvedValue impValue = objcVisitor.getValueFor(impVMAddr);
+            return (const char*)impValue.value();
+        }
+        case Kind::relativeDirect: {
+            assert(0);
+        }
+        case Kind::pointer: {
+            ResolvedValue impField = this->getIMPField(objcVisitor);
+            return (const char*)objcVisitor.resolveRebase(impField).value();
+        }
+    }
+}
+
+// Get the selector string name.  A method often indirects via a selector reference.  This returns
+// the vmAddr of the final selector string, not the selector reference.
+VMAddress Method::getNameVMAddr(const Visitor& objcVisitor) const
+{
+    switch ( this->kind ) {
+        case Kind::relativeIndirect: {
+            // The uint32_t name field is an offset from itself to a selref.  The selref then points to the selector string
+            const uint8_t* fieldPos = (const uint8_t*)&((const relative_method_t*)this->methodPos.value())->nameOffset;
+            int32_t relativeOffsetFromField = *(int32_t*)fieldPos;
+            VMOffset relativeOffsetFromMethod((uint64_t)offsetof(relative_method_t, nameOffset) + relativeOffsetFromField);
+
+            VMAddress methodVMAddr = this->methodPos.vmAddress();
+            VMAddress nameSelRefVMAddr = methodVMAddr + relativeOffsetFromMethod;
+
+            ResolvedValue nameSelRefValue = objcVisitor.getValueFor(nameSelRefVMAddr);
+            return objcVisitor.resolveRebase(nameSelRefValue).vmAddress();
+        }
+        case Kind::relativeDirect: {
+#if BUILDING_DYLD || BUILDING_CLOSURE_UTIL || BUILDING_UNIT_TESTS
+            // dyld should never walk direct methods as the objc closure optimizations skip cache dylibs
+            assert(0);
+#else
+            const uint8_t* fieldPos = (const uint8_t*)&((const relative_method_t*)this->methodPos.value())->nameOffset;
+            uint32_t nameOffsetInBuffer = *(uint32_t*)fieldPos;
+
+            return objcVisitor.sharedCacheSelectorStringsBaseAddress() + VMOffset((uint64_t)nameOffsetInBuffer);
+#endif
+        }
+        case Kind::pointer: {
+            ResolvedValue nameSelRefValue = this->getNameField(objcVisitor);
+            return objcVisitor.resolveRebase(nameSelRefValue).vmAddress();
+        }
+    }
+}
+
+VMAddress Method::getTypesVMAddr(const Visitor& objcVisitor) const
+{
+    switch ( this->kind ) {
+        case Kind::relativeIndirect:
+        case Kind::relativeDirect: {
+            const uint8_t* fieldPos = (const uint8_t*)&((const relative_method_t*)this->methodPos.value())->typesOffset;
+            int32_t relativeOffsetFromField = *(int32_t*)fieldPos;
+            VMOffset relativeOffsetFromMethod((uint64_t)offsetof(relative_method_t, typesOffset) + relativeOffsetFromField);
+
+            VMAddress methodVMAddr = this->methodPos.vmAddress();
+            VMAddress typeVMAddr = methodVMAddr + relativeOffsetFromMethod;
+            return typeVMAddr;
+        }
+        case Kind::pointer: {
+            ResolvedValue typesRefValue = this->getTypesField(objcVisitor);
+            return objcVisitor.resolveRebase(typesRefValue).vmAddress();
+        }
+    }
+}
+
+std::optional<VMAddress> Method::getIMPVMAddr(const Visitor& objcVisitor) const
+{
+    switch ( this->kind ) {
+        case Kind::relativeIndirect:
+        case Kind::relativeDirect:  {
+            const uint8_t* fieldPos = (const uint8_t*)&((const relative_method_t*)this->methodPos.value())->impOffset;
+            int32_t relativeOffsetFromField = *(int32_t*)fieldPos;
+
+            // protocols have null impls
+            if ( relativeOffsetFromField == 0 )
+                return std::nullopt;
+
+            VMOffset relativeOffsetFromMethod((uint64_t)offsetof(relative_method_t, impOffset) + relativeOffsetFromField);
+
+            VMAddress methodVMAddr = this->methodPos.vmAddress();
+            VMAddress impVMAddr = methodVMAddr + relativeOffsetFromMethod;
+            return impVMAddr;
+        }
+        case Kind::pointer: {
+            ResolvedValue impRefValue = this->getIMPField(objcVisitor);
+            return objcVisitor.resolveOptionalRebaseToVMAddress(impRefValue);
+        }
+    }
+}
+
+// Get the selector string name.  A method often indirects via a selector reference.  This returns
+// the vmAddr of the selector reference, not the final selector string
+VMAddress Method::getNameSelRefVMAddr(const Visitor& objcVisitor) const
+{
+    switch ( this->kind ) {
+        case Kind::relativeIndirect: {
+            // The uint32_t name field is an offset from itself to a selref.  The selref then points to the selector string
+            const uint8_t* fieldPos = (const uint8_t*)&((const relative_method_t*)this->methodPos.value())->nameOffset;
+            int32_t relativeOffsetFromField = *(int32_t*)fieldPos;
+            VMOffset relativeOffsetFromMethod((uint64_t)offsetof(relative_method_t, nameOffset) + relativeOffsetFromField);
+
+            VMAddress methodVMAddr = this->methodPos.vmAddress();
+            VMAddress nameSelRefVMAddr = methodVMAddr + relativeOffsetFromMethod;
+
+            ResolvedValue nameSelRefValue = objcVisitor.getValueFor(nameSelRefVMAddr);
+            return nameSelRefValue.vmAddress();
+        }
+        case Kind::relativeDirect: {
+            assert(0);
+        }
+        case Kind::pointer: {
+            assert(0);
+        }
+    }
+}
+
+#if BUILDING_CACHE_BUILDER || BUILDING_CACHE_BUILDER_UNIT_TESTS
+void Method::setName(const Visitor& objcVisitor, VMAddress nameVMAddr)
+{
+    switch ( this->kind ) {
+        case Kind::relativeIndirect: {
+            assert(0);
+        }
+        case Kind::relativeDirect: {
+            const uint8_t* fieldPos = (const uint8_t*)&((const relative_method_t*)this->methodPos.value())->nameOffset;
+
+            VMOffset nameOffsetInBuffer = nameVMAddr - objcVisitor.sharedCacheSelectorStringsBaseAddress();
+            uint64_t relativeOffset = (uint64_t)nameOffsetInBuffer.rawValue();
+
+            assert((uint32_t)relativeOffset == relativeOffset);
+            *(uint32_t*)fieldPos = (uint32_t)relativeOffset;
+            break;
+        }
+        case Kind::pointer: {
+            ResolvedValue selRefValue = this->getNameField(objcVisitor);
+            objcVisitor.updateTargetVMAddress(selRefValue, CacheVMAddress(nameVMAddr.rawValue()));
+        }
+    }
+}
+
+void Method::setTypes(const Visitor& objcVisitor, VMAddress typesVMAddr)
+{
+    switch ( this->kind ) {
+        case Kind::relativeIndirect:
+        case Kind::relativeDirect: {
+            VMAddress methodVMAddr = this->methodPos.vmAddress();
+            VMAddress typesFieldVMAddr = methodVMAddr + VMOffset((uint64_t)offsetof(relative_method_t, typesOffset));
+
+            VMOffset typesRelativeOffset = typesVMAddr - typesFieldVMAddr;
+            int64_t relativeOffset = (int64_t)typesRelativeOffset.rawValue();
+
+            const uint8_t* fieldPos = (const uint8_t*)&((const relative_method_t*)this->methodPos.value())->typesOffset;
+            assert((int32_t)relativeOffset == relativeOffset);
+            *(int32_t*)fieldPos = (int32_t)relativeOffset;
+            break;
+        }
+        case Kind::pointer: {
+            ResolvedValue refValue = this->getTypesField(objcVisitor);
+            objcVisitor.updateTargetVMAddress(refValue, CacheVMAddress(typesVMAddr.rawValue()));
+        }
+    }
+}
+
+void Method::setIMP(const Visitor& objcVisitor, std::optional<VMAddress> impVMAddr)
+{
+    switch ( this->kind ) {
+        case Kind::relativeIndirect:
+        case Kind::relativeDirect: {
+            if ( !impVMAddr.has_value() ) {
+                // A NULL imp is probably a protocol, and is expected.  Every other IMP in the
+                // protocol is also going to be NULL, so just make sure this one matches
+                assert(!this->getIMPVMAddr(objcVisitor).has_value());
+            } else {
+                VMAddress methodVMAddr = this->methodPos.vmAddress();
+                VMAddress impFieldVMAddr = methodVMAddr + VMOffset((uint64_t)offsetof(relative_method_t, impOffset));
+
+                VMOffset impRelativeOffset = impVMAddr.value() - impFieldVMAddr;
+                int64_t relativeOffset = (int64_t)impRelativeOffset.rawValue();
+
+                const uint8_t* fieldPos = (const uint8_t*)&((const relative_method_t*)this->methodPos.value())->impOffset;
+                assert((int32_t)relativeOffset == relativeOffset);
+                *(int32_t*)fieldPos = (int32_t)relativeOffset;
+            }
+            break;
+        }
+        case Kind::pointer: {
+            if ( !impVMAddr.has_value() ) {
+                // A NULL imp is probably a protocol, and is expected.  Every other IMP in the
+                // protocol is also going to be NULL, so just make sure this one matches
+                assert(!this->getIMPVMAddr(objcVisitor).has_value());
+            } else {
+                ResolvedValue refValue = this->getIMPField(objcVisitor);
+                objcVisitor.updateTargetVMAddress(refValue, CacheVMAddress(impVMAddr->rawValue()));
+            }
+        }
+    }
+}
+#endif
+
+void Method::convertNameToOffset(const Visitor& objcVisitor, uint32_t nameOffset)
+{
+    switch ( this->kind ) {
+        case Kind::relativeIndirect: {
+            // We are always looking at an indirect method when converting a name to an offset
+            uint8_t* fieldPos = (uint8_t*)&((const relative_method_t*)this->methodPos.value())->nameOffset;
+            *(uint32_t*)fieldPos = (uint32_t)nameOffset;
+
+            // FIXME: Should we convert the kind field on this method to relativeDirect?
+            break;
+        }
+        case Kind::relativeDirect: {
+            // This shouldn't happen
+            assert(0);
+        }
+        case Kind::pointer: {
+            // This shouldn't happen
+            assert(0);
+        }
+    }
+}
+
+uint32_t Method::getSize(bool is64)
+{
+    return is64 ? sizeof(method64_t) : sizeof(method32_t);
+}
+
+//
+// MARK: --- IVarList methods ---
+//
+
+uint32_t IVarList::numIVars() const
+{
+    if ( !ivarListPos.has_value() )
+        return 0;
+
+    const ResolvedValue& ivarListValue = this->ivarListPos.value();
+
+    const ivar_list_t* ivarList = (const ivar_list_t*)ivarListValue.value();
+    assert(ivarList != nullptr);
+
+    return ivarList->getCount();
+}
+
+IVar IVarList::getIVar(const Visitor& objcVisitor, uint32_t i) const
+{
+    assert(ivarListPos.has_value());
+
+    const ResolvedValue& ivarListValue = this->ivarListPos.value();
+
+    const ivar_list_t* ivarList = (const ivar_list_t*)ivarListValue.value();
+    assert(ivarList != nullptr);
+
+    const uint8_t* ivarListBase = ivarList->ivarBase();
+    const uint8_t* ivar = ivarListBase + (i * ivarList->getElementSize());
+
+    ResolvedValue ivarValue = objcVisitor.getField(ivarListValue, ivar);
+    return IVar(ivarValue);
+}
+
+#if BUILDING_CACHE_BUILDER_UNIT_TESTS
+const void* IVarList::getLocation() const
+{
+    if ( !this->ivarListPos.has_value() )
+        return nullptr;
+    return this->ivarListPos->value();
+}
+
+std::optional<VMAddress> IVarList::getVMAddress() const
+{
+    return this->ivarListPos->vmAddress();
+}
+#endif
+
+//
+// MARK: --- PropertyList methods ---
+//
+uint32_t PropertyList::numProperties() const
+{
+    if ( !this->propertyListPos.has_value() )
+        return 0;
+
+    const ResolvedValue& propertyListValue = this->propertyListPos.value();
+
+    const property_list_t* propertyList = (const property_list_t*)propertyListValue.value();
+    assert(propertyList != nullptr);
+
+    return propertyList->getCount();
+}
+
+Property PropertyList::getProperty(const Visitor& objcVisitor, uint32_t i) const
+{
+    assert(this->propertyListPos.has_value());
+
+    const ResolvedValue& propertyListValue = this->propertyListPos.value();
+
+    const property_list_t* propertyList = (const property_list_t*)propertyListValue.value();
+    assert(propertyList != nullptr);
+
+    const uint8_t* propertyListBase = propertyList->propertyBase();
+    const uint8_t* property = propertyListBase + (i * propertyList->getElementSize());
+
+    ResolvedValue propertyValue = objcVisitor.getField(propertyListValue, property);
+    return Property(propertyValue);
+}
+
+const void* PropertyList::getLocation() const
+{
+    if ( !this->propertyListPos.has_value() )
+        return nullptr;
+    return this->propertyListPos->value();
+}
+
+std::optional<VMAddress> PropertyList::getVMAddress() const
+{
+    if ( !this->propertyListPos.has_value() )
+        return { };
+    return this->propertyListPos->vmAddress();
+}
+
+bool PropertyList::isListOfLists() const
+{
+    if ( !propertyListPos.has_value() )
+        return false;
+
+    const ResolvedValue& propertyListValue = this->propertyListPos.value();
+    return propertyListValue.vmAddress().rawValue() & 1;
+}
+
+//
+// MARK: --- ProtocolList methods ---
+//
+
+uint64_t ProtocolList::numProtocols(const Visitor& objcVisitor) const
+{
+    if ( !this->protocolListPos.has_value() )
+        return 0;
+
+    const ResolvedValue& protocolListValue = this->protocolListPos.value();
+    const void* protocolList = protocolListValue.value();
+    assert(protocolList != nullptr);
+
+    if ( objcVisitor.pointerSize == 4 ) {
+        return ((const protocol_list32_t*)protocolList)->count;
+    } else {
+        return ((const protocol_list64_t*)protocolList)->count;
+    }
+}
+
+ResolvedValue ProtocolList::getProtocolField(const Visitor& objcVisitor, uint64_t i) const
+{
+    assert(this->protocolListPos.has_value());
+    assert(i < this->numProtocols(objcVisitor));
+
+    const ResolvedValue& protocolListValue = this->protocolListPos.value();
+    const void* protocolList = protocolListValue.value();
+    assert(protocolList != nullptr);
+
+    const void* protocolFixupLoc = nullptr;
+    if ( objcVisitor.pointerSize == 4 ) {
+        protocolFixupLoc = &((const protocol_list32_t*)protocolList)->list[i];
+    } else {
+        protocolFixupLoc = &((const protocol_list64_t*)protocolList)->list[i];
+    }
+
+    return objcVisitor.getField(protocolListValue, protocolFixupLoc);
+}
+
+Protocol ProtocolList::getProtocol(const Visitor& objcVisitor, uint64_t i) const
+{
+    ResolvedValue field = this->getProtocolField(objcVisitor, i);
+    ResolvedValue protocolValue = objcVisitor.resolveRebase(field);
+    return Protocol(protocolValue);
+}
+
+#if BUILDING_CACHE_BUILDER || BUILDING_CACHE_BUILDER_UNIT_TESTS
+void ProtocolList::setProtocol(const Visitor& objcVisitor, uint64_t i, VMAddress vmAddr)
+{
+    ResolvedValue field = this->getProtocolField(objcVisitor, i);
+    objcVisitor.updateTargetVMAddress(field, CacheVMAddress(vmAddr.rawValue()));
+}
+#endif
+
+const void* ProtocolList::getLocation() const
+{
+    if ( !this->protocolListPos.has_value() )
+        return nullptr;
+    return this->protocolListPos->value();
+}
+
+std::optional<VMAddress> ProtocolList::getVMAddress() const
+{
+    if ( !this->protocolListPos.has_value() )
+        return { };
+    return this->protocolListPos->vmAddress();
+}
+
+bool ProtocolList::isListOfLists() const
+{
+    if ( !protocolListPos.has_value() )
+        return false;
+
+    const ResolvedValue& protocolListValue = this->protocolListPos.value();
+    return protocolListValue.vmAddress().rawValue() & 1;
+}
+
+
+void ProtocolList::dump(const Visitor& objcVisitor) const
+{
+    if ( !this->protocolListPos.has_value() ) {
+        fprintf(stdout, "no value\n");
+        return;
+    }
+
+    const ResolvedValue& protocolListValue = this->protocolListPos.value();
+    uint64_t count = this->numProtocols(objcVisitor);
+    fprintf(stdout, "Protocol list (count %lld): vmAddr 0x%llx at %p\n",
+            count, protocolListValue.vmAddress().rawValue(), protocolListValue.value());
+    for ( uint64_t i = 0; i != count; ++i ) {
+        Protocol objCProtocol = this->getProtocol(objcVisitor, i);
+        fprintf(stdout, "  Protocol[%lld]: vmAddr 0x%llx at %p\n", i,
+                objCProtocol.getVMAddress().rawValue(),
+                objCProtocol.getLocation());
+    }
+}
+
+//
+// MARK: --- IVar methods ---
+//
+
+const void* IVar::getFieldPos(const Visitor& objcVisitor, Field field) const
+{
+    if ( objcVisitor.pointerSize == 4 ) {
+        const ivar32_t* ivar32 = (const ivar32_t*)this->ivarPos.value();
+        switch ( field ) {
+            case Field::offset:
+                return &ivar32->offsetVMAddr;
+            case Field::name:
+                return &ivar32->nameVMAddr;
+            case Field::type:
+                return &ivar32->typeVMAddr;
+            case Field::alignment:
+                return &ivar32->alignment;
+            case Field::size:
+                return &ivar32->size;
+        }
+    } else {
+        const ivar64_t* ivar64 = (const ivar64_t*)this->ivarPos.value();
+        switch ( field ) {
+            case Field::offset:
+                return &ivar64->offsetVMAddr;
+            case Field::name:
+                return &ivar64->nameVMAddr;
+            case Field::type:
+                return &ivar64->typeVMAddr;
+            case Field::alignment:
+                return &ivar64->alignment;
+            case Field::size:
+                return &ivar64->size;
+        }
+    }
+}
+
+std::optional<uint32_t> IVar::getOffset(const Visitor& objcVisitor) const
+{
+    ResolvedValue field = objcVisitor.getField(this->ivarPos, this->getFieldPos(objcVisitor, Field::offset));
+
+    // The offset might not be set, if it points to 0
+    std::optional<ResolvedValue> targetValue = objcVisitor.resolveOptionalRebase(field);
+    if ( targetValue.has_value() )
+        return *(uint32_t*)targetValue->value();
+
+    return std::nullopt;
+}
+
+void IVar::setOffset(const Visitor& objcVisitor, uint32_t offset) const
+{
+    ResolvedValue field = objcVisitor.getField(this->ivarPos, this->getFieldPos(objcVisitor, Field::offset));
+    ResolvedValue targetValue = objcVisitor.resolveRebase(field);
+    *(uint32_t*)targetValue.value() = offset;
+}
+
+const char* IVar::getName(const Visitor& objcVisitor) const
+{
+    ResolvedValue field = objcVisitor.getField(this->ivarPos, this->getFieldPos(objcVisitor, Field::name));
+    return (const char*)objcVisitor.resolveRebase(field).value();
+}
+
+uint32_t IVar::getAlignment(const Visitor& objcVisitor) const
+{
+    return *(uint32_t*)this->getFieldPos(objcVisitor, Field::alignment);
+}
+
+bool IVar::elided(const Visitor& objcVisitor) const
+{
+    uint32_t size = *(uint32_t*)this->getFieldPos(objcVisitor, Field::size);
+    // swift can optimize away ivars.  It leaves the meta data about them, but they have no ivar offset to update
+    return (size == 0);
+}
+
+//
+// MARK: --- Property methods ---
+//
+
+const void* Property::getFieldPos(const Visitor& objcVisitor, Field field) const
+{
+    if ( objcVisitor.pointerSize == 4 ) {
+        const property32_t* property32 = (const property32_t*)this->propertyPos.value();
+        switch ( field ) {
+            case Field::name:
+                return &property32->nameVMAddr;
+            case Field::attributes:
+                return &property32->attributesVMAddr;
+        }
+    } else {
+        const property64_t* property64 = (const property64_t*)this->propertyPos.value();
+        switch ( field ) {
+            case Field::name:
+                return &property64->nameVMAddr;
+            case Field::attributes:
+                return &property64->attributesVMAddr;
+        }
+    }
+}
+
+const char* Property::getName(const Visitor& objcVisitor) const
+{
+    ResolvedValue field = objcVisitor.getField(this->propertyPos, this->getFieldPos(objcVisitor, Field::name));
+    return (const char*)objcVisitor.resolveRebase(field).value();
+}
+
+const char* Property::getAttributes(const Visitor& objcVisitor) const
+{
+    ResolvedValue field = objcVisitor.getField(this->propertyPos, this->getFieldPos(objcVisitor, Field::attributes));
+    return (const char*)objcVisitor.resolveRebase(field).value();
+}
+
+//
+// MARK: --- Visitor::Section methods ---
+//
+
+std::optional<Visitor::Section> Visitor::findSection(std::span<const char*> altSegNames, const char *sectionName) const
+{
+#if SUPPORT_VM_LAYOUT
+    const dyld3::MachOFile* mf = this->dylibMA;
+#else
+    const dyld3::MachOFile* mf = this->dylibMF;
+#endif
+
+    __block std::optional<Visitor::Section> objcDataSection;
+    ((const Header*)mf)->forEachSection(^(const Header::SegmentInfo& segInfo, const Header::SectionInfo& sectInfo, bool& stop) {
+        bool segMatch = std::any_of(altSegNames.begin(), altSegNames.end(), [&sectInfo](const char* segName) {
+            return sectInfo.segmentName == segName;
+        });
+        if ( !segMatch )
+            return;
+        if ( sectInfo.sectionName != sectionName )
+            return;
+
+#if SUPPORT_VM_LAYOUT
+        const void* targetValue = (const void*)(sectInfo.address + this->dylibMA->getSlide());
+        ResolvedValue target(targetValue, VMAddress(sectInfo.address));
+#else
+        VMOffset offsetInSegment(sectInfo.address - segInfo.vmaddr);
+        ResolvedValue target(this->segments[sectInfo.segIndex], offsetInSegment);
+#endif
+        objcDataSection.emplace(std::move(target), sectInfo.size);
+
+        stop = true;
+    });
+    return objcDataSection;
+}
+
+std::optional<Visitor::Section> Visitor::findObjCDataSection(const char *sectionName) const
+{
+    static const char* objcDataSegments[] = {
+        "__DATA", "__DATA_CONST", "__DATA_DIRTY"
+    };
+    return findSection(objcDataSegments, sectionName);
+}
+
+std::optional<Visitor::Section> Visitor::findObjCTextSection(const char *sectionName) const
+{
+    static const char* objcTextSegments[] = {
+        "__TEXT"
+    };
+    return findSection(objcTextSegments, sectionName);
+}
+
+//
+// MARK: --- Visitor methods ---
+//
+
+void Visitor::forEachClass(bool visitMetaClasses, const Visitor::Section& classListSection,
+                           void (^callback)(Class& objcClass, bool isMetaClass, bool& stopClass))
+{
+    assert((classListSection.sectSize % pointerSize) == 0);
+    uint64_t numClasses = classListSection.sectSize / pointerSize;
+
+    // Use the segment index to find the corresponding cache segment
+    const ResolvedValue& sectionValue = classListSection.sectionBase;
+    const uint8_t* sectionBase = (const uint8_t*)sectionValue.value();
+    for ( uint64_t classIndex = 0; classIndex != numClasses; ++classIndex ) {
+        const uint8_t* classRefPos = sectionBase + (classIndex * pointerSize);
+
+        ResolvedValue classRefValue = this->getField(sectionValue, classRefPos);
+
+        bool isPatchableClass = false;
+        ResolvedValue classPos = this->resolveBindOrRebase(classRefValue, isPatchableClass);
+
+        Class objcClass(classPos, false, isPatchableClass);
+        bool stopClass = false;
+        callback(objcClass, false, stopClass);
+        if ( stopClass )
+            return;
+
+        // If we don't want the metaclass then skip to the next class
+        if ( !visitMetaClasses)
+            continue;
+
+        bool isPatchableMetaClass = false;
+        ResolvedValue objcClassISA = objcClass.getISA(*this, isPatchableMetaClass);
+        Class objcMetaClass(objcClassISA, true, isPatchableMetaClass);
+        callback(objcMetaClass, true, stopClass);
+        if ( stopClass )
+            return;
+    }
+}
+
+void Visitor::forEachClass(bool visitMetaClasses, void (^callback)(Class& objcClass, bool isMetaClass, bool& stopClass))
+{
+    std::optional<Section> classListSection = this->findObjCDataSection("__objc_classlist");
+    if ( !classListSection.has_value() )
+        return;
+
+    this->forEachClass(visitMetaClasses, classListSection.value(), callback);
+}
+
+void Visitor::forEachClass(void (^callback)(const Class& objcClass, bool& stopClass))
+{
+    auto adaptor = ^(Class& objcClass, bool isMetaClass, bool& stopClass) {
+        callback(objcClass, stopClass);
+    };
+    forEachClass(false, adaptor);
+}
+
+void Visitor::forEachClass(void (^callback)(Class& objcClass, bool& stopClass))
+{
+    auto adaptor = ^(Class& objcClass, bool isMetaClass, bool& stopClass) {
+        callback(objcClass, stopClass);
+    };
+    forEachClass(false, adaptor);
+}
+
+void Visitor::forEachClassAndMetaClass(void (^callback)(const Class& objcClass, bool& stopClass))
+{
+    auto adaptor = ^(Class& objcClass, bool isMetaClass, bool& stopClass) {
+        callback(objcClass, stopClass);
+    };
+    forEachClass(true, adaptor);
+}
+
+void Visitor::forEachClassAndMetaClass(void (^callback)(Class& objcClass, bool& stopClass))
+{
+    auto adaptor = ^(Class& objcClass, bool isMetaClass, bool& stopClass) {
+        callback(objcClass, stopClass);
+    };
+    forEachClass(true, adaptor);
+}
+
+void Visitor::forEachCategory(void (^callback)(const Category& objcCategory, bool& stopCategory))
+{
+    for ( bool isCatlist2 : { false, true }) {
+        const char* listSection = isCatlist2 ? "__objc_catlist2" : "__objc_catlist";
+        std::optional<Section> categoryListSection = findObjCDataSection(listSection);
+        if ( !categoryListSection.has_value() )
+            continue;
+
+        assert((categoryListSection->sectSize % pointerSize) == 0);
+        uint64_t numCategories = categoryListSection->sectSize / pointerSize;
+
+        const ResolvedValue& sectionValue = categoryListSection->sectionBase;
+        const uint8_t* sectionBase = (const uint8_t*)sectionValue.value();
+        for ( uint64_t categoryIndex = 0; categoryIndex != numCategories; ++categoryIndex ) {
+            const uint8_t* categoryRefPos = sectionBase + (categoryIndex * pointerSize);
+            ResolvedValue categoryRefValue = this->getField(sectionValue, categoryRefPos);
+
+            // Follow the category reference to get to the actual category
+            ResolvedValue categoryPos = resolveRebase(categoryRefValue);
+            Category objcCategory(categoryPos, isCatlist2);
+            bool stopCategory = false;
+            callback(objcCategory, stopCategory);
+            if ( stopCategory )
+                break;
+        }
+    }
+}
+
+void Visitor::forEachProtocol(void (^callback)(const Protocol& objcProtocol, bool& stopProtocol))
+{
+    std::optional<Section> protocolListSection = findObjCDataSection("__objc_protolist");
+    if ( !protocolListSection.has_value() )
+        return;
+
+    assert((protocolListSection->sectSize % pointerSize) == 0);
+    uint64_t numCategories = protocolListSection->sectSize / pointerSize;
+
+    const ResolvedValue& sectionValue = protocolListSection->sectionBase;
+    const uint8_t* sectionBase = (const uint8_t*)sectionValue.value();
+    for ( uint64_t protocolIndex = 0; protocolIndex != numCategories; ++protocolIndex ) {
+        const uint8_t* protocolRefPos = sectionBase + (protocolIndex * pointerSize);
+        ResolvedValue protocolRefValue = this->getField(sectionValue, protocolRefPos);
+
+        // Follow the protocol reference to get to the actual protocol
+        ResolvedValue protocolPos = resolveRebase(protocolRefValue);
+        Protocol objcProtocol(protocolPos);
+        bool stopProtocol = false;
+        callback(objcProtocol, stopProtocol);
+        if ( stopProtocol )
+            break;
+    }
+}
+
+void Visitor::forEachSelectorReference(void (^callback)(ResolvedValue& value)) const
+{
+    std::optional<Section> selRefsSection = findObjCDataSection("__objc_selrefs");
+    if ( !selRefsSection.has_value() )
+        return;
+
+    assert((selRefsSection->sectSize % pointerSize) == 0);
+    uint64_t numSelRefs = selRefsSection->sectSize / pointerSize;
+
+    const ResolvedValue& sectionValue = selRefsSection->sectionBase;
+    const uint8_t* sectionBase = (const uint8_t*)sectionValue.value();
+    for ( uint64_t selRefIndex = 0; selRefIndex != numSelRefs; ++selRefIndex ) {
+        const uint8_t* selRefPos = sectionBase + (selRefIndex * pointerSize);
+        ResolvedValue selRefValue = this->getField(sectionValue, selRefPos);
+
+        callback(selRefValue);
+    }
+}
+
+void Visitor::forEachSelectorReference(void (^callback)(VMAddress selRefVMAddr, VMAddress selRefTargetVMAddr,
+                                                            const char* selectorString)) const
+{
+    this->forEachSelectorReference(^(ResolvedValue& selRefValue) {
+        ResolvedValue selRefTarget = this->resolveRebase(selRefValue);
+
+        VMAddress selRefVMAddr = selRefValue.vmAddress();
+        VMAddress selRefTargetVMAddr = selRefTarget.vmAddress();
+        const char* selectorString = (const char*)selRefTarget.value();
+
+        callback(selRefVMAddr, selRefTargetVMAddr, selectorString);
+    });
+}
+
+void Visitor::forEachProtocolReference(void (^callback)(ResolvedValue& value))
+{
+    std::optional<Section> protocolRefsSection = findObjCDataSection("__objc_protorefs");
+    if ( !protocolRefsSection.has_value() )
+        return;
+
+    assert((protocolRefsSection->sectSize % pointerSize) == 0);
+    uint64_t numProtocolRefs = protocolRefsSection->sectSize / pointerSize;
+
+    const ResolvedValue& sectionValue = protocolRefsSection->sectionBase;
+    const uint8_t* sectionBase = (const uint8_t*)sectionValue.value();
+    for ( uint64_t protocolRefIndex = 0; protocolRefIndex != numProtocolRefs; ++protocolRefIndex ) {
+        const uint8_t* protocolRefPos = sectionBase + (protocolRefIndex * pointerSize);
+        ResolvedValue protocolRefValue = this->getField(sectionValue, protocolRefPos);
+
+        callback(protocolRefValue);
+    }
+}
+
+#if BUILDING_CACHE_BUILDER || BUILDING_CACHE_BUILDER_UNIT_TESTS
+void Visitor::forEachMethodList(void (^callback)(MethodList& objcMethodList, std::optional<metadata_visitor::ResolvedValue> extendedMethodTypes))
+{
+    __block std::unordered_set<const void*> visitedLists;
+
+    forEachClassAndMetaClass(^(const objc_visitor::Class& objcClass, bool&) {
+        objc_visitor::MethodList objcMethodList = objcClass.getBaseMethods(*this);
+
+        callback(objcMethodList, std::nullopt);
+        visitedLists.insert(objcMethodList.getLocation());
+    });
+
+    forEachCategory(^(const objc_visitor::Category& objcCategory, bool&) {
+        objc_visitor::MethodList instanceMethodList = objcCategory.getInstanceMethods(*this);
+        objc_visitor::MethodList classMethodList    = objcCategory.getClassMethods(*this);
+
+        callback(instanceMethodList, std::nullopt);
+        visitedLists.insert(instanceMethodList.getLocation());
+
+        callback(classMethodList, std::nullopt);
+        visitedLists.insert(classMethodList.getLocation());
+    });
+
+    forEachProtocol(^(const objc_visitor::Protocol& objcProtocol, bool&) {
+        objc_visitor::MethodList instanceMethodList         = objcProtocol.getInstanceMethods(*this);
+        objc_visitor::MethodList classMethodList            = objcProtocol.getClassMethods(*this);
+        objc_visitor::MethodList optionalInstanceMethodList = objcProtocol.getOptionalInstanceMethods(*this);
+        objc_visitor::MethodList optionalClassMethodList    = objcProtocol.getOptionalClassMethods(*this);
+
+        // This is an optional flat array with entries for all method lists.
+        // Each method list of length N has N char* entries in this list, if its present
+        std::optional<metadata_visitor::ResolvedValue> extendedMethodTypes = objcProtocol.getExtendedMethodTypes(*this);
+        const uint8_t* currentMethodTypes = extendedMethodTypes.has_value() ? (const uint8_t*)extendedMethodTypes->value() : nullptr;
+
+        callback(instanceMethodList, extendedMethodTypes);
+        visitedLists.insert(instanceMethodList.getLocation());
+        if ( extendedMethodTypes.has_value() ) {
+            currentMethodTypes += (instanceMethodList.numMethods() * pointerSize);
+            extendedMethodTypes.emplace(metadata_visitor::ResolvedValue(extendedMethodTypes.value(), currentMethodTypes));
+        }
+
+        callback(classMethodList, extendedMethodTypes);
+        visitedLists.insert(classMethodList.getLocation());
+        if ( extendedMethodTypes.has_value() ) {
+            currentMethodTypes += (classMethodList.numMethods() * pointerSize);
+            extendedMethodTypes.emplace(metadata_visitor::ResolvedValue(extendedMethodTypes.value(), currentMethodTypes));
+        }
+
+        callback(optionalInstanceMethodList, extendedMethodTypes);
+        visitedLists.insert(optionalInstanceMethodList.getLocation());
+        if ( extendedMethodTypes.has_value() ) {
+            currentMethodTypes += (optionalInstanceMethodList.numMethods() * pointerSize);
+            extendedMethodTypes.emplace(metadata_visitor::ResolvedValue(extendedMethodTypes.value(), currentMethodTypes));
+        }
+
+        callback(optionalClassMethodList, extendedMethodTypes);
+        visitedLists.insert(optionalClassMethodList.getLocation());
+        if ( extendedMethodTypes.has_value() ) {
+            currentMethodTypes += (optionalClassMethodList.numMethods() * pointerSize);
+            extendedMethodTypes.emplace(metadata_visitor::ResolvedValue(extendedMethodTypes.value(), currentMethodTypes));
+        }
+    });
+
+    // rdar://129304028 (dyld cache builder support for relative method lists in Swift generic classes)
+    // Also scan the entire __objc_methlist section looking for other method lists that
+    // aren't referenced through the regular ObjC metadata.
+    std::optional<Section> methodListSection = findObjCTextSection("__objc_methlist");
+    if ( !methodListSection.has_value() )
+        return;
+    assert((methodListSection->sectSize % 4) == 0);
+
+    const ResolvedValue& sectionValue = methodListSection->sectionBase;
+    const uint8_t* sectionPos = (const uint8_t*)sectionValue.value();
+    const uint8_t* sectionEnd = (const uint8_t*)sectionValue.value() + methodListSection->sectSize;
+
+    while ( sectionPos < sectionEnd ) {
+        ResolvedValue methodListValue = this->getField(sectionValue, sectionPos);
+
+        // method lists are 8-byte alligned, a valid method list can never start
+        // with a 0 because that's where the method size entry and flags are encoded
+        if ( *(uint32_t*)methodListValue.value() == 0 ) {
+            sectionPos += sizeof(uint32_t);
+            continue;
+        }
+
+        MethodList methodList(methodListValue);
+
+        // sanity check entry - all lists in __objc_methlist are relative and
+        // a relative method list entry is 12 bytes large
+        assert(methodList.usesRelativeOffsets() && methodList.methodSize() == 12
+                && "not a relative method list");
+
+        // skip method lists that were visited through classes etc.
+        if ( !visitedLists.contains(methodList.getLocation()) ) {
+            callback(methodList, std::nullopt);
+        }
+
+        uint32_t size = methodList.listSize();
+        assert(size != 0 && "method list can't be empty");
+        sectionPos += size;
+    }
+    assert(sectionPos == sectionEnd && "malformed __objc_methlist section");
+}
+#endif
+
+void Visitor::withImageInfo(void (^callback)(const uint32_t version, const uint32_t flags)) const
+{
+    std::optional<Section> imageInfoSection = findObjCDataSection("__objc_imageinfo");
+    if ( !imageInfoSection.has_value() )
+        return;
+
+    assert((imageInfoSection->sectSize % pointerSize) == 0);
+    const ResolvedValue& sectionValue = imageInfoSection->sectionBase;
+
+    struct objc_image_info {
+        int32_t version;
+        uint32_t flags;
+    };
+    const objc_image_info* sectionBase = (const objc_image_info*)sectionValue.value();
+    callback(sectionBase->version, sectionBase->flags);
+}
+
+#endif // !TARGET_OS_EXCLAVEKIT