问题
I am trying to:
1) Iterate instructions and find all relevant nodes
2) Insert custom code before found nodes
I used streams and iterator to make it and insert, which works only for the first node
InsnList instructions = methodNode.instructions;
InsnList addition = ...
//It work: found n nodes for n return instructions
Stream<AbstractInsnNode> returnNodes =
Stream.iterate(instructions.getFirst(), AbstractInsnNode::getNext).limit(instructions.size())
.filter(n -> returnOpcodes.contains(n.getOpcode()));
//It not work: inserted only before first node
returnNodes.forEach(n -> instructions.insertBefore(n, addition));
I also tried iterator and it also does not work
ListIterator<AbstractInsnNode> iterator = instructions.iterator();
while (iterator.hasNext()) {
AbstractInsnNode node = iterator.next();
if (returnOpcodes.contains(node.getOpcode()))
instructions.insertBefore(node, addition);
}
I was expecting that addition
would be inserted before all the return nodes, but it inserted before the first.
InsnList is a linked list, and such insertion must work. Where am I wrong?
回答1:
As the term node in the class name AbstractInsnNode
suggests, instances of this class are part of a linked object graph and for this reason, can only be part of one InsnList
. See also this answer.
Copying lists may become quiet inefficient, especially, as ASM’s visitor API, on which the Tree API builds on, can handle the task of inserting instructions at occurrences of certain original instructions easily in a single pass.
Since InsnList
interoperates with the Visitor API nicely, you may still use it to define the instruction sequence to insert, but instead of copying it to another list, you can use it to emit the instructions at the right place.
Here is the sketch for copying an entire class definition while inserting an instruction sequence at certain places:
class Victim {
static void foo() {
System.out.println("original code");
}
}
public class InjectCode extends ClassVisitor {
public static void main(String[] args) throws IOException, IllegalAccessException {
ClassReader cr = new ClassReader(
InjectCode.class.getResourceAsStream("Victim.class"));
ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS);
cr.accept(new InjectCode(cw), 0);
byte[] code = cw.toByteArray();
MethodHandles.lookup().defineClass(code); // Java 9, for simplification
Victim.foo();
}
InsnList insnList; // to be filled
public InjectCode(ClassVisitor target) {
super(Opcodes.ASM5, target);
// just an example
insnList = new InsnList();
insnList.add(new FieldInsnNode(Opcodes.GETSTATIC,
"java/lang/System", "out", "Ljava/io/PrintStream;"));
insnList.add(new LdcInsnNode("Hello World"));
insnList.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
"java/io/PrintStream", "println", "(Ljava/lang/Object;)V"));
}
@Override
public MethodVisitor visitMethod(
int access, String name, String desc, String sign, String[] excp) {
MethodVisitor target = super.visitMethod(access, name, desc, sign, excp);
if(name.equals("foo")) { // fill your own trigger condition
target = new InjectCodeMethodVisitor(api, target);
}
return target;
}
class InjectCodeMethodVisitor extends MethodVisitor {
public InjectCodeMethodVisitor(int api, MethodVisitor methodVisitor) {
super(api, methodVisitor);
}
@Override
public void visitInsn(int opcode) {
switch(opcode) {
case Opcodes.RETURN: case Opcodes.ARETURN: case Opcodes.IRETURN:
case Opcodes.LRETURN: case Opcodes.FRETURN: case Opcodes.DRETURN:
case Opcodes.ATHROW:
insnList.accept(mv); // inject exiting method
}
super.visitInsn(opcode);
}
}
}
来源:https://stackoverflow.com/questions/57530863/inserting-to-insnlist-before-several-nodes