[GH-ISSUE #5442] [Bug] Hopper procedure tools return another procedure for a non-entry symbol without explaining containment #3301

Closed
opened 2026-10-31 00:38:57 +00:00 by bsakaguchi · 1 comment
Owner

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

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

With Hopper, procedure_address and procedure_info accept _cold_helper, a symbol absent from list_procedures, and return the containing _warm_path procedure without explaining that containment resolution. The symbol is at 0x1000004a4; the returned procedure entry is 0x100000460.

Evidence preserves parameters.procedure: "_cold_helper", but does not report the resolved symbol coordinate or identify the returned procedure as its container. read_function_instructions, procedure_assembly, procedure_pseudo_code, and analyze_function likewise describe _warm_path for that selector. These are independently observed real-provider MCP results, not simulated-provider results.

Reproduction

The independent verifier compiled this reduced fixture with clang -O1 -g -o fixture fixture.c:

#include <stdio.h>
__attribute__((noinline)) int __attribute__((cold)) cold_helper(int x) { return x * 7 + 1; }
__attribute__((noinline)) int warm_path(int x) {
  if (x > 1000) return cold_helper(x);
  return x + 1;
}
int main(int argc, char **argv) {
  printf("%d\n", warm_path(argc));
  return 0;
}

The verifier started node scripts/rea.mjs mcp from the source checkout with isolated HOME and XDG config/data/cache/state directories and an explicit Hopper launcher. On one MCP connection, the verifier called:

  1. open_binary with {"path":"<absolute fixture path>","provider_id":"hopper"}.
  2. list_procedures and list_names, each with {}. Only list_names contained _cold_helper, at 0x1000004a4; list_procedures contained _warm_path at 0x100000460.
  3. procedure_address and procedure_info, each with {"procedure":"_cold_helper"}. Inspect structuredContent.normalized_result and the surrounding Evidence.
  4. As controls, procedure_address with {"procedure":"_warm_path"}, procedure_info with {"procedure":"0x1000004a4"}, and resolve_containing_procedure with {"address":"0x1000004a4"}.
  5. close_binary with {}, then closed the MCP client. Closure succeeded.

Addresses describe the tested build; use the inventories to derive them for other compiler layouts. One retry completed all selector and control calls successfully. The first attempt failed before bridge readiness and supplied no selector observation.

Expected behavior

When a name resolves inside a differently named analyzed procedure, return an actionable selection failure or explicitly report the containment relationship and preserve the symbol coordinate. This does not require Hopper to create a separate procedure for each compiler symbol or split its analyzed body. Explicit address containment and inventoried procedure names must continue to work.

Source-derived contract basis: src/contracts/officialToolContracts.ts:150-176 advertises unambiguous procedure symbol/address selection; docs/tool-design.md:49-63 requires preserving caller choices and distinguishing relationships. Explicit containment is separately advertised at officialToolContracts.ts:199-204. The Ghidra-specific interior-label guidance is not used as a Hopper contract.

Impact and scope

Function-specific analysis can silently describe a different analyzed procedure and combined body for a non-entry symbol. Observed scope is one source-built fixture and one successful Hopper session; broader frequency and other providers are untested. Impact assessment: P2, bounded consequential selection ambiguity.

Demonstrated workaround: obtain the symbol coordinate from list_names, call resolve_containing_procedure explicitly, and select the container's inventoried entry. The observed container assembly includes the cold block, so this preserves access to the analyzed code without changing the database. No executable or stored database mutation was performed.

REA version

6.7.0; source commit 074768901f3cdc8205b61773fb1faf6f6c7b3ca8. Latest published release remains rea-agents-6.7.0. Upstream checked at 052956af3416fa01d93837e54fdec3ad2739f6d1; no intervening changes to the cited contract, bridge, or provider files. No new runtime reproduction at that later revision is claimed.

Runtime and platform

Node v26.12.0; macOS 27.0.1, Darwin 27.0.0, arm64; Hopper Disassembler 6.1.0-demo, build 42.6295552; Apple clang 21.0.0, clang-2100.3.34.2.

Target and artifact context

Source-built arm64 Mach-O C executable from the fixture above. SHA-256: 8b9f7d0043221086b3c06cc215fdc59d8832c0c70c1bacb6a47a086d8cdcdeb4.

Interface

MCP

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

Successful independent MCP responses, reduced to relevant fields:

procedure_address({"procedure":"_cold_helper"})
  normalized_result: "0x100000460"
procedure_info({"procedure":"_cold_helper"})
  name: "_warm_path"
  entrypoint: "0x100000460"
  basicblock_count: 4
  length: 36

Both responses retained the requested name in Evidence parameters; their only limitation was dependence on Hopper's completed static analysis. Neither reported the symbol coordinate or an explicit containment observation.

Observed controls:

  • procedure_address({"procedure":"_warm_path"}) returned "0x100000460".
  • Numeric procedure_info at 0x1000004a4 also returned _warm_path.
  • resolve_containing_procedure({"address":"0x1000004a4"}) explicitly returned query_address: "0x1000004a4", found: true, and _warm_path at 0x100000460.
  • xrefs for _cold_helper and its numeric coordinate both returned ["0x100000470"].
  • Instruction and assembly results for _cold_helper included _warm_path's opening conditional and its out-of-line cold block at 0x1000004a4 through 0x1000004b0; pseudocode and the function dossier also represented _warm_path.

Typed outcome or recovery expected

An actionable non-entry/procedure-selection diagnostic, or an explicit successful containment observation tying the requested name and symbol coordinate to the containing procedure. Keep valid entry selection and explicit numeric containment working, and preserve the provider-analyzed cold block.

Analysis or suspected cause

Source-derived explanation: bridge/hopper_bridge.py:239-276 resolves a general symbol/address and asks Hopper for its procedure. Dispatch at :940-965 returns that procedure's entry or metadata without checking whether the requested name identifies its entry. src/hopper/HopperProvider.ts:307-326 projects the native result. src/application/AnalysisProvider.ts:134-148 preserves the original selector in Evidence, but does not add the missing containment relationship.

Additional context

Prepared with AI assistance and independently reproduced through real Hopper MCP. Related #1243 and #1270 concern batch decompilation identity; this report concerns Hopper's single-procedure name-selection tools. NOT_ENOUGH_INFO: no last-known-good version or regression window was established.

Originally created by @coygeek on GitHub (Oct 20, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/5442 ### 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 With Hopper, `procedure_address` and `procedure_info` accept `_cold_helper`, a symbol absent from `list_procedures`, and return the containing `_warm_path` procedure without explaining that containment resolution. The symbol is at `0x1000004a4`; the returned procedure entry is `0x100000460`. Evidence preserves `parameters.procedure: "_cold_helper"`, but does not report the resolved symbol coordinate or identify the returned procedure as its container. `read_function_instructions`, `procedure_assembly`, `procedure_pseudo_code`, and `analyze_function` likewise describe `_warm_path` for that selector. These are independently observed real-provider MCP results, not simulated-provider results. ### Reproduction The independent verifier compiled this reduced fixture with `clang -O1 -g -o fixture fixture.c`: ```c #include <stdio.h> __attribute__((noinline)) int __attribute__((cold)) cold_helper(int x) { return x * 7 + 1; } __attribute__((noinline)) int warm_path(int x) { if (x > 1000) return cold_helper(x); return x + 1; } int main(int argc, char **argv) { printf("%d\n", warm_path(argc)); return 0; } ``` The verifier started `node scripts/rea.mjs mcp` from the source checkout with isolated HOME and XDG config/data/cache/state directories and an explicit Hopper launcher. On one MCP connection, the verifier called: 1. `open_binary` with `{"path":"<absolute fixture path>","provider_id":"hopper"}`. 2. `list_procedures` and `list_names`, each with `{}`. Only `list_names` contained `_cold_helper`, at `0x1000004a4`; `list_procedures` contained `_warm_path` at `0x100000460`. 3. `procedure_address` and `procedure_info`, each with `{"procedure":"_cold_helper"}`. Inspect `structuredContent.normalized_result` and the surrounding Evidence. 4. As controls, `procedure_address` with `{"procedure":"_warm_path"}`, `procedure_info` with `{"procedure":"0x1000004a4"}`, and `resolve_containing_procedure` with `{"address":"0x1000004a4"}`. 5. `close_binary` with `{}`, then closed the MCP client. Closure succeeded. Addresses describe the tested build; use the inventories to derive them for other compiler layouts. One retry completed all selector and control calls successfully. The first attempt failed before bridge readiness and supplied no selector observation. ### Expected behavior When a name resolves inside a differently named analyzed procedure, return an actionable selection failure or explicitly report the containment relationship and preserve the symbol coordinate. This does not require Hopper to create a separate procedure for each compiler symbol or split its analyzed body. Explicit address containment and inventoried procedure names must continue to work. Source-derived contract basis: `src/contracts/officialToolContracts.ts:150-176` advertises unambiguous procedure symbol/address selection; `docs/tool-design.md:49-63` requires preserving caller choices and distinguishing relationships. Explicit containment is separately advertised at `officialToolContracts.ts:199-204`. The Ghidra-specific interior-label guidance is not used as a Hopper contract. ### Impact and scope Function-specific analysis can silently describe a different analyzed procedure and combined body for a non-entry symbol. Observed scope is one source-built fixture and one successful Hopper session; broader frequency and other providers are untested. Impact assessment: P2, bounded consequential selection ambiguity. Demonstrated workaround: obtain the symbol coordinate from `list_names`, call `resolve_containing_procedure` explicitly, and select the container's inventoried entry. The observed container assembly includes the cold block, so this preserves access to the analyzed code without changing the database. No executable or stored database mutation was performed. ### REA version 6.7.0; source commit `074768901f3cdc8205b61773fb1faf6f6c7b3ca8`. Latest published release remains `rea-agents-6.7.0`. Upstream checked at `052956af3416fa01d93837e54fdec3ad2739f6d1`; no intervening changes to the cited contract, bridge, or provider files. No new runtime reproduction at that later revision is claimed. ### Runtime and platform Node v26.12.0; macOS 27.0.1, Darwin 27.0.0, arm64; Hopper Disassembler 6.1.0-demo, build 42.6295552; Apple clang 21.0.0, clang-2100.3.34.2. ### Target and artifact context Source-built arm64 Mach-O C executable from the fixture above. SHA-256: `8b9f7d0043221086b3c06cc215fdc59d8832c0c70c1bacb6a47a086d8cdcdeb4`. ### Interface MCP ### Earliest failure stage Native analysis or reconstruction ### Relevant output or evidence Successful independent MCP responses, reduced to relevant fields: ```text procedure_address({"procedure":"_cold_helper"}) normalized_result: "0x100000460" procedure_info({"procedure":"_cold_helper"}) name: "_warm_path" entrypoint: "0x100000460" basicblock_count: 4 length: 36 ``` Both responses retained the requested name in Evidence parameters; their only limitation was dependence on Hopper's completed static analysis. Neither reported the symbol coordinate or an explicit containment observation. Observed controls: - `procedure_address({"procedure":"_warm_path"})` returned `"0x100000460"`. - Numeric `procedure_info` at `0x1000004a4` also returned `_warm_path`. - `resolve_containing_procedure({"address":"0x1000004a4"})` explicitly returned `query_address: "0x1000004a4"`, `found: true`, and `_warm_path` at `0x100000460`. - `xrefs` for `_cold_helper` and its numeric coordinate both returned `["0x100000470"]`. - Instruction and assembly results for `_cold_helper` included `_warm_path`'s opening conditional and its out-of-line cold block at `0x1000004a4` through `0x1000004b0`; pseudocode and the function dossier also represented `_warm_path`. ### Typed outcome or recovery expected An actionable non-entry/procedure-selection diagnostic, or an explicit successful containment observation tying the requested name and symbol coordinate to the containing procedure. Keep valid entry selection and explicit numeric containment working, and preserve the provider-analyzed cold block. ### Analysis or suspected cause Source-derived explanation: `bridge/hopper_bridge.py:239-276` resolves a general symbol/address and asks Hopper for its procedure. Dispatch at `:940-965` returns that procedure's entry or metadata without checking whether the requested name identifies its entry. `src/hopper/HopperProvider.ts:307-326` projects the native result. `src/application/AnalysisProvider.ts:134-148` preserves the original selector in Evidence, but does not add the missing containment relationship. ### Additional context Prepared with AI assistance and independently reproduced through real Hopper MCP. Related #1243 and #1270 concern batch decompilation identity; this report concerns Hopper's single-procedure name-selection tools. NOT_ENOUGH_INFO: no last-known-good version or regression window was established.
Author
Owner

@coygeek commented on GitHub (Oct 24, 2026):

Checked the reported fix on main at d87e73f2db.

The shared _procedure resolver now retains whether a selector came from a symbol or a numeric address. If a symbol resolves inside a procedure rather than to its entry, it raises InvalidRequestError containing the symbol coordinate and container identity. procedure_address/info/assembly/pseudo_code and read_function_instructions consume this resolver, so the original silent substitution is refused.

Implementation: hopper_bridge.py, hopper_bridge.py.

The relevant regression checks in hopperProcedureSelection.test.ts passed in the focused test run on this revision. This closure is based on the current source and those checks; provider fixtures do not establish a real-engine run. Closing as completed on main.

<!-- gh-comment-id:6380725693 --> @coygeek commented on GitHub (Oct 24, 2026): Checked the reported fix on `main` at d87e73f2db50be72d33a4b3c7199af35518b441c. The shared _procedure resolver now retains whether a selector came from a symbol or a numeric address. If a symbol resolves inside a procedure rather than to its entry, it raises InvalidRequestError containing the symbol coordinate and container identity. procedure_address/info/assembly/pseudo_code and read_function_instructions consume this resolver, so the original silent substitution is refused. Implementation: [hopper_bridge.py](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/bridge/hopper_bridge.py#L340), [hopper_bridge.py](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/bridge/hopper_bridge.py#L381). The relevant regression checks in [hopperProcedureSelection.test.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/tests/boundary/providers/hopper/hopperProcedureSelection.test.ts) passed in the focused test run on this revision. This closure is based on the current source and those checks; provider fixtures do not establish a real-engine run. Closing as completed on main.
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#3301
No description provided.