Abstract
Object escapes keep data alive past its intended lifetime, creating performance, stability, and correctness risks across runtimes. This paper presents Graphene-HA, a fused-evidence escape-analysis workflow that unifies static analysis with heap-growth tracking across Python, Java, JavaScript, Go, and Rust under a single command and report format. The system combines a Python CLI, a Rust orchestration core, and language-specific bridges that directly measure heap allocation deltas during execution. Each bridge uses native runtime introspection (tracemalloc for Python, MemoryMXBean for Java, process.memoryUsage for Node.js, runtime.MemStats for Go, and custom GlobalAlloc for Rust) to capture heap growth as evidence of object escape. We evaluate the workflow on 1,421 labeled targets spanning five languages. Overall accuracy improves from 61.2% with static analysis to 83.7% with combined analysis, while dynamic heap tracking achieves 100.0% accuracy on this benchmark, with an F1 score of 1.0. The largest combined gains are observed in Python, where F1 rises from 20.2% to 92.0%, and in Java, where F1 rises from 31.5% to 100.0%. Go and Rust remain constrained by static false-positive patterns when evidence is combined. These results suggest that direct heap measurement is a reliable dynamic signal for object escape detection, and that a unified combined pipeline can substantially improve correctness compared to static analysis alone. Future work will extend Graphene-HA to additional languages and to stronger detection patterns for edge cases in Go and Rust.
Motivation
An object escape occurs when temporary data created for one task is stored in a longer-lived location. When the task ends, the data remains reachable.
The following example demonstrates the basic form of the problem. The make_escape
function creates a local object, but instead of allowing it to end with the function, it
appends the object to a global list. Each call to make_escape adds another object to
that list, which means the memory associated with those objects is retained indefinitely.
global_store = []
def make_escape(): # escape: stored in global scope
local_obj = {"x": 1}
global_store.append(local_obj)
Every call to make_escape() is intended to complete and release its temporary state, but
the object remains in global_store. If the function is invoked repeatedly, the container
grows over time. In production systems, this appears as slowdowns, memory pressure,
increased operational cost, and eventually crashes or restarts.
Escape-related failures also represent a correctness problem. If stale state remains reachable, later work can read outdated data and make an incorrect decision. For users, this appears as inconsistent outputs. For operators, it appears as incidents that are difficult to reproduce and diagnose.
This pattern recurs across languages despite syntactic differences. In Python, a queued callable can keep a large object alive. In Java, deferred workers can retain request state after response completion. In JavaScript, timer callbacks can hold stale closures. In Go and Rust, background workers can preserve references through queue or channel paths. This cross-language consistency is one reason a unified detection workflow is useful, because the engineering pattern changes less than the surface syntax suggests.
Goals
Real-world detection. Detect object-escape behaviour that appears in production systems, including retained callback captures, queue jobs that hold stale request state, deferred tasks without clear closure, and resource lifetimes that outlive their intended scope.
Unified workflow. Maintain workflow consistency across languages so findings can be compared directly within one investigation cycle. Language-specific bridges remain native to their environments; orchestration and reporting remain consistent.
Low barrier of entry. Reduce onboarding cost through a constrained interface, sensible defaults, and artifact formats that are readable without custom scripts.
Modular design. Allow additional language bridges, stronger static heuristics, and richer reporting without full-system redesign.
Method
Implementation stack
The implementation combines a lightweight Python entry layer with a strongly typed Rust orchestration core and language-native analyzer bridges.
- CLI and entrypoint — Python (
graphene_ha/cli.py) for argument parsing, validation and dispatch. - Core orchestration — Rust (
src/main.rs,src/orchestrator.rs,src/analyzer.rs) for startup checks, mode routing, bridge execution, evidence merge and artifact handoff. - Static analysis path — the Rust core static analyzer plus language-specific static
analyzers under
analyzers/. - Dynamic execution path — language bridges using runtime-native memory introspection.
- Report generation — a Rust reporting layer (
src/report.rs) producing reproducible session artifacts (README.md,results.csv, optionalvulnerabilities.md).
Heap tracking
The dynamic layer measures heap allocation growth as direct evidence of object escape. Each language bridge executes the target function under a timeout, capturing heap state before and after invocation, then computes the allocation delta. Rather than inferring escape from indirect signals such as thread persistence, the bridges directly measure whether unfreed memory accumulates, which is the operational consequence of object escape.
| Language | Heap-tracking mechanism |
|---|---|
| Python | tracemalloc start plus paired snapshots before and after each execution |
| Java | MemoryMXBean |
| JavaScript / Node.js | process.memoryUsage, async_hooks |
| Go | runtime.MemStats |
| Rust | Custom GlobalAlloc allocator instrumentation |
Target resolution follows two implementation families. Python, Java and Node.js load symbols directly at runtime using each language’s native import or reflection model. Go and Rust instead generate temporary runner programs that call the requested symbol, compile those runners, and execute them as subprocesses. This distinction is central to reproducibility: dynamic-language bridges perform in-process symbol loading, while Go and Rust perform out-of-process compiled invocation.
Fusion
The fused stage can be viewed as confidence fusion over static and dynamic signals. Let
c_s be static confidence and c_d be dynamic confidence in [0, 1]. A conservative
combined score is:
where c_m is the merged confidence used for unified reporting. For evaluation-oriented
summaries, standard metrics remain applicable:
These equations are not presented as universal claims of model optimality. They provide a transparent formal basis for interpreting merged evidence and reporting quality.
Experimental design
The research question is: does Graphene’s dynamic heap-growth detection method provide a reliable signal for object-escape detection, and does it improve correctness when combined with static analysis? The hypothesis is that heap-growth detection will achieve high precision and recall because allocation growth is a direct operational consequence of object escape.
The labeled suite includes language-specific case files in tests/<language>/cases/. Each
case is labeled with a SAFE: marker when no escape is expected; the absence of that
marker indicates an expected escape. After filtering to cases with resolvable expected
labels and available results, the labeled evaluation set contains 1,421 targets across
five languages: Go 282, JavaScript 283, Python 286, Java 285, Rust 285.
Results
Overall correctness
| Approach | Accuracy | Mismatch | Precision | Recall | F1 |
|---|---|---|---|---|---|
| Static | 61.2% | 38.8% | 68.8% | 61.5% | 64.9% |
| Dynamic | 100.0% | 0.0% | 100.0% | 100.0% | 100.0% |
| Fused (static + dynamic) | 83.7% | 16.3% | 78.2% | 100.0% | 87.8% |
Relative to static analysis, the fused mode improves overall accuracy by 22.5 percentage points and raises recall from 61.5% to 100.0%, eliminating false negatives in this dataset. Dynamic mode is perfect on this benchmark slice, while fused mode still reflects static false-positive pressure in languages where static rules over-approximate escapes.
Per-language correctness
| Language | Approach | Accuracy | Mismatch | Precision | Recall | F1 |
|---|---|---|---|---|---|---|
| Go | Static | 83.7% | 16.3% | 78.1% | 100.0% | 87.7% |
| Go | Dynamic | 100.0% | 0.0% | 100.0% | 100.0% | 100.0% |
| Go | Fused | 83.7% | 16.3% | 78.1% | 100.0% | 87.7% |
| JavaScript | Static | 83.7% | 16.3% | 80.5% | 95.2% | 87.2% |
| JavaScript | Dynamic | 100.0% | 0.0% | 100.0% | 100.0% | 100.0% |
| JavaScript | Fused | 86.6% | 13.4% | 81.3% | 100.0% | 89.7% |
| Python | Static | 39.2% | 60.8% | 43.1% | 13.2% | 20.2% |
| Python | Dynamic | 100.0% | 0.0% | 100.0% | 100.0% | 100.0% |
| Python | Fused | 89.9% | 10.1% | 85.2% | 100.0% | 92.0% |
| Java | Static | 42.8% | 57.2% | 58.0% | 21.6% | 31.5% |
| Java | Dynamic | 100.0% | 0.0% | 100.0% | 100.0% | 100.0% |
| Java | Fused | 100.0% | 0.0% | 100.0% | 100.0% | 100.0% |
| Rust | Static | 58.6% | 41.4% | 58.6% | 100.0% | 73.9% |
| Rust | Dynamic | 100.0% | 0.0% | 100.0% | 100.0% | 100.0% |
| Rust | Fused | 58.6% | 41.4% | 58.6% | 100.0% | 73.9% |
The per-language results suggest three trends. Static performance remains uneven, especially in Python and Java. Dynamic mode is perfect on this benchmark set. Fused mode raises recall to 100% but does not eliminate static false positives in Go and Rust, so its accuracy remains below dynamic mode in those language slices.
Error analysis
Dynamic heap tracking performs well because it measures direct runtime consequences instead of inferring retention from static structure alone. Each bridge uses runtime-native memory APIs (or allocator instrumentation) to compare before/after allocation state, and positive deltas are reported in a normalized cross-language schema. The remaining fused-mode errors are largely inherited static false positives in language slices such as Go and Rust.
Mode choice is therefore policy-dependent. Dynamic or fused mode is appropriate for high-sensitivity triage, while tuned static mode is appropriate for narrow, low-overhead scans where false-positive control is prioritised.
Threats to validity
Construct validity rests on the dynamic signal, which is based on post-invocation heap-growth deltas. Although this is a strong operational proxy for retained state, not every positive delta indicates harmful retention, and not every escape manifests as a clean persistent delta.
Internal validity is affected by bridge-level measurement differences, including garbage-collection timing, allocator behaviour and runtime scheduling. Controls were implemented to mitigate these effects, but residual runtime noise remains possible.
External validity is limited by evaluation on curated and labeled suites, which enable controlled comparison but do not fully represent production distributions.
Statistical conclusion validity is constrained because the report emphasises descriptive metrics and effect sizes without formal confidence intervals, significance testing, or uncertainty quantification across repeated randomized trials.
Ecological validity is limited because experiments focus on bounded invocation windows, whereas many real-world incidents involve multi-request interactions, queue backlogs and temporal retention spanning minutes or hours.
Conclusions
The significance of this work lies in transforming cross-language escape triage from an ad-hoc investigation process into a repeatable engineering workflow. Graphene-HA contributes an integration layer that standardises artifacts and failure semantics, reducing translation overhead during triage and improving the reproducibility of follow-up investigations.
The strongest positive finding is that direct heap-growth observation functioned as a reliable runtime signal under the benchmark conditions used in this study. This does not imply that dynamic analysis should replace static analysis: static analysis continues to provide low-cost candidate generation and broad initial coverage, dynamic analysis contributes execution-grounded confirmation, and fusion contributes robustness against missed cases. The operative question is not which mode is superior, but which evidence policy best aligns with operational risk in the current context.
Future work will extend the system with retention lineage and causal attribution, static precision improvements for weak profiles, adaptive dynamic input and path exploration, multi-context and long-horizon retention analysis, language ecosystem expansion, and error type diversification.
Figures
Figures 1–13 of the source report — subsystem architecture maps, workflow charts, and the two result charts — are available in the full PDF. Figures 12 and 13 plot the same values given in the two tables above.
References
- Jong-Deok Choi, Manish Gupta, Mauricio Serrano, Vugranam C. Sreedhar, and Sam Midkiff. 1999. Escape analysis for Java. In Proceedings of the 14th ACM SIGPLAN Conference on Object-Oriented Programming, Systems, Languages, and Applications. ACM, New York, NY, USA, 1–19. https://doi.org/10.1145/320384.320386
- Jong-Deok Choi, Manish Gupta, Mauricio J. Serrano, Vugranam C. Sreedhar, and Samuel P. Midkiff. 2003. Stack allocation and synchronization optimizations for Java using escape analysis. ACM Trans. Program. Lang. Syst. 25, 6 (2003), 876–910. https://doi.org/10.1145/945885.945892
- Boyao Ding, Qingwei Li, Yu Zhang, Fugen Tang, and Jinbao Chen. 2024. MEA2: A Lightweight Field-Sensitive Escape Analysis with Points-to Calculation for Golang. Proc. ACM Program. Lang. 8, OOPSLA2 (2024), 1362–1389. https://doi.org/10.1145/3689759
- Ralf Jung, Benjamin Kimock, Christian Poveda, Eduardo Sánchez Muñoz, Oli Scherer, and Qian Wang. 2026. Miri: Practical Undefined Behavior Detection for Rust. Proc. ACM Program. Lang. 10, POPL (2026), 1383–1411. https://doi.org/10.1145/3776690
- Clemens Lang and Isabella Stilkerich. 2020. Design and Implementation of an Escape Analysis in the Context of Safety-Critical Embedded Systems. ACM Trans. Embed. Comput. Syst. 19, 1 (2020), 1–20. https://doi.org/10.1145/3372133
- Maurizio Martignano. 2022. Static Analysis for Ada, C/C++ and Python: Different Languages, Different Needs. Ada Lett. 41, 2 (2022), 77–80. https://doi.org/10.1145/3530801.3530807
- Dustin Rhodes, Cormac Flanagan, and Stephen N. Freund. 2017. Correctness of Partial Escape Analysis for Multithreading Optimization. In Proceedings of the 19th Workshop on Formal Techniques for Java-like Programs. ACM, New York, NY, USA, 1–6. https://doi.org/10.1145/3103111.3104039
- Konstantin Serebryany and Timur Iskhodzhanov. 2009. ThreadSanitizer: data race detection in practice. In Proceedings of the Workshop on Binary Instrumentation and Applications. ACM, New York, NY, USA, 62–71. https://doi.org/10.1145/1791194.1791203
- Lukas Stadler, Thomas Würthinger, and Hanspeter Mössenböck. 2014. Partial Escape Analysis and Scalar Replacement for Java. In Proceedings of Annual IEEE/ACM International Symposium on Code Generation and Optimization. ACM, New York, NY, USA, 165–174. https://doi.org/10.1145/2581122.2544157
- Lukas Stadler, Thomas Würthinger, and Hanspeter Mössenböck. 2018. Partial Escape Analysis and Scalar Replacement for Java. In Proceedings of Annual IEEE/ACM International Symposium on Code Generation and Optimization. ACM, New York, NY, USA, 165–174. https://doi.org/10.1145/2544137.2544157
- Cong Wang, Mingrui Zhang, Yu Jiang, Huafeng Zhang, Zhenchang Xing, and Ming Gu. 2020. Escape from escape analysis of Golang. In Proceedings of the ACM/IEEE 42nd International Conference on Software Engineering: Software Engineering in Practice. ACM, New York, NY, USA, 142–151. https://doi.org/10.1145/3377813.3381368
- Matthew Edwin Weingarten, Theodoros Theodoridis, and Aleksandar Prokopec. 2022. Inlining-Benefit Prediction with Interprocedural Partial Escape Analysis. In Proceedings of the 14th ACM SIGPLAN International Workshop on Virtual Machines and Intermediate Languages. ACM, New York, NY, USA, 13–24. https://doi.org/10.1145/3563838.3567677