为什么Java 7 Bytecode Verifier抱怨这个Stack Frame?

时间:2013-12-22 00:48:29

标签: bytecode bytecode-manipulation

我有一个方法,我在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可能不喜欢我的合成地图?

3 个答案:

答案 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结构,则不应将其添加到MethodInfoaddAttribute 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