A Java bytecode library with class-file parsing, bytecode editing, mutable IRs, (de)compilation, and a set of higher-level analyses.
// Load a class
ClassPool pool = ClassPool.getDefault();
ClassFile cf = pool.loadClass(inputStream);
// Create a new class with a default constructor
int access = new AccessBuilder().setPublic().build();
ClassFile newClass = ClassFactory.createClass(pool, "com/example/MyClass", access);
// Add a field with a getter and setter
FieldEntry field = newClass.createNewField(access, "value", "I", new ArrayList<>());
ClassFactory.generateGetter(newClass, field, false);
ClassFactory.generateSetter(newClass, field, false);
// Write the class
newClass.rebuild();
byte[] bytes = newClass.write();| Guide | Description |
|---|---|
| Quick Start | Getting started |
| Architecture | Module structure and design |
| Class Files | ClassPool, ClassFile, ConstPool |
| Bytecode API | Bytecode editing |
| Generation API | Fluent class generation |
| Visitors | Traversal patterns |
| SSA Guide | SSA intermediate representation |
| SSA Transforms | Optimizations and analysis |
| LLVM Lowering | Lower SSA IR to textual LLVM IR |
| LLVM Lifting | Lift LLVM IR back to SSA |
| SSA IR Migration | API changes from the SSA IR redesign |
| Analysis APIs | Code analysis and semantic queries |
| PDG API | Program Dependence Graph with slicing |
| SDG API | Interprocedural System Dependence Graph |
| CPG API | Code Property Graph with taint analysis |
| Query API | Bytecode query language |
| Abstract Execution API | Operand-stack and local def-use |
| AST Guide | AST recovery, mutation, and emission |
| AST Editor | ExprEditor-style AST transformation |
| Renamer API | Class, method, and field renaming |
| Frame Computation | StackMapTable generation |
import com.tonic.builder.ClassBuilder;
import com.tonic.type.AccessFlags;
byte[] bytes = ClassBuilder.create("com/example/Calculator")
.version(AccessFlags.V11, 0)
.access(AccessFlags.ACC_PUBLIC)
.addMethod(AccessFlags.ACC_PUBLIC | AccessFlags.ACC_STATIC, "add", "(II)I")
.code()
.iload(0)
.iload(1)
.iadd()
.ireturn()
.end()
.end()
.toByteArray();Bytecode -> Lift -> SSA IR -> Optimize -> Lower -> Bytecode
| ^
[Recover] [Lower]
| |
v |
AST ----[Mutate]----> AST
SSA ssa = new SSA(constPool)
.withConstantFolding()
.withCopyPropagation()
.withDeadCodeElimination();
ssa.transform(method); // lift, optimize, and lowerThe IR can also be lowered to textual LLVM IR (.ll) and lifted back. See
LLVM Lowering and LLVM Lifting.
import com.tonic.analysis.source.decompile.ClassDecompiler;
String source = ClassDecompiler.decompile(classFile);
// With per-method bytecode-offset-to-source-line maps
DecompileResult result = new ClassDecompiler(classFile).decompileWithLineMap();Call graph, dependency, type inference, pattern search, xrefs, data flow, similarity, and graph analyses (PDG, SDG, CPG) are documented in Analysis APIs.
Runnable examples are in examples/src/main/java/com/tonic/demo/,
including class creation, SSA transformation, LLVM lowering, AST mutation, decompilation, call
graphs, dependency analysis, type inference, and pattern search.
YABR is a Gradle multi-project. Production code is split into layered modules (core, bytecode,
renamer, ssa, source, analyses, execution, query) whose dependencies are acyclic and
build-enforced; the all module merges them into a single jar. See
Architecture.
The published artifact is the merged jar from the all module. Via JitPack:
repositories {
mavenCentral()
maven { url = uri("https://jitpack.io") }
}
dependencies {
implementation("com.github.Tonic-Box.YABR:all:<version>")
}./gradlew build # build and test all modules
./gradlew :all:shadowJar # produce the merged jar- Java 11+
Inspired by classpooly.
MIT