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
24 changes: 16 additions & 8 deletions Docs/generation.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,14 +143,22 @@ Loaded-image reads of relative method/property list-of-lists consult each
entry's runtime loaded state, while file-backed reads inspect every structurally
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. One malformed loaded list preserves its
valid siblings and produces a typed member-list degradation; unloaded lists are
skipped without warning. 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 malformed metadata cannot grow process
output without limit. Live warning presentation is also capped across the run;
one aggregate warning points to the retained per-target details in the database.
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,
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
malformed metadata cannot grow process output without limit. Live warning
presentation is also capped across the run; one aggregate warning points to the
retained per-target details in the database.

State, attempts, publication intent, and run diagnostics are stored in
`generation.sqlite`, outside the published header tree. The `.privateheaderkit`
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: "0d17e3d77556991dc128aa92547ea1b1ea8f9e2e"
revision: "41a1652d95c34bbb2fdf32ba4b7f5cd9d3ba20ba"
),
.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: "a7e5982ed7de5dab5dec76036682ea55825b77a8"
revision: "3344bd04e6c0819b17df126bea57bae1bbed3022"
),
.package(
url: "https://github.com/MxIris-Reverse-Engineering/swift-demangling",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1156,11 +1156,14 @@ private func dumpObjC(
options: DumpOptions,
fileManager: FileManager
) async throws {
var metadata = switch machO {
var metadata: CollectedObjCMetadata
switch machO {
case .file(let file):
collectObjCMetadata(from: file.objc, in: machO, options: options)
metadata = collectObjCMetadata(from: file.objc, in: machO, options: options)
case .loaded(let image):
collectObjCMetadata(from: image.objc, in: machO, options: options)
let roots = image.objc.readRoots()
options.objcDiagnostics.append(contentsOf: roots.tableDiagnostics)
metadata = collectObjCMetadata(from: roots, in: machO, options: options)
}

#if canImport(ObjectiveC)
Expand Down Expand Up @@ -1198,6 +1201,24 @@ private struct CollectedObjCMetadata {
var runtimeOriginClassNames: Set<String> = []
}

private protocol RawDumpObjCRoots {
var classes64: [ObjCClass64]? { get }
var classes32: [ObjCClass32]? { get }
var nonLazyClasses64: [ObjCClass64]? { get }
var nonLazyClasses32: [ObjCClass32]? { get }
var protocols64: [ObjCProtocol64]? { get }
var protocols32: [ObjCProtocol32]? { get }
var categories64: [ObjCCategory64]? { get }
var categories32: [ObjCCategory32]? { get }
var nonLazyCategories64: [ObjCCategory64]? { get }
var nonLazyCategories32: [ObjCCategory32]? { get }
var categories2_64: [ObjCCategory64]? { get }
var categories2_32: [ObjCCategory32]? { get }
}

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

@discardableResult
func supplementMissingRuntimeClassInfos(
_ runtimeInfos: [ObjCClassInfo],
Expand Down Expand Up @@ -1242,8 +1263,8 @@ private func runtimeClassSortKey(_ name: String) -> String {
return "\(rank):\(name)"
}

private func collectObjCMetadata<Section: ObjCSectionRepresentable>(
from objc: Section,
private func collectObjCMetadata<Roots: RawDumpObjCRoots>(
from objc: Roots,
in machO: RawMachO,
options: DumpOptions
) -> CollectedObjCMetadata {
Expand Down
220 changes: 220 additions & 0 deletions Sources/PrivateHeaderKitRawDumpCore/RawDumpObjCDiagnostics.swift
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ final class RawDumpObjCDiagnosticsAccumulator {
append(contentsOf: result.fieldDiagnostics)
append(contentsOf: result.diagnostics)
append(contentsOf: result.memberListDiagnostics)
append(contentsOf: result.tableDiagnostics)
}

func append(contentsOf diagnostics: [ObjCMetadataFieldDiagnostic]) {
Expand All @@ -95,6 +96,12 @@ final class RawDumpObjCDiagnosticsAccumulator {
}
}

func append(contentsOf diagnostics: [ObjCMetadataTableDiagnostic]) {
for diagnostic in diagnostics {
memberDiagnostics.append(privateHeaderKitDiagnostic(from: diagnostic))
}
}

var report: PrivateHeaderKitRawDumpDiagnosticsReport {
var selected: [PrivateHeaderKitRawDumpDiagnostic] = []
selected.reserveCapacity(PrivateHeaderKitRawDumpDiagnosticsReport.maximumDiagnosticCount)
Expand Down Expand Up @@ -251,6 +258,45 @@ private func privateHeaderKitDiagnostic(
)
}

private func privateHeaderKitDiagnostic(
from diagnostic: ObjCMetadataTableDiagnostic
) -> PrivateHeaderKitRawDumpDiagnostic {
rawDumpMetadataTableDiagnostic(
owner: diagnostic.owner,
site: diagnostic.site,
failure: diagnostic.failure
)
}

private func rawDumpMetadataTableDiagnostic(
owner: ObjCMetadataTableDiagnostic.Owner,
site: ObjCMetadataTableDiagnostic.Site,
failure: ObjCMetadataTableDiagnostic.Failure
) -> PrivateHeaderKitRawDumpDiagnostic {
let ownerDescription: String
let metadataDescription: String
switch owner {
case let .member(subject, kind):
ownerDescription = subjectDescription(subject)
metadataDescription = "\(metadataTableMemberKindDescription(kind)) metadata table"
case let .loadedImageRoot(section, pointerWidth):
ownerDescription = "Objective-C loaded-image roots"
metadataDescription =
"\(pointerWidthDescription(pointerWidth))"
+ " \(rootSectionDescription(section)) root table"
case let .loadedRelationship(subject, role):
ownerDescription = subjectDescription(subject)
metadataDescription = "\(loadedRelationshipDescription(role)) relationship"
}

return PrivateHeaderKitRawDumpDiagnostic(
owner: ownerDescription,
degradation:
"\(metadataDescription)\(metadataTableSiteDescription(site))"
+ " could not be fully read: \(failureDescription(failure))"
)
}

func rawDumpMemberListDiagnostic(
className: String,
kind: ObjCMemberListDiagnostic.Kind,
Expand All @@ -276,6 +322,81 @@ private func memberKindDescription(_ kind: ObjCMemberListDiagnostic.Kind) -> Str
}
}

private func metadataTableMemberKindDescription(
_ kind: ObjCMetadataTableDiagnostic.MemberKind
) -> String {
switch kind {
case .ivar: "ivar"
case .instanceMethod: "instance-method"
case .classMethod: "class-method"
case .optionalInstanceMethod: "optional-instance-method"
case .optionalClassMethod: "optional-class-method"
case .instanceProperty: "instance-property"
case .classProperty: "class-property"
}
}

private func rootSectionDescription(
_ section: ObjCMetadataTableDiagnostic.LoadedImageRootSection
) -> String {
switch section {
case .classList: "class-list"
case .nonLazyClassList: "non-lazy-class-list"
case .protocolList: "protocol-list"
case .categoryList: "category-list"
case .nonLazyCategoryList: "non-lazy-category-list"
case .categoryList2: "category-list-2"
}
}

private func pointerWidthDescription(
_ pointerWidth: ObjCMetadataTableDiagnostic.PointerWidth
) -> String {
switch pointerWidth {
case .bits32: "32-bit"
case .bits64: "64-bit"
}
}

private func loadedRelationshipDescription(
_ role: ObjCMetadataTableDiagnostic.LoadedRelationshipRole
) -> String {
switch role {
case .metaclass: "metaclass"
case .superclass: "superclass"
case .categoryClass: "category-class"
case .categoryStubClass: "category-stub-class"
}
}

private func metadataTableSiteDescription(
_ site: ObjCMetadataTableDiagnostic.Site
) -> String {
switch site {
case .table(let provenance), .relationship(let provenance):
provenanceDescription(provenance)
case let .entry(index, provenance):
" entry \(index)\(provenanceDescription(provenance))"
}
}

private func provenanceDescription(
_ provenance: ObjCMetadataTableDiagnostic.Provenance
) -> String {
var coordinates: [String] = []
if let logicalOffset = provenance.logicalOffset {
coordinates.append("logical offset \(logicalOffset)")
}
if let fileOffset = provenance.fileOffset {
coordinates.append("file offset \(fileOffset)")
}
if let imageAddress = provenance.imageAddress {
coordinates.append("image address \(imageAddress)")
}
guard !coordinates.isEmpty else { return "" }
return " (\(coordinates.joined(separator: ", ")))"
}

private func memberLocationDescription(
_ location: ObjCMemberListDiagnostic.Location
) -> String {
Expand Down Expand Up @@ -308,6 +429,20 @@ private func subjectDescription(_ subject: ObjCMetadataFieldDiagnostic.Subject)
}
}

private func subjectDescription(
_ subject: ObjCMetadataTableDiagnostic.MetadataSubject
) -> String {
switch subject {
case .class(let name):
"Objective-C class \(boundedMetadataString(name))"
case .protocol(let name):
"Objective-C protocol \(boundedMetadataString(name))"
case let .category(className, name):
"Objective-C category \(boundedMetadataString(className))"
+ "(\(boundedMetadataString(name)))"
}
}

private func classRoleDescription(
_ role: ObjCMetadataFieldDiagnostic.ClassRole
) -> String {
Expand Down Expand Up @@ -456,3 +591,88 @@ private func failureDescription(
"image range at address \(address) is not readable for \(byteCount) bytes"
}
}

private func failureDescription(
_ failure: ObjCMetadataTableDiagnostic.Failure
) -> String {
switch failure {
case .unsupportedListEncoding:
"list encoding is unsupported by this reader"
case .invalidListOffset(let offset):
"list offset \(offset) is not a readable address"
case .invalidElementCount(let count):
"element count \(count) cannot be represented"
case .invalidSignedElementCount(let count):
"signed element count \(count) is negative"
case .invalidElementStride(let stride):
"element stride \(stride) cannot be represented"
case let .elementStrideTooSmall(advertised, minimum):
"element stride \(advertised) is smaller than \(minimum)"
case let .unexpectedElementStride(advertised, expected):
"element stride \(advertised) does not match expected size \(expected)"
case let .misalignedTableOffset(offset, requiredAlignment):
"table offset \(offset) is not aligned to \(requiredAlignment) bytes"
case let .misalignedTableAddress(address, requiredAlignment):
"table address \(address) is not aligned to \(requiredAlignment) bytes"
case let .excessiveElementCount(actual, maximum):
"element count \(actual) exceeds the safety limit \(maximum)"
case let .excessiveByteCount(actual, maximum):
"table size \(actual) bytes exceeds the safety limit \(maximum)"
case let .byteCountOverflow(elementCount, elementSize):
"byte count overflowed for \(elementCount) elements of size \(elementSize)"
case let .rangeOverflow(startOffset, byteCount):
"range overflowed from offset \(startOffset) for \(byteCount) bytes"
case let .unreadableFileRange(offset, byteCount):
"file range at offset \(offset) is not readable for \(byteCount) bytes"
case let .unreadableImageRange(address, byteCount):
"image range at address \(address) is not readable for \(byteCount) bytes"
case .invalidFileListOffset(let offset):
"file list offset \(offset) cannot be represented"
case .unresolvedListPointer:
"list pointer could not be rebased"
case .missingListBackingData:
"list pointer has no readable backing data"
case let .unreadableFileHeader(offset, byteCount):
"file header at offset \(offset) is not readable for \(byteCount) bytes"
case .invalidEntryLogicalOffset:
"entry logical offset overflowed"
case .invalidMethodImplementationOffset:
"method implementation offset overflowed"
case .invalidRelativeDisplacement:
"relative field displacement overflowed"
case let .invalidSectionByteCount(byteCount, pointerSize):
"section size \(byteCount) is not a multiple of pointer size \(pointerSize)"
case let .invalidSectionCoordinates(
sectionAddress,
sectionSize,
sectionFileOffset,
segmentAddress,
segmentSize,
segmentFileOffset,
segmentFileSize
):
"section coordinates (address \(sectionAddress), size \(sectionSize),"
+ " file offset \(sectionFileOffset)) are outside segment coordinates"
+ " (address \(segmentAddress), size \(segmentSize),"
+ " file offset \(segmentFileOffset), file size \(segmentFileSize))"
case .missingImageBaseSegment:
"the loaded image has no __TEXT base segment"
case let .invalidLoadedSectionAddress(
imageBase,
imageVirtualMemoryAddress,
sectionAddress
):
"section address \(sectionAddress) cannot be mapped from image base"
+ " \(imageBase) and image virtual address \(imageVirtualMemoryAddress)"
case .invalidPointer(let rawValue):
"pointer value \(rawValue) is not a readable address"
case .missingReferencedImage(let address):
"address \(address) does not belong to an available loaded image"
case let .unreadableReferencedLayout(address, byteCount):
"referenced layout at image address \(address)"
+ " is not readable for \(byteCount) bytes"
case let .invalidEntryArithmetic(baseAddress, targetAddress):
"target address \(targetAddress) cannot be represented relative"
+ " to base address \(baseAddress)"
}
}
Loading