ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Using the ASM Toolkit for Bytecode Manipulation
Pages: 1, 2, 3, 4

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:

  1. A new interface was added to the class declaration.
  2. One new field was added.
  3. Some instructions were added to the end of the <init> method, representing code for constructor and class initialization.
  4. visitMaxs() have different parameters (used stack has been changed in modified bytecode).
  5. Some instructions were added to the beginning of existing class methods.
  6. Two new methods were added.

Pages: 1, 2, 3, 4

Next Pagearrow