gpt4 book ai didi

java - 内联 tryCatchBlock 导致当前帧的堆栈大小与堆栈映射异常不匹配

转载 作者:行者123 更新时间:2023-12-01 11:35:15 24 4
gpt4 key购买 nike

我正在使用 ASM 将 Callee::calcualte(int,int)int 的正文(其中包含 try-catch block )内联到 Caller::test方法。生成的字节码看起来没问题,但由于异常而验证失败:

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

生成代码中标签14到标签52的字节码指令来自Callee::calculate主体,标签9到12的3条指令弹出两个int参数和接收者(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 ]

}

有人可以提供建议吗?我被这个问题困扰了三天了。这里的堆栈图处理应该有问题,但我不知道如何调整这个错误。

为了您的方便,我还发布了 Caller 和 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..........");
}
}
<小时/>

更新

原始 Callee::calculate 的字节码:

 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 */

我的代码也被推送到 Github Repository ,将ASM lib添加到类路径后可以直接运行类MainInliner

项目中的主要程序是MethodCallInliner::visitMethodInsn(..) ,其中创建了一个新的 InlinedAdapter 并用于访问 Callee::calculate() 主体。

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

LocalVariableTable 更新:

根据@Holger的解释和一些选项:

  • 避免声明任何形式变量,让 ASM 推断一切。

要禁用声明形式变量,visitLocalVariable 会被覆盖,但在 InlinedAdapterMethodCallInliner 中都为空实现,LocalVariableTable code> 在生成的代码中消失,但验证仍然失败并出现相同的错误。我也尝试过

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

但结果与空覆盖相同visitLocalVariable

  • 将被调用者的本地变量表合并到生成的表中。

完整生成的字节码是:

  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 ]

}

原来的LocalVariableTables是:

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

合并看起来不错(我认为如果这些名称位于不同的区域,则没有必要避免变量名称冲突,例如,两个 this 符号位于不同的区域)。但验证在 @50::iload 处仍然失败,并显示相同的消息。

最佳答案

在调用calculate之前,堆栈上有一个int值,该值将在调用之后使用。正常完成的方法调用只会消耗适当的参数值,而不会影响所有其他操作数堆栈值,无论调用的方法内部发生什么。如果该方法不是void,则返回值将被压入堆栈。

当您内联方法的代码时,情况会发生变化。然后,除了消耗参数和推送一个返回值之外,代码还可能对操作数堆栈产生影响。在您的情况下,有一个异常处理程序将恢复正常执行。但是,正如this answer中讨论的那样,异常处理程序从仅包含一个值(遇到的异常)的操作数堆栈开始。在遇到异常之前压入堆栈的所有值都将被刷新。将方法的代码内联到调用者中后,这也会影响调用者的操作数堆栈。

因此,在您的情况下,两个代码路径在内联代码的末尾附近合并,一个用于正常完成的情况,其中堆栈上的 int 值将被保留,并且异常处理程序,其中值已被删除。这种不匹配会导致 VerifyError

没有简单的解决方案。您不能强制异常处理程序保留该值,因此您必须重写代码以不依赖于要保留的推送值,这使得您最初通过插入指令进行内联的想法不起作用。你必须意识到,甚至相反的情况也是可能的:当一个方法遇到返回指令时,堆栈上悬挂着多少额外的未使用的值并不重要,因为当返回到调用者时,堆栈帧将被销毁。因此,天真的内联代码可能会在堆栈上留下额外的值,当您在方法上有分支或方法有多个返回指令时,这将导致错误。

关于java - 内联 tryCatchBlock 导致当前帧的堆栈大小与堆栈映射异常不匹配,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30059938/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com