没有类型信息的 JVM 调用接口
JVM invokeinterface without type information
我目前正在使用 Java ASM5 生成一些代码,我想知道为什么我可以在我的参数上调用接口方法,该参数仅声明为 java/lang/Object.[=11 类型=]
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC | ACC_STATIC, "test", "(Ljava/lang/Object;)V", null, null);
mv.visitVarInsn(ALOAD, 0);
mv.visitInsn(DUP);
mv.visitMethodInsn(INVOKEINTERFACE, "org/mydomain/Foo", "foo", "()V", true);
mv.visitMethodInsn(INVOKEINTERFACE, "org/mydomain/Bar", "bar", "()V", true);
mv.visitInsn(RETURN);
mv.visitMaxs(-1,-1);
mv.visitEnd();
一般来说,我希望这段代码在调用该方法之前需要额外的强制转换,以确保该对象确实实现了该接口。
只要我保证这个对象真的实现了这个接口,那么在没有额外转换的情况下调用一个方法是否安全,或者我可以 运行 进入一些陷阱吗?
VM 的类型检查似乎并不关心它。如果我用一个对象调用它,它没有实现接口,我得到一个 java.lang.IncompatibleClassChangeError,这并不奇怪。
我可能有性能损失吗?
从 JVM spec 看来,只要您传入的对象实现接口,invokeinterface
指令就可以正常工作。
您不会有任何性能损失,因为 invokeinterface
指令将检查类型和方法签名,即使它前面有 checkcast
指令 - here is the JVM source which does the check for reference。
您遇到了一个已知的 HotSpot 验证程序的草率问题,没有验证 invokeinterface
调用的接收器类型的类型兼容性。但这并不意味着这样的代码在形式上是正确的。
JVMSpec, §4.9.2 Structural Constraints 状态:
- The type of every class instance that is the target of a method invocation instruction must be assignment compatible with the class or interface type specified in the instruction (JLS §5.2).
但是,按照现在称为“通过类型推断验证”的算法为旧 JVM 的验证器静态验证此约束存在一个实际问题,并且仍然是 class 具有版本低于 50。 解释了这个问题。如果必须在分支后合并两个不同的引用类型,则可能会导致公共超类型未实现接口,而两种类型实际上都实现了。因此,拒绝后续的 invokeinterface
调用可能会导致拒绝正确的代码。
从50版本开始,有“通过类型检查进行验证”,对于50以上的版本甚至是强制性的。它使用stackmap表,明确声明类型合并的预期结果,消除了昂贵的操作。因此,“通过类型检查进行验证”formal rules 要求静态类型与接口类型兼容:
invokeinterface
An invokeinterface instruction is type safe iff all of the following conditions hold:
…
- One can validly replace types matching the type
MethodIntfName
and the argument types given in Descriptor
on the incoming operand stack with the return type given in Descriptor
, yielding the outgoing type state.
(请注意,这些规则与 the rules of an invokevirtual
instruction 相同,只是它使用 MethodIntfName
而不是 MethodClassName
)
作为旁注,代码在 java/lang/Object
恰好同时实现 org/mydomain/Foo
和 org/mydomain/Bar
的环境中是正确的。只是这里少了对实际环境的验证。
直截了当地说,由于缺少检查,您的代码恰好可以在 HotSpot 上运行,但不可移植,即可能会在其他 JVM 上失败,甚至可能在未来版本的 HotSpot 上失败,其中类型检查规则是强制执行。
省略 checkcast
没有性能优势,因为将以一种或另一种方式进行检查,如果类型不匹配,将抛出一个 throwable。因此,我会坚持创建形式上正确的代码,即使这个特定的 JVM 不强制执行它。
我目前正在使用 Java ASM5 生成一些代码,我想知道为什么我可以在我的参数上调用接口方法,该参数仅声明为 java/lang/Object.[=11 类型=]
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC | ACC_STATIC, "test", "(Ljava/lang/Object;)V", null, null);
mv.visitVarInsn(ALOAD, 0);
mv.visitInsn(DUP);
mv.visitMethodInsn(INVOKEINTERFACE, "org/mydomain/Foo", "foo", "()V", true);
mv.visitMethodInsn(INVOKEINTERFACE, "org/mydomain/Bar", "bar", "()V", true);
mv.visitInsn(RETURN);
mv.visitMaxs(-1,-1);
mv.visitEnd();
一般来说,我希望这段代码在调用该方法之前需要额外的强制转换,以确保该对象确实实现了该接口。 只要我保证这个对象真的实现了这个接口,那么在没有额外转换的情况下调用一个方法是否安全,或者我可以 运行 进入一些陷阱吗? VM 的类型检查似乎并不关心它。如果我用一个对象调用它,它没有实现接口,我得到一个 java.lang.IncompatibleClassChangeError,这并不奇怪。 我可能有性能损失吗?
从 JVM spec 看来,只要您传入的对象实现接口,invokeinterface
指令就可以正常工作。
您不会有任何性能损失,因为 invokeinterface
指令将检查类型和方法签名,即使它前面有 checkcast
指令 - here is the JVM source which does the check for reference。
您遇到了一个已知的 HotSpot 验证程序的草率问题,没有验证 invokeinterface
调用的接收器类型的类型兼容性。但这并不意味着这样的代码在形式上是正确的。
JVMSpec, §4.9.2 Structural Constraints 状态:
- The type of every class instance that is the target of a method invocation instruction must be assignment compatible with the class or interface type specified in the instruction (JLS §5.2).
但是,按照现在称为“通过类型推断验证”的算法为旧 JVM 的验证器静态验证此约束存在一个实际问题,并且仍然是 class 具有版本低于 50。invokeinterface
调用可能会导致拒绝正确的代码。
从50版本开始,有“通过类型检查进行验证”,对于50以上的版本甚至是强制性的。它使用stackmap表,明确声明类型合并的预期结果,消除了昂贵的操作。因此,“通过类型检查进行验证”formal rules 要求静态类型与接口类型兼容:
invokeinterface
An invokeinterface instruction is type safe iff all of the following conditions hold:
…
- One can validly replace types matching the type
MethodIntfName
and the argument types given inDescriptor
on the incoming operand stack with the return type given inDescriptor
, yielding the outgoing type state.
(请注意,这些规则与 the rules of an invokevirtual
instruction 相同,只是它使用 MethodIntfName
而不是 MethodClassName
)
作为旁注,代码在 java/lang/Object
恰好同时实现 org/mydomain/Foo
和 org/mydomain/Bar
的环境中是正确的。只是这里少了对实际环境的验证。
直截了当地说,由于缺少检查,您的代码恰好可以在 HotSpot 上运行,但不可移植,即可能会在其他 JVM 上失败,甚至可能在未来版本的 HotSpot 上失败,其中类型检查规则是强制执行。
省略 checkcast
没有性能优势,因为将以一种或另一种方式进行检查,如果类型不匹配,将抛出一个 throwable。因此,我会坚持创建形式上正确的代码,即使这个特定的 JVM 不强制执行它。