[GH-ISSUE #8284] [Bug] Native value trace reports missing parameter bindings for a known zero-argument function #6525

Open
opened 2026-11-01 21:11:55 +00:00 by bsakaguchi · 4 comments
Owner

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

Originally assigned to: @kaoru0822-kitauji on GitHub.

Before submitting

  • I searched 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

trace_native_values reports Parameter bindings ... are unavailable or omitted for a direct call to a Ghidra function with an explicitly authored zero-argument prototype. The call and return edges are otherwise present. This adds a missing-bindings unknown where there are no arguments in the controlled prototype.

Reproduction

  1. Build the source below for arm64 Mach-O with clang -arch arm64 -O1 -g -fno-inline -fno-optimize-sibling-calls fixture.c -o fixture. Do not run the target.
  2. Import it into a disposable Ghidra 12.1.4 project. A headless Ghidra script set audit_zero to a user-defined int audit_zero(void) and audit_one to a user-defined int audit_one(int x). Ghidra reported USER_DEFINED, parameter counts 0 and 1 respectively. Select that project in REA; REA copies the project and opens it read-only.
  3. In a real stdio MCP session call trace_native_values for _audit_call_zero with max_depth: 1, max_functions: 4, max_nodes: 100, max_edges: 200, limit: 100. The result contains the direct call and a return-binding edge, truncated: false, plus unknown Parameter bindings for 0x1000003c0 are unavailable or omitted (address for this build). The retained dossier reports int audit_zero(void), a non-truncated native value flow, and an empty parameter list.
  4. Control: tracing _audit_call_one using the same selected project returned a derived argument-binding edge, a return-binding edge, and no unknowns. With a fresh default import instead of the authored project, REA reported that argument 0 for audit_one was not recovered; that uncertainty is appropriate to preserve.
volatile int audit_global = 42;
__attribute__((noinline)) int audit_zero(void) { return audit_global; }
__attribute__((noinline)) int audit_one(int x) { return x + 1; }
__attribute__((noinline)) int audit_call_zero(void) { return audit_zero() + 3; }
__attribute__((noinline)) int audit_call_one(int x) { return audit_one(x) + 3; }
int main(void) { return audit_call_zero() + audit_call_one(5); }

Headless signature setup for the two selected functions, after resolving them in the program:

zero.setReturnType(IntegerDataType.dataType, SourceType.USER_DEFINED);
zero.replaceParameters(Function.FunctionUpdateType.DYNAMIC_STORAGE_ALL_PARAMS,
    true, SourceType.USER_DEFINED, new Parameter[0]);
one.setReturnType(IntegerDataType.dataType, SourceType.USER_DEFINED);
one.replaceParameters(Function.FunctionUpdateType.DYNAMIC_STORAGE_ALL_PARAMS,
    true, SourceType.USER_DEFINED,
    new ParameterImpl("x", IntegerDataType.dataType, currentProgram));

Expected behavior

Distinguish a known zero-parameter prototype from unavailable, omitted or truncated parameter facts, so a zero-argument call does not acquire a spurious missing-binding unknown. Preserve uncertainty when the provider has not established arity or call inputs, as in the fresh-import control. Any typed parameter-coverage fact should come from the producing boundary; do not infer authority solely by parsing display signature text.

Impact and scope

This can make an otherwise complete local value-flow trace appear unresolved and cause an agent to investigate nonexistent parameter bindings. The known result is one Ghidra selected-project control. Runtime execution, other architectures, variadics and other providers are untested. No source-file or project mutation occurred during the REA session.

REA version

b43c329df779708e43ae297f498a844e45d9c923 (source build). Relevant NativeValueTrace.ts unchanged at origin/main 23de10bb2849cdce8ddbe70504aa7cc57de75a98; later main was not runtime tested.

Runtime and platform

Node 24.22.1, macOS 26.3 arm64, Ghidra 12.1.4, JDK 21, MCP compact output schema profile.

Target and artifact context

Self-built arm64 Mach-O, SHA-256 962cdd4d5f291d5859a7d0b4c9a2db5a41ee10c67ee8b08b794683412d6a9aa8. Target digest and all 10 source-project file hashes stayed unchanged across the selected-project REA session. DWARF import logged some unrecoverable expressions; authored prototype provenance does not depend on complete DWARF recovery.

Interface

MCP

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

Source NativeValueTrace.ts unconditionally appends the unknown when a traversed callee's collected parameter nodes are empty. The same empty list can also arise from genuinely unavailable or omitted observations; handling only this fixture by removing the unknown is insufficient.

Follow-up: real-provider check of #8343

On exact PR head 84f66cc672f4bf7725e21bd3b5c73c2c5c4b35d0, macOS arm64, Ghidra 12.1.4/JDK 21, I repeated the original source-owned Mach-O task over real MCP with SDK 2.3.1 validating advertised compact output schemas. The selected authored nonvariadic zero-parameter case returned {basis: "signature", variadic: false} and no missing-parameter unknown; the authored one-parameter control retained its argument binding. A separately edited copy with a zero-fixed-parameter variadic prototype returned {basis: "signature", variadic: true} and retained the unknown. A fresh import returned {basis: "recovered", variadic: false} for the zero-parameter example and retained the unknown. The variadic edit in that control was deliberately inconsistent with the target's source; it is a metadata control, not a real variadic ABI test.

I also tested a separate compiled arm64 Mach-O whose source really declares audit_variadic(int, ...), without executing it. With an explicitly authored variadic project prototype, the PR returned {basis: "signature", variadic: true}; in a fresh import of the same target, Ghidra recovered one fixed parameter and the PR returned {basis: "recovered", variadic: false}. The latter is the decompiler prototype's flag, not proof that the source function is fixed-arity. The current trace's zero-parameter suppression correctly also requires basis: "signature"; the separate public boundary question in #5523 asks whether an unresolved variadic fact should be represented as unknown rather than a boolean. Distinguish the observed Ghidra prototype from source-level arity when interpreting this result. Both target and debug-companion hashes, and the selected project file hashes, were unchanged during the REA sessions. This is not runtime execution or a general guarantee about DWARF import.

For both the authored and fresh-import variadic fixture sessions, I exported the completed function dossier and imported it into a separate target-free MCP server. Schema-validated inspect_analysis_view native API facets matched their respective live results exactly, including the distinct {basis, variadic} values; the source target and project hashes were unchanged.

Originally created by @morluto on GitHub (Oct 31, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/8284 Originally assigned to: @kaoru0822-kitauji on GitHub. ### Before submitting - [x] I searched 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 `trace_native_values` reports `Parameter bindings ... are unavailable or omitted` for a direct call to a Ghidra function with an explicitly authored zero-argument prototype. The call and return edges are otherwise present. This adds a missing-bindings unknown where there are no arguments in the controlled prototype. ### Reproduction 1. Build the source below for arm64 Mach-O with `clang -arch arm64 -O1 -g -fno-inline -fno-optimize-sibling-calls fixture.c -o fixture`. Do not run the target. 2. Import it into a disposable Ghidra 12.1.4 project. A headless Ghidra script set `audit_zero` to a user-defined `int audit_zero(void)` and `audit_one` to a user-defined `int audit_one(int x)`. Ghidra reported `USER_DEFINED`, parameter counts 0 and 1 respectively. Select that project in REA; REA copies the project and opens it read-only. 3. In a real stdio MCP session call `trace_native_values` for `_audit_call_zero` with `max_depth: 1`, `max_functions: 4`, `max_nodes: 100`, `max_edges: 200`, `limit: 100`. The result contains the direct call and a return-binding edge, `truncated: false`, plus unknown `Parameter bindings for 0x1000003c0 are unavailable or omitted` (address for this build). The retained dossier reports `int audit_zero(void)`, a non-truncated native value flow, and an empty parameter list. 4. Control: tracing `_audit_call_one` using the same selected project returned a derived argument-binding edge, a return-binding edge, and no unknowns. With a fresh default import instead of the authored project, REA reported that argument 0 for `audit_one` was not recovered; that uncertainty is appropriate to preserve. ```c volatile int audit_global = 42; __attribute__((noinline)) int audit_zero(void) { return audit_global; } __attribute__((noinline)) int audit_one(int x) { return x + 1; } __attribute__((noinline)) int audit_call_zero(void) { return audit_zero() + 3; } __attribute__((noinline)) int audit_call_one(int x) { return audit_one(x) + 3; } int main(void) { return audit_call_zero() + audit_call_one(5); } ``` Headless signature setup for the two selected functions, after resolving them in the program: ```java zero.setReturnType(IntegerDataType.dataType, SourceType.USER_DEFINED); zero.replaceParameters(Function.FunctionUpdateType.DYNAMIC_STORAGE_ALL_PARAMS, true, SourceType.USER_DEFINED, new Parameter[0]); one.setReturnType(IntegerDataType.dataType, SourceType.USER_DEFINED); one.replaceParameters(Function.FunctionUpdateType.DYNAMIC_STORAGE_ALL_PARAMS, true, SourceType.USER_DEFINED, new ParameterImpl("x", IntegerDataType.dataType, currentProgram)); ``` ### Expected behavior Distinguish a known zero-parameter prototype from unavailable, omitted or truncated parameter facts, so a zero-argument call does not acquire a spurious missing-binding unknown. Preserve uncertainty when the provider has not established arity or call inputs, as in the fresh-import control. Any typed parameter-coverage fact should come from the producing boundary; do not infer authority solely by parsing display signature text. ### Impact and scope This can make an otherwise complete local value-flow trace appear unresolved and cause an agent to investigate nonexistent parameter bindings. The known result is one Ghidra selected-project control. Runtime execution, other architectures, variadics and other providers are untested. No source-file or project mutation occurred during the REA session. ### REA version `b43c329df779708e43ae297f498a844e45d9c923` (source build). Relevant `NativeValueTrace.ts` unchanged at `origin/main` `23de10bb2849cdce8ddbe70504aa7cc57de75a98`; later main was not runtime tested. ### Runtime and platform Node 24.22.1, macOS 26.3 arm64, Ghidra 12.1.4, JDK 21, MCP compact output schema profile. ### Target and artifact context Self-built arm64 Mach-O, SHA-256 `962cdd4d5f291d5859a7d0b4c9a2db5a41ee10c67ee8b08b794683412d6a9aa8`. Target digest and all 10 source-project file hashes stayed unchanged across the selected-project REA session. DWARF import logged some unrecoverable expressions; authored prototype provenance does not depend on complete DWARF recovery. ### Interface MCP ### Earliest failure stage Native analysis or reconstruction ### Relevant output or evidence Source [`NativeValueTrace.ts`](https://github.com/morluto/rea/blob/b43c329df779708e43ae297f498a844e45d9c923/src/application/native/NativeValueTrace.ts) unconditionally appends the unknown when a traversed callee's collected parameter nodes are empty. The same empty list can also arise from genuinely unavailable or omitted observations; handling only this fixture by removing the unknown is insufficient. ### Follow-up: real-provider check of #8343 On exact PR head `84f66cc672f4bf7725e21bd3b5c73c2c5c4b35d0`, macOS arm64, Ghidra 12.1.4/JDK 21, I repeated the original source-owned Mach-O task over real MCP with SDK 2.3.1 validating advertised compact output schemas. The selected authored nonvariadic zero-parameter case returned `{basis: "signature", variadic: false}` and no missing-parameter unknown; the authored one-parameter control retained its argument binding. A separately edited copy with a zero-fixed-parameter variadic prototype returned `{basis: "signature", variadic: true}` and retained the unknown. A fresh import returned `{basis: "recovered", variadic: false}` for the zero-parameter example and retained the unknown. The variadic edit in that control was deliberately inconsistent with the target's source; it is a metadata control, not a real variadic ABI test. I also tested a separate compiled arm64 Mach-O whose source really declares `audit_variadic(int, ...)`, without executing it. With an explicitly authored variadic project prototype, the PR returned `{basis: "signature", variadic: true}`; in a fresh import of the same target, Ghidra recovered one fixed parameter and the PR returned `{basis: "recovered", variadic: false}`. The latter is the decompiler prototype's flag, not proof that the source function is fixed-arity. The current trace's zero-parameter suppression correctly also requires `basis: "signature"`; the separate public boundary question in #5523 asks whether an unresolved variadic fact should be represented as unknown rather than a boolean. Distinguish the observed Ghidra prototype from source-level arity when interpreting this result. Both target and debug-companion hashes, and the selected project file hashes, were unchanged during the REA sessions. This is not runtime execution or a general guarantee about DWARF import. For both the authored and fresh-import variadic fixture sessions, I exported the completed function dossier and imported it into a separate target-free MCP server. Schema-validated `inspect_analysis_view` native API facets matched their respective live results exactly, including the distinct `{basis, variadic}` values; the source target and project hashes were unchanged.
Author
Owner

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

Looping in @gensokyo-dev @kaoru0822-kitauji @N0zoM1z0 for visibility on the REA tool-design investigation.

<!-- gh-comment-id:6541457620 --> @morluto commented on GitHub (Oct 31, 2026): Looping in @gensokyo-dev @kaoru0822-kitauji @N0zoM1z0 for visibility on the REA tool-design investigation.
Author
Owner

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

Real CLI parity control

On the same authored, read-only Ghidra project and target, rea trace-native-values <target> _audit_call_zero --provider ghidra --max-depth 1 --max-functions 4 --max-nodes 100 --max-edges 200 --limit 100 --format json --full-output exited 0. Its normalized result and Evidence ID exactly matched the prior real MCP call, including the missing-parameter unknown and the call and return edges. This confirms the observed result crosses the shared CLI/MCP workflow on this host and fixture; it does not establish other versions or platforms. The target hash and all ten caller-owned project-file hashes remained unchanged.

<!-- gh-comment-id:6541473451 --> @morluto commented on GitHub (Oct 31, 2026): ### Real CLI parity control On the same authored, read-only Ghidra project and target, `rea trace-native-values <target> _audit_call_zero --provider ghidra --max-depth 1 --max-functions 4 --max-nodes 100 --max-edges 200 --limit 100 --format json --full-output` exited 0. Its normalized result and Evidence ID exactly matched the prior real MCP call, including the missing-parameter unknown and the call and return edges. This confirms the observed result crosses the shared CLI/MCP workflow on this host and fixture; it does not establish other versions or platforms. The target hash and all ten caller-owned project-file hashes remained unchanged.
Author
Owner

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

Existing real-provider test coverage

The existing real Ghidra verifyNativeValueE2e checks that a selected fixture graph contains valid argument, parameter-use and return bindings and compares actual CLI/MCP outputs. It reports the graph's unknown count but does not assert that a known zero-argument callee lacks a missing-parameter unknown. The repository fixture has zero-argument functions, but this assertion does not trace the explicitly authored zero-argument prototype from the reproduction above.

A useful real-provider regression should retain the one-argument and missing-prototype controls alongside a user-defined zero-argument prototype. Empty recovered parameter arrays alone must not be promoted to proof of zero arity. The selected-project setup above is one reproducible way to establish the prototype; this is a proposed check, not a claim it has been added.

<!-- gh-comment-id:6541569068 --> @morluto commented on GitHub (Oct 31, 2026): ### Existing real-provider test coverage The existing real Ghidra [`verifyNativeValueE2e`](https://github.com/morluto/rea/blob/b43c329df779708e43ae297f498a844e45d9c923/scripts/lib/native-value-e2e.mjs) checks that a selected fixture graph contains valid argument, parameter-use and return bindings and compares actual CLI/MCP outputs. It reports the graph's unknown count but does not assert that a known zero-argument callee lacks a missing-parameter unknown. The repository fixture has zero-argument functions, but this assertion does not trace the explicitly authored zero-argument prototype from the reproduction above. A useful real-provider regression should retain the one-argument and missing-prototype controls alongside a user-defined zero-argument prototype. Empty recovered parameter arrays alone must not be promoted to proof of zero arity. The selected-project setup above is one reproducible way to establish the prototype; this is a proposed check, not a claim it has been added.
Author
Owner

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

Existing structured API facts and a variadic control

Real b43c329 / Ghidra 12.1.4 MCP on the existing fixture confirms REA already exposes a useful provenance distinction through inspect_native_api: the authored audit_zero has signature_source: "user_defined" and parameters: []; the fresh-import observation has signature_source: "default" and parameters: []. The authored one-argument control has user_defined and one parameter. The existing value trace still reports the zero-argument missing-bindings unknown in both cases, while the authored one-argument trace has no such unknown.

To test whether user_defined plus an empty array alone can prove a fixed zero-argument signature, I made a disposable copy of the authored project and deliberately set Ghidra's hasVarArgs flag on that same function, keeping zero fixed parameters and USER_DEFINED signature source. A headless script reported int audit_zero(void) / variadic=false before the edit and int audit_zero(...) / variadic=true after it. This is an intentionally inconsistent analyst-authored metadata control on a binary whose C source is non-variadic; it is not a real variadic ABI/runtime test.

REA's complete dossiers preserved the different displayed signatures, but the two inspect_native_api normalized results were identical, each with user_defined and an empty parameter array; their analysis-profile digests correctly differed because the selected projects differed. Neither structured API boundary exposes the variadic flag. Both value traces returned the same missing-bindings unknown. Thus the existing structured source fact helps separate these cases from a default signature, but does not by itself distinguish known zero fixed/non-variadic parameters from a variadic prototype. Do not infer completeness merely from signature_source plus an empty list or parse the display signature as the authority. A producer-owned typed variadic/parameter-coverage fact, with explicit unknown where unavailable, would let consumers make a narrower distinction.

All target, original-project and edited-copy regular-file hashes remained unchanged during the REA sessions; the copy's deliberate headless edit occurred before them. No target executed. One constructed metadata case, not broad compiler/provider coverage. The named bridge and schema/consumer source files are unchanged at local origin/main 23de10b.

<!-- gh-comment-id:6542365015 --> @morluto commented on GitHub (Oct 31, 2026): ### Existing structured API facts and a variadic control Real `b43c329` / Ghidra 12.1.4 MCP on the existing fixture confirms REA already exposes a useful provenance distinction through `inspect_native_api`: the authored `audit_zero` has `signature_source: "user_defined"` and `parameters: []`; the fresh-import observation has `signature_source: "default"` and `parameters: []`. The authored one-argument control has `user_defined` and one parameter. The existing value trace still reports the zero-argument missing-bindings unknown in both cases, while the authored one-argument trace has no such unknown. To test whether `user_defined` plus an empty array alone can prove a fixed zero-argument signature, I made a **disposable copy** of the authored project and deliberately set Ghidra's `hasVarArgs` flag on that same function, keeping zero fixed parameters and `USER_DEFINED` signature source. A headless script reported `int audit_zero(void)` / `variadic=false` before the edit and `int audit_zero(...)` / `variadic=true` after it. This is an intentionally inconsistent analyst-authored metadata control on a binary whose C source is non-variadic; it is not a real variadic ABI/runtime test. REA's complete dossiers preserved the different displayed signatures, but the two `inspect_native_api` **normalized results were identical**, each with `user_defined` and an empty parameter array; their analysis-profile digests correctly differed because the selected projects differed. Neither structured API boundary exposes the variadic flag. Both value traces returned the same missing-bindings unknown. Thus the existing structured source fact helps separate these cases from a default signature, but does not by itself distinguish known zero fixed/non-variadic parameters from a variadic prototype. Do not infer completeness merely from `signature_source` plus an empty list or parse the display signature as the authority. A producer-owned typed variadic/parameter-coverage fact, with explicit unknown where unavailable, would let consumers make a narrower distinction. All target, original-project and edited-copy regular-file hashes remained unchanged **during the REA sessions**; the copy's deliberate headless edit occurred before them. No target executed. One constructed metadata case, not broad compiler/provider coverage. The named bridge and schema/consumer source files are unchanged at local `origin/main` `23de10b`.
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#6525
No description provided.