简体   繁体   中英

“VerifyError: Expecting to find object/array on stack” when using ASM to monitor object creation in Java?

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);
    }
}

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. 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 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;
  }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM