Question 1 is perhaps the most difficult. You'll need to find out the position to insert the instructions by recognizing some pattern. If you assume that firstPar.check()
is called only once, then you can look for the following bytecode instructions for if(firstPar.check())
:
ALOAD 1
INVOKEVIRTUAL Data.check ()Z
IFEQ L3
where L3
is the jump label if check
return false
.
For question 2, notice that bytecode instructions for if(firstPar.check() && !Utilities.someOtherCheck(firstPar, secondPar))
is:
ALOAD 1
INVOKEVIRTUAL Data.check ()Z
IFEQ L3
ALOAD 1
ALOAD 2
INVOKESTATIC Utilities.someOtherCheck (LData;LData;)Z
IFNE L3
So, you will need to insert 4 new instructions right after IFEQ L3
.
You can do this by using the Tree API, where you create an adapter for targetMethod
by subclassing ClassVisitor
and MethodNode
:
private static class ClassAdapter extends ClassVisitor {
public ClassAdapter(ClassVisitor cv) {
super(Opcodes.ASM5, cv);
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc,
String signature, String[] exceptions) {
MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
if (name.equals("targetMethod"))
return new MethodAdapter(access, name, desc, signature, exceptions, mv);
else
return mv;
}
}
private static class MethodAdapter extends MethodNode {
public MethodAdapter(int access, String name, String desc,
String signature, String[] exceptions, MethodVisitor mv) {
super(Opcodes.ASM5, access, name, desc, signature, exceptions);
this.mv = mv;
}
// More to come ...
}
Inside MethodAdapter
, you can override visitEnd
to iterate all instructions
inside the method, and try to detect the above 3 instructions and insert 4 new instructions after them:
@Override
public void visitEnd() {
// Iterates all instructions in the method
ListIterator<AbstractInsnNode> itr = instructions.iterator();
while (itr.hasNext()) {
// Checks whether the instruction is ALOAD 1
AbstractInsnNode node = itr.next();
if (node.getOpcode() != Opcodes.ALOAD
|| ((VarInsnNode) node).var != 1)
continue;
// Checks whether the next instruction is INVOKEVIRTUAL
if (node.getNext() == null
|| node.getNext().getOpcode() != Opcodes.INVOKEVIRTUAL)
continue;
// Checks the invoked method name and signature
MethodInsnNode next = (MethodInsnNode) node.getNext();
if (!next.owner.equals("Data")
|| !next.name.equals("check")
|| !next.desc.equals("()Z"))
continue;
// Checks whether the next of the next instruction is IFEQ
AbstractInsnNode next2 = next.getNext();
if (next2 == null
|| next2.getOpcode() != Opcodes.IFEQ)
continue;
// Creates a list instructions to be inserted
InsnList list = new InsnList();
list.add(new VarInsnNode(Opcodes.ALOAD, 1));
list.add(new VarInsnNode(Opcodes.ALOAD, 2));
list.add(new MethodInsnNode(Opcodes.INVOKESTATIC,
"Utilities", "someOtherCheck",
"(LData;LData;)Z", false));
list.add(new JumpInsnNode(Opcodes.IFNE, ((JumpInsnNode) next2).label));
// Inserts the list, updates maxStack to at least 2, and we are done
instructions.insert(next2, list);
maxStack = Math.max(2, maxStack);
break;
}
accept(mv);
}
To use the adapter, you can chain it with a ClassReader
and a ClassWriter
. Below, I also chain a TraceClassVisitor
to print out a log file in the tmp directory:
ClassReader reader = new ClassReader("Target");
ClassWriter writer = new ClassWriter(reader, 0);
TraceClassVisitor printer = new TraceClassVisitor(writer,
new PrintWriter(System.getProperty("java.io.tmpdir") + File.separator + "Target.log"));
ClassAdapter adapter = new ClassAdapter(printer);
reader.accept(adapter, 0);
byte[] b = writer.toByteArray(); // The modified bytecode