[GH-ISSUE #4644] [Bug] Ghidra: one decompiler failure discards the whole analyze_function dossier and is reported as an environment problem #2422

Closed
opened 2026-10-17 21:04:27 +00:00 by bsakaguchi · 0 comments
Owner

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

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

When Ghidra's native decompiler fails on one function, analyze_function returns no dossier for that function. ReaGhidraBridge.analyzeFunctionObservation() calls decompile(), which throws RequestFailure("decompile_failed", ...) when DecompileResults.decompileCompleted() is false. Nothing catches it, so the whole request fails before any facet is collected. That includes assembly, comments, callers, callees, references, strings, names and basic blocks, which come from the program listing and don't need the decompiler.

Source-derived (not separately observed): on the TypeScript side, projectPrimarySessionError in src/ghidra/GhidraProviderClient.ts has no case for the remote decompile_failed code. It falls through to a generic ProviderAdapterError, so the user message is "Analysis could not complete. Run rea doctor and review the reported details; repeating the unchanged request is not expected to succeed." rea doctor can't help here. The installation is fine and the session is still usable.

Reproduction

Natural decompiler failures aren't easy to reproduce on demand, so I injected one. I used the same DecompInterface replacement that tests/conformance/ghidra/ReaFileOffsetSourceProbe.java already uses for procedurePseudocode.

  1. Build tests/conformance/ghidra/cross-format.c as a shared library (cc -dynamiclib -fPIC -O0).
  2. Import it with real Ghidra 12.1.4 analyzeHeadless, and run a post-script that loads ReaGhidraBridge.java.
  3. Call analyzeFunctionObservation on rea_cross_branch with the real decompiler. The dossier is returned.
  4. Replace the bridge's decompiler field with one whose decompileFunction returns a DecompileResults with error "Injected backend failure" and DisposeState.NOT_DISPOSED.
  5. Call analyzeFunctionObservation on the same function again.

Result: step 5 throws and no dossier is returned.

Expected behavior

The dossier contract already has a way to express this. facet_availability.pseudocode, native_api and native_value_flow each have an available: false + reason form. compare_functions and inspect_analysis_view already treat an unavailable facet as unknown (docs/mcp-contracts.md, "Function dossiers can declare per-facet coverage").

Expected:

  • analyze_function still returns the listing-backed facets. Pseudocode, native_api and native_value_flow are unavailable, with the decompiler's failure text as the reason.
  • decompile_cancelled still ends the request.
  • procedure_pseudo_code still fails, since pseudocode is its only output. Its error should say the decompiler failed for this procedure and the session is still usable, and point to analyze_function instead of rea doctor.

Impact and scope

Affects any Ghidra target where the decompiler fails on a function, e.g. a low-level decompiler error or the decompiler process dying mid-request. Those facts are lost for that function in analyze_function, inspect_native_api, compare_functions, and the dossier returned after annotate_native_function. How often natural decompiler failures happen on real binaries is unknown. Hopper/IDA/Kuna were not checked.

REA version

source commit <5c0472c57> (main)

Runtime and platform

Node 24.18.0; macOS arm64; Ghidra 12.1.4; Temurin JDK 21.0.12.1

Target and artifact context

Mach-O arm64 dylib built from tests/conformance/ghidra/cross-format.c

Interface

MCP

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

Real Ghidra 12.1.4 run on main's bridge with the injected failure from the reproduction steps:

ERROR REPORT SCRIPT ERROR: (HeadlessAnalyzer) java.lang.reflect.InvocationTargetException
Caused by: ReaGhidraBridge$RequestFailure: Ghidra decompilation failed: Injected backend failure

Source references:

  • bridge/ghidra/ReaGhidraBridge.java: decompile() throws decompile_failed; analyzeFunctionObservation() calls it without a catch.
  • src/ghidra/GhidraProviderClient.ts: projectPrimarySessionError maps decompile_cancelled but not decompile_failed.

Remaining uncertainty: the failure was injected. I don't have a binary that makes Ghidra 12.1.4's decompiler fail naturally and every time.

Typed outcome or recovery expected

analyze_function succeeds, with facet_availability.pseudocode = { available: false, reason: "Ghidra decompilation failed: <detail>" } and the same reason on native_api and native_value_flow. procedure_pseudo_code keeps failing with execution_failure, but the message names the decompiler failure and the remediation points to analyze_function / rea function.

Analysis or suspected cause

Source-derived: decompile() is shared by procedurePseudocode and analyzeFunctionObservation. A hard failure is right for the first, because pseudocode is its only output. It's too strict for the second, because most of the dossier doesn't depend on the decompiler.

Additional context

Related: the decompile-failure point raised in #487. I have a fix with a real-Ghidra regression lane ready and would like to take this.

Originally created by @RishTheFish21 on GitHub (Oct 17, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/4644 ### 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 When Ghidra's native decompiler fails on one function, `analyze_function` returns no dossier for that function. `ReaGhidraBridge.analyzeFunctionObservation()` calls `decompile()`, which throws `RequestFailure("decompile_failed", ...)` when `DecompileResults.decompileCompleted()` is false. Nothing catches it, so the whole request fails before any facet is collected. That includes assembly, comments, callers, callees, references, strings, names and basic blocks, which come from the program listing and don't need the decompiler. Source-derived (not separately observed): on the TypeScript side, `projectPrimarySessionError` in `src/ghidra/GhidraProviderClient.ts` has no case for the remote `decompile_failed` code. It falls through to a generic `ProviderAdapterError`, so the user message is "Analysis could not complete. Run `rea doctor` and review the reported details; repeating the unchanged request is not expected to succeed." `rea doctor` can't help here. The installation is fine and the session is still usable. ### Reproduction Natural decompiler failures aren't easy to reproduce on demand, so I injected one. I used the same `DecompInterface` replacement that `tests/conformance/ghidra/ReaFileOffsetSourceProbe.java` already uses for `procedurePseudocode`. 1. Build `tests/conformance/ghidra/cross-format.c` as a shared library (`cc -dynamiclib -fPIC -O0`). 2. Import it with real Ghidra 12.1.4 `analyzeHeadless`, and run a post-script that loads `ReaGhidraBridge.java`. 3. Call `analyzeFunctionObservation` on `rea_cross_branch` with the real decompiler. The dossier is returned. 4. Replace the bridge's `decompiler` field with one whose `decompileFunction` returns a `DecompileResults` with error "Injected backend failure" and `DisposeState.NOT_DISPOSED`. 5. Call `analyzeFunctionObservation` on the same function again. Result: step 5 throws and no dossier is returned. ### Expected behavior The dossier contract already has a way to express this. `facet_availability.pseudocode`, `native_api` and `native_value_flow` each have an `available: false` + `reason` form. `compare_functions` and `inspect_analysis_view` already treat an unavailable facet as unknown (docs/mcp-contracts.md, "Function dossiers can declare per-facet coverage"). Expected: - `analyze_function` still returns the listing-backed facets. Pseudocode, `native_api` and `native_value_flow` are unavailable, with the decompiler's failure text as the reason. - `decompile_cancelled` still ends the request. - `procedure_pseudo_code` still fails, since pseudocode is its only output. Its error should say the decompiler failed for this procedure and the session is still usable, and point to `analyze_function` instead of `rea doctor`. ### Impact and scope Affects any Ghidra target where the decompiler fails on a function, e.g. a low-level decompiler error or the decompiler process dying mid-request. Those facts are lost for that function in `analyze_function`, `inspect_native_api`, `compare_functions`, and the dossier returned after `annotate_native_function`. How often natural decompiler failures happen on real binaries is unknown. Hopper/IDA/Kuna were not checked. ### REA version source commit <5c0472c57> (main) ### Runtime and platform Node 24.18.0; macOS arm64; Ghidra 12.1.4; Temurin JDK 21.0.12.1 ### Target and artifact context Mach-O arm64 dylib built from tests/conformance/ghidra/cross-format.c ### Interface MCP ### Earliest failure stage Native analysis or reconstruction ### Relevant output or evidence Real Ghidra 12.1.4 run on main's bridge with the injected failure from the reproduction steps: ERROR REPORT SCRIPT ERROR: (HeadlessAnalyzer) java.lang.reflect.InvocationTargetException Caused by: ReaGhidraBridge$RequestFailure: Ghidra decompilation failed: Injected backend failure Source references: - bridge/ghidra/ReaGhidraBridge.java: `decompile()` throws `decompile_failed`; `analyzeFunctionObservation()` calls it without a catch. - src/ghidra/GhidraProviderClient.ts: `projectPrimarySessionError` maps `decompile_cancelled` but not `decompile_failed`. Remaining uncertainty: the failure was injected. I don't have a binary that makes Ghidra 12.1.4's decompiler fail naturally and every time. ### Typed outcome or recovery expected `analyze_function` succeeds, with `facet_availability.pseudocode = { available: false, reason: "Ghidra decompilation failed: <detail>" }` and the same reason on `native_api` and `native_value_flow`. `procedure_pseudo_code` keeps failing with `execution_failure`, but the message names the decompiler failure and the remediation points to `analyze_function` / `rea function`. ### Analysis or suspected cause Source-derived: `decompile()` is shared by `procedurePseudocode` and `analyzeFunctionObservation`. A hard failure is right for the first, because pseudocode is its only output. It's too strict for the second, because most of the dossier doesn't depend on the decompiler. ### Additional context Related: the decompile-failure point raised in #487. I have a fix with a real-Ghidra regression lane ready and would like to take this.
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#2422
No description provided.