Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Binary compatibility of changing a class with static methods to interface in Java

Tags:

I've faced the following weird case of an incompleteness of the Java/JVM specification. Suppose we have the classes (we will use Java 1.8 and HotSpot):

public class Class {
  public static void foo() {
    System.out.println("hi");
  }
}

public class User {
  public static void main(String[] args) {
    Class.foo();
  }
}

Then recompile the Class to be an interface without recompiling theUser`:

public interface Class {
  public static void foo() {
    System.out.println("hi");
  }
}

Running the User.main now produces the same output hi. It seems obvious, but I would expect it to fail with the IncompatibleClassChangeError and that's why:

I know that changing a class to an interface is a binary incompatibility according to JVM 5.3.5#3 statement:

If the class or interface named as the direct superclass of C is, in fact, an interface, loading throws an IncompatibleClassChangeError.

But let's assume we don't have inheritors of the Class. We now have to refer the JVM specification about method's resolution. The first version is compiled into this bytecode:

public static void main(java.lang.String[]);
    Code:
       0: invokestatic  #2                  // Method examples/Class.foo:()V
       3: return

So we have here something called CONSTANT_Methodref_info in classpool.

Let's quote the actions of the invokestatic.

... The run-time constant pool item at that index must be a symbolic reference to a method or an interface method (§5.1), which gives the name and descriptor (§4.3.3) of the method as well as a symbolic reference to the class or interface in which the method is to be found. The named method is resolved (§5.4.3.3).

So the JVM treats method and interface methods in a different manner. In our case, JVM sees the method to be a method of the class (not interface). JVM tries to resolve it accordingly 5.4.3.3 Method Resolution:

According to JVM specification, the JVM must fail on the following statement:

1) If C is an interface, method resolution throws an IncompatibleClassChangeError.

...because Class is not actually a class, but an interface.

Unfortunately, I haven't found any mentions about binary compatibility of changing a class to interface in the Java Language Specification Chapter 13. Binary Compatibility. Moreover, there is nothing said about such tricky case of referencing the same static method.

Could anybody elaborate on that and show me if I missed something?

like image 675
Sergei Patrikeev Avatar asked Feb 17 '17 09:02

Sergei Patrikeev


1 Answers

First, since your example doesn’t contain inheritance, we don’t have to “assume we don't have inheritors of the Class”, there simply are none. So the cited part of §5.3.5 is irrelevant for this example.

The cited part of §6.5, naming “symbolic reference to a method or an interface method” is ironically a change made an Java 8 to relax the restrictions. The invokestatic instruction is explicitly allowed to be invoked on interface methods, if they are static.

The first bullet of §5.4.3.3, which you refer to at the end, stating that the method resolution should fail unconditionally, if the declaring type is an interface, is indeed violated, but it makes no sense anyway. Since it’s referred to unconditionally, i.e. the documentation of invokestatic doesn’t state that a different lookup algorithm should be used for interface methods, it would imply that invoking a static method of an interface is impossible in general.

That’s obviously not the intention of the specification, which incorporates the explicitly added feature of static methods in interfaces, which of course shall also be callable.

In your example, the calling class does indeed violate the specification, namely §4.4.2, as it refers to an interface method via CONSTANT_Methodref_info instead of CONSTANT_InterfaceMethodref_info, after the declaring class has been changed. But the current state of the invokestatic instruction’s documentation doesn’t mandate to change the behavior based on the constant pool item’s type (that might be the actual intention, but it’s not there). And, as said, adhering to current wording would imply rejecting any invokestatic on an interface.

So the specification needs another cleanup, but since the distinction between Methodref_info and InterfaceMethodref_info is by far not as useful as it might have been before Java 8 (compare with the answer linked above), I wouldn’t be surprised, if the ultimate fix turns out to be removing the distinction altogether in the future.

like image 112
Holger Avatar answered Sep 21 '22 09:09

Holger