[GH-ISSUE #2908] [Feature] Compile packaged bridge Java sources as a CI preflight #1023

Closed
opened 2026-10-15 18:30:26 +00:00 by bsakaguchi · 0 comments
Owner

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

Before submitting

  • I searched the open and closed issues and pull requests for a related proposal.
  • I understand the contribution scope must be reviewed before integration; open questions are welcome here.
  • This request does not include credentials, private samples, or sensitive target data.

Area

Native analysis or decompilation (packaged bridge sources)

Current limitation

Nothing in the repository or in CI compiles the packaged Java bridge sources under bridge/. The bridge is compiled at runtime by the host tool: Ghidra's script provider compiles bridge/ghidra/*.java when a session starts. A bridge source that does not compile therefore fails only at session start, on a host that has Ghidra installed, after import and auto-analysis have already run — and the whole bridge is compiled before any operation is served, so one broken source disables every operation of that provider, not just the code path that references the missing type.

That is how the missing import java.util.HashMap; in bridge/ghidra/ReaGhidraBridge.java (introduced with the Swift literal decoder, fixed in #2844) reached main. The failure chain, observed on base revision 8e6f37e2a with a stock Ghidra 12.1.4 release (Ghidra_12.1.4_build) and OpenJDK 21.0.12.1 on Linux x64:

  1. rea doctor --json reports ghidra, ghidra-version, ghidra-installation, ghidra-headless, ghidra-native_decompiler, and ghidra-java healthy. Doctor never compiles the bridge, so it cannot detect this class of defect.
  2. rea analyze <binary> --json fails with execution_failure, details.provider_id: ghidra, details.operation: list_documents, failure_kind: process, failure_message: "Ghidra exited before bridge startup". A direct support/analyzeHeadless run imports and analyzes the same file without REA, so the installation itself looks sound.
  3. The only place the cause appears is the ephemeral session's ghidra.log (path in details.diagnostics.ghidra_log_path):
INFO  (HeadlessAnalyzer) REPORT: Execute script: .../bridge/ghidra/ReaGhidraBridge.java '.../session.json'
ERROR (HeadlessAnalyzer) ... with 1 file failing in previous build(s):
ReaGhidraBridge.java:3446: error: cannot find symbol
        Map<String, Boolean> selectedValues = new HashMap<>();
                                                  ^
  symbol:   class HashMap
  location: class ReaGhidraBridge

ERROR (HeadlessAnalyzer) REPORT SCRIPT ERROR: ... ReaGhidraBridge not found by 64a1011b [1]
ghidra.app.script.GhidraScriptLoadException: The class could not be found. It must be the public class of the .java file

REA deletes the temporary project and its logs when the request fails, so that message has to be snapshotted while the provider process is still alive. The failure message the client receives does not name the compile error.

Use cases

  • Any change to a packaged bridge source (bridge/ghidra/, bridge/nativeaot/, bridge/android/) that compiles only under one host's script provider and fails under another.
  • A release that ships a bridge source referencing a type it does not import. Reverting to the previous release would also restore the fix in #2844, so the cost of not catching this before merge is paid by every user of the affected host tool.

Desired outcome and success criteria

A check that compiles the packaged bridge Java sources against the API jars of the supported host tool, so a missing or incorrect import fails a PR check instead of shipping.

Success criteria:

  • On base revision 8e6f37e2a the check fails with ReaGhidraBridge.java:3446: error: cannot find symbol ... class HashMap.
  • With the #2844 import applied the check passes for all nine bridge/ghidra/*.java sources.
  • The check runs without launching Ghidra and without selecting, importing, or executing a target.
  • Its failure output names the offending file, line, and symbol — not just "bridge startup failed".

Expected interaction with targets

  • Read existing files or captured data.
  • Launch or attach to a process.
  • Inject instrumentation or run scripts/code.
  • Change process memory or registers.
  • Write or change files/configuration.
  • Contact network services.
  • Recover protected code or work around protections.
  • No target interaction (documentation or usability only).
  • Not sure yet

The check compiles bridge sources and reads the installed host tool's jars. No target is selected, imported, or executed, and no host process is started.

Verification available so far

I have a prototype or observed results.

Verification details

Reproduced on Linux x64 with Ghidra 12.1.4 (Ghidra_12.1.4_build, 2026-09-21) and OpenJDK 21.0.12.1. A plain javac invocation against the installation's jar set reproduces the runtime failure exactly, without launching Ghidra:

CP=$(find "$GHIDRA_INSTALL_DIR" -name '*.jar' | tr '\n' ':')   # 202 jars in the extracted release
javac -proc:none -cp "$CP" -d /tmp/out bridge/ghidra/*.java
  • base 8e6f37e2a → ReaGhidraBridge.java:3446: error: cannot find symbol ... symbol: class HashMap / location: class ReaGhidraBridge (1 error, 9 sources)
  • with import java.util.HashMap; → exit 0, all nine sources compiled to class files

Runtime confirmation on the same host, through the production CLI path with real Ghidra: with the import, rea analyze /bin/true --json returns binary_overview (procedure_count: 134, segment_count: 34, string_count: 132, provider ghidra 12.1.4) and rea decompile /bin/true --procedure 0x2db0 --format md returns decompiler pseudo-code for the ELF entry. Before the import both fail with Ghidra exited before bridge startup.

The Windows real-Ghidra lane already runs on a host with Ghidra and JDK 21 installed, and the reporter of #2865 compiled all nine packaged bridge sources against 195 API jars manually on Windows — so the check is cheap once a jar classpath is available. Which lane should own it (the real-Ghidra lane as a fast preflight, or its own scheduled lane) is a maintainer decision. I have not measured runtime or jar-count stability across releases; a pinned Ghidra version already used by the lanes would keep the check deterministic.

Alternatives considered

  • rea doctor — validates platform, architecture, version, headless launcher, native decompiler, and JDK, but never compiles the bridge. It reported healthy for the entire failure above.
  • Existing real-provider lanes (verify:ghidra*) — they do exercise the bridge end to end, but they need a host with Ghidra plus a C compiler, they run long, and each lane only covers the targets and architectures it selects. A compile preflight is faster and covers every packaged bridge source regardless of target.
  • lint / knip / format:check — these cover the TypeScript sources; nothing lints or compiles the Java sources under bridge/.
  • Relying on diagnostics alone — the compile error appears only in the ephemeral ghidra.log that is removed with the failed session; the client sees a generic execution_failure. This is worth improving, but it does not prevent a broken bridge from being merged.
  • scripts/build-ghidra-nativeaot.mjs — it already compiles bridge/nativeaot/*.java with javac --release 21 against the Ghidra jars, but it belongs to the NativeAOT lane and does not cover bridge/ghidra/*.java.

Possible direction

Compile bridge/ghidra/*.java with the JDK the Ghidra lanes already require (javac --release 21, following the pattern in scripts/build-ghidra-nativeaot.mjs), using the jar set from the installed Ghidra. Run it as a preflight in the Windows real-Ghidra lane, or as its own lane on any host with Ghidra present. Extending the same check to bridge/android/ReaJadxBridge.java needs a JADX API classpath, so that is a separate design question. A related, optional improvement is to have the bridge-startup failure surface the compile error instead of only the removed log — happy to discuss it as a separate request if it is in scope. I have not opened a PR for this; I can prototype the lane if maintainers want it.

References

  • #2844 — fix(ghidra): import HashMap in the bridge script (the shipped defect and its fix)
  • #2865 — same fix reported on Windows; includes the "nine packaged bridge sources against 195 API jars" compile check
  • docs/testing.md — real-toolchain verification lanes
  • scripts/build-ghidra-nativeaot.mjs — existing in-repo javac compile of bridge sources
Originally created by @ake117 on GitHub (Oct 13, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/2908 ### Before submitting - [x] I searched the open and closed issues and pull requests for a related proposal. - [x] I understand the [contribution scope](https://github.com/morluto/rea/blob/main/CONTRIBUTING.md#contribution-scope) must be reviewed before integration; open questions are welcome here. - [x] This request does not include credentials, private samples, or sensitive target data. ### Area Native analysis or decompilation (packaged bridge sources) ### Current limitation Nothing in the repository or in CI compiles the packaged Java bridge sources under `bridge/`. The bridge is compiled at runtime by the host tool: Ghidra's script provider compiles `bridge/ghidra/*.java` when a session starts. A bridge source that does not compile therefore fails only at session start, on a host that has Ghidra installed, after import and auto-analysis have already run — and the whole bridge is compiled before any operation is served, so one broken source disables every operation of that provider, not just the code path that references the missing type. That is how the missing `import java.util.HashMap;` in `bridge/ghidra/ReaGhidraBridge.java` (introduced with the Swift literal decoder, fixed in #2844) reached `main`. The failure chain, observed on base revision `8e6f37e2a` with a stock Ghidra 12.1.4 release (`Ghidra_12.1.4_build`) and OpenJDK 21.0.12.1 on Linux x64: 1. `rea doctor --json` reports `ghidra`, `ghidra-version`, `ghidra-installation`, `ghidra-headless`, `ghidra-native_decompiler`, and `ghidra-java` healthy. Doctor never compiles the bridge, so it cannot detect this class of defect. 2. `rea analyze <binary> --json` fails with `execution_failure`, `details.provider_id: ghidra`, `details.operation: list_documents`, `failure_kind: process`, `failure_message: "Ghidra exited before bridge startup"`. A direct `support/analyzeHeadless` run imports and analyzes the same file without REA, so the installation itself looks sound. 3. The only place the cause appears is the ephemeral session's `ghidra.log` (path in `details.diagnostics.ghidra_log_path`): ``` INFO (HeadlessAnalyzer) REPORT: Execute script: .../bridge/ghidra/ReaGhidraBridge.java '.../session.json' ERROR (HeadlessAnalyzer) ... with 1 file failing in previous build(s): ReaGhidraBridge.java:3446: error: cannot find symbol Map<String, Boolean> selectedValues = new HashMap<>(); ^ symbol: class HashMap location: class ReaGhidraBridge ERROR (HeadlessAnalyzer) REPORT SCRIPT ERROR: ... ReaGhidraBridge not found by 64a1011b [1] ghidra.app.script.GhidraScriptLoadException: The class could not be found. It must be the public class of the .java file ``` REA deletes the temporary project and its logs when the request fails, so that message has to be snapshotted while the provider process is still alive. The failure message the client receives does not name the compile error. ### Use cases - Any change to a packaged bridge source (`bridge/ghidra/`, `bridge/nativeaot/`, `bridge/android/`) that compiles only under one host's script provider and fails under another. - A release that ships a bridge source referencing a type it does not import. Reverting to the previous release would also restore the fix in #2844, so the cost of not catching this before merge is paid by every user of the affected host tool. ### Desired outcome and success criteria A check that compiles the packaged bridge Java sources against the API jars of the supported host tool, so a missing or incorrect import fails a PR check instead of shipping. Success criteria: - On base revision `8e6f37e2a` the check fails with `ReaGhidraBridge.java:3446: error: cannot find symbol ... class HashMap`. - With the #2844 import applied the check passes for all nine `bridge/ghidra/*.java` sources. - The check runs without launching Ghidra and without selecting, importing, or executing a target. - Its failure output names the offending file, line, and symbol — not just "bridge startup failed". ### Expected interaction with targets - [x] Read existing files or captured data. - [ ] Launch or attach to a process. - [ ] Inject instrumentation or run scripts/code. - [ ] Change process memory or registers. - [ ] Write or change files/configuration. - [ ] Contact network services. - [ ] Recover protected code or work around protections. - [ ] No target interaction (documentation or usability only). - [ ] Not sure yet The check compiles bridge sources and reads the installed host tool's jars. No target is selected, imported, or executed, and no host process is started. ### Verification available so far I have a prototype or observed results. ### Verification details Reproduced on Linux x64 with Ghidra 12.1.4 (`Ghidra_12.1.4_build`, 2026-09-21) and OpenJDK 21.0.12.1. A plain `javac` invocation against the installation's jar set reproduces the runtime failure exactly, without launching Ghidra: ```bash CP=$(find "$GHIDRA_INSTALL_DIR" -name '*.jar' | tr '\n' ':') # 202 jars in the extracted release javac -proc:none -cp "$CP" -d /tmp/out bridge/ghidra/*.java ``` - base `8e6f37e2a` → `ReaGhidraBridge.java:3446: error: cannot find symbol ... symbol: class HashMap / location: class ReaGhidraBridge` (1 error, 9 sources) - with `import java.util.HashMap;` → exit 0, all nine sources compiled to class files Runtime confirmation on the same host, through the production CLI path with real Ghidra: with the import, `rea analyze /bin/true --json` returns `binary_overview` (`procedure_count: 134`, `segment_count: 34`, `string_count: 132`, provider `ghidra` 12.1.4) and `rea decompile /bin/true --procedure 0x2db0 --format md` returns decompiler pseudo-code for the ELF entry. Before the import both fail with `Ghidra exited before bridge startup`. The Windows real-Ghidra lane already runs on a host with Ghidra and JDK 21 installed, and the reporter of #2865 compiled all nine packaged bridge sources against 195 API jars manually on Windows — so the check is cheap once a jar classpath is available. Which lane should own it (the real-Ghidra lane as a fast preflight, or its own scheduled lane) is a maintainer decision. I have not measured runtime or jar-count stability across releases; a pinned Ghidra version already used by the lanes would keep the check deterministic. ### Alternatives considered - **`rea doctor`** — validates platform, architecture, version, headless launcher, native decompiler, and JDK, but never compiles the bridge. It reported healthy for the entire failure above. - **Existing real-provider lanes (`verify:ghidra*`)** — they do exercise the bridge end to end, but they need a host with Ghidra plus a C compiler, they run long, and each lane only covers the targets and architectures it selects. A compile preflight is faster and covers every packaged bridge source regardless of target. - **`lint` / `knip` / `format:check`** — these cover the TypeScript sources; nothing lints or compiles the Java sources under `bridge/`. - **Relying on diagnostics alone** — the compile error appears only in the ephemeral `ghidra.log` that is removed with the failed session; the client sees a generic `execution_failure`. This is worth improving, but it does not prevent a broken bridge from being merged. - **`scripts/build-ghidra-nativeaot.mjs`** — it already compiles `bridge/nativeaot/*.java` with `javac --release 21` against the Ghidra jars, but it belongs to the NativeAOT lane and does not cover `bridge/ghidra/*.java`. ### Possible direction Compile `bridge/ghidra/*.java` with the JDK the Ghidra lanes already require (`javac --release 21`, following the pattern in `scripts/build-ghidra-nativeaot.mjs`), using the jar set from the installed Ghidra. Run it as a preflight in the Windows real-Ghidra lane, or as its own lane on any host with Ghidra present. Extending the same check to `bridge/android/ReaJadxBridge.java` needs a JADX API classpath, so that is a separate design question. A related, optional improvement is to have the bridge-startup failure surface the compile error instead of only the removed log — happy to discuss it as a separate request if it is in scope. I have not opened a PR for this; I can prototype the lane if maintainers want it. ### References - #2844 — fix(ghidra): import HashMap in the bridge script (the shipped defect and its fix) - #2865 — same fix reported on Windows; includes the "nine packaged bridge sources against 195 API jars" compile check - [docs/testing.md](https://github.com/morluto/rea/blob/main/docs/testing.md#real-toolchain-verification-lanes) — real-toolchain verification lanes - [scripts/build-ghidra-nativeaot.mjs](https://github.com/morluto/rea/blob/main/scripts/build-ghidra-nativeaot.mjs) — existing in-repo javac compile of bridge sources
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#1023
No description provided.