Beyond Java 6: Reviving Reflection-Aware Static Analysis for Modern JVMs
Abstract
Static program analysis for Java is significantly hindered by the use of reflection and custom class loaders. To address this, the popular TamiFlex framework traces concrete targets of reflective calls and dumps loaded classes via a Play-out agent, enabling static analysis followed by re-insertion of transformed classes through a Play-in agent. While effective for Java 6, the evolution of the Java Virtual Machine (JVM) has rendered TamiFlex incompatible with Java versions released over the last decade. Modern runtime behaviors such as hidden-class introduction, randomized runtime-class generation, and class name collisions within frameworks cause the original agents to either crash or fail to converge. In this paper, we present an updated TamiFlex for modern JVMs, addressing three key limitations of the original. We introduce a hidden-class dumping and loading mechanism that captures classes unavailable to the agents, a normalization strategy that reconciles bytecode variations across runs, and a classloader-aware isolation strategy that resolves class-name collisions across different classloaders. We evaluate our toolchain on the latest DaCapo benchmark suite running on Java 21 across two JVMs, and find that for all 22 benchmarks, our Play-out agent produces a reflection log, while our Play-in agent successfully re-inserts transformed classes. To demonstrate the practical value of the updated framework, we also extend a recent static escape analysis and find that both its coverage and precision improve noticeably.