Friday, September 04, 2009

Using AsmNodeBuilder to Test Bytecodes with Pleasure

DSL of Groovy never made me bored. I am working on some bytecode transformation and having a number of tests to run.
Testing low-level transformation means dealing with JVM instructions. For example,

ILOAD 0
INVOKESTATIC java/lang/Integer.valueOf(I)Ljava/lang/Integer;
LDC 0
INVOKESTATIC java/lang/Integer.valueOf(I)Ljava/lang/Integer;
INVOKESTATIC org/codehaus/groovy/runtime/ScriptBytecodeAdapter
             .compareLessThan(Ljava/lang/Object;Ljava/lang/Object;)Z
IFEQ L2


There is class MethodNode in the tree package of ASM. The class has the field instructions of class InsnList, which contains its method body. In the following, I have InsnList insn = methodNode.instructions; to perform some optimisation over it.

Before making AsmNodeBuilder, I had to create
a test data using Asm's Tree Nodes. Something looks like:

insn.add(new VarInsnNode(ILOAD, 0))

With Groovy and AsmNodeBuilder, now I can write:

insn.append {
  iload 0
}


When comparing results, I had to do:

assert insn.get(0).opcode == ILOAD
assert insn.get(0).var    == 0


Now, it becomes:

assertEquals asm { iload 0 }, insn[0]

Of course, I can also do:

assertEquals asm {
  iload 0
  invokestatic Integer,"valueOf",[int],Integer
}, insn


to assert all instructions in the list.

Look fun? Convinced? There are two classes you may use:

- InsnListHelper.groovy
- AsmNodeBuilder.java (generated from gen_asm_builder.groovy)

Here's how to
  1. call InsnListHelper.install() in the very beginning of your Groovy test case.
  2. Write a test case.
  3. use <InsnList>.append { ... } to create a method body.
  4. do your transformation.
  5. assert the result against an asm { ... } block. Note that Groovy's power assertion won't work with the block for some reasons, use assertEquals instead.
You can also look at an example here.