没有类型信息的 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/Fooorg/mydomain/Bar 的环境中是正确的。只是这里少了对实际环境的验证。

直截了当地说,由于缺少检查,您的代码恰好可以在 HotSpot 上运行,但不可移植,即可能会在其他 JVM 上失败,甚至可能在未来版本的 HotSpot 上失败,其中类型检查规则是强制执行。

省略 checkcast 没有性能优势,因为将以一种或另一种方式进行检查,如果类型不匹配,将抛出一个 throwable。因此,我会坚持创建形式上正确的代码,即使这个特定的 JVM 不强制执行它。