
Basics of ObjectWeb ASM in Java
I’m assuming you already added ASM to your dependencies, so let’s get started!
First, you need to read the bytecode of a class and write it back. So let’s implement this real quick, so we have a foundation to work on later:
byte[] bytes = ...;
ClassReader reader = new ClassReader(bytes);
ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
return writer.toByteArray();
Let’s dissect this piece of code. We have a byte array (the bytecode of the class), then we do something with the reader and writer and return the byte array.
If you want to know how to modify a class in runtime, check my other article.
ClassReader
parses the bytes and ClassWriter
does the exact opposite, returning the bytes.
The argument in ClassWriter
’s constructor (COMPUTE_FRAMES
) means that we want the writer to compute the frames for us. Frames are parts of method code. Also, that means that the writer will compute the maximums (aka maxs
) of the method.
You can also pass COMPUTE_MAXS
or no arguments at all, to create a writer that will suit your needs.
ASM’s APIs
ASM has 2 APIs to analyze/modify bytecode: event-based and tree-based API.
Event-based
Exactly what you think. The way of using this API is to subclass the Visitor
classes (ClassVisitor
, MethodVisitor
, FieldVisitor
and etc).
add code snippet
Tree-based
This API is much more suitable for analyzing the bytecode, since it first parses the bytecode with the event-based API and puts the data into objects, giving you various node objects, which you can read and modify the properties of, including method instructions!
add code snippet
Utilizing an API
Whichever API you chose, you’ll still have to pass the parsed bytecode to your classes and put the result into the writer.
// Event-based API
ClassVisitor cv = new MyClassVisitor(writer);
reader.accept(cv); // reader's data -> visitor
// Tree-based API
ClassNose node = new ClassNode();
reader.accept(node); // reader's parsed stuff -> node (visitor)
node.accept(writer); // node's data -> writer
Let’s break it down! When you pass the writer to your visitor, you’re making sure that after you filter/modify everything you need in the class, the calls will pass down to the parent visitor. Here, it’s the ClassWriter
, so you will write the filtered result of your class visitor to the class writer!
When you call accept
on the reader for your visitor, it will pass down the parse results of the reader to the supplied visitor.
In tree-based API, we call accept
as well, but this time it makes the parsed data go to the supplied visitor (the writer).
WRITING IN PROGRESS