Class invoking method which exists as default on interface and private on super

Alex Buckley alex.buckley at oracle.com
Tue Feb 13 23:00:11 UTC 2018


On 2/13/2018 8:33 AM, Michael Rasmussen wrote:
> If I try to invoke a method that has a default implementation on an
> implementing interface, and a private implementation on the super
> class, the compilation passes, yet at runtime running the code
> produces an IllegalAccessError, trying to invoke the private method.

You are right to be surprised by this. If code can be compiled together 
without error, then it should link and execute without error -- that's 
basically the guarantee from JLS 15.12.4.4. However, as you've seen, a 
private method in the class hierarchy can throw a spanner in the works. 
Finessing the JLS guarantee is an open issue, tracked by 
https://bugs.openjdk.java.net/browse/JDK-8021581.

For the rest of this mail, I'll add light commentary to your example.

> In the code below, the first line of Test.run compiles into an
> invokeinterface, and the second compiles into invokevirtual. (javac
> from 8u152, 9.0.1 and 10-ea43 all produce that).

As expected.

> Running the code (on the Oracle builds listed above) those throws an
> IllegalAccessError when running the invokeinterface; presumably
> trying to invoke the private method on C. Similarly, if commenting
> out the first line, the invokevirtual call fails with a similar
> IllegalAccessError.

As expected. Taking the calls in reverse order:

- For the invokevirtual, the method to be resolved is Test::foo, and the 
first matching method found is C::foo, and thus resolution fails because 
C::foo is inaccessible to Test. Nothing more to say.

- For the invokeinterface, the method to be resolved is I::foo, and it 
is resolved successfully. Then, invokeinterface selects the method 
C::foo (because superclasses are searched before superinterfaces), and 
fails because C::foo is not public.

That's invokeinterface in Java SE 8/9/10. In Java SE 11, we plan to 
relax its must-be-public check in order to regularize the invoke* 
instructions and prepare them for further evolution. See 
https://bugs.openjdk.java.net/browse/JDK-8024806.

Under the SE 11 behavior, the private C::foo method would be skipped 
because it doesn't override the I::foo method which was resolved. 
invokeinterface would end up selecting the I::foo method for execution, 
which is apparently what J9 8.0.5.5 does already. While that was a neat 
prediction of the future by J9, it's not compliant with SE 8. (I'm 
assuming the 8.0.5.5 version string indicates an implementation of SE 8.)

Alex

> For comparison, compiling it with IBM J9 8.0.5.5, it produces the
> same bytecode, but at runtime, the invokeinterface calls the default
> interface method, and the invokevirtual call fails with
> IllegalAccessError.
>
> Seeing that the code compiles without warning, yet fails at runtime,
> I don't know if this should have been a compiler error instead?
>
> Or in case this is an issue with the method lookup at runtime, which
> list would be appropriate to forward this to?
>
> Kind regards Michael Rasmussen
>
> // -- app1/pkg/C.java package app1.pkg;
>
> public class C { private void foo() { System.out.println("C.foo"); }
> }
>
> // -- app1/pkg/I.java package app1.pkg;
>
> public interface I { public default void foo() {
> System.out.println("I.foo"); } }
>
> // -- app1/pkg/Test.java package app1.pkg;
>
> public class Test extends C implements I { public void run() {
> ((I)this).foo(); foo(); }
>
> public static void main(String[] args) { new Test().run(); } }
>


More information about the compiler-dev mailing list