I'm currently generating some code with Java ASM5 and I am wondering, why I can invoke an interface method on my parameter, which is declared only of type 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();
Generally I would have expected this code would require an additional cast before invoking the method to ensure, that this object really implements this interface. Is it safe to call a method without additional cast, as long as I guarantee that this Object really implements this interface or can I run into some pitfalls? The type checking of the VM doesn't seem to care about it. If I invoke it with an Object, that doesn't implement the interface I get an java.lang.IncompatibleClassChangeError, which is not surprising. Do I maybe have a performance loss?
You encountered a known sloppiness of HotSpot’s Verifier, not to verify the type compatibility of receiver types for invokeinterface calls. But this does not imply that such code is formally correct.
JVMSpec, §4.9.2 Structural Constraints states:
- 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).
However, there was a practical problem with verifying this constraint statically for verifiers of older JVMs following the algorithm which is now called “Verification by type inference” and still is the standard for class files having a version below 50. This answer explains the issue. If two different reference types have to be merged after branches, it may result in a common super type not implementing the interface while both types actually do. So rejecting a subsequent invokeinterface call could lead in rejecting correct code.
Starting with version 50, there is “Verification by type checking”, which is even mandatory for versions higher than 50. It uses stackmap tables, explicitly declaring the supposed result of type merging, eliminating the expensive operations. Consequently, the “Verification by type checking” has formal rules which mandate that the static type is compatible with the interface type:
invokeinterface
An invokeinterface instruction is type safe iff all of the following conditions hold:
…
- One can validly replace types matching the type
MethodIntfNameand the argument types given inDescriptoron the incoming operand stack with the return type given inDescriptor, yielding the outgoing type state.
(Note that these rules are identical to the rules of an invokevirtual instruction, except that it uses MethodIntfName instead of MethodClassName)
As a side note, the code would be correct in an environment where java/lang/Object happens to implement both, org/mydomain/Foo and org/mydomain/Bar. It’s only that verification against the actual environment that is missing here.
To put it straight, your code happens to work on HotSpot due to a missing check, but is not portable, i.e. may fail on other JVMs and may even fail on future versions of HotSpot, where the type checking rules are enforced.
There is no performance advantage in omitting the checkcast, as there will be a check one way or the other and a throwable will be thrown if the type doesn’t match. Therefore, I would stick to creating formally correct code, even if this particular JVM does not enforce it.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With