我有一个方法,我在Java 7(主要版本51)类中进行了更改。使用javap
,我查看了字节码和堆栈帧映射。一切都很好看:
public int addOne(int);
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=2
0: iload_1
1: iconst_0
2: invokestatic #50 // Method isSomething:(I)Z
5: ifeq 12
8: iconst_0
9: goto 13
12: iconst_1
13: iadd
14: ireturn
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 12
locals = [ class test/Target, int ]
stack = [ int ]
frame_type = 255 /* full_frame */
offset_delta = 0
locals = [ class test/Target, int ]
stack = [ int, int ]
此验证程序抛出此异常:
java.lang.VerifyError: Expecting a stackmap frame at branch target 12
Exception Details:
Location:
test/Target.addOne(I)I @5: ifeq
Reason:
Expected stackmap frame at this location.
Bytecode:
0000000: 1b03 b800 3299 0007 03a7 0004 0460 ac
令我发疯的是我让编译器从Java源代码生成相同的代码,它看起来像这样:
public int addOne(int);
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=2
0: iload_1
1: iconst_0
2: invokestatic #16 // Method isSomething:(I)Z
5: ifeq 12
8: iconst_0
9: goto 13
12: iconst_1
13: iadd
14: ireturn
StackMapTable: number_of_entries = 2
frame_type = 76 /* same_locals_1_stack_item */
stack = [ int ]
frame_type = 255 /* full_frame */
offset_delta = 0
locals = [ class test/Target, int ]
stack = [ int, int ]
请注意,堆栈帧映射中的唯一区别是合成映射具有所有完整帧 - 但这不应导致差异。有谁知道为什么verfier可能不喜欢我的合成地图?
答案 0 :(得分:1)
我无法重现此问题。也许您正在以javap仍然会读取但实际上无效的方式创建堆栈帧?因为我编辑的类具有相同的javap输出但它验证就好了。如果您发布实际的类文件,我可以看到我是否能找到问题,因为我认为只有Javap输出还有什么可以做的。
来源:
public class ArrayTest {
public int addOne(int x){
return x + (isSomething(0) ? 0 : 1);
}
public static boolean isSomething(int z) {return true;}
}
原始类中方法的Javap输出
public int addOne(int);
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=2
0: iload_1
1: iconst_0
2: invokestatic #2 // Method isSomething:(I)Z
5: ifeq 12
8: iconst_0
9: goto 13
12: iconst_1
13: iadd
14: ireturn
StackMapTable: number_of_entries = 2
frame_type = 76 /* same_locals_1_stack_item */
stack = [ int ]
frame_type = 255 /* full_frame */
offset_delta = 0
locals = [ class ArrayTest, int ]
stack = [ int, int ]
编辑类
中方法的Javap输出 public int addOne(int);
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=2
0: iload_1
1: iconst_0
2: invokestatic #15 // Method ArrayTest.isSomething:(
)Z
5: ifeq 12
8: iconst_0
9: goto 13
12: iconst_1
13: iadd
14: ireturn
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 12
locals = [ class ArrayTest2, int ]
stack = [ int ]
frame_type = 255 /* full_frame */
offset_delta = 0
locals = [ class ArrayTest2, int ]
stack = [ int, int ]
正如你所看到的,我有相同的Javap输出,但我的课程工作正常。
答案 1 :(得分:1)
答案是javassist糟透了,我对使用它感到非常后悔。
StackMapTable
属性来自对CodeAttribute.getAttribute(String tag)
的调用。即使这是您访问它的方式,也没有API可以将其添加回来,除非它是StackMapTable
类型。接受vanilla AttributeInfo
作为参数的唯一API位于MethodInfo
类。
如果方法不需要(或拥有)堆栈帧,则会得到null
。如果您为新的堆栈框架地图创建AttributeInfo
结构,则不应将其添加到MethodInfo
(addAttribute
API所在的位置),而CodeAttribute
所在的位置它属于。
这就是我在做的事情:
MethodInfo mi ...
AttributeInfo attr ...
mi.addAttribute(attr);
这就是我需要做的事情:
CodeAttribute ca ...
ca.getAttributes().add(attr);
(当然,ca.getAttributes()
会返回无类型的List
,因为我们都错过了2004年。)
我深入研究了允许您向StackFramMap
添加类型 CodeAttribute
的方法,并找出了缺乏通用API的方法。
使用top构造的结果是javap
会使你看起来有一个合适的StackMapTable。你做,但它附加到了错误的对象,你无法从javap
输出中看到它。
我没有在我的项目中使用ASM,因为我发现其对访问者模式的过度使用令人讨厌。我现在承认这是一个糟糕的决定。由于javassist自2012年以来没有更新,我想知道该项目是否已经死亡。我肯定会推出一大堆修订版。这是一团糟。
编辑哦哇。 Javassist内部代码假定任何StackMapTable
属性都是它自己的内部StackMapTable
类型(因为你还要添加StackMapTable
属性)。我想我可以创建自己的StackMapTable
实例,除了SFM构造函数是没有明显原因的包保护。它变得越来越糟......
答案 2 :(得分:0)
http://www.csg.ci.i.u-tokyo.ac.jp/~chiba/javassist/html/javassist/bytecode/MethodInfo.html
现在你可以致电:
mi.rebuildStackMapIf6(pool, cf);
它将重建堆栈映射,不要使用或需要-XX:-UseSplitVerifier