Introduction to the ASM 2.0 Bytecode Framework
Pages: 1, 2, 3, 4, 5, 6, 7, 8
Bytecode consumers can be linked together in a "Chain
of responsibility" pattern by either manually delegating events
to the next visitor in the chain, or with using visitors derived
from ClassAdapter and/or MethodAdapter
that delegate all visit methods to their underlying visitors. Those
delegators act as bytecode consumers from one side and as bytecode
producers from the other. They can decide to modify natural
delegation in order to implement specific bytecode
transformation:
- Visit call delegation can be omitted in order to remove class fields, methods, method instructions, etc.
- Visit call parameters can be modified in order to rename classes, methods, types, etc.
- New visit calls can be added in order to introduce new fields or methods, or to inject new code into existing methods.
The chain can be ended by a ClassWriter visitor,
which will produce the resulting bytecode. For example:
ClassWriter cw = new ClassWriter(computeMax);
ClassVisitor cc = new CheckClassAdapter(cw);
ClassVisitor tv =
new TraceClassVisitor(cc, new PrintWriter(System.out));
ClassVisitor cv = new TransformingClassAdapter(tv);
ClassReader cr = new ClassReader(bytecode);
cr.accept(cv, skipDebug);
byte[] newBytecode = cw.toByteArray();
In the above code, the TransformingClassAdapter
implements custom class transformations and sends the results to the
TraceClassVisitor passed to its constructor.
TraceClassVisitor prints the transformed class and
delegates the same events to CheckClassAdapter, which
does simple bytecode verification and then passes the event to the
ClassWriter.
Most of the visit methods receive simple parameters such as
int, boolean, and String. In
all visit methods where String parameters refer to
constants within the bytecode, ASM uses the same representation as
used by the JVM. For instance, all class names (e.g. super class,
interfaces, exceptions, owner classes for the field, and methods
referenced from method code) should be specified in the
Internal Form. Field and method descriptors (usually the
desc parameter) also should be in
JVM representation. The same approach is taken for
signature parameters used to represent generics
information, so they should follow the grammar defined in Section 4.4.4
of the
Revised Class File Format (PDF). This approach helps avoid
unnecessary calculations when no transformation is required. To
help construct and parse such descriptors, there is a
Type class that provides several static methods:
String getMethodDescriptor(Type returnType, Type[] argumentTypes)String getInternalName(Class c)String getDescriptor(Class c)String getMethodDescriptor(Method m)Type getType(String typeDescriptor)Type getType(Class c)Type getReturnType(String methodDescriptor)Type getReturnType(Method m)Type[] getArgumentTypes(String methodDescriptor)Type[] getArgumentTypes(Method m)
Note that these descriptors are using an "erasured" representation, which does not contain generics
information. Generics info is actually stored as a separate
bytecode attribute, but ASM takes care of this attribute and passes
the generic signature string in a signature parameter
of the appropriate visit methods. The value of the
signature string also uses the JVM representation (see
Section 4.4.4 in the "Class File Format" (PDF) as revised for Java 5), which uniquely maps
from the generic declarations in Java code, but presents an
additional challenge for tools that need to retrieve details from
generic types. To handle signatures, ASM provides
SignatureVisitor, SignatureReader, and
SignatureWriter classes modelled in a way similar to
other ASM visitors, as illustrated in Figure 3.

Figure 3. Sequence diagram for Signature classes
The Util package contains TraceSignatureVisitor,
which implements SignatureVisitor and allows you to
convert a signature value into a Java declaration with
generic types. The following example converts a method signature
into a Java method declaration.
TraceSignatureVisitor v =
new TraceSignatureVisitor(access);
SignatureReader r = new SignatureReader(sign);
r.accept(v);
String genericDecl = v.getDeclaration();
String genericReturn = v.getReturnType();
String genericExceptions = v.getExceptions();
String methodDecl = genericReturn + " " +
methodName + genericDecl;
if(genericExceptions!=null) {
methodDecl += " throws " + genericExceptions;
}
Up to this point, we have talked about the general design of the ASM framework and manipulating class structure. However, the most interesting part is how ASM handles method code.