Commit 76c8df0
fix: resident Ghidra bridge empties decompiles over time (DecompInterface leak + reused TaskMonitor) (#271)
* fix: resident Ghidra bridge empties decompiles over time (DecompInterface leak + reused TaskMonitor)
On a long-lived resident Ghidra bridge (re_bridge_start), re_decompile_* started
returning empty pseudo-C (signature + callees, no body) for many functions after
hours of use. Two independent container-side defects in the bridge's pyghidra
server caused it — both masked on the headless path (one process per call, so OS
teardown reclaims everything) and surfacing only on the long-lived resident bridge.
1. Undisposed DecompInterface (dominant). _focus_facts and taint_core built a
DecompInterface per call and never dispose()d it. Each spawns a native
`decompile` subprocess + I/O threads, so a resident server accumulates them
until pthread_create fails (EAGAIN) and decompileFunction can no longer start
its subprocess -> decompileCompleted()==false -> empty body. Dispose in a
finally (per request) / before return (taint reuses one across its loop). The
emulator already disposed, so this was an oversight.
2. Reused ConsoleTaskMonitor. The probe minted one monitor and serve_bridge reused
it for every RPC. decompileFunction(f, 60, monitor) cancels the monitor it is
handed on the 60s timeout, and a ConsoleTaskMonitor stays cancelled, so a single
slow function poisoned every later decompile into an empty body. Mint a fresh
monitor per request (pass the ConsoleTaskMonitor class as a factory).
The empty body is a FRESH failed decompile, not a stale cache read: the decompile
path never reads back from the Observation store. (The identical content_hash across
a target's observations is by design — it is the target's sha256, scoping obs to the
exact bytes; dedup also keys on tool+args+result_kind, so it is not defeated.)
Tests: added offline regression tests — a fresh-monitor-per-request check and two
DecompInterface-dispose checks (success + error path), all of which fail on the old
code. Full offline fast tier green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(taint): dispose the taint DecompInterface in a finally + cover it offline
Addresses the two non-blocking merge-gate review findings on the resident-bridge
decompiler-leak fix. taint_core reused ONE DecompInterface across its candidate loop
but disposed it only in a bare pre-return statement, so an unsuppressed Java error in
the loop (or in openProgram) leaked it on the error path — the same defect class this
PR fixes for _focus_facts. Wrap the loop + return in try/finally (openProgram now
inside the try), matching _focus_facts.
Add two offline regression tests: taint_core disposes on the success path, and — the
one that locks in the finding — disposes via the finally when the try raises (fails on
the pre-finally pre-return dispose). The re-indent is logic-preserving (git diff -w
shows only the try/finally wrapper).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>1 parent 1a9c52f commit 76c8df0
3 files changed
Lines changed: 451 additions & 220 deletions
File tree
- src/hexgraph/sandbox/probes
- tests
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
40 | 40 | | |
41 | 41 | | |
42 | 42 | | |
43 | | - | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
44 | 48 | | |
45 | 49 | | |
46 | 50 | | |
| |||
0 commit comments