2

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?

Holger
  • 285,553
  • 42
  • 434
  • 765
Spille
  • 527
  • 4
  • 12

2 Answers2

2

From the JVM spec, it looks like the invokeinterface instruction will work fine as long as the object you pass in implements the interface.

You won't have any performance loss because the invokeinterface instruction will check the types and method signature, even if it is preceeded by a checkcast instruction - here is the JVM source which does the check for reference.

Hearen
  • 7,420
  • 4
  • 53
  • 63
konsolas
  • 1,041
  • 11
  • 24
2

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 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.

(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.

Community
  • 1
  • 1
Holger
  • 285,553
  • 42
  • 434
  • 765
  • You exactly hit the point, why I was asking this question. I want to convert code, that was compiled with older versions, where the mandatory type information would only contained in the stack map table. – Spille Sep 06 '17 at 23:54