[GH-ISSUE #2565] fix(native): retain reported targetless sites during call-route fallback #882

Closed
opened 2026-10-15 18:29:15 +00:00 by bsakaguchi · 1 comment
Owner

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

Summary

When a native provider reports targetless call sites but cannot supply detailed reference kinds, the UI action trace keeps inferred callees and drops those reported sites. The trace therefore omits address-specific unresolved edges that an analyst needs to distinguish from inferred routes. Its overall static-call coverage remains marked partial.

Runtime or environment

The failure was verified at source baseline: a2d8b94dd5 and is still present at 8b181716ac. The local reproduction used the focused service test lane with a simulated provider; a live Hopper session was not run.

Steps to reproduce

  1. At the stated baseline, add the following test as src/application/native/native_routes_fallback_probe.test.ts.
  2. Run npm run test:focused -- src/application/native/native_routes_fallback_probe.test.ts.
  3. Observe that the typed-kind control passes and the fallback case fails on its expected unknowns entry. The fixture represents a valid procedure_references response with one resolved reference and one separately reported targetless site; procedure_callees returns the resolved target.
import { describe, expect, it } from "vitest";
import { createAnalysisExecution } from "../AnalysisProvider.js";
import type { AnalysisOperationPort } from "../AnalysisProvider.js";
import { ok } from "../../domain/result.js";
import { readNativeCallRoutes } from "./NativeCallRoutes.js";

const provider = { id: "fixture", name: "Fixture", version: "1" };
const unresolved = [{ address: "0x1008", reason: "No static target" }];
const port = (typed: boolean): AnalysisOperationPort => ({
  execute: async (operation) => {
    if (operation === "procedure_references")
      return ok(createAnalysisExecution({
        reference_kinds_available: typed,
        references: [{
          source_address: "0x1004",
          target_address: "0x2000",
          target_procedure: { address: "0x2000" },
          kind: typed
            ? { available: true, provenance: "fixture", type: "COMPUTED_CALL", flow: true, call: true, jump: false, data: false, read: false, write: false, indirect: true, computed: true, external: false, conditional: false, terminal: false, primary: true, operand_index: 0 }
            : { available: false, reason: "Detailed kinds unavailable" },
        }],
        unresolved_calls: unresolved,
      }, provider));
    if (operation === "procedure_callees")
      return ok(createAnalysisExecution(["0x2000"], provider));
    throw new Error(`Unexpected operation ${operation}`);
  },
});

describe("reported targetless sites", () => {
  it.each([true, false])("retains sites when typed kinds are %s", async (typed) => {
    const result = await readNativeCallRoutes(port(typed), "0x1000");
    expect(result).toMatchObject({ ok: true, value: { unknowns: unresolved } });
  });
});

Expected behavior

For this response, both the typed and fallback routes should retain the provider-reported 0x1008 targetless site as an unresolved observation. The trace contract distinguishes reported targetless sites from inferred untyped callees; a lack of detailed reference kinds does not erase a separate call-site observation.

Actual behavior

The local focused test passed with typed kinds and failed with unavailable kinds: the fallback returned unknowns: [] instead of the reported 0x1008 entry. The caller that builds the UI action trace creates unresolved edges from unknowns, so this source-level branch drops that edge. The Hopper bridge can produce this response shape, but its behavior was established from source rather than a live provider run.

Evidence

  • Expected source: docs/native-investigation.md:210-218 states that targetless sites remain distinct in a bounded native trace; src/application/native/NativeCallRoutes.ts:27 states that the route reader preserves targetless sites; src/domain/hopperValues.ts:286-290 defines reported unresolved sites.
  • Failure source: src/application/native/NativeCallRoutes.ts:52-107 parses the reported sites, enters the untyped callee fallback for unavailable kinds, and returns an empty unknowns list; src/application/native/NativeUiActionTrace.ts:420-459 consumes that list for unresolved edges.
  • Evidence provenance: observed
  • Local verification: reproduced
  • Reproduction completeness: complete
  • The focused fixture produced one passing typed control and one failing fallback assertion. Expected unknowns: [{ address: "0x1008", reason: "No static target" }]; received unknowns: []. bridge/hopper_bridge.py:383-421 provides static evidence that Hopper reports unavailable detailed kinds alongside unresolved_calls when a native call reference lacks a resolved target.
  • Related issue: #2222 was closed for a different consumer, compare_functions, that also lost uncertainty from unresolved calls; its fix does not address this route-reader fallback.

Restoration check

With the same provider response, both typed and untyped fallback cases retain the reported 0x1008 site and its reason in unknowns; the fallback still identifies 0x2000 as an inferred callee. A bounded UI action trace constructed from that result includes an unresolved edge for 0x1008 while retaining its partial static-call coverage. Keep the typed control to detect accidental loss in the existing path.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: Providers without detailed reference kinds can lose specific reported targetless sites from a static trace, leaving an incomplete address-level call picture. The affected result is bounded to this response shape, and the trace still marks static-call coverage partial; real-provider frequency was not measured.
  • Workaround status: unverified
  • Workaround basis: Inspecting raw reference output or switching providers may recover the sites, but neither route was verified as practical for an affected Hopper session.
Originally created by @coygeek on GitHub (Oct 13, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/2565 ## Summary When a native provider reports targetless call sites but cannot supply detailed reference kinds, the UI action trace keeps inferred callees and drops those reported sites. The trace therefore omits address-specific unresolved edges that an analyst needs to distinguish from inferred routes. Its overall static-call coverage remains marked partial. ## Runtime or environment The failure was verified at source baseline: a2d8b94dd50dab8d966239dd63ab84718aa8b4b4 and is still present at 8b181716aca2cb467be499cba226abfb5ae19141. The local reproduction used the focused service test lane with a simulated provider; a live Hopper session was not run. ## Steps to reproduce 1. At the stated baseline, add the following test as `src/application/native/native_routes_fallback_probe.test.ts`. 2. Run `npm run test:focused -- src/application/native/native_routes_fallback_probe.test.ts`. 3. Observe that the typed-kind control passes and the fallback case fails on its expected `unknowns` entry. The fixture represents a valid `procedure_references` response with one resolved reference and one separately reported targetless site; `procedure_callees` returns the resolved target. ```ts import { describe, expect, it } from "vitest"; import { createAnalysisExecution } from "../AnalysisProvider.js"; import type { AnalysisOperationPort } from "../AnalysisProvider.js"; import { ok } from "../../domain/result.js"; import { readNativeCallRoutes } from "./NativeCallRoutes.js"; const provider = { id: "fixture", name: "Fixture", version: "1" }; const unresolved = [{ address: "0x1008", reason: "No static target" }]; const port = (typed: boolean): AnalysisOperationPort => ({ execute: async (operation) => { if (operation === "procedure_references") return ok(createAnalysisExecution({ reference_kinds_available: typed, references: [{ source_address: "0x1004", target_address: "0x2000", target_procedure: { address: "0x2000" }, kind: typed ? { available: true, provenance: "fixture", type: "COMPUTED_CALL", flow: true, call: true, jump: false, data: false, read: false, write: false, indirect: true, computed: true, external: false, conditional: false, terminal: false, primary: true, operand_index: 0 } : { available: false, reason: "Detailed kinds unavailable" }, }], unresolved_calls: unresolved, }, provider)); if (operation === "procedure_callees") return ok(createAnalysisExecution(["0x2000"], provider)); throw new Error(`Unexpected operation ${operation}`); }, }); describe("reported targetless sites", () => { it.each([true, false])("retains sites when typed kinds are %s", async (typed) => { const result = await readNativeCallRoutes(port(typed), "0x1000"); expect(result).toMatchObject({ ok: true, value: { unknowns: unresolved } }); }); }); ``` ## Expected behavior For this response, both the typed and fallback routes should retain the provider-reported `0x1008` targetless site as an unresolved observation. The trace contract distinguishes reported targetless sites from inferred untyped callees; a lack of detailed reference kinds does not erase a separate call-site observation. ## Actual behavior The local focused test passed with typed kinds and failed with unavailable kinds: the fallback returned `unknowns: []` instead of the reported `0x1008` entry. The caller that builds the UI action trace creates unresolved edges from `unknowns`, so this source-level branch drops that edge. The Hopper bridge can produce this response shape, but its behavior was established from source rather than a live provider run. ## Evidence - Expected source: `docs/native-investigation.md:210-218` states that targetless sites remain distinct in a bounded native trace; `src/application/native/NativeCallRoutes.ts:27` states that the route reader preserves targetless sites; `src/domain/hopperValues.ts:286-290` defines reported unresolved sites. - Failure source: `src/application/native/NativeCallRoutes.ts:52-107` parses the reported sites, enters the untyped callee fallback for unavailable kinds, and returns an empty `unknowns` list; `src/application/native/NativeUiActionTrace.ts:420-459` consumes that list for unresolved edges. - Evidence provenance: observed - Local verification: reproduced - Reproduction completeness: complete - The focused fixture produced one passing typed control and one failing fallback assertion. Expected `unknowns: [{ address: "0x1008", reason: "No static target" }]`; received `unknowns: []`. `bridge/hopper_bridge.py:383-421` provides static evidence that Hopper reports unavailable detailed kinds alongside `unresolved_calls` when a native call reference lacks a resolved target. - Related issue: [#2222](https://github.com/morluto/rea/issues/2222) was closed for a different consumer, `compare_functions`, that also lost uncertainty from unresolved calls; its fix does not address this route-reader fallback. ## Restoration check With the same provider response, both typed and untyped fallback cases retain the reported `0x1008` site and its reason in `unknowns`; the fallback still identifies `0x2000` as an inferred callee. A bounded UI action trace constructed from that result includes an unresolved edge for `0x1008` while retaining its partial static-call coverage. Keep the typed control to detect accidental loss in the existing path. ## Triage assessment - Impact level: P2 - Assessment status: supported - Impact basis: Providers without detailed reference kinds can lose specific reported targetless sites from a static trace, leaving an incomplete address-level call picture. The affected result is bounded to this response shape, and the trace still marks static-call coverage partial; real-provider frequency was not measured. - Workaround status: unverified - Workaround basis: Inspecting raw reference output or switching providers may recover the sites, but neither route was verified as practical for an affected Hopper session.
bsakaguchi 2026-10-15 18:29:15 +00:00
Author
Owner

@morluto commented on GitHub (Oct 14, 2026):

Untyped native call-route fallback now retains unresolved targetless call sites and the provider that reported them while adding any fallback callees. Both empty and nonempty fallback cases pass on current main.

Root cause: Untyped fallback returned fallback callees but dropped provider-reported unresolved call sites from the reference response.

Validation: src/application/native/NativeCallRoutes.test.ts: 'preserves targetless calls and their provider when fallback callees are []' and nonempty fallback variant check unknown address/reason/provider plus inferred route. These assertions passed in CI at 500e32399; the reviewed implementation is present on current main. Fix: 48b90fbaa.

Verification scope: Controlled provider port; no live Hopper run claimed.

<!-- gh-comment-id:6161945673 --> @morluto commented on GitHub (Oct 14, 2026): Untyped native call-route fallback now retains unresolved targetless call sites and the provider that reported them while adding any fallback callees. Both empty and nonempty fallback cases pass on current main. Root cause: Untyped fallback returned fallback callees but dropped provider-reported unresolved call sites from the reference response. Validation: src/application/native/NativeCallRoutes.test.ts: 'preserves targetless calls and their provider when fallback callees are []' and nonempty fallback variant check unknown address/reason/provider plus inferred route. These assertions passed in [CI at 500e32399](https://github.com/morluto/rea/actions/runs/38514789942); the reviewed implementation is present on current main. Fix: [48b90fbaa](https://github.com/morluto/rea/commit/48b90fbaa). Verification scope: Controlled provider port; no live Hopper run claimed.
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#882
No description provided.