diff --git a/.github/workflows/macOS.yml b/.github/workflows/macOS.yml index b986e4c4..c193f507 100644 --- a/.github/workflows/macOS.yml +++ b/.github/workflows/macOS.yml @@ -89,11 +89,11 @@ jobs: swift test \ -c debug \ --build-path .build-test-debug \ - --filter '\.(SymbolTestsCoreDumpSnapshotTests|SymbolTestsCoreInterfaceSnapshotTests|SymbolTestsCoreCoverageInvariantTests|STCoreE2ETests|STCoreTests|GenericSpecializationTests|GenericSpecializerAPITests|MultiPayloadEnumTests|MetadataReaderDemanglingTests)(/|$)' + --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)(/|$)' + --filter '\.(SymbolTestsCoreDumpSnapshotTests|SymbolTestsCoreInterfaceSnapshotTests|SymbolTestsCoreCoverageInvariantTests|STCoreE2ETests|STCoreTests|GenericSpecializationTests|MultiPayloadEnumTests|MetadataReaderDemanglingTests)(/|$)' 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 ffdaaa2a..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,7 +116,31 @@ 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) } } } diff --git a/Sources/MachOSwiftSection/MachOImage+Swift.swift b/Sources/MachOSwiftSection/MachOImage+Swift.swift index e924da59..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) } } @@ -114,4 +114,26 @@ extension MachOImage.Swift { 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 + } + } +} 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..b060fa6a 100644 --- a/Tests/SwiftInterfaceTests/GenericSpecializationTests.swift +++ b/Tests/SwiftInterfaceTests/GenericSpecializationTests.swift @@ -72,12 +72,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()