From e61952b069c42c4a7b09d83f1951f77703b9439e Mon Sep 17 00:00:00 2001 From: Mx-Iris Date: Wed, 22 Apr 2026 14:18:27 +0800 Subject: [PATCH 1/3] fix(SymbolIndexStore): guard demangledNodeBySymbol against parallel-test race CI was hitting a documented race in `demangledNode(for:in:)` where the cache-miss branch wrote to `demangledNodeBySymbol` without synchronization. When sibling test suites ran in parallel, concurrent writers corrupted the Dictionary's internal layout, surfacing as SIGSEGV in the hash lookup or as downstream `.invalidContextDescriptor` when subsequently reading trashed bytes as context-descriptor flags. - SymbolIndexStore: decorate `demangledNodeBySymbol` with `@Mutex` so the generated `_modify` accessor keeps `dict[key] = value` atomic. - GenericSpecializationTests: fold the API tests into the same suite and mark it `.serialized`. - macOS.yml: pass `--no-parallel` to both `swift test` invocations and drop the merged-away `GenericSpecializerAPITests` filter alternative. --- .github/workflows/macOS.yml | 6 ++++-- Sources/MachOSymbols/SymbolIndexStore.swift | 1 + .../SwiftInterfaceTests/GenericSpecializationTests.swift | 8 ++------ 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/.github/workflows/macOS.yml b/.github/workflows/macOS.yml index b986e4c4..f307aff3 100644 --- a/.github/workflows/macOS.yml +++ b/.github/workflows/macOS.yml @@ -89,11 +89,13 @@ jobs: swift test \ -c debug \ --build-path .build-test-debug \ - --filter '\.(SymbolTestsCoreDumpSnapshotTests|SymbolTestsCoreInterfaceSnapshotTests|SymbolTestsCoreCoverageInvariantTests|STCoreE2ETests|STCoreTests|GenericSpecializationTests|GenericSpecializerAPITests|MultiPayloadEnumTests|MetadataReaderDemanglingTests)(/|$)' + --no-parallel \ + --filter '\.(SymbolTestsCoreDumpSnapshotTests|SymbolTestsCoreInterfaceSnapshotTests|SymbolTestsCoreCoverageInvariantTests|STCoreE2ETests|STCoreTests|GenericSpecializationTests|MultiPayloadEnumTests|MetadataReaderDemanglingTests)(/|$)' - name: Build and run tests in release mode run: | swift test \ -c release \ --build-path .build-test-release \ - --filter '\.(SymbolTestsCoreDumpSnapshotTests|SymbolTestsCoreInterfaceSnapshotTests|SymbolTestsCoreCoverageInvariantTests|STCoreE2ETests|STCoreTests|GenericSpecializationTests|GenericSpecializerAPITests|MultiPayloadEnumTests|MetadataReaderDemanglingTests)(/|$)' + --no-parallel \ + --filter '\.(SymbolTestsCoreDumpSnapshotTests|SymbolTestsCoreInterfaceSnapshotTests|SymbolTestsCoreCoverageInvariantTests|STCoreE2ETests|STCoreTests|GenericSpecializationTests|MultiPayloadEnumTests|MetadataReaderDemanglingTests)(/|$)' diff --git a/Sources/MachOSymbols/SymbolIndexStore.swift b/Sources/MachOSymbols/SymbolIndexStore.swift index 98297552..0a3cf851 100644 --- a/Sources/MachOSymbols/SymbolIndexStore.swift +++ b/Sources/MachOSymbols/SymbolIndexStore.swift @@ -152,6 +152,7 @@ public final class SymbolIndexStore: SharedCache, @unc private(set) var symbolsByOffset: OrderedDictionary = [:] + @Mutex private(set) var demangledNodeBySymbol: [Symbol: Node] = [:] private(set) var thunkAttributeMembersByKindAndTypeName: [Node.Kind: [String: [ThunkAttributeMember]]] = [:] diff --git a/Tests/SwiftInterfaceTests/GenericSpecializationTests.swift b/Tests/SwiftInterfaceTests/GenericSpecializationTests.swift index e4dd70c2..f0b000c0 100644 --- a/Tests/SwiftInterfaceTests/GenericSpecializationTests.swift +++ b/Tests/SwiftInterfaceTests/GenericSpecializationTests.swift @@ -18,6 +18,7 @@ struct TestGenericStruct where A: Collection, B: Equatable, C: Hashable let c: C } +@Suite(.serialized) final class GenericSpecializationTests: MachOImageTests, @unchecked Sendable { override class var imageName: MachOImageName { .SwiftUICore } @@ -72,12 +73,7 @@ final class GenericSpecializationTests: MachOImageTests, @unchecked Sendable { ) try #expect(#require(metadata.value.resolve().struct).fieldOffsets() == [0, 8, 16]) } -} - -// MARK: - GenericSpecializer API Tests - -@Suite -struct GenericSpecializerAPITests { + @Test func makeRequest() async throws { let machO = MachOImage.current() From ab0d033b387ccec8bf8d1cf73387340d2eae0ddc Mon Sep 17 00:00:00 2001 From: Mx-Iris Date: Wed, 22 Apr 2026 16:06:30 +0800 Subject: [PATCH 2/3] try: read section entries via RelativeIndirectablePointer; drop serialization _readRelativeDescriptors now uses RelativeIndirectablePointer> instead of RelativeDirectPointer. Some Swift section entries are indirect on CI binaries; treating them as direct pointers reads the wrong offset and is the likely root cause of the release-mode invalidContextDescriptor failure (the prior SymbolIndexStore race fix did not clear release CI). Adds retroactive LayoutProtocol conformances for RelativeIndirectPointer, RelativeIndirectablePointer, and RelativeIndirectablePointerIntPair so that AnyLocatableLayoutWrapper accepts them. Also reverts --no-parallel on both test steps and @Suite(.serialized) on GenericSpecializationTests to check whether the pointer-type change is sufficient on its own. @Mutex on demangledNodeBySymbol is kept as independent concurrency hardening. --- .github/workflows/macOS.yml | 2 -- Sources/MachOSwiftSection/MachOFile+Swift.swift | 3 +++ Sources/MachOSwiftSection/MachOImage+Swift.swift | 7 +++++-- Tests/SwiftInterfaceTests/GenericSpecializationTests.swift | 1 - 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/workflows/macOS.yml b/.github/workflows/macOS.yml index f307aff3..c193f507 100644 --- a/.github/workflows/macOS.yml +++ b/.github/workflows/macOS.yml @@ -89,7 +89,6 @@ jobs: swift test \ -c debug \ --build-path .build-test-debug \ - --no-parallel \ --filter '\.(SymbolTestsCoreDumpSnapshotTests|SymbolTestsCoreInterfaceSnapshotTests|SymbolTestsCoreCoverageInvariantTests|STCoreE2ETests|STCoreTests|GenericSpecializationTests|MultiPayloadEnumTests|MetadataReaderDemanglingTests)(/|$)' - name: Build and run tests in release mode @@ -97,5 +96,4 @@ jobs: swift test \ -c release \ --build-path .build-test-release \ - --no-parallel \ --filter '\.(SymbolTestsCoreDumpSnapshotTests|SymbolTestsCoreInterfaceSnapshotTests|SymbolTestsCoreCoverageInvariantTests|STCoreE2ETests|STCoreTests|GenericSpecializationTests|MultiPayloadEnumTests|MetadataReaderDemanglingTests)(/|$)' diff --git a/Sources/MachOSwiftSection/MachOFile+Swift.swift b/Sources/MachOSwiftSection/MachOFile+Swift.swift index ffdaaa2a..59143115 100644 --- a/Sources/MachOSwiftSection/MachOFile+Swift.swift +++ b/Sources/MachOSwiftSection/MachOFile+Swift.swift @@ -121,3 +121,6 @@ extension MachOFile.Swift { } extension RelativeDirectPointer: LayoutProtocol {} +extension RelativeIndirectPointer: LayoutProtocol {} +extension RelativeIndirectablePointer: LayoutProtocol {} +extension RelativeIndirectablePointerIntPair: LayoutProtocol {} diff --git a/Sources/MachOSwiftSection/MachOImage+Swift.swift b/Sources/MachOSwiftSection/MachOImage+Swift.swift index e924da59..83b1f99d 100644 --- a/Sources/MachOSwiftSection/MachOImage+Swift.swift +++ b/Sources/MachOSwiftSection/MachOImage+Swift.swift @@ -110,8 +110,11 @@ extension MachOImage.Swift { let vmaddrSlide = try required(machO.vmaddrSlide) let start = try required(UnsafeRawPointer(bitPattern: section.address + vmaddrSlide)) let offset = start.bitPattern.int - machO.ptr.bitPattern.int - let pointerSize: Int = MemoryLayout>.size - let data: [AnyLocatableLayoutWrapper>] = try machO.readWrapperElements(offset: offset, numberOfElements: section.size / pointerSize) + typealias P = RelativeIndirectablePointer> + let pointerSize: Int = MemoryLayout

