|
Using the ASM Toolkit for Bytecode Manipulation
ASM Overview
Before looking at bytecode transformation, we need a better understanding
of the events defined for the ClassVisitor interface.
These events should come in the following order and contain parameters as described below.
| Once |
visit |
Class access flags (public, private, static, etc.), bytecode version, name,
super class, implemented interfaces, and source file name. |
Multiple times |
visitField |
Field access flags, name and signature, init value, and field attributes (e.g., annotations). |
visitMethod |
Method access flags, name and signature and method attributes. |
visitInnerClass |
Inner class access flags, its name and outer name |
visitAttribute |
Class-level attributes |
| Once |
visitEnd |
Complete processing |
visitMethod is different from the others, because it returns a new instance of
CodeVisitor for every call. That instance will
handle processing events for method bytecode (including method and parameter
attributes, information for try-catch blocks, etc.).
The table below outlines the methods of CodeVisitor.
These methods must be called in the sequential order of the
bytecode instructions of the visited code. Each method can either handle bytecode instructions
grouped by the similar parameters or other bytecode artifacts, such as the local variable table,
line numbers, try-catch blocks, and nonstandard attributes (marked grey in the table below).
visitInsn |
Visits a zero operand instruction: NOP, ACONST_NULL, ICONST_M1, ICONST_0, ICONST_1, ICONST_2, ICONST_3, ICONST_4, ICONST_5, LCONST_0, LCONST_1, FCONST_0, FCONST_1, FCONST_2, DCONST_0, DCONST_1, IALOAD, LALOAD, FALOAD, DALOAD, AALOAD, BALOAD, CALOAD, SALOAD, IASTORE, LASTORE, FASTORE, DASTORE, AASTORE, BASTORE, CASTORE, SASTORE, POP, POP2, DUP, DUP_X1, DUP_X2, DUP2, DUP2_X1, DUP2_X2, SWAP, IADD, LADD, FADD, DADD, ISUB, LSUB, FSUB, DSUB, IMUL, LMUL, FMUL, DMUL, IDIV, LDIV, FDIV, DDIV, IREM, LREM, FREM, DREM, INEG, LNEG, FNEG, DNEG, ISHL, LSHL, ISHR, LSHR, IUSHR, LUSHR, IAND, LAND, IOR, LOR, IXOR, LXOR, I2L, I2F, I2D, L2I, L2F, L2D, F2I, F2L, F2D, D2I, D2L, D2F, I2B, I2C, I2S, LCMP, FCMPL, FCMPG, DCMPL, DCMPG, IRETURN, LRETURN, FRETURN, DRETURN, ARETURN, RETURN, ARRAYLENGTH, ATHROW, MONITORENTER, or MONITOREXIT. |
visitFieldInsn |
Visits a field instructions: GETSTATIC, PUTSTATIC, GETFIELD, or PUTFIELD. |
visitIntInsn |
Visits an instruction with a single int operand: BIPUSH, SIPUSH, or NEWARRAY. |
visitJumpInsn |
Visits a jump instruction: IFEQ, IFNE, IFLT, IFGE, IFGT, IFLE, IF_ICMPEQ, IF_ICMPNE, IF_ICMPLT, IF_ICMPGE, IF_ICMPGT, IF_ICMPLE, IF_ACMPEQ, IF_ACMPNE, GOTO, JSR, IFNULL, or IFNONNULL. |
visitTypeInsn |
Visits a type instruction: NEW, ANEWARRAY, CHECKCAST, or INSTANCEOF. |
visitVarInsn |
Visits a local variable instruction: ILOAD, LLOAD, FLOAD, DLOAD, ALOAD, ISTORE, LSTORE, FSTORE, DSTORE, ASTORE, or RET. |
visitMethodInsn |
Visits a method instruction: INVOKEVIRTUAL, INVOKESPECIAL, INVOKESTATIC, or INVOKEINTERFACE. |
visitIincInsn |
Visits an IINC instruction. |
visitLdcInsn |
Visits a LDC instruction. |
visitMultiANewArrayInsn |
Visits a MULTIANEWARRAY instruction. |
visitLookupSwitchInsn |
Visits a LOOKUPSWITCH instruction. |
visitTableSwitchInsn |
Visits a TABLESWITCH instruction. |
visitLabel |
Visits a label. |
visitLocalVariable |
Visits a local variable declaration. |
visitLineNumber |
Visits a line-number declaration. |
visitTryCatchBlock |
Visits a try-catch block. |
visitMaxs |
Visits the maximum stack size and the maximum number of local variables of the method. |
visitAttribute |
Visits a non-standard attribute of the code. |
The visitMaxs method is called
after all of the instructions have been visited. The visitTryCatchBlock,
visitLocalVariable, and visitLineNumber methods may be
called in any order, at any time (provided the labels passed as arguments have
already been visited with visitLabel).
In order to specify positions in the method bytecode and not have to use absolute
offsets, ASM uses the Label class. Label instances
are passed as parameters of visitJumpInsn,
visitLookupSwitchInsn, visitTableSwitchInsn,
visitTryCatchBlock, visitLocalVariable, and visitLineNumber,
to refer to a specific place in method code; a visitLabel method with the
same Label instance is used to actually mark that place.
The next section shows how the ClassVisitor and CodeVisitor
interfaces can work together in a bytecode transformation scenario.
Bytecode Transformation
Imagine that we need to transform some classes in the runtime, and implement
the Notifier interface from the example above. In our case,
all registered observers should receive events when any of the methods
of the original class have been called. We can pick some simple class and
use ASMifierClassVisitor to see what the transformation
should look like.
For example:
public class Counter1 {
private int n;
public void increment() {
n++;
}
private int count() {
return n;
}
}
After implementing the Notifier interface, this class may look like something like the following:
import java.util.ArrayList;
import java.util.Observer;
public class Counter2 implements Notifier {
private int n;
private ArrayList __lst = new ArrayList();
public void increment() {
notify( "increment()");
n++;
}
private int count() {
notify( "count()");
return n;
}
// Listener implementation
public void notify( String msg) {
for( int i = 0; i<__lst.size(); i++) {
((Listener)__lst.get(i)).update(this, msg);
}
}
public void addListener( Listener listener) {
__lst.add( listener);
}
}
Now you can compile both sources, run ASMifierClassVisitor as described
above, and then compare the resulting files using your favorite diff application.
Here are the comparison results. Removed lines are shown in red with
a minus sign (-) at the left, while additions are shown in green with
a plus sign (+).
...
ClassWriter cw = new ClassWriter(false);
CodeVisitor cv;
|
- cw.visit(ACC_PUBLIC + ACC_SUPER, "asm1/Counter1",
|
+ cw.visit(ACC_PUBLIC + ACC_SUPER, "asm1/Counter2",
|
"java/lang/Object",
|
- null,
|
+ new String[] { "asm1/Notifier" },
|
[ 1 ] |
- "Counter1.java");
|
+ "Counter2.java");
|
cw.visitField(ACC_PRIVATE, "n", "I",null,null);
|
+ cw.visitField(ACC_PRIVATE, "__lst",
+ "Ljava/util/ArrayList;", null, null);
|
[ 2 ] |
{
cv = cw.visitMethod(ACC_PUBLIC,
"<init>", "()V", null, null);
cv.visitVarInsn(ALOAD, 0);
cv.visitMethodInsn(INVOKESPECIAL,
"java/lang/Object", "<init>", "()V");
|
+ cv.visitVarInsn(ALOAD, 0);
+ cv.visitTypeInsn(NEW, "java/util/ArrayList");
+ cv.visitInsn(DUP);
+ cv.visitMethodInsn(INVOKESPECIAL,
+ "java/util/ArrayList", "<init>", "()V");
+ cv.visitFieldInsn(PUTFIELD, "asm1/Counter2",
+ "__lst", "Ljava/util/ArrayList;");
|
[ 3 ] |
cv.visitInsn(RETURN);
|
- cv.visitMaxs(1, 1);
|
+ cv.visitMaxs(3, 1);
|
[ 4 ] |
}
{
cv = cw.visitMethod(ACC_PUBLIC, "increment",
"()V", null, null);
|
+ cv.visitVarInsn(ALOAD, 0);
+ cv.visitLdcInsn("increment()");
+ cv.visitMethodInsn(INVOKEVIRTUAL, "asm1/Counter2",
+ "notify", "(Ljava/lang/String;)V");
|
[ 5 ] |
cv.visitVarInsn(ALOAD, 0);
cv.visitInsn(DUP);
|
- cv.visitFieldInsn(GETFIELD, "asm1/Counter1","n","I");
|
+ cv.visitFieldInsn(GETFIELD, "asm1/Counter2","n","I");
|
cv.visitInsn(ICONST_1);
cv.visitInsn(IADD);
|
- cv.visitFieldInsn(PUTFIELD, "asm1/Counter1","n","I");
|
+ cv.visitFieldInsn(PUTFIELD, "asm1/Counter2","n","I");
|
cv.visitInsn(RETURN);
cv.visitMaxs(3, 1);
}
{
cv = cw.visitMethod(ACC_PRIVATE, "count",
"()I", null, null);
|
+ cv.visitVarInsn(ALOAD, 0);
+ cv.visitLdcInsn("count()");
+ cv.visitMethodInsn(INVOKEVIRTUAL, "asm1/Counter2",
+ "notify", "(Ljava/lang/String;)V");
|
[ 5 ] |
cv.visitVarInsn(ALOAD, 0);
|
- cv.visitFieldInsn(GETFIELD, "asm1/Counter1","n","I");
|
+ cv.visitFieldInsn(GETFIELD, "asm1/Counter2","n","I");
|
cv.visitInsn(IRETURN);
|
- cv.visitMaxs(1, 1);
|
+ cv.visitMaxs(2, 1);
|
[ 4 ] |
}
{
|
+ cv = cw.visitMethod(ACC_PUBLIC, "notify",
+ "(Ljava/lang/String;)V", null, null);
+ cv.visitInsn(ICONST_0);
+ cv.visitVarInsn(ISTORE, 2);
+ Label l0 = new Label();
+ cv.visitLabel(l0);
+ cv.visitVarInsn(ILOAD, 2);
+ cv.visitVarInsn(ALOAD, 0);
+ cv.visitFieldInsn(GETFIELD, "asm1/Counter2",
+ "__lst", "Ljava/util/ArrayList;");
+ cv.visitMethodInsn(INVOKEVIRTUAL,
+ "java/util/ArrayList", "size", "()I");
+ Label l1 = new Label();
+ cv.visitJumpInsn(IF_ICMPGE, l1);
+ cv.visitVarInsn(ALOAD, 0);
+ cv.visitFieldInsn(GETFIELD, "asm1/Counter2",
+ "__lst", "Ljava/util/ArrayList;");
+ cv.visitVarInsn(ILOAD, 2);
+ cv.visitMethodInsn(INVOKEVIRTUAL,
+ "java/util/ArrayList", "get",
+ "(I)Ljava/lang/Object;");
+ cv.visitTypeInsn(CHECKCAST, "asm1/Listener");
+ cv.visitVarInsn(ALOAD, 0);
+ cv.visitVarInsn(ALOAD, 1);
+ cv.visitMethodInsn(INVOKEINTERFACE,
+ "asm1/Listener", "notify",
+ "(Ljava/lang/Object;Ljava/lang/Object;)V");
+ cv.visitIincInsn(2, 1);
+ cv.visitJumpInsn(GOTO, l0);
+ cv.visitLabel(l1);
+ cv.visitInsn(RETURN);
+ cv.visitMaxs(3, 3);
|
[ 6 ] |
}
{
|
+ cv = cw.visitMethod(ACC_PUBLIC, "addListener",
+ "(Lasm1/Listener;)V", null, null);
+ cv.visitVarInsn(ALOAD, 0);
+ cv.visitFieldInsn(GETFIELD, "asm1/Counter2",
+ "__lst", "Ljava/util/ArrayList;");
+ cv.visitVarInsn(ALOAD, 1);
+ cv.visitMethodInsn(INVOKEVIRTUAL,
+ "java/util/ArrayList", "add",
+ "(Ljava/lang/Object;)Z");
+ cv.visitInsn(POP);
+ cv.visitInsn(RETURN);
+ cv.visitMaxs(2, 2);
|
[ 6 ] |
}
cw.visitEnd();
...
|
You can see the following groups of changes:
- A new interface was added to the class declaration.
- One new field was added.
- Some instructions were added to the end of the
<init> method,
representing code for constructor and class initialization.
visitMaxs() have different parameters (used stack has
been changed in modified bytecode).
- Some instructions were added to the beginning of existing class methods.
- Two new methods were added.
Prev [1] [2] [3] [4] Next |