JVM调用接口没有类型信息

问题描述 投票:2回答:2

我目前正在使用Java ASM5生成一些代码,我想知道为什么我可以在我的参数上调用一个接口方法,该参数只声明为java / lang / Object类型。

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();

一般来说,我希望这个代码在调用方法之前需要额外的强制转换,以确保该对象真正实现了这个接口。如果我保证这个Object真的实现了这个接口,或者我可能遇到一些陷阱,那么在没有额外强制转换的情况下调用方法是否安全? VM的类型检查似乎并不关心它。如果我使用Object调用它,但是没有实现接口,我会得到一个java.lang.IncompatibleClassChangeError,这并不奇怪。我可能会有性能损失吗?

java java-bytecode-asm
2个回答
2
投票

你遇到了HotSpot验证器的已知问题,而不是验证invokeinterface调用的接收器类型的类型兼容性。但这并不意味着这样的代码在形式上是正确的。

JVMSpec, §4.9.2 Structural Constraints说:

  • 作为方法调用指令目标的每个类实例的类型必须与指令中指定的类或接口类型兼容(JLS§5.2)。

然而,对于遵循该算法的旧JVM的验证者,存在实际问题,现在称为“通过类型推断进行验证”,并且仍然是版本低于50的类文件的标准.This answer解释了该问题。如果必须在分支之后合并两个不同的引用类型,则可能导致在两种类型实际执行时不实现接口的公共超类型。所以拒绝随后的invokeinterface调用可能导致拒绝正确的代码。

从版本50开始,有“按类型检查验证”,对于高于50的版本甚至是强制性的。它使用堆栈映射表,明确声明类型合并的假设结果,从而消除了昂贵的操作。因此,“通过类型检查进行验证”具有formal rules,它要求静态类型与接口类型兼容:

invokeinterface

如果满足以下所有条件,则invokeinterface指令是类型安全的:

  • 可以使用MethodIntfName中给出的返回类型有效地替换与Descriptor类型和Descriptor中给定的参数类型相匹配的类型,返回类型为the rules of an invokevirtual instruction,从而产生传出类型状态。

(请注意,这些规则与MethodIntfName相同,只是它使用MethodClassName而不是java/lang/Object

作为旁注,代码在org/mydomain/Foo恰好实现org/mydomain/Barcheckcast的环境中是正确的。这只是对这里遗漏的实际环境的验证。

说实话,您的代码由于缺少检查而在HotSpot上运行,但是不可移植,即可能在其他JVM上失败,甚至可能在未来版本的HotSpot上失败,其中强制执行类型检查规则。

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


2
投票

invokeinterface看,只要您传入的对象实现接口,invokeinterface指令就可以正常工作。

你不会有任何性能损失,因为checkcast指令将检查类型和方法签名,即使它之前是here is the JVM source which does the check for reference指令 - qazxswpoi。

© www.soinside.com 2019 - 2024. All rights reserved.