.size + let data: [AnyLocatableLayoutWrapper

] = try machO.readWrapperElements(offset: offset, numberOfElements: section.size / pointerSize) return try data.map { try $0.layout.resolve(from: $0.offset, in: machO) } } } + + diff --git a/Tests/SwiftInterfaceTests/GenericSpecializationTests.swift b/Tests/SwiftInterfaceTests/GenericSpecializationTests.swift index f0b000c0..b060fa6a 100644 --- a/Tests/SwiftInterfaceTests/GenericSpecializationTests.swift +++ b/Tests/SwiftInterfaceTests/GenericSpecializationTests.swift @@ -18,7 +18,6 @@ struct TestGenericStruct where A: Collection, B: Equatable, C: Hashable let c: C } -@Suite(.serialized) final class GenericSpecializationTests: MachOImageTests, @unchecked Sendable { override class var imageName: MachOImageName { .SwiftUICore } From 67a2824d47d8b024ad6c74ccf363a3118c6b0fba Mon Sep 17 00:00:00 2001 From: Mx-Iris Date: Wed, 22 Apr 2026 17:00:23 +0800 Subject: [PATCH 3/3] refactor(sections): mirror TypeMetadataRecord / ProtocolRecord per ABI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the Indirectable-as-everything workaround from ab0d033 with ABI-accurate wrappers for each Swift section entry type, matching swift/include/swift/ABI/Metadata.h: - TypeMetadataRecord (Metadata.h:2720) — struct around RelativeDirectPointerIntPair. Resolution branches on the 2-bit TypeReferenceKind like TargetTypeMetadataRecord::getContextDescriptor: direct → RelativeDirectPointer, indirect → RelativeIndirectPointer, ObjC kinds → nil (mirroring the runtime nullptr fallback at Metadata.h:2753). - ProtocolRecord (Metadata.h:2766) — struct around RelativeIndirectablePointerIntPair>. Optional Pointee matches nullable=true at MetadataRef.h:109; the reserved low-bit tag is kept as Bit (runtime reads only the pointer at MetadataLookup.cpp:821). - __swift5_proto — left on RelativeDirectPointer on both ends to match the naked typealias at Metadata.h:3054. Infra: - Add RelativeDirectPointerIntPair / TargetRelativeDirectPointerIntPair mirroring RelativePointer.h:575-619 (low 2 bits = int, no indirect bit — distinct from the existing indirectable variant). - MachOFile and MachOImage readers now throw strictly (no try?), symmetric across ends. - Drop retroactive LayoutProtocol conformances that are no longer reached. --- ...RelativeDirectPointerIntPairProtocol.swift | 27 ++++++++++ Sources/MachOPointers/RelativePointers.swift | 4 ++ .../TargetRelativeDirectPointerIntPair.swift | 11 ++++ .../MachOSwiftSection/MachOFile+Swift.swift | 33 +++++++++--- .../MachOSwiftSection/MachOImage+Swift.swift | 29 +++++++++-- .../Models/Protocol/ProtocolRecord.swift | 34 ++++++++++++ .../Models/Type/TypeMetadataRecord.swift | 52 +++++++++++++++++++ 7 files changed, 179 insertions(+), 11 deletions(-) create mode 100644 Sources/MachOPointers/Protocol/RelativeDirectPointerIntPairProtocol.swift create mode 100644 Sources/MachOPointers/TargetRelativeDirectPointerIntPair.swift create mode 100644 Sources/MachOSwiftSection/Models/Protocol/ProtocolRecord.swift create mode 100644 Sources/MachOSwiftSection/Models/Type/TypeMetadataRecord.swift diff --git a/Sources/MachOPointers/Protocol/RelativeDirectPointerIntPairProtocol.swift b/Sources/MachOPointers/Protocol/RelativeDirectPointerIntPairProtocol.swift new file mode 100644 index 00000000..c49a2be4 --- /dev/null +++ b/Sources/MachOPointers/Protocol/RelativeDirectPointerIntPairProtocol.swift @@ -0,0 +1,27 @@ +import MachOKit +import MachOReading +import MachOExtensions + +public protocol RelativeDirectPointerIntPairProtocol: RelativeDirectPointerProtocol { + typealias Integer = Value.RawValue + associatedtype Value: RawRepresentable where Value.RawValue: FixedWidthInteger + var relativeOffsetPlusInt: Offset { get } +} + +extension RelativeDirectPointerIntPairProtocol { + public var relativeOffset: Offset { + relativeOffsetPlusInt & ~mask + } + + public var mask: Offset { + Offset(MemoryLayout.alignment - 1) + } + + public var intValue: Integer { + numericCast(relativeOffsetPlusInt & mask) + } + + public var value: Value { + return Value(rawValue: intValue)! + } +} diff --git a/Sources/MachOPointers/RelativePointers.swift b/Sources/MachOPointers/RelativePointers.swift index 6adc24a3..f2b794cb 100644 --- a/Sources/MachOPointers/RelativePointers.swift +++ b/Sources/MachOPointers/RelativePointers.swift @@ -8,6 +8,10 @@ public typealias RelativeDirectPointer = TargetRelativeDire public typealias RelativeDirectRawPointer = TargetRelativeDirectPointer +public typealias RelativeDirectPointerIntPair = TargetRelativeDirectPointerIntPair where Integer.RawValue: FixedWidthInteger + +public typealias RelativeDirectRawPointerIntPair = TargetRelativeDirectPointerIntPair where Integer.RawValue: FixedWidthInteger + public typealias RelativeIndirectPointer = TargetRelativeIndirectPointer where Pointee == IndirectType.Resolved public typealias RelativeIndirectRawPointer = TargetRelativeIndirectPointer> diff --git a/Sources/MachOPointers/TargetRelativeDirectPointerIntPair.swift b/Sources/MachOPointers/TargetRelativeDirectPointerIntPair.swift new file mode 100644 index 00000000..3da4a59a --- /dev/null +++ b/Sources/MachOPointers/TargetRelativeDirectPointerIntPair.swift @@ -0,0 +1,11 @@ +import MachOReading +import MachOResolving +import MachOExtensions + +public struct TargetRelativeDirectPointerIntPair: RelativeDirectPointerIntPairProtocol where Value.RawValue: FixedWidthInteger { + public let relativeOffsetPlusInt: Offset + + public init(relativeOffsetPlusInt: Offset) { + self.relativeOffsetPlusInt = relativeOffsetPlusInt + } +} diff --git a/Sources/MachOSwiftSection/MachOFile+Swift.swift b/Sources/MachOSwiftSection/MachOFile+Swift.swift index 59143115..ac5c20ac 100644 --- a/Sources/MachOSwiftSection/MachOFile+Swift.swift +++ b/Sources/MachOSwiftSection/MachOFile+Swift.swift @@ -47,7 +47,7 @@ extension MachOFile.Swift: SwiftSectionRepresentable { public var contextDescriptors: [ContextDescriptorWrapper] { get throws { - return try _readRelativeDescriptors(from: .__swift5_types, in: machO) + (try? _readRelativeDescriptors(from: .__swift5_types2, in: machO)) + return try _readTypeMetadataRecords(from: .__swift5_types, in: machO) + (try? _readTypeMetadataRecords(from: .__swift5_types2, in: machO)) } } @@ -59,7 +59,7 @@ extension MachOFile.Swift: SwiftSectionRepresentable { public var protocolDescriptors: [ProtocolDescriptor] { get throws { - return try _readRelativeDescriptors(from: .__swift5_protos, in: machO) + return try _readProtocolRecords(from: .__swift5_protos, in: machO) } } @@ -116,11 +116,32 @@ extension MachOFile.Swift { section.offset } let data: [AnyLocatableLayoutWrapper>] = try machO.readWrapperElements(offset: offset, numberOfElements: section.size / pointerSize) - return data.compactMap { try? $0.layout.resolve(from: $0.offset, in: machO) } + return try data.map { try $0.layout.resolve(from: $0.offset, in: machO) } + } + + private func _readTypeMetadataRecords(from swiftMachOSection: MachOSwiftSectionName, in machO: MachOFile) throws -> [ContextDescriptorWrapper] { + let section = try machO.section(for: swiftMachOSection) + let offset = if let cache = machO.cache { + section.address - cache.mainCacheHeader.sharedRegionStart.cast() + } else { + section.offset + } + let recordSize = MemoryLayout.size + let records: [TypeMetadataRecord] = try machO.readWrapperElements(offset: offset, numberOfElements: section.size / recordSize) + return try records.compactMap { try $0.contextDescriptor(in: machO) } + } + + private func _readProtocolRecords(from swiftMachOSection: MachOSwiftSectionName, in machO: MachOFile) throws -> [ProtocolDescriptor] { + let section = try machO.section(for: swiftMachOSection) + let offset = if let cache = machO.cache { + section.address - cache.mainCacheHeader.sharedRegionStart.cast() + } else { + section.offset + } + let recordSize = MemoryLayout.size + let records: [ProtocolRecord] = try machO.readWrapperElements(offset: offset, numberOfElements: section.size / recordSize) + return try records.compactMap { try $0.protocolDescriptor(in: machO) } } } extension RelativeDirectPointer: LayoutProtocol {} -extension RelativeIndirectPointer: LayoutProtocol {} -extension RelativeIndirectablePointer: LayoutProtocol {} -extension RelativeIndirectablePointerIntPair: LayoutProtocol {} diff --git a/Sources/MachOSwiftSection/MachOImage+Swift.swift b/Sources/MachOSwiftSection/MachOImage+Swift.swift index 83b1f99d..bdf7c1ba 100644 --- a/Sources/MachOSwiftSection/MachOImage+Swift.swift +++ b/Sources/MachOSwiftSection/MachOImage+Swift.swift @@ -47,7 +47,7 @@ extension MachOImage.Swift: SwiftSectionRepresentable { public var contextDescriptors: [ContextDescriptorWrapper] { get throws { - return try _readRelativeDescriptors(from: .__swift5_types, in: machO) + (try? _readRelativeDescriptors(from: .__swift5_types2, in: machO)) + return try _readTypeMetadataRecords(from: .__swift5_types, in: machO) + (try? _readTypeMetadataRecords(from: .__swift5_types2, in: machO)) } } @@ -59,7 +59,7 @@ extension MachOImage.Swift: SwiftSectionRepresentable { public var protocolDescriptors: [ProtocolDescriptor] { get throws { - return try _readRelativeDescriptors(from: .__swift5_protos, in: machO) + return try _readProtocolRecords(from: .__swift5_protos, in: machO) } } @@ -110,11 +110,30 @@ extension MachOImage.Swift { let vmaddrSlide = try required(machO.vmaddrSlide) let start = try required(UnsafeRawPointer(bitPattern: section.address + vmaddrSlide)) let offset = start.bitPattern.int - machO.ptr.bitPattern.int - typealias P = RelativeIndirectablePointer> - let pointerSize: Int = MemoryLayout

.size - let data: [AnyLocatableLayoutWrapper

] = try machO.readWrapperElements(offset: offset, numberOfElements: section.size / pointerSize) + let pointerSize: Int = MemoryLayout>.size + let data: [AnyLocatableLayoutWrapper>] = try machO.readWrapperElements(offset: offset, numberOfElements: section.size / pointerSize) return try data.map { try $0.layout.resolve(from: $0.offset, in: machO) } } + + private func _readTypeMetadataRecords(from swiftMachOSection: MachOSwiftSectionName, in machO: MachOImage) throws -> [ContextDescriptorWrapper] { + let section = try machO.section(for: swiftMachOSection) + let vmaddrSlide = try required(machO.vmaddrSlide) + let start = try required(UnsafeRawPointer(bitPattern: section.address + vmaddrSlide)) + let offset = start.bitPattern.int - machO.ptr.bitPattern.int + let recordSize = MemoryLayout.size + let records: [TypeMetadataRecord] = try machO.readWrapperElements(offset: offset, numberOfElements: section.size / recordSize) + return try records.compactMap { try $0.contextDescriptor(in: machO) } + } + + private func _readProtocolRecords(from swiftMachOSection: MachOSwiftSectionName, in machO: MachOImage) throws -> [ProtocolDescriptor] { + let section = try machO.section(for: swiftMachOSection) + let vmaddrSlide = try required(machO.vmaddrSlide) + let start = try required(UnsafeRawPointer(bitPattern: section.address + vmaddrSlide)) + let offset = start.bitPattern.int - machO.ptr.bitPattern.int + let recordSize = MemoryLayout.size + let records: [ProtocolRecord] = try machO.readWrapperElements(offset: offset, numberOfElements: section.size / recordSize) + return try records.compactMap { try $0.protocolDescriptor(in: machO) } + } } diff --git a/Sources/MachOSwiftSection/Models/Protocol/ProtocolRecord.swift b/Sources/MachOSwiftSection/Models/Protocol/ProtocolRecord.swift new file mode 100644 index 00000000..05ecb79a --- /dev/null +++ b/Sources/MachOSwiftSection/Models/Protocol/ProtocolRecord.swift @@ -0,0 +1,34 @@ +import Foundation +import MachOKit +import MachOFoundation + +/// Mirrors `TargetProtocolRecord` from +/// `swift/include/swift/ABI/Metadata.h:2766`. One entry per 4-byte slot of +/// `__swift5_protos`. +/// +/// The C++ declaration stores a single +/// `RelativeContextPointerIntPair` +/// (`MetadataRef.h:109` — a `RelativeIndirectablePointerIntPair` with +/// `nullable=true`). The low bit is the indirect flag handled by the pointer +/// itself; the next bit ("reserved for future use", see +/// `Metadata.h:2769`) is exposed via `Bit` and currently ignored by the +/// runtime (`MetadataLookup.cpp:821` only calls `getPointer()`). +public struct ProtocolRecord: ResolvableLocatableLayoutWrapper { + public struct Layout: LayoutProtocol { + public let `protocol`: RelativeIndirectablePointerIntPair> + } + + public let offset: Int + public var layout: Layout + + public init(layout: Layout, offset: Int) { + self.offset = offset + self.layout = layout + } +} + +extension ProtocolRecord { + public func protocolDescriptor(in machO: MachO) throws -> ProtocolDescriptor? { + try layout.protocol.resolve(from: offset(of: \.protocol), in: machO) + } +} diff --git a/Sources/MachOSwiftSection/Models/Type/TypeMetadataRecord.swift b/Sources/MachOSwiftSection/Models/Type/TypeMetadataRecord.swift new file mode 100644 index 00000000..ae510848 --- /dev/null +++ b/Sources/MachOSwiftSection/Models/Type/TypeMetadataRecord.swift @@ -0,0 +1,52 @@ +import Foundation +import MachOKit +import MachOFoundation + +/// Mirrors `TargetTypeMetadataRecord` from +/// `swift/include/swift/ABI/Metadata.h:2720`. One entry per 4-byte slot of +/// `__swift5_types` / `__swift5_types2`. +/// +/// In C++ the record is a union over two arms, both +/// `RelativeDirectPointerIntPair<…, TypeReferenceKind>` with identical +/// in-memory layout, so a single storage field is enough; the +/// `TypeReferenceKind` tag picks which arm to resolve at access time. +public struct TypeMetadataRecord: ResolvableLocatableLayoutWrapper { + public struct Layout: LayoutProtocol { + public let nominalTypeDescriptor: RelativeDirectPointerIntPair + } + + public let offset: Int + public var layout: Layout + + public init(layout: Layout, offset: Int) { + self.offset = offset + self.layout = layout + } +} + +extension TypeMetadataRecord { + public var typeKind: TypeReferenceKind { + return layout.nominalTypeDescriptor.value + } + + /// Resolves the referenced context descriptor, branching on + /// `TypeReferenceKind` the same way Swift runtime does in + /// `TargetTypeMetadataRecord::getContextDescriptor()` + /// (`swift/include/swift/ABI/Metadata.h:2743`). ObjC kinds are never + /// populated in this section (see the comment at Metadata.h:2751); return + /// `nil` for them to mirror the runtime's `nullptr` fallback. + public func contextDescriptor(in machO: MachO) throws -> ContextDescriptorWrapper? { + let fieldOffset = offset(of: \.nominalTypeDescriptor) + let relativeOffset = layout.nominalTypeDescriptor.relativeOffset + switch typeKind { + case .directTypeDescriptor: + let pointer = RelativeDirectPointer(relativeOffset: relativeOffset) + return try pointer.resolve(from: fieldOffset, in: machO) + case .indirectTypeDescriptor: + let pointer = RelativeIndirectPointer>(relativeOffset: relativeOffset) + return try pointer.resolve(from: fieldOffset, in: machO) + case .directObjCClassName, .indirectObjCClass: + return nil + } + } +}