[GH-ISSUE #6529] [Bug] Symbol dispatch fallback overwrites same-method implementations at distinct addresses #3920

Closed
opened 2026-10-31 00:45:14 +00:00 by bsakaguchi · 0 comments
Owner

Originally created by @coygeek on GitHub (Oct 23, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/6529

Before submitting

  • I searched the open and closed issues for a related report.
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

Native analysis or decompilation

Observed behavior

The symbol fallback for inspect_native_dispatch_metadata accepts two list_names rows named -[Controller run:] at different addresses, but returns only the last implementation. Reversing the rows changes the selected address. Passing that result to the production Interface Builder dispatch join produces a singular inferred handler instead of preserving ambiguity.

This was observed with a mocked provider boundary calling production inspectNativeDispatch and joinInterfaceBuilderDispatch. No native engine, target binary, public CLI, or MCP transport was run.

Reproduction

The initial probe made inspect_native_dispatch_metadata return AnalysisCapabilityUnavailableError, then made list_names return each inventory below. It called inspectNativeDispatch(analysis, 5000), which validates the inventory before executing the symbol fallback.

[
  {"address":"0x1000","value":"-[Controller run:]"},
  {"address":"0x2000","value":"-[Controller run:]"}
]

The executed mock and production call were:

const provider = {
  id: "mock-native", name: "Mock native provider", version: null
};
const sha256 = "a".repeat(64);

async function inspect(addresses: string[]) {
  const calls: string[] = [];
  const analysis = {
    async execute(operation: string) {
      calls.push(operation);
      if (operation === "inspect_native_dispatch_metadata")
        return err(new AnalysisCapabilityUnavailableError(
          provider.id, operation,
          "Mock boundary supplies symbol inventory only."
        ));
      assert.equal(operation, "list_names");
      return ok({
        result: addresses.map(address => ({
          address, value: "-[Controller run:]"
        })),
        provider, subject: { sha256, architecture: "x86_64" },
        limitations: []
      });
    }
  };
  const result = await inspectNativeDispatch(analysis, 5000);
  assert.equal(result.ok, true);
  return { metadata: result.value.result, calls };
}

const single = await inspect(["0x1000"]);
const forward = await inspect(["0x1000", "0x2000"]);
const reverse = await inspect(["0x2000", "0x1000"]);

Imports in the probe came from node:assert/strict, src/application/native/NativeDispatchMetadataInspection.ts, src/domain/analysisErrorCore.ts, and src/domain/result.ts. The script ran with the checkout-local tsx loader under the pinned Node runtime. The excerpt omits the module-loading wrapper and graph fixture.

The graph fixture contained one action with selector run:, an observed action-to-selector connection, and an observed action-to-view_controller connection whose receiver class was Controller. The probe joined each metadata result to this same validated graph. A control supplied the join with both implementation records, each obtained from a separate single-address fallback call.

Both distinct-address orderings showed the failure in one successful probe process. NOT_ENOUGH_INFO: no real-provider inventory exhibiting this condition was collected.

Expected behavior

Preserve distinct implementation addresses and their evidence for a recognized class/selector/method-type combination. The UI join must retain an ambiguous dispatch when these facts do not identify a unique implementation; inventory order must not decide the handler.

The existing dispatch join explicitly selects an implementation only when there is exactly one candidate and otherwise reports selector_has_multiple_candidate_implementations. The ambiguity control exercised that existing behavior.

Impact and scope

A valid accepted symbol inventory loses one implementation and its provenance. A downstream UI trace can infer the wrong handler and follow its calls, depending on inventory order. The demonstrated scope is the provider-neutral symbol fallback with two recognized method labels at distinct addresses; the direct binary-metadata path was not exercised.

Real-engine occurrence and frequency remain unknown. Manual inspection of list_names may expose the alternatives, but no workaround restoring the automatic UI join was tested.

REA version

Package manifest 6.7.0; source revision e073f64be0a5a30c32d8b3cec23f9bd411080903. The probe used source at this revision, not the published npm runtime.

Runtime and platform

Node.js v24.18.0; Linux 7.2.8-9-omarchy x86_64; checkout-locked dependencies and tsx. Provider execute was mocked; no Hopper, Ghidra, IDA, or Kuna process ran.

Target and artifact context

Synthetic two-address Objective-C method inventory and an in-memory authored UI graph sharing a synthetic target digest. No binary artifact or real client configuration was used.

Interface

Other

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

Observed normalized results from the initial production-function probe:

Input addresses, in order Retained implementation addresses UI dispatch
["0x1000"] ["0x1000"] inferred → native:function:0x1000
["0x1000","0x2000"] ["0x2000"] inferred → native:function:0x2000
["0x2000","0x1000"] ["0x1000"] inferred → native:function:0x1000
Control retaining both records ["0x1000","0x2000"] unresolved, to: null, reason: "selector_has_multiple_candidate_implementations"

Each fallback call requested exactly inspect_native_dispatch_metadata followed by list_names. The process exited 0 with assertions confirming the observed last-row selection and the ambiguity control. The class method list also retained only the last address.

  • Evidence provenance: observed with mocked provider input; public workflow reachability is source-derived.
  • Local verification: reproduced at the production application/domain boundary.
  • Reproduction completeness: incomplete for real-provider integration.
  • Missing fact: a native provider/target that emits identical recognized Objective-C method labels at distinct addresses was not exercised.

Typed outcome or recovery expected

Retain both implementation candidates and their evidence, and keep the dispatch unresolved with an ambiguity reason. A restoration check should leave the single-address control usable and produce equivalent candidate sets for both inventory orderings. No fix was applied.

Analysis or suspected cause

Source-derived: symbol metadata production keys implementations by class name, selector, and method type, omitting implementation address. Map.set therefore replaces the earlier address and evidence. Both implementation output and class method output are constructed from that map.

The application fallback accepts the row array without a name-uniqueness constraint and passes all rows to this producer. UI tracing consumes the result before its dispatch join. The probe executed these producer/join functions directly; it did not execute the full UI trace workflow.

Additional context

GitHub searches used repo:morluto/rea is:issue with both is:open "objc_dispatch_implementations" (one result, #5424) and is:closed "objc_dispatch_implementations" (two results, #4534 and #4012). Searches for "implementations.set" and "selector_has_multiple_candidate_implementations" returned no issues. Open/closed searches for "dispatch" "duplicate", plus "dispatch" "overwrites" and "symbol" "same" "implementation", found related reports but no matching producer overwrite.

Closest comparisons: #5424 concerns recognition of Ghidra label spellings and false classes; this report starts with already-recognized method labels. #5908 selects the first ambiguous name in the xrefs consumer; this report loses alternatives in the metadata producer without invoking xrefs. #3343 skips non-controller receivers; this fixture uses an accepted controller. The closed selector/category reports concern binary decoder recovery, which this symbol-only path does not invoke.

No last-known-good version was established. Per the audit request, no reproduction or review pass was run after writing this report.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: The executed production fallback discarded one accepted implementation and made the inferred handler depend on row order; the control preserved ambiguity when both records reached the join. Real-provider frequency remains unmeasured.
  • Workaround status: unverified
  • Workaround basis: Reviewing the original name inventory may reveal both candidates, but no available operation restoring the automatic authored UI dispatch join was exercised.
Originally created by @coygeek on GitHub (Oct 23, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/6529 ## Before submitting - [x] I searched the open and closed issues for a related report. - [x] I removed credentials, tokens, private paths, and sensitive target data. ## Area Native analysis or decompilation ## Observed behavior The symbol fallback for `inspect_native_dispatch_metadata` accepts two `list_names` rows named `-[Controller run:]` at different addresses, but returns only the last implementation. Reversing the rows changes the selected address. Passing that result to the production Interface Builder dispatch join produces a singular inferred handler instead of preserving ambiguity. This was observed with a mocked provider boundary calling production `inspectNativeDispatch` and `joinInterfaceBuilderDispatch`. No native engine, target binary, public CLI, or MCP transport was run. ## Reproduction The initial probe made `inspect_native_dispatch_metadata` return `AnalysisCapabilityUnavailableError`, then made `list_names` return each inventory below. It called `inspectNativeDispatch(analysis, 5000)`, which validates the inventory before executing the symbol fallback. ```json [ {"address":"0x1000","value":"-[Controller run:]"}, {"address":"0x2000","value":"-[Controller run:]"} ] ``` The executed mock and production call were: ```ts const provider = { id: "mock-native", name: "Mock native provider", version: null }; const sha256 = "a".repeat(64); async function inspect(addresses: string[]) { const calls: string[] = []; const analysis = { async execute(operation: string) { calls.push(operation); if (operation === "inspect_native_dispatch_metadata") return err(new AnalysisCapabilityUnavailableError( provider.id, operation, "Mock boundary supplies symbol inventory only." )); assert.equal(operation, "list_names"); return ok({ result: addresses.map(address => ({ address, value: "-[Controller run:]" })), provider, subject: { sha256, architecture: "x86_64" }, limitations: [] }); } }; const result = await inspectNativeDispatch(analysis, 5000); assert.equal(result.ok, true); return { metadata: result.value.result, calls }; } const single = await inspect(["0x1000"]); const forward = await inspect(["0x1000", "0x2000"]); const reverse = await inspect(["0x2000", "0x1000"]); ``` Imports in the probe came from `node:assert/strict`, `src/application/native/NativeDispatchMetadataInspection.ts`, `src/domain/analysisErrorCore.ts`, and `src/domain/result.ts`. The script ran with the checkout-local `tsx` loader under the pinned Node runtime. The excerpt omits the module-loading wrapper and graph fixture. The graph fixture contained one action with selector `run:`, an observed action-to-selector connection, and an observed action-to-`view_controller` connection whose receiver class was `Controller`. The probe joined each metadata result to this same validated graph. A control supplied the join with both implementation records, each obtained from a separate single-address fallback call. Both distinct-address orderings showed the failure in one successful probe process. NOT_ENOUGH_INFO: no real-provider inventory exhibiting this condition was collected. ## Expected behavior Preserve distinct implementation addresses and their evidence for a recognized class/selector/method-type combination. The UI join must retain an ambiguous dispatch when these facts do not identify a unique implementation; inventory order must not decide the handler. The existing [dispatch join](https://github.com/morluto/rea/blob/e073f64be0a5a30c32d8b3cec23f9bd411080903/src/domain/native/nativeInvestigationGraph.ts#L332-L360) explicitly selects an implementation only when there is exactly one candidate and otherwise reports `selector_has_multiple_candidate_implementations`. The ambiguity control exercised that existing behavior. ## Impact and scope A valid accepted symbol inventory loses one implementation and its provenance. A downstream UI trace can infer the wrong handler and follow its calls, depending on inventory order. The demonstrated scope is the provider-neutral symbol fallback with two recognized method labels at distinct addresses; the direct binary-metadata path was not exercised. Real-engine occurrence and frequency remain unknown. Manual inspection of `list_names` may expose the alternatives, but no workaround restoring the automatic UI join was tested. ## REA version Package manifest 6.7.0; source revision `e073f64be0a5a30c32d8b3cec23f9bd411080903`. The probe used source at this revision, not the published npm runtime. ## Runtime and platform Node.js v24.18.0; Linux 7.2.8-9-omarchy x86_64; checkout-locked dependencies and `tsx`. Provider `execute` was mocked; no Hopper, Ghidra, IDA, or Kuna process ran. ## Target and artifact context Synthetic two-address Objective-C method inventory and an in-memory authored UI graph sharing a synthetic target digest. No binary artifact or real client configuration was used. ## Interface Other ## Earliest failure stage Native analysis or reconstruction ## Relevant output or evidence Observed normalized results from the initial production-function probe: | Input addresses, in order | Retained implementation addresses | UI dispatch | | --- | --- | --- | | `["0x1000"]` | `["0x1000"]` | `inferred` → `native:function:0x1000` | | `["0x1000","0x2000"]` | `["0x2000"]` | `inferred` → `native:function:0x2000` | | `["0x2000","0x1000"]` | `["0x1000"]` | `inferred` → `native:function:0x1000` | | Control retaining both records | `["0x1000","0x2000"]` | `unresolved`, `to: null`, `reason: "selector_has_multiple_candidate_implementations"` | Each fallback call requested exactly `inspect_native_dispatch_metadata` followed by `list_names`. The process exited 0 with assertions confirming the observed last-row selection and the ambiguity control. The class method list also retained only the last address. - Evidence provenance: observed with mocked provider input; public workflow reachability is source-derived. - Local verification: reproduced at the production application/domain boundary. - Reproduction completeness: incomplete for real-provider integration. - Missing fact: a native provider/target that emits identical recognized Objective-C method labels at distinct addresses was not exercised. ## Typed outcome or recovery expected Retain both implementation candidates and their evidence, and keep the dispatch unresolved with an ambiguity reason. A restoration check should leave the single-address control usable and produce equivalent candidate sets for both inventory orderings. No fix was applied. ## Analysis or suspected cause Source-derived: [symbol metadata production](https://github.com/morluto/rea/blob/e073f64be0a5a30c32d8b3cec23f9bd411080903/src/domain/native/objcSwiftMetadata.ts#L455-L479) keys `implementations` by class name, selector, and method type, omitting implementation address. `Map.set` therefore replaces the earlier address and evidence. Both implementation output and class method output are constructed from that map. The [application fallback](https://github.com/morluto/rea/blob/e073f64be0a5a30c32d8b3cec23f9bd411080903/src/application/native/NativeDispatchMetadataInspection.ts#L59-L90) accepts the row array without a name-uniqueness constraint and passes all rows to this producer. [UI tracing](https://github.com/morluto/rea/blob/e073f64be0a5a30c32d8b3cec23f9bd411080903/src/application/native/NativeUiActionTrace.ts#L39-L86) consumes the result before its dispatch join. The probe executed these producer/join functions directly; it did not execute the full UI trace workflow. ## Additional context GitHub searches used `repo:morluto/rea is:issue` with both `is:open "objc_dispatch_implementations"` (one result, #5424) and `is:closed "objc_dispatch_implementations"` (two results, #4534 and #4012). Searches for `"implementations.set"` and `"selector_has_multiple_candidate_implementations"` returned no issues. Open/closed searches for `"dispatch" "duplicate"`, plus `"dispatch" "overwrites"` and `"symbol" "same" "implementation"`, found related reports but no matching producer overwrite. Closest comparisons: [#5424](https://github.com/morluto/rea/issues/5424) concerns recognition of Ghidra label spellings and false classes; this report starts with already-recognized method labels. [#5908](https://github.com/morluto/rea/issues/5908) selects the first ambiguous name in the xrefs consumer; this report loses alternatives in the metadata producer without invoking xrefs. [#3343](https://github.com/morluto/rea/issues/3343) skips non-controller receivers; this fixture uses an accepted controller. The closed selector/category reports concern binary decoder recovery, which this symbol-only path does not invoke. No last-known-good version was established. Per the audit request, no reproduction or review pass was run after writing this report. ## Triage assessment - Impact level: P2 - Assessment status: supported - Impact basis: The executed production fallback discarded one accepted implementation and made the inferred handler depend on row order; the control preserved ambiguity when both records reached the join. Real-provider frequency remains unmeasured. - Workaround status: unverified - Workaround basis: Reviewing the original name inventory may reveal both candidates, but no available operation restoring the automatic authored UI dispatch join was exercised.
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#3920
No description provided.