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:
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
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, e.g., 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.