Search code examples
javabytecodejavassistbytecode-manipulation

Code modification with Javassist generate a java.lang.VerifyError: Expecting to find integer on stack


I use javassist to rewrite a method called compile (which takes an array of String as argument) : I created a new method having the signature as the compile method ( it's a copy of the original one ) , rename the actual compile method to compile$Impl and added some calls to a class of Mine.

The copy is done this way:

CtMethod interceptMethod = CtNewMethod.copy(method, methodName, ctClass, null);

The javassist rewritten code :

try { 
  com.company.CustomClass.instance().preintercept(this);
  boolean result = compile$impl($$);
  result = com.company.CustomClass.instance().dointercept(result, new Object[] { this , $1 });
  return result;
 } finally { 
    com.company.CustomClass.instance().postintercept(this);
 }

The code is written in a StringBuffer variable called body and the content of the later variable is written as the body of the new compile method like this:

        interceptMethod.setBody(body.toString());
        ctClass.addMethod(interceptMethod);

My com.company.CustomClass have the methods called by the new compile method:

   public Object dointercept(boolean retval, Object.. args) {
        return (Boolean)returned;
    }

public static synchronized CustomClass instance() {
    // _instance is already instanciated when this method is called
    return _instance;
}

public void preintercept(Object arg) {
 // some stuff before
}

public void postintercept(Object arg) {
 // some stuff after

}

When executing the code I got a verifyError:

java.lang.VerifyError: (class: org/eclipse/jdt/internal/compiler/batch/Main, method: compile signature: ([Ljava/lang/String;)Z) Expecting to find integer on stack

I have written the bytecode of the modified class to disk, to try to spot the problem. Here is what I have gotten for the new compile method:

   public boolean compile(java.lang.String[] arg0) {
        try {
            0 invokestatic 2130;      /* com.company.CustomClass.instance() */
            3 aload_0;
            4 invokevirtual 2134;     /* void preintercept(java.lang.Object arg0) */
            7 aload_0;
            8 aload_1;
            9 invokevirtual 2136;     /* boolean compile$impl(java.lang.String[] arg0) */
            12 istore_2;
            13 invokestatic 2130;     /* com.company.CustomClass.instance() */
            16 iload_2;
            17 iconst_2;
            18 anewarray 4;           /* new java.lang.Object[] */
            21 dup;
            22 iconst_0;
            23 aload_0;
            24 aastore;
            25 dup;
            26 iconst_1;
            27 aload_1;
            28 aastore;
            29 invokevirtual 2140;    /* java.lang.Object dointercept(boolean arg0, java.lang.Object[] arg1) */
            32 istore_2;
            33 iload_2;
            34 istore_3;
            35 goto 17;
            38 iload_3;
            39 ireturn;
        }
        finally {                 /* covers bytes 0 to 40 */
            40 astore 4;
            42 invokestatic 2130;     /* com.company.CustomClass.instance() */
            45 aload_0;
            46 invokevirtual 2143;    /* void postintercept(java.lang.Object arg0) */
            49 aload 4;
            51 athrow;
            52 invokestatic 2130;     /* com.company.CustomClass.instance() */
            55 aload_0;
            56 invokevirtual 2143;    /* void postintercept(java.lang.Object arg0) */
            59 goto -21;
        }
    }

When I had passed a type analysis on the bytecode of the class, the error was at line 32 istore_2 instruction. Here is the analysis:

Type based analysis: compile([Ljava/lang/String;)Z
Offset  Bytecode        Stack before              Stack after               
----------------------------------------------------------------------------
0       invokestatic    <empty>                   L                         
3       aload_0         L                         L, L                      
4       invokevirtual   L, L                      <empty>                   
7       aload_0         <empty>                   L                         
8       aload_1         L                         L, L                      
9       invokevirtual   L, L                      I                         
12      istore_2        I                         <empty>                   
13      invokestatic    <empty>                   L                         
16      iload_2         L                         L, I                      
17      iconst_2        L, I                      L, I, I                   
18      anewarray       L, I, I                   L, I, L                   
21      dup             L, I, L                   L, I, L, L                
22      iconst_0        L, I, L, L                L, I, L, L, I             
23      aload_0         L, I, L, L, I             L, I, L, L, I, L          
24      aastore         L, I, L, L, I, L          L, I, L                   
25      dup             L, I, L                   L, I, L, L                
26      iconst_1        L, I, L, L                L, I, L, L, I             
27      aload_1         L, I, L, L, I             L, I, L, L, I, L          
28      aastore         L, I, L, L, I, L          L, I, L                   
29      invokevirtual   L, I, L                   L                         
32      istore_2        L                         <empty>                   
                        Error: Expecting to find I on stack, type on stack L. 

33      iload_2         <empty>                   I                         
34      istore_3        I                         <empty>                   
35      goto            <empty>                   <empty>                   
38      iload_3         <empty>                   I                         
39      ireturn         I                         <empty>                   
40      astore          L                         <empty>                   
42      invokestatic    <empty>                   L                         
45      aload_0         L                         L, L                      
46      invokevirtual   L, L                      <empty>                   
49      aload           <empty>                   L                         
51      athrow          L                         L                         
52      invokestatic    <empty>                   L                         
55      aload_0         L                         L, L                      
56      invokevirtual   L, L                      <empty>                   
59      goto            <empty>                   <empty>                   
----------------------------------------------------------------------------

But I can't see why there is a problem with storing an int, knowing that I don't use any integers (I am sure missing something).

Thanks


Solution

  • It's because doIntercept is returning a Boolean Object, and this is stored as a pointer to an object (i.e. the L that appears at 29). It then triest to store this value into a primitive boolean, i.e. a raw integer I.

    If you use an explicit cast, or add .booleanValue() at the end

    com.company.CustomClass.instance().dointercept(result, new Object[] { this , $1 }).booleanValue();
    

    it might work?

    The problem is it hasn't compiled the 'automatic unboxing' of objects to primitives.

    https://docs.oracle.com/javase/tutorial/java/data/autoboxing.html