There are several libraries currently available for java bytecode manipulation such as ASM, ApacheBCEL, Javassist and etc. In this article I will discuss about the ASM library, what is it for, how to use it, and how you could resolve some common issues that you may encounter. This will be a series of articles, and the first one will focus on introducing the library and its features.
What is ASM?
ASM is a framework that allows manipulating and generating JVM bytecode. It allows modifying existing classes, programmatically generating new classes and analyzing existing classes, directly in their bytecode (binary) format. It also has some inbuilt algorithms for ceratin analyses as well.
ASM provides two basic sets of APIs: A tree-based API and an event-based API.
Constructs a tree-like structure of the classes it visits. A ‘ClassNode’ being the root of the tree, it consists of fields, methods, inner classes and other information as children.
- Provides full control over the class that is being visited.
- Since the entire class is available as a tree, it is easy to manipulate, transform and even rewrite parts of the tree.
- Slower than the event-based API.
This API primarily based on the visitor pattern and provides two basic visitors: ClassVisitor - for visiting analyzing and transforming an existing class, and ClassWriter - for generating new classes.
- Faster than the tree API.
- Has less control over the currently visited class.
- Requires to strictly follow the order of the methods of the visitors.
- Rewriting a class (or parts of it) is not straightforward - Have to generate a new class completely.
However, due to the performance and the lightweight nature of the event-based API, it is being used more often for generating and manipulating bytecode. Hence I will also discuss the event-based API in this article.
Let's look at how we can generate a simple class using the ASM event-based API.
Note: Here I have used
ClassWriter.COMPUTE_MAXS as the flag for the class writer. This tells the AMS to automatically calculate the maximum stack size and the maximum number of local variables of the methods.
The above code will generate a simple class with no methods. To write this as a class file, we can get the bytes using
cw.toByteArray() and write it to a file.
Let's add a java main method for the above class. For simplicity, our main method will not have any logic inside.
Even though the main method is empty, you can see I have added a return statement
mv.VisitInsn(Opcodes.RETURN). It is followed by
mv.visitMaxs(0,0) which is used to set the mas-stack size for the current method. Since we have asked the ASM to calculate this for us during the ClassWriter initialization, whatever the value we set via
mv.visitMaxs(... , ...) will be ignored. However, it is still required to call this method before the
mv.visitEnd() is called.
A Complete Sample
Here’s a complete sample with a java class that prints a “Hello world!”.
Running the above sample will generate a class file
GeneratedClass.class. You can run this using:
$ java GeneratedClass
This will print
Hello world! in the console.
In the next article I will discuss a bit more detail about the JVM byte codes, methods of the ASM API to be used to emit different bytes codes, and how to write some complex logic from bytecode level using the ASM.