简体   繁体   中英

Inlining tryCatchBlock results in Current frame's stack size doesn't match stackmap exception

I am using ASM to inline body of Callee::calcualte(int,int)int , which contains a try-catch block, to the Caller::test method. The generated bytecode seems OK but verification fails due to Exception:

Exception in thread "main" java.lang.VerifyError: Instruction type does not match stack map
Exception Details:
  Location:
    CallerI.test(II)V @50: iload
  Reason:
    Current frame's stack size doesn't match stackmap.
  Current Frame:
    bci: @50
    flags: { }
    locals: { 'CallerI', integer, integer, integer, integer, 'code/sxu/asm/example/Callee', integer, 'java/lang/ReflectiveOperationException' }
    stack: { }
  Stackmap Frame:
    bci: @50
    flags: { }
    locals: { 'CallerI', integer, integer, integer, integer, 'code/sxu/asm/example/Callee', integer, 'java/lang/Object' }
    stack: { integer }
  Bytecode:
    0000000: 1b1c 602a b400 0e1b 1c3e 3604 3a05 0336
    0000010: 06b8 002e 1230 1232 b200 3812 30b8 003e
    0000020: b600 443a 07a7 000d 3a07 b200 4a12 4cb6
    0000030: 0052 1506 a700 0364 3605 b200 5515 05b6
    0000040: 0058 b200 5512 5ab6 0052 b1            
  Exception Handler Table:
    bci [17, 37] => handler: 40
    bci [17, 37] => handler: 40
  Stackmap Table:
    full_frame(@40,{Object[#2],Integer,Integer,Integer,Integer,Object[#21],Integer},{Object[#101]})
    full_frame(@50,{Object[#2],Integer,Integer,Integer,Integer,Object[#21],Integer,Object[#4]},{Integer})
    full_frame(@55,{Object[#2],Integer,Integer,Integer,Integer,Object[#21],Integer,Object[#4]},{Integer,Integer})

The bytecode instructions from label 14 to label 52 in the generated code are from body of Callee::calculate, and three instructions from label 9 to 12 pops up two int arguments and the receiver (Callee).

 //The generated bytecode method.
 public void test(int, int);
    flags: ACC_PUBLIC
    Code:
      stack=6, locals=8, args_size=3
         0: iload_1       
         1: iload_2       
         2: iadd          
         3: aload_0       
         4: getfield      #14                 // Field _callee:Lcode/sxu/asm/example/Callee;
         7: iload_1       
         8: iload_2       
         9: istore_3      
        10: istore        4
        12: astore        5
        14: iconst_0      
        15: istore        6
        17: invokestatic  #46                 // Method java/lang/invoke/MethodHandles.publicLookup:()Ljava/lang/invoke/MethodHandles$Lookup;
        20: ldc           #48                 // class java/lang/String
        22: ldc           #50                 // String say
        24: getstatic     #56                 // Field java/lang/Void.TYPE:Ljava/lang/Class;
        27: ldc           #48                 // class java/lang/String
        29: invokestatic  #62                 // Method java/lang/invoke/MethodType.methodType:(Ljava/lang/Class;Ljava/lang/Class;)Ljava/lang/invoke/MethodType;
        32: invokevirtual #68                 // Method java/lang/invoke/MethodHandles$Lookup.findVirtual:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/MethodHandle;
        35: astore        7
        37: goto          50
        40: astore        7
        42: getstatic     #74                 // Field java/lang/System.err:Ljava/io/PrintStream;
        45: ldc           #76                 // String I find exception in the catch
        47: invokevirtual #82                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        50: iload         6
        52: goto          55
        55: isub          
        56: istore        5
        58: getstatic     #85                 // Field java/lang/System.out:Ljava/io/PrintStream;
        61: iload         5
        63: invokevirtual #88                 // Method java/io/PrintStream.println:(I)V
        66: getstatic     #85                 // Field java/lang/System.out:Ljava/io/PrintStream;
        69: ldc           #90                 // String 1..........
        71: invokevirtual #82                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        74: return        
      Exception table:
         from    to  target type
            17    37    40   Class java/lang/NoSuchMethodException
            17    37    40   Class java/lang/IllegalAccessException
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
              14      41     0  this   Lcode/sxu/asm/example/Callee;
              14      41     1     t   I
              14      41     2     p   I
              17      38     3   tmp   I
              42       8     4     e   Ljava/lang/ReflectiveOperationException;
               0      75     0  this   LCallerI;
               0      75     1     a   I
               0      75     2     b   I
              58      17     5     r   I
      LineNumberTable:
        line 16: 0
        line 18: 14
        line 26: 17
        line 27: 37
        line 29: 42
        line 31: 50
        line 18: 58
        line 19: 66
        line 20: 74
      StackMapTable: number_of_entries = 3
           frame_type = 255 /* full_frame */
          offset_delta = 40
          locals = [ class CallerI, int, int, int, int, class code/sxu/asm/example/Callee, int ]
          stack = [ class java/lang/ReflectiveOperationException ]
           frame_type = 255 /* full_frame */
          offset_delta = 9
          locals = [ class CallerI, int, int, int, int, class code/sxu/asm/example/Callee, int, class java/lang/Object ]
          stack = [ int ]
           frame_type = 255 /* full_frame */
          offset_delta = 4
          locals = [ class CallerI, int, int, int, int, class code/sxu/asm/example/Callee, int, class java/lang/Object ]
          stack = [ int, int ]

}

Can anyone provide advice for it? I have been stub by this problem for three days. There should be something wrong with the stackmap processing here but i have no idea how to adjust this error.

For your convenice, i also post the origina methods of Caller and Callee:

public class Callee {
    public final String _a;
    public final String _b;
    public Callee(String a, String b){
        _a = a;
        _b = b;
    }
  ....
    public int calculate(int t, int p){
        int tmp=0;
        try {
            MethodHandle handle  = MethodHandles.publicLookup().findVirtual(String.class, "say", MethodType.methodType(void.class, String.class));
        } catch (NoSuchMethodException | IllegalAccessException e) {
            // TODO Auto-generated catch block
            System.err.println("I find exception in the catch");
        }
        return tmp;
    }
}

public class Caller {
    final Callee _callee;

     public Caller(Callee callee){
    _callee = callee;
}
  ...   
public void test(int a, int b){
    int r = a+b-_callee.calculate(a, b);

    System.out.println(r);
    System.out.println("1..........");
}
}

UPDATE

The original Callee::calculate's bytecode:

 public int calculate(int, int);
    flags: ACC_PUBLIC
    Code:
      stack=5, locals=5, args_size=3
         0: iconst_0      
         1: istore_3      
         2: invokestatic  #26                 // Method java/lang/invoke/MethodHandles.publicLookup:()Ljava/lang/invoke/MethodHandles$Lookup;
         5: ldc           #32                 // class java/lang/String
         7: ldc           #34                 // String say
         9: getstatic     #36                 // Field java/lang/Void.TYPE:Ljava/lang/Class;
        12: ldc           #32                 // class java/lang/String
        14: invokestatic  #42                 // Method java/lang/invoke/MethodType.methodType:(Ljava/lang/Class;Ljava/lang/Class;)Ljava/lang/invoke/MethodType;
        17: invokevirtual #48                 // Method java/lang/invoke/MethodHandles$Lookup.findVirtual:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/MethodHandle;
        20: astore        4
        22: goto          35
        25: astore        4
        27: getstatic     #54                 // Field java/lang/System.err:Ljava/io/PrintStream;
        30: ldc           #60                 // String I find exception in the catch
        32: invokevirtual #62                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        35: iload_3       
        36: ireturn       
      Exception table:
         from    to  target type
             2    22    25   Class java/lang/NoSuchMethodException
             2    22    25   Class java/lang/IllegalAccessException
      LineNumberTable:
        line 18: 0
        line 26: 2
        line 27: 22
        line 29: 27
        line 31: 35
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
               0      37     0  this   Lcode/sxu/asm/example/Callee;
               0      37     1     t   I
               0      37     2     p   I
               2      35     3   tmp   I
              27       8     4     e   Ljava/lang/ReflectiveOperationException;
      StackMapTable: number_of_entries = 2
           frame_type = 255 /* full_frame */
          offset_delta = 25
          locals = [ class code/sxu/asm/example/Callee, int, int, int ]
          stack = [ class java/lang/ReflectiveOperationException ]
           frame_type = 9 /* same */

My code is also pushed to Github Repository , and the class MainInliner can be run directly after adding ASM lib to the classpath.

The main procedure in the project is MethodCallInliner::visitMethodInsn(..) , in which a new InliningAdapter is created and used to visit Callee::calculate() body.

=========================================

Update for LocalVariableTable :

According to @Holger's explanation and some options:

  • Avoid declaring any formal variable and let ASM infer everything.

To disable declare formal variable, the visitLocalVariable are override but empty implementation in both InliningAdapter and MethodCallInliner , the LocalVariableTable disappeared in the generated code but verification still fails with the same error. I also tried

 ClassReader.accept(, ClassReader.EXPAND_FRAME|ClassReader.SKIP_DEBUG)

But the result is the same as empty override visitLocalVariable

  • Merge Callee's localvariabletable to the generated table.

The full generated bytecodes are:

  public void test(int, int);
    flags: ACC_PUBLIC
    Code:
      stack=6, locals=8, args_size=3
         0: iload_1       
         1: iload_2       
         2: iadd          
         3: aload_0       
         4: getfield      #14                 // Field _callee:Lcode/sxu/asm/example/Callee;
         7: iload_1       
         8: iload_2       
         9: istore_3      
        10: istore        4
        12: astore        5
        14: iconst_0      
        15: istore        6
        17: invokestatic  #46                 // Method java/lang/invoke/MethodHandles.publicLookup:()Ljava/lang/invoke/MethodHandles$Lookup;
        20: ldc           #48                 // class java/lang/String
        22: ldc           #50                 // String say
        24: getstatic     #56                 // Field java/lang/Void.TYPE:Ljava/lang/Class;
        27: ldc           #48                 // class java/lang/String
        29: invokestatic  #62                 // Method java/lang/invoke/MethodType.methodType:(Ljava/lang/Class;Ljava/lang/Class;)Ljava/lang/invoke/MethodType;
        32: invokevirtual #68                 // Method java/lang/invoke/MethodHandles$Lookup.findVirtual:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/MethodHandle;
        35: astore        7
        37: goto          50
        40: astore        7
        42: getstatic     #74                 // Field java/lang/System.err:Ljava/io/PrintStream;
        45: ldc           #76                 // String I find exception in the catch
        47: invokevirtual #82                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        50: iload         6
        52: goto          55
        55: isub          
        56: istore_3      
        57: getstatic     #85                 // Field java/lang/System.out:Ljava/io/PrintStream;
        60: iload_3       
        61: invokevirtual #88                 // Method java/io/PrintStream.println:(I)V
        64: getstatic     #85                 // Field java/lang/System.out:Ljava/io/PrintStream;
        67: ldc           #90                 // String 1..........
        69: invokevirtual #82                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        72: return        
      Exception table:
         from    to  target type
            17    37    40   Class java/lang/NoSuchMethodException
            17    37    40   Class java/lang/IllegalAccessException
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
              14      41     5  this   Lcode/sxu/asm/example/Callee;
              14      41     4     t   I
              14      41     3     p   I
              17      38     6   tmp   I
              42       8     7     e   Ljava/lang/ReflectiveOperationException;
               0      73     0  this   LCallerI;
               0      73     1     a   I
               0      73     2     b   I
              57      16     3     r   I
      LineNumberTable:
        line 20: 0
        ..
        line 24: 72
      StackMapTable: number_of_entries = 3
           frame_type = 255 /* full_frame */
          offset_delta = 40
          locals = [ class CallerI, int, int, int, int, class code/sxu/asm/example/Callee, int ]
          stack = [ class java/lang/ReflectiveOperationException ]
           frame_type = 255 /* full_frame */
          offset_delta = 9
          locals = [ class CallerI, int, int, int, int, class code/sxu/asm/example/Callee, int, class java/lang/Object ]
          stack = [ int ]
           frame_type = 255 /* full_frame */
          offset_delta = 4
          locals = [ class CallerI, int, int, int, int, class code/sxu/asm/example/Callee, int, class java/lang/Object ]
          stack = [ int, int ]

}

and the original LocalVariableTables are:

Callee: LocalVariableTable:
        Start  Length  Slot  Name   Signature
               0      37     0  this   Lcode/sxu/asm/example/Callee;
               0      37     1     t   I
               0      37     2     p   I
               2      35     3   tmp   I
              27       8     4     e   Ljava/lang/ReflectiveOperationException;
Caller: LocalVariableTable:
        Start  Length  Slot  Name   Signature
               0      30     0  this   Lcode/sxu/asm/example/Caller;
               0      30     1     a   I
               0      30     2     b   I
              14      16     3     r   I

The merge seems OK(I think it is unnecessary avoid variable name conflict if these names are in different region, eg, two this symbol in different regions). But the verification still fails at @50::iload with the same message.

Before the invocation of calculate , there is one int value on the stack which will be used after the invocation. Method invocations which complete normally will only consume the appropriate parameter values and leave all other operand stack values untouched, regardless of what happens inside the invoked method. If the method is not void , the return value will be pushed onto the stack afterwards.

Things change when you inline the method's code. Then, the code may have an impact on the operand stack beyond consuming parameters and pushing one return value. In your case, there is an exception handler which will resume normal execution. But, as discussed in this answer , exception handlers start with an operand stack containing only one value, the encountered exception. All values pushed onto the stack before encountering the exception will be flushed. After inlining the method's code into the caller, this affects the operand stack of the caller as well.

So in your case, two code paths merge near the end of the inlined code, one for the case of normal completion, where the int value on the stack will be retained and the path of the exception handler, where the value has been dropped. This mismatch causes the VerifyError .

There is no easy solution. You can't force exception handlers to retain the value, so you have to rewrite the code to not rely on the pushed value to be retained, which renders your original idea of inlining by just inserting the instructions non-working. You have to be aware, that even the opposite is possible: when a method encounters a return instruction, it doesn't matter how many additional unconsumed values are dangling on the stack as the stack frame will be destroyed when returning to the caller. As a consequence, naively inlined code may leave additional values on the stack which will cause errors when you have branches over the method or if the method has more than one return instruction.

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