Reputation: 3554
What I want to do is to monitor the object creation and record a unique ID for that object. So I use ASM to monitor the "NEW" instruction. In my method vistor adapter:
public void visitTypeInsn(int opcode, String desc){
mv.visitTypeInsn(opcode, desc);
if (opcode == NEW){
mv.visitInsn(DUP);
mv.visitMethodInsn(INVOKESTATIC, "org/.../.../MyRecorder", "object_new",
"(Ljava/lang/Object;)V", false);
}
}
In MyRecorder.java
:
public static void object_new(Object ref){
log("object_new !");
log("MyRecorder: " + ref);
log("ref.getClass().getName(): " + ref.getClass().getName());
}
However, these code result in java.lang.VerifyError: (...) Expecting to find object/array on stack
. I have no idea why this can not work. If this way is not right, how can I monitor the object creation?
Actually, I also tried to monitor object.<init>
instead of monitor the NEW
instruction. However, with the following code, it throws java.lang.VerifyError: (...) Unable to pop operand off an empty stack
:
public void visitMethodInsn(int opc, String owner, String name, String desc, boolean isInterface) {
...
mv.visitMethodInsn(opc, owner, name, desc, isInterface);
if (opc == INVOKESPECIAL && name.equals("<init>")) {
mv.visitInsn(DUP);
mv.visitMethodInsn(INVOKESTATIC, "org/myekstazi/agent/PurityRecorder", "object_new",
"(Ljava/lang/Object;)V", false);
}
}
Upvotes: 3
Views: 1241
Reputation: 3296
The object instantiation is separated into two instructions. This can make it difficult to work with object instantiation. It was actually the biggest problem in multiple of my projects with ASM. The simple expression new Integer(0)
might compile to the following code.
new java/lang/Integer ═╤═
dup ═╪═══╤═
iconst_0 │ │ ═╤═
invokespecial java/lang/Integer.<init>(I)V │ ═╧═══╧═
↓
(The ASCII art on the right shall represent the lifetime of values on the stack. The first column represents the first item on the stack, the second column the second item on the stack, and so on.)
The reference created with new
cannot be used until it is initialized by invokespecial
. Therefore, you cannot put you instrumentation directly after new
. In the example above, you could put the instrumentation behind invokespecial
. However, you need to distinguish it from calls to the super constructor. A statement like super(0)
might compile to the following code.
aload_0 ═╤═
iconst_0 │ ═╤═
invokespecial {super class}.<init>(I)V ═╧═══╧═
This two examples might cover the two most common cases where <init>
is called. If you don't do anything with the value created by new Integer(0)
, a compiler could also decide to skip the dup
instruction. I'm not sure if they actually do it. Anyway, the first example wourld become the following code.
new java/lang/Integer ═╤═
iconst_0 │ ═╤═
invokespecial java/lang/Integer.<init>(I)V ═╧═══╧═
This can still be handled by injecting dup
behind new
, and invokestatic
behind invokespecial
.
Anyway, if you want to handle even more exotic cases, the bytecode could also contain instructions like dup_x1
or dup_x2
. GeneratorAdapter.box(Type)
actually generates such code. Some "lazy" bytecode manipulators could also cause new
instructions where the instance is never initialized before it is removed with pop
. As you can see, working with object instantiations in bytecode can take a lot of work. However, depending on your situation, you might not need to suport all this cases. Unfortunately, I don't have the experiance to estimate which cases you need to support.
Here is an example that doesn't handle dup_x1
and similar instructions. Therefore, it would not work with code generated by GeneratorAdapter.box(Type)
. Beside that, it seems to work reasonable well in my tests. It uses AnalyzerAdapter
to get some information about the operand stack.
public final class InstrumentationMethodVisitor extends AnalyzerAdapter {
public InstrumentationMethodVisitor(
String owner, int access, String name, String descriptor,
MethodVisitor methodVisitor) {
super(ASM8, owner, access, name, descriptor, methodVisitor);
}
@Override
public void visitTypeInsn(int opcode, String type) {
super.visitTypeInsn(opcode, type);
if (opcode == NEW && stack != null) {
mv.visitInsn(DUP);
}
}
@Override
public void visitInsn(int opcode) {
if (opcode == POP && stack != null && stack.get(stack.size() - 1) instanceof Label) {
mv.visitInsn(POP);
}
super.visitInsn(opcode);
}
@Override
public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
if (opcode == INVOKESPECIAL && name.equals("<init>")) {
boolean hasRelatedNew = stack != null && stack.get(stack.size() - getAmountOfArgumentSlots(descriptor) - 1) instanceof Label;
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
if (hasRelatedNew) {
mv.visitMethodInsn(
INVOKESTATIC,
"org/myekstazi/agent/PurityRecorder",
"object_new",
"(Ljava/lang/Object;)V",
false);
}
}
else {
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
}
}
private static int getAmountOfArgumentSlots(String descriptor) {
int slots = 0;
for (Type type : Type.getArgumentTypes(descriptor)) {
slots += type.getSize();
}
return slots;
}
}
Upvotes: 2