From c9ac96072e2f995f55817cfdc417caebfb9d46d8 Mon Sep 17 00:00:00 2001 From: Kazuki Nakashima <65545348+lynnswap@users.noreply.github.com> Date: Wed, 26 Aug 2026 13:39:04 +0900 Subject: [PATCH 1/6] chore: track issue 87 remediation --- Docs/issue-remediation-progress.md | 43 ++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 Docs/issue-remediation-progress.md diff --git a/Docs/issue-remediation-progress.md b/Docs/issue-remediation-progress.md new file mode 100644 index 0000000..4f7d4b5 --- /dev/null +++ b/Docs/issue-remediation-progress.md @@ -0,0 +1,43 @@ +# Issue remediation progress + +Base: `main` at `600a3c275621dde24ac6d9b72d5cfec7fedb0f00` + +## Current issue: #87 + +Branch: `codex/issue-87-bounded-loaded-objc-rw-arrays` + +Goal: + +- Make loaded `class_rw_ext_t` method, property, and protocol list fields + validate their tagged representation, table ranges, and referenced lists + before dereferencing runtime memory. +- Preserve existing public query signatures as compatibility projections while + exposing typed recoverable diagnostics from one checked owner. + +Confirmed scope: + +- Single-list, pointer-array, and relative-list tagged representations. +- 32/64-bit pointer width, exact address arithmetic, complete readable ranges, + shared count/byte budgets, and ordered good/bad/good preservation. +- MachOObjCSection implementation, its direct tests, coherent downstream pins, + and contract documentation required by the final consumer diff. + +Non-goals: + +- Referenced C-string payload hardening. +- Reworking regular member tables or loaded/file root sections completed by + #83 and #88. +- Attributing the original iOS 27 failures to this currently unobserved surface. + +Design gate: pending evidence-first owner and runtime-ABI audits. + +Validation gate: + +- Deterministic 32/64 loaded-image fixtures for every tagged representation, + exact boundaries, truncation, excessive counts, unreadable pointers, and + good/bad/good ordering. +- #60, #65, #79, #83, and #88 regressions; Debug/Release and Apple + cross-builds. +- Exact MachOObjCSection → MachOSwiftSection → PrivateHeaderKit cohort pins. +- PrivateHeaderKit full tests, release-script tests, codex-review, Ready PR, + GitHub review/CI, and merge to `main`. From 0f91a7d7e2f50b7d81ae20d50ca887ed0d1012a4 Mon Sep 17 00:00:00 2001 From: Kazuki Nakashima <65545348+lynnswap@users.noreply.github.com> Date: Wed, 26 Aug 2026 13:58:01 +0900 Subject: [PATCH 2/6] docs: approve issue 87 design gate --- Docs/issue-remediation-progress.md | 114 ++++++++++++++++++++++++++++- 1 file changed, 113 insertions(+), 1 deletion(-) diff --git a/Docs/issue-remediation-progress.md b/Docs/issue-remediation-progress.md index 4f7d4b5..52b7515 100644 --- a/Docs/issue-remediation-progress.md +++ b/Docs/issue-remediation-progress.md @@ -29,7 +29,114 @@ Non-goals: #83 and #88. - Attributing the original iOS 27 failures to this currently unobserved surface. -Design gate: pending evidence-first owner and runtime-ABI audits. +## Design gate + +Approved against MachOObjCSection +`8f0ff76f02c0865422a72662177b5e687e43522d`. + +Runtime representation contract: + +- Apple objc4 `951.7` and current `main` encode `class_rw_ext_t` list arrays + as a native-width `PointerUnion4`: low tags `0 = single`, `1 = pointer + array`, `2 = relative list-of-lists`, `3 = reserved/dummy`; the payload is + the remaining address after clearing the low two bits. +- Pointer-array storage is `UInt32 count` followed by native-width list + pointers: header/table offset and pointer stride are both 4 bytes for 32-bit + and 8 bytes for 64-bit. A readable zero-count array is legal empty. +- Relative storage has an 8-byte entry-size/count header and ordered 8-byte + entries. Unloaded target images are normal omissions; other entry failures + remain diagnostics while later entries continue. +- Raw zero is absent. Nonzero tag-only values, null array entries, tag 3, bad + alignment, and unrepresentable addresses are malformed input, not empty. +- Apple documents `PointerUnion4` as non-stable ABI. Unknown encodings must stop + as typed unsupported failures; no fallback representation is inferred. + +Owner map: + +- One neutral `ObjCLoadedListArrayReader` owns PAC/TBI stripping while retaining + the low tag, exact address/displacement arithmetic, representation routing, + alignment, pointer width, array header/count/table reads, list-specific + validation, ordering, and diagnostics. +- The existing `ObjCMetadataTableReader` remains the sole count/byte-budget and + complete-range owner. Its loaded-image reads must copy bounded bytes with + `mach_vm_read_overwrite` and decode the local snapshot instead of probing and + then directly dereferencing mutable runtime memory. +- Existing checked method/property/protocol list readers validate referenced + headers and complete inner tables. Existing relative resolvers own outer + count/stride/range and good/bad/good entry resolution. +- Existing raw-pointer initializers remain for their checked callers, but the + RW-array path never calls one before a checked local header read. +- Rename the internal UInt32/UInt64 widening protocol from root-specific + `ObjCRootPointer` to neutral `ObjCMetadataPointer`; add no parallel widening + or budget implementation. + +Diagnostics and public Diagnostics SPI: + +```swift +@_spi(Diagnostics) +public enum ObjCLoadedListArrayRepresentation: Sendable, Equatable { + case single, array, relative +} + +@_spi(Diagnostics) +public struct ObjCLoadedListArrayEntry { + public let image: MachOImage + public let list: List +} + +@_spi(Diagnostics) +public struct ObjCLoadedListArrayReadResult { + public let representation: ObjCLoadedListArrayRepresentation? + public let entries: [ObjCLoadedListArrayEntry] + public let relativeListList: RelativeList? + public let tableDiagnostics: [ObjCMetadataTableDiagnostic] +} +``` + +- `ObjCClassRWDataExtProtocol` adds `readMethodLists(in:)`, + `readPropertyLists(in:)`, and `readProtocolLists(in:)`. +- `ObjCMethodArray`, `ObjCPropertyArray`, and `ObjCProtocolArrayProtocol` add + Diagnostics SPI `readLists(in:)` so direct queries and extension-field + queries share the same owner. +- `entries` contains `(image, list)` membership in source order for every + representation. `relativeListList` is non-nil only after a checked relative + header read. Internal representation storage keeps those states consistent. +- Reuse `ObjCMetadataTableDiagnostic`, adding owner + `.loadedRWExtension(kind:pointerWidth:)` and the minimum relative-image + failure vocabulary. Do not add a parallel diagnostic hierarchy: these are the + same table/entry provenance and structural failures as the existing reader. + +Observable failure semantics: + +- raw zero: representation `nil`, no entries, no diagnostics; legacy field + query returns `nil`; +- legal empty array/relative table: present representation, empty entries, no + diagnostics; +- malformed whole storage/table: present representation when the tag is known, + empty entries, one table diagnostic; +- malformed pointer-array or relative entry: omit only that entry, retain later + entries in order, and emit one indexed diagnostic; +- tag 3: no representation or dereference, one unsupported table diagnostic; +- legacy `lists(in:)` continues to return only single/array lists and returns + `[]` for relative storage; legacy `relativeListList(in:)` continues to return + only the relative wrapper. All legacy methods project the checked outcome. + +Concurrency boundary: + +- Each header/table/reference read is copied safely into local bytes, so a + freed/unmapped pointer degrades instead of trapping. The private objc + `runtimeLock` is unavailable, so the API does not claim one atomic snapshot + across the extension word and every referenced list. Concurrent mutation may + yield a partial result plus diagnostics, never a fallback guess. + +Implementation non-goals: + +- Do not make PrivateHeaderKit consume RW-extension arrays; its current raw dump + uses class RO member lists. +- Do not change C-string reads, objc runtime locking, RO tag semantics, or + unrelated raw initializer visibility. +- Do not treat tag `1` in class RO as the RW-extension pointer-array encoding; + the two owners have different contracts. Validation gate: @@ -39,5 +146,10 @@ Validation gate: - #60, #65, #79, #83, and #88 regressions; Debug/Release and Apple cross-builds. - Exact MachOObjCSection → MachOSwiftSection → PrivateHeaderKit cohort pins. +- A disposable, safe-copy-only exact iOS 27 beta `24A5390f` runtime probe must + confirm tags 0/1/2, 64-bit array header offset/stride, empty semantics, and + public Objective-C runtime count parity; restore the prior runtime match and + delete all probe artifacts afterward. arm64e authentication remains an + explicitly unverified condition because the Simulator is arm64. - PrivateHeaderKit full tests, release-script tests, codex-review, Ready PR, GitHub review/CI, and merge to `main`. From d4507fd21452fd2cb3468daf19b9eb5efd37ed1f Mon Sep 17 00:00:00 2001 From: Kazuki Nakashima <65545348+lynnswap@users.noreply.github.com> Date: Wed, 26 Aug 2026 14:40:28 +0900 Subject: [PATCH 3/6] docs: record issue 87 validation progress --- Docs/issue-remediation-progress.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Docs/issue-remediation-progress.md b/Docs/issue-remediation-progress.md index 52b7515..60ddf0e 100644 --- a/Docs/issue-remediation-progress.md +++ b/Docs/issue-remediation-progress.md @@ -153,3 +153,22 @@ Validation gate: explicitly unverified condition because the Simulator is arm64. - PrivateHeaderKit full tests, release-script tests, codex-review, Ready PR, GitHub review/CI, and merge to `main`. + +## Current progress + +- MachOObjCSection implementation is staged on `codex/issue87-rw-arrays`. + The checked reader now copies bounded loaded-memory reads before decoding and + routes method, property, and protocol tag `0`/`1`/`2` representations through + shared count and byte budgets. +- Eight initial RW-extension fixtures and the combined #60/#65/#79/#83/#88/#87 + regression set pass. The deterministic full suite passes when excluding the + pre-existing host-only `MachOObjCSectionTests` fixture class; the same hardcoded + missing `/Users/JH/Downloads/iOS18.5-SwiftUI` failure reproduces unchanged at + the dependency base commit. +- Release builds pass for macOS, iOS 13 arm64, watchOS 6 arm64, and watchOS 6 + arm64_32 with Xcode 26.5 / Swift 6.3.2. +- Independent owner audit found one remaining unsafe single-entry relative + protocol projection plus representation/provenance ownership gaps and missing + boundary fixtures. Those findings are being repaired before the final review. +- The exact `24A5390f` runtime probe and downstream cohort pin updates remain + pending. From ecc8b49b1bf19cfb20a1ef4f9daa3e1aee7846d9 Mon Sep 17 00:00:00 2001 From: Kazuki Nakashima <65545348+lynnswap@users.noreply.github.com> Date: Wed, 26 Aug 2026 14:50:15 +0900 Subject: [PATCH 4/6] docs: record issue 87 runtime probe --- Docs/issue-remediation-progress.md | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/Docs/issue-remediation-progress.md b/Docs/issue-remediation-progress.md index 60ddf0e..3d3c962 100644 --- a/Docs/issue-remediation-progress.md +++ b/Docs/issue-remediation-progress.md @@ -170,5 +170,17 @@ Validation gate: - Independent owner audit found one remaining unsafe single-entry relative protocol projection plus representation/provenance ownership gaps and missing boundary fixtures. Those findings are being repaired before the final review. -- The exact `24A5390f` runtime probe and downstream cohort pin updates remain - pending. +- A disposable arm64 Simulator probe on exact runtime build `24A5390f` passed: + all three fields produced canonical absent, tag `0` single, and tag `1` + two-list arrays; shared-cache classes produced tag `2` relative method, + property, and protocol lists. The probe confirmed 8-byte pointers, array + table offset/stride 8, relative header/table offset 8 and stride 8, unloaded + image omission, and count parity with `class_copy*List`. It performed 1,240 + exact-size `mach_vm_read_overwrite` snapshot reads with no raw dereferences or + failures. Result SHA-256: + `6aae3aae8c60b9f8e8c7eb54d9c74e8f82abf2c5b813d64b984993ed4dcfc821`. +- The disposable device and executable were removed and the runtime-match JSON + was restored byte-for-byte to user override `24A5423a` with default + `24A5422a`; no `24A5390f` override remains. Simulator arm64 does not verify + arm64e pointer authentication. +- Downstream cohort pin updates remain pending. From d08b27719f90ec2ae9aa0116682bce8e0d50c78b Mon Sep 17 00:00:00 2001 From: Kazuki Nakashima <65545348+lynnswap@users.noreply.github.com> Date: Wed, 26 Aug 2026 15:14:42 +0900 Subject: [PATCH 5/6] fix: bound loaded Objective-C RW arrays --- Docs/generation.md | 6 ++-- Package.resolved | 4 +-- Package.swift | 4 +-- .../RawDumpObjCDiagnostics.swift | 19 ++++++++++- .../PrivateHeaderKitHelperProtocolTests.swift | 4 +-- .../ObjCMemberListDiagnosticsTests.swift | 34 +++++++++++++++++++ 6 files changed, 62 insertions(+), 9 deletions(-) diff --git a/Docs/generation.md b/Docs/generation.md index e6df7ae..da56bae 100644 --- a/Docs/generation.md +++ b/Docs/generation.md @@ -153,8 +153,10 @@ 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 +strings. MachOObjCSection's public loaded class RW-extension list-array queries +now apply the same bounded structural reads, although PrivateHeaderKit's current +raw dump does not query that dependency surface. When a queried target is +degraded, 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 diff --git a/Package.resolved b/Package.resolved index c9c2268..847bda4 100644 --- a/Package.resolved +++ b/Package.resolved @@ -59,7 +59,7 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/lynnswap/MachOObjCSection.git", "state" : { - "revision" : "8f0ff76f02c0865422a72662177b5e687e43522d" + "revision" : "cc9a70f46d69683882dedce7d6d293a565e7863a" } }, { @@ -67,7 +67,7 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/lynnswap/MachOSwiftSection.git", "state" : { - "revision" : "06ed57ab39fc24956f9079beeb407fffa30d50f4" + "revision" : "905703af9d192c778054417a1a5052a26051cc96" } }, { diff --git a/Package.swift b/Package.swift index a0d2693..718e912 100644 --- a/Package.swift +++ b/Package.swift @@ -37,7 +37,7 @@ let package = Package( ), .package( url: "https://github.com/lynnswap/MachOObjCSection.git", - revision: "8f0ff76f02c0865422a72662177b5e687e43522d" + revision: "cc9a70f46d69683882dedce7d6d293a565e7863a" ), .package( url: "https://github.com/MxIris-Reverse-Engineering/swift-objc-dump.git", @@ -45,7 +45,7 @@ let package = Package( ), .package( url: "https://github.com/lynnswap/MachOSwiftSection.git", - revision: "06ed57ab39fc24956f9079beeb407fffa30d50f4" + revision: "905703af9d192c778054417a1a5052a26051cc96" ), .package( url: "https://github.com/MxIris-Reverse-Engineering/swift-demangling", diff --git a/Sources/PrivateHeaderKitRawDumpCore/RawDumpObjCDiagnostics.swift b/Sources/PrivateHeaderKitRawDumpCore/RawDumpObjCDiagnostics.swift index bbfeee3..477087c 100644 --- a/Sources/PrivateHeaderKitRawDumpCore/RawDumpObjCDiagnostics.swift +++ b/Sources/PrivateHeaderKitRawDumpCore/RawDumpObjCDiagnostics.swift @@ -268,7 +268,7 @@ private func privateHeaderKitDiagnostic( ) } -private func rawDumpMetadataTableDiagnostic( +func rawDumpMetadataTableDiagnostic( owner: ObjCMetadataTableDiagnostic.Owner, site: ObjCMetadataTableDiagnostic.Site, failure: ObjCMetadataTableDiagnostic.Failure @@ -289,6 +289,11 @@ private func rawDumpMetadataTableDiagnostic( metadataDescription = "\(pointerWidthDescription(pointerWidth))" + " \(rootSectionDescription(section)) root table" + case let .loadedRWExtension(kind, pointerWidth): + ownerDescription = "Objective-C loaded RW-extension list arrays" + metadataDescription = + "\(pointerWidthDescription(pointerWidth))" + + " \(rwExtensionListKindDescription(kind)) list array" case let .loadedRelationship(subject, role): ownerDescription = subjectDescription(subject) metadataDescription = "\(loadedRelationshipDescription(role)) relationship" @@ -363,6 +368,16 @@ private func pointerWidthDescription( } } +private func rwExtensionListKindDescription( + _ kind: ObjCMetadataTableDiagnostic.RWExtensionListKind +) -> String { + switch kind { + case .method: "method" + case .property: "property" + case .protocol: "protocol" + } +} + private func loadedRelationshipDescription( _ role: ObjCMetadataTableDiagnostic.LoadedRelationshipRole ) -> String { @@ -690,5 +705,7 @@ private func failureDescription( case let .invalidEntryArithmetic(baseAddress, targetAddress): "target address \(targetAddress) cannot be represented relative" + " to base address \(baseAddress)" + case .relativeImageUnavailable(let imageIndex): + "cache image index \(imageIndex) is unavailable" } } diff --git a/Tests/PrivateHeaderKitHelperProtocolTests/PrivateHeaderKitHelperProtocolTests.swift b/Tests/PrivateHeaderKitHelperProtocolTests/PrivateHeaderKitHelperProtocolTests.swift index 7443cc2..1062683 100644 --- a/Tests/PrivateHeaderKitHelperProtocolTests/PrivateHeaderKitHelperProtocolTests.swift +++ b/Tests/PrivateHeaderKitHelperProtocolTests/PrivateHeaderKitHelperProtocolTests.swift @@ -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 == "8f0ff76f02c0865422a72662177b5e687e43522d") + #expect(state["revision"] as? String == "cc9a70f46d69683882dedce7d6d293a565e7863a") #expect(state["version"] == nil) let swiftSectionPin = try #require( @@ -307,7 +307,7 @@ struct PrivateHeaderKitHelperProtocolTests { ) #expect( swiftSectionState["revision"] as? String - == "06ed57ab39fc24956f9079beeb407fffa30d50f4" + == "905703af9d192c778054417a1a5052a26051cc96" ) #expect(swiftSectionState["version"] == nil) diff --git a/Tests/PrivateHeaderKitRawDumpTests/ObjCMemberListDiagnosticsTests.swift b/Tests/PrivateHeaderKitRawDumpTests/ObjCMemberListDiagnosticsTests.swift index 307a794..6a5dd45 100644 --- a/Tests/PrivateHeaderKitRawDumpTests/ObjCMemberListDiagnosticsTests.swift +++ b/Tests/PrivateHeaderKitRawDumpTests/ObjCMemberListDiagnosticsTests.swift @@ -56,6 +56,40 @@ struct ObjCMemberListDiagnosticsTests { ) } + @Test func loadedRWExtensionDiagnosticsKeepKindWidthAndUnavailableImage() throws { + let fixture = try InvalidFileRootFixture() + let seedDiagnostic = try #require( + fixture.machO.objc.readRoots().tableDiagnostics.first + ) + let cases: [( + ObjCMetadataTableDiagnostic.RWExtensionListKind, + ObjCMetadataTableDiagnostic.PointerWidth, + String + )] = [ + (.method, .bits64, "64-bit method"), + (.property, .bits32, "32-bit property"), + (.protocol, .bits64, "64-bit protocol"), + ] + + for (kind, pointerWidth, prefix) in cases { + let record = rawDumpMetadataTableDiagnostic( + owner: .loadedRWExtension( + kind: kind, + pointerWidth: pointerWidth + ), + site: seedDiagnostic.site, + failure: .relativeImageUnavailable(imageIndex: 1_194) + ) + + #expect(record.owner == "Objective-C loaded RW-extension list arrays") + #expect( + record.degradation + == "\(prefix) list array could not be fully read:" + + " cache image index 1194 is unavailable" + ) + } + } + @Test func wholeTableFailureKeepsMemberKindAndOuterOffset() { let record = rawDumpMemberListDiagnostic( className: "Owner", From c4056f98e59735bbff3166058184ab00904fc3b7 Mon Sep 17 00:00:00 2001 From: Kazuki Nakashima <65545348+lynnswap@users.noreply.github.com> Date: Wed, 26 Aug 2026 15:18:49 +0900 Subject: [PATCH 6/6] chore: remove completed issue ledger --- Docs/issue-remediation-progress.md | 186 ----------------------------- 1 file changed, 186 deletions(-) delete mode 100644 Docs/issue-remediation-progress.md diff --git a/Docs/issue-remediation-progress.md b/Docs/issue-remediation-progress.md deleted file mode 100644 index 3d3c962..0000000 --- a/Docs/issue-remediation-progress.md +++ /dev/null @@ -1,186 +0,0 @@ -# Issue remediation progress - -Base: `main` at `600a3c275621dde24ac6d9b72d5cfec7fedb0f00` - -## Current issue: #87 - -Branch: `codex/issue-87-bounded-loaded-objc-rw-arrays` - -Goal: - -- Make loaded `class_rw_ext_t` method, property, and protocol list fields - validate their tagged representation, table ranges, and referenced lists - before dereferencing runtime memory. -- Preserve existing public query signatures as compatibility projections while - exposing typed recoverable diagnostics from one checked owner. - -Confirmed scope: - -- Single-list, pointer-array, and relative-list tagged representations. -- 32/64-bit pointer width, exact address arithmetic, complete readable ranges, - shared count/byte budgets, and ordered good/bad/good preservation. -- MachOObjCSection implementation, its direct tests, coherent downstream pins, - and contract documentation required by the final consumer diff. - -Non-goals: - -- Referenced C-string payload hardening. -- Reworking regular member tables or loaded/file root sections completed by - #83 and #88. -- Attributing the original iOS 27 failures to this currently unobserved surface. - -## Design gate - -Approved against MachOObjCSection -`8f0ff76f02c0865422a72662177b5e687e43522d`. - -Runtime representation contract: - -- Apple objc4 `951.7` and current `main` encode `class_rw_ext_t` list arrays - as a native-width `PointerUnion4`: low tags `0 = single`, `1 = pointer - array`, `2 = relative list-of-lists`, `3 = reserved/dummy`; the payload is - the remaining address after clearing the low two bits. -- Pointer-array storage is `UInt32 count` followed by native-width list - pointers: header/table offset and pointer stride are both 4 bytes for 32-bit - and 8 bytes for 64-bit. A readable zero-count array is legal empty. -- Relative storage has an 8-byte entry-size/count header and ordered 8-byte - entries. Unloaded target images are normal omissions; other entry failures - remain diagnostics while later entries continue. -- Raw zero is absent. Nonzero tag-only values, null array entries, tag 3, bad - alignment, and unrepresentable addresses are malformed input, not empty. -- Apple documents `PointerUnion4` as non-stable ABI. Unknown encodings must stop - as typed unsupported failures; no fallback representation is inferred. - -Owner map: - -- One neutral `ObjCLoadedListArrayReader` owns PAC/TBI stripping while retaining - the low tag, exact address/displacement arithmetic, representation routing, - alignment, pointer width, array header/count/table reads, list-specific - validation, ordering, and diagnostics. -- The existing `ObjCMetadataTableReader` remains the sole count/byte-budget and - complete-range owner. Its loaded-image reads must copy bounded bytes with - `mach_vm_read_overwrite` and decode the local snapshot instead of probing and - then directly dereferencing mutable runtime memory. -- Existing checked method/property/protocol list readers validate referenced - headers and complete inner tables. Existing relative resolvers own outer - count/stride/range and good/bad/good entry resolution. -- Existing raw-pointer initializers remain for their checked callers, but the - RW-array path never calls one before a checked local header read. -- Rename the internal UInt32/UInt64 widening protocol from root-specific - `ObjCRootPointer` to neutral `ObjCMetadataPointer`; add no parallel widening - or budget implementation. - -Diagnostics and public Diagnostics SPI: - -```swift -@_spi(Diagnostics) -public enum ObjCLoadedListArrayRepresentation: Sendable, Equatable { - case single, array, relative -} - -@_spi(Diagnostics) -public struct ObjCLoadedListArrayEntry { - public let image: MachOImage - public let list: List -} - -@_spi(Diagnostics) -public struct ObjCLoadedListArrayReadResult { - public let representation: ObjCLoadedListArrayRepresentation? - public let entries: [ObjCLoadedListArrayEntry] - public let relativeListList: RelativeList? - public let tableDiagnostics: [ObjCMetadataTableDiagnostic] -} -``` - -- `ObjCClassRWDataExtProtocol` adds `readMethodLists(in:)`, - `readPropertyLists(in:)`, and `readProtocolLists(in:)`. -- `ObjCMethodArray`, `ObjCPropertyArray`, and `ObjCProtocolArrayProtocol` add - Diagnostics SPI `readLists(in:)` so direct queries and extension-field - queries share the same owner. -- `entries` contains `(image, list)` membership in source order for every - representation. `relativeListList` is non-nil only after a checked relative - header read. Internal representation storage keeps those states consistent. -- Reuse `ObjCMetadataTableDiagnostic`, adding owner - `.loadedRWExtension(kind:pointerWidth:)` and the minimum relative-image - failure vocabulary. Do not add a parallel diagnostic hierarchy: these are the - same table/entry provenance and structural failures as the existing reader. - -Observable failure semantics: - -- raw zero: representation `nil`, no entries, no diagnostics; legacy field - query returns `nil`; -- legal empty array/relative table: present representation, empty entries, no - diagnostics; -- malformed whole storage/table: present representation when the tag is known, - empty entries, one table diagnostic; -- malformed pointer-array or relative entry: omit only that entry, retain later - entries in order, and emit one indexed diagnostic; -- tag 3: no representation or dereference, one unsupported table diagnostic; -- legacy `lists(in:)` continues to return only single/array lists and returns - `[]` for relative storage; legacy `relativeListList(in:)` continues to return - only the relative wrapper. All legacy methods project the checked outcome. - -Concurrency boundary: - -- Each header/table/reference read is copied safely into local bytes, so a - freed/unmapped pointer degrades instead of trapping. The private objc - `runtimeLock` is unavailable, so the API does not claim one atomic snapshot - across the extension word and every referenced list. Concurrent mutation may - yield a partial result plus diagnostics, never a fallback guess. - -Implementation non-goals: - -- Do not make PrivateHeaderKit consume RW-extension arrays; its current raw dump - uses class RO member lists. -- Do not change C-string reads, objc runtime locking, RO tag semantics, or - unrelated raw initializer visibility. -- Do not treat tag `1` in class RO as the RW-extension pointer-array encoding; - the two owners have different contracts. - -Validation gate: - -- Deterministic 32/64 loaded-image fixtures for every tagged representation, - exact boundaries, truncation, excessive counts, unreadable pointers, and - good/bad/good ordering. -- #60, #65, #79, #83, and #88 regressions; Debug/Release and Apple - cross-builds. -- Exact MachOObjCSection → MachOSwiftSection → PrivateHeaderKit cohort pins. -- A disposable, safe-copy-only exact iOS 27 beta `24A5390f` runtime probe must - confirm tags 0/1/2, 64-bit array header offset/stride, empty semantics, and - public Objective-C runtime count parity; restore the prior runtime match and - delete all probe artifacts afterward. arm64e authentication remains an - explicitly unverified condition because the Simulator is arm64. -- PrivateHeaderKit full tests, release-script tests, codex-review, Ready PR, - GitHub review/CI, and merge to `main`. - -## Current progress - -- MachOObjCSection implementation is staged on `codex/issue87-rw-arrays`. - The checked reader now copies bounded loaded-memory reads before decoding and - routes method, property, and protocol tag `0`/`1`/`2` representations through - shared count and byte budgets. -- Eight initial RW-extension fixtures and the combined #60/#65/#79/#83/#88/#87 - regression set pass. The deterministic full suite passes when excluding the - pre-existing host-only `MachOObjCSectionTests` fixture class; the same hardcoded - missing `/Users/JH/Downloads/iOS18.5-SwiftUI` failure reproduces unchanged at - the dependency base commit. -- Release builds pass for macOS, iOS 13 arm64, watchOS 6 arm64, and watchOS 6 - arm64_32 with Xcode 26.5 / Swift 6.3.2. -- Independent owner audit found one remaining unsafe single-entry relative - protocol projection plus representation/provenance ownership gaps and missing - boundary fixtures. Those findings are being repaired before the final review. -- A disposable arm64 Simulator probe on exact runtime build `24A5390f` passed: - all three fields produced canonical absent, tag `0` single, and tag `1` - two-list arrays; shared-cache classes produced tag `2` relative method, - property, and protocol lists. The probe confirmed 8-byte pointers, array - table offset/stride 8, relative header/table offset 8 and stride 8, unloaded - image omission, and count parity with `class_copy*List`. It performed 1,240 - exact-size `mach_vm_read_overwrite` snapshot reads with no raw dereferences or - failures. Result SHA-256: - `6aae3aae8c60b9f8e8c7eb54d9c74e8f82abf2c5b813d64b984993ed4dcfc821`. -- The disposable device and executable were removed and the runtime-match JSON - was restored byte-for-byte to user override `24A5423a` with default - `24A5422a`; no `24A5390f` override remains. Simulator arm64 does not verify - arm64e pointer authentication. -- Downstream cohort pin updates remain pending.