Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions Docs/generation.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,14 +145,15 @@ valid entry. Both preserve outer-table order and validate the outer table and
each nonempty inner member table before decoding; an empty inner list does not
require an otherwise unused entry size. Regular method, property, and ivar
tables use the same finite count and byte budgets, checked arithmetic, and
complete-range validation. Loaded class, protocol, and category root tables
validate both the complete pointer table and each referenced layout before
decoding. One malformed relative list, regular member-table entry, loaded-root
entry, or loaded class/category relationship preserves its readable siblings
and produces a typed degradation; unloaded relative lists are skipped without
warning. These guarantees cover structural table, list, and layout ranges; they
do not validate referenced C strings, loaded class RW-extension arrays, or
file-backed root-section tables. Once that target is published,
complete-range validation. File-backed and loaded class, protocol, and category
root tables validate both the complete pointer table and each referenced layout
before decoding. File-backed roots also validate raw section/segment coordinates
and map cache offsets to their actual subcache backing. One malformed relative
list, regular member-table entry, root entry, or loaded class/category
relationship preserves its readable siblings and produces a typed degradation;
unloaded relative lists are skipped without warning. These guarantees cover
structural table, list, and layout ranges; they do not validate referenced C
strings or loaded class RW-extension arrays. Once that target is published,
PrivateHeaderKit reports the precise owner and degradation as an
`objc-metadata-warning` and persists the warning in `generation.sqlite`. A
bounded diagnostics report records when additional warnings were omitted, so
Expand Down
6 changes: 3 additions & 3 deletions Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,15 @@ let package = Package(
),
.package(
url: "https://github.com/lynnswap/MachOObjCSection.git",
revision: "41a1652d95c34bbb2fdf32ba4b7f5cd9d3ba20ba"
revision: "8f0ff76f02c0865422a72662177b5e687e43522d"
),
.package(
url: "https://github.com/MxIris-Reverse-Engineering/swift-objc-dump.git",
from: "0.8.100"
),
.package(
url: "https://github.com/lynnswap/MachOSwiftSection.git",
revision: "3344bd04e6c0819b17df126bea57bae1bbed3022"
revision: "06ed57ab39fc24956f9079beeb407fffa30d50f4"
),
.package(
url: "https://github.com/MxIris-Reverse-Engineering/swift-demangling",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1159,7 +1159,9 @@ private func dumpObjC(
var metadata: CollectedObjCMetadata
switch machO {
case .file(let file):
metadata = collectObjCMetadata(from: file.objc, in: machO, options: options)
let roots = file.objc.readRoots()
options.objcDiagnostics.append(contentsOf: roots.tableDiagnostics)
metadata = collectObjCMetadata(from: roots, in: machO, options: options)
case .loaded(let image):
let roots = image.objc.readRoots()
options.objcDiagnostics.append(contentsOf: roots.tableDiagnostics)
Expand Down Expand Up @@ -1216,7 +1218,7 @@ private protocol RawDumpObjCRoots {
var categories2_32: [ObjCCategory32]? { get }
}

extension MachOFile.ObjectiveC: RawDumpObjCRoots {}
extension ObjCFileRootReadResult: RawDumpObjCRoots {}
extension ObjCImageRootReadResult: RawDumpObjCRoots {}

@discardableResult
Expand Down
16 changes: 16 additions & 0 deletions Sources/PrivateHeaderKitRawDumpCore/RawDumpObjCDiagnostics.swift
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,11 @@ private func rawDumpMetadataTableDiagnostic(
case let .member(subject, kind):
ownerDescription = subjectDescription(subject)
metadataDescription = "\(metadataTableMemberKindDescription(kind)) metadata table"
case let .fileRoot(section, pointerWidth):
ownerDescription = "Objective-C file-backed roots"
metadataDescription =
"\(pointerWidthDescription(pointerWidth))"
+ " \(rootSectionDescription(section)) root table"
case let .loadedImageRoot(section, pointerWidth):
ownerDescription = "Objective-C loaded-image roots"
metadataDescription =
Expand Down Expand Up @@ -632,6 +637,17 @@ private func failureDescription(
"list pointer could not be rebased"
case .missingListBackingData:
"list pointer has no readable backing data"
case let .invalidFileRootOffset(sectionAddress, sharedRegionStart):
"root section address \(sectionAddress) cannot be represented relative"
+ " to shared-region start \(sharedRegionStart)"
case .missingFileRootBackingData(let sectionAddress):
"root section at address \(sectionAddress) has no readable backing data"
case .unresolvedFileRootPointer(let rawValue):
"root pointer value \(rawValue) could not be rebased"
case .invalidReferencedFileOffset(let offset):
"resolved root offset \(offset) cannot be represented"
case .missingReferencedFileBackingData(let logicalOffset):
"resolved root at logical offset \(logicalOffset) has no readable backing data"
case let .unreadableFileHeader(offset, byteCount):
"file header at offset \(offset) is not readable for \(byteCount) bytes"
case .invalidEntryLogicalOffset:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ struct PrivateHeaderKitHelperProtocolTests {
let state = try #require(pin["state"] as? [String: Any])

#expect(pin["location"] as? String == "https://github.com/lynnswap/MachOObjCSection.git")
#expect(state["revision"] as? String == "41a1652d95c34bbb2fdf32ba4b7f5cd9d3ba20ba")
#expect(state["revision"] as? String == "8f0ff76f02c0865422a72662177b5e687e43522d")
#expect(state["version"] == nil)

let swiftSectionPin = try #require(
Expand All @@ -307,7 +307,7 @@ struct PrivateHeaderKitHelperProtocolTests {
)
#expect(
swiftSectionState["revision"] as? String
== "3344bd04e6c0819b17df126bea57bae1bbed3022"
== "06ed57ab39fc24956f9079beeb407fffa30d50f4"
)
#expect(swiftSectionState["version"] == nil)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,27 @@ import Testing
@testable import PrivateHeaderKitRawDumpCore

struct ObjCMemberListDiagnosticsTests {
@Test func fileRootDiagnosticsUseTheExistingBoundedMemberChannel() throws {
let fixture = try InvalidFileRootFixture()
let result = fixture.machO.objc.readRoots()

#expect(result.classes64?.isEmpty == true)
#expect(result.tableDiagnostics.count == 1)

let accumulator = RawDumpObjCDiagnosticsAccumulator()
accumulator.append(contentsOf: result.tableDiagnostics)

let report = accumulator.report
#expect(report.diagnostics.count == 1)
#expect(report.omittedDiagnosticCount == 0)
#expect(report.diagnostics.first?.owner == "Objective-C file-backed roots")
#expect(
report.diagnostics.first?.degradation
== "64-bit class-list root table could not be fully read:"
+ " section size 9 is not a multiple of pointer size 8"
)
}

@Test func regularTableDiagnosticsUseTheExistingBoundedMemberChannel() throws {
let fixture = try InvalidMemberListFixture(
memberDiagnosticCount: 0,
Expand Down Expand Up @@ -223,6 +244,64 @@ struct ObjCMemberListDiagnosticsTests {
}
}

private final class InvalidFileRootFixture {
let machO: MachOFile
private let url: URL

init() throws {
let fileSize = 0x1000
let rootOffset = 0x400
let vmAddress: UInt64 = 0x1_0000_0000
var data = Data(count: fileSize)

var header = mach_header_64()
header.magic = UInt32(MH_MAGIC_64)
header.cputype = CPU_TYPE_ARM64
header.cpusubtype = CPU_SUBTYPE_ARM64_ALL
header.filetype = UInt32(MH_DYLIB)
header.ncmds = 1
header.sizeofcmds = UInt32(
MemoryLayout<segment_command_64>.size + MemoryLayout<section_64>.size
)
data.storeValue(header, at: 0)

var segment = segment_command_64()
segment.cmd = UInt32(LC_SEGMENT_64)
segment.cmdsize = header.sizeofcmds
segment.vmaddr = vmAddress
segment.vmsize = UInt64(fileSize)
segment.fileoff = 0
segment.filesize = UInt64(fileSize)
segment.maxprot = VM_PROT_READ
segment.initprot = VM_PROT_READ
segment.nsects = 1
segment.writeName("__DATA")
data.storeValue(segment, at: MemoryLayout<mach_header_64>.size)

var section = section_64()
section.addr = vmAddress + UInt64(rootOffset)
section.size = 9
section.offset = UInt32(rootOffset)
section.align = 3
section.writeSectionName("__objc_classlist", segmentName: "__DATA")
data.storeValue(
section,
at: MemoryLayout<mach_header_64>.size
+ MemoryLayout<segment_command_64>.size
)

url = FileManager.default.temporaryDirectory.appendingPathComponent(
"PrivateHeaderKitInvalidFileRoot-\(UUID().uuidString)"
)
try data.write(to: url)
machO = try MachOFile(url: url)
}

deinit {
try? FileManager.default.removeItem(at: url)
}
}

private final class InvalidMemberListFixture {
let machO: MachOFile
let objcClass: ObjCClass64
Expand Down Expand Up @@ -449,6 +528,25 @@ private struct RawIvar64 {
let size: UInt32
}

private extension segment_command_64 {
mutating func writeName(_ value: String) {
Swift.withUnsafeMutableBytes(of: &segname) { destination in
destination.copyBytes(from: value.utf8)
}
}
}

private extension section_64 {
mutating func writeSectionName(_ value: String, segmentName: String) {
Swift.withUnsafeMutableBytes(of: &sectname) { destination in
destination.copyBytes(from: value.utf8)
}
Swift.withUnsafeMutableBytes(of: &segname) { destination in
destination.copyBytes(from: segmentName.utf8)
}
}
}

private extension Data {
func loadValue<Value>(_ type: Value.Type, at offset: Int) -> Value {
self[offset..<(offset + MemoryLayout<Value>.size)].withUnsafeBytes {
Expand Down