Invokespecial Verify Error: Type is not assignable

Invokespecial Verify Error: Type is not assignable

我修改了下面字节码的第 15 行,并将其从 invokevirtual 更改为 invokespecial (JAVA 8)。不幸的是我得到一个验证错误(操作数堆栈上的类型错误)

我知道操作数堆栈的值必须是 objectref 中指定的 class 的子 class 但在这种情况下 #18 是 Type 而不是像错误那样的 Type$ClassType建议。或者换句话说,第 15 行的 stackmapframe 不应该在 stack[0] 中有 Type 而不是 Type$ClassType?我错过了什么?

编辑:stackmapframes 在更改前后相同。 (以防我使用的 ASM COMPUTE FRAMES 改变它们)

Exception Details:
  Location:
    com/sun/tools/javac/code/Type$ClassType.toString()Ljava/lang/String; @15: invokespecial
  Reason:
    Type 'com/sun/tools/javac/code/Type' (current frame, stack[0]) is not assignable to 'com/sun/tools/javac/code/Type$ClassType'
  Current Frame:
    bci: @15
    flags: { }
    locals: { 'com/sun/tools/javac/code/Type$ClassType', 'java/lang/StringBuilder' }
    stack: { 'com/sun/tools/javac/code/Type', 'com/sun/tools/javac/code/TypeTag' }
  ...     
  Stackmap Table:
append_frame(@71,Object[#108])
same_frame(@85)
same_frame(@121)

这是代码。 Type$ClassType 是 Type 的直接子 class,com/sun/tools/javac/code/Type$ClassType 是当前的 class,它允许我们使用 invokespecial[= 调用 superclass(如 Type) 12=]

    public class com.sun.tools.javac.code.Type$ClassType extends com.sun.tools.javac.code.Type implements
 javax.lang.model.type.DeclaredType
    ....
    public java.lang.String toString();
        descriptor: ()Ljava/lang/String;
        flags: ACC_PUBLIC
        Code:
          stack=4, locals=2, args_size=1
             0: new           #108                // class java/lang/StringBuilder
             3: dup
             4: invokespecial #17                 // Method java/lang/StringBuilder."<init>":()V
             7: astore_1
             8: aload_0
             9: invokevirtual #13                 // Method com/sun/tools/javac/code/Type$ClassType.getEnclosingType:()Lcom/sun/tools/javac/code/Type;
            12: getstatic     #10                 // Field com/sun/tools/javac/code/TypeTag.CLASS:Lcom/sun/tools/javac/code/TypeTag;
            15: invokespecial #18                 // Method com/sun/tools/javac/code/Type.hasTag:(Lcom/sun/tools/javac/code/TypeTag;)Z
            18: ifeq          71
            .....
            StackMapTable: number_of_entries = 3
              frame_type = 252 /* append */
                offset_delta = 71
                locals = [ class java/lang/StringBuilder ]
              frame_type = 13 /* same */
              frame_type = 35 /* same */

您尝试在 Type 的实例上执行 invokespecial(由 invokevirtual @9 返回),而验证者需要当前 class 的引用,即Type$ClassType.

参见 JVMS §4.10.1.9:

One can validly replace types matching the current class and the argument types given in Descriptor on the incoming operand stack with the return type given in Descriptor, yielding the outgoing type state.

invokespecial用于实现三件事之一

  1. 构造函数调用
  2. 调用 private 方法
  3. 正在进行 super. … 通话

虽然 1. 不适用于此处(因为目标方法的名称不是 <init>),但其他任何一种情况都要求接收器类型为当前 class 或子class其中。因此,即使方法的声明 class 是 Type,实际接收者的类型也应可分配给当前的 class、Type$ClassType.

与您通过更改创建的内容最接近的等效项是 super 调用,但在 Java 源代码中,通过 super 调用方法会强制执行接收方引用与 this 相同,本质上可分配给当前 class.

在字节码级别,规则限制较少,但允许绕过当前 class 或其子classes 中的方法声明的方法调用不允许在一个类型引用,它可以指向一个完全不相关的 subclass 层次结构的实例,即 Type 不是 Type$ClassType.

相关JVMS规则已在中引用。