JDK-8021581 : Private class methods interfere with invocations of interface methods
  • Type: Bug
  • Component: specification
  • Sub-Component: vm
  • Affected Version: 7,8
  • Priority: P4
  • Status: Open
  • Resolution: Unresolved
  • Submitted: 2013-07-26
  • Updated: 2018-10-08
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
Other
tbd_majorUnresolved
Related Reports
Blocks :  
Relates :  
Relates :  
Relates :  
Relates :  
Relates :  
Description
This is a very simple scenario.  Just a class declaring a private method, and an interface declaring a public method with the same signature.  It compiles without error.  But the compile-time and runtime semantics don't match.

------

public class A {
 private void m() { System.out.println("A.m"); }
 public static void main(String... args) { B b = new C(); b.m(); }
}

public interface I {
 public void m();
}

public abstract class B extends A implements I {
}

public class C extends B {
 public void m() { System.out.println("C.m"); }
}

public class Test {
 public static void main(String... args) { B b = new C(); b.m(); }
}

java Test
Exception in thread "main" java.lang.IllegalAccessError: tried to access method A.m()V from class Test

java A
A.m

------

Intuitively, a private method in A should be irrelevant, not affecting the behavior of other classes.  Per JLS, I.m is a member of B, and that's what the invocations refer to.  But per JVMS, a reference to 'B.m()V' resolves to A.m.

We could address the problem by changing resolution so that it ignores inherited private methods.

We could also address it by catching the conflict in the JLS and reporting an error (sort of like the "same erased signature" check).

Comments
Related to JDK-6810795 JDK 6
21-12-2017

In the case of non-private methods, we need to consider selection behavior as well. Don't want to skip a non-inherited method during resolution and then turn around and choose it during selection.
21-12-2017

A note on the package-access methods: e.g. public class p1.A { void x(){}} // public class, package-private method public class p2.B extends p1.A caller p1.C invoke virtual p2.B.x() step 1: resolve REFC p2.B, check class access - public class, ok step 2: resolve p2.B.x(), finds p1.A.x() - checks method access - p1.C CAN access p1.A.x() since in same package. So I think that with package-access methods there are many more cases in which we have successful operation today so greater risk of breakage if we were to change this.
06-07-2017

Apparently this problem was recognized as far back as 2008 -- see JDK-6691741.
31-07-2014

Worth noting that package-access methods suffer from the same problem -- in some cases, the VM's resolution search will believe they are inherited, even though the language believes they are not. This feels less bad for some reason, perhaps because package access is a bit of a mess anyway, and this is probably not the only bug. But it is, fundamentally, the same problem. We could enhance resolution to account for it, too.
13-09-2013

An analysis of compatibility concerns, were we to change the VM resolution behavior to skip private methods of superclasses. (C is the calling class/interface, A is the declaring class of a private method, and B is a subclass of A): a) Reference from C resolves to A.m, C!=A. Current behavior is IAE. Fixed behavior would be to find another method, or NSME. Fully compatible change. b) Direct reference from A to A.m, which resolves to A.m. No change being proposed here. c) invokevirtual call from A to B.m, resolves to A.m. Current behavior is executing A.m. Fixed behavior would be to find another method, or NSME. This is either behaviorally or binary incompatible. d) invokestatic call from A to B.m, resolves to (static) A.m. Same as (c), but worth thinking about whether this case should be treated differently than (c). e) invokespecial call from A to B.m. This is a VerifyError (invokespecial can't call subclass methods). No change. (I'm ignoring invokedynamic, but I assume it has nothing unique to contribute.) So while we can't identify every "in the wild" occurrence of relevant bytecode, we know that every compatibility issue would look like (c) or (d). And it's hard to feel too much sympathy there -- clearly, when the compiler generated the call, it did not intend to invoke A.m. So that fact that it runs at all is an accident, and probably a bug (I say "probably" only because maybe A.m accidentally does exactly what was wanted, by coincidence). If we're willing to add some complexity, we can be fully-compatible by simply guarding the behavior on a version number (with extra effort, we can carve out just (c) and (d) for legacy behavior). Then again, (c) and (d) are pretty narrow circumstances, and I'm not sure they justify extreme measures to preserve compatibility...
13-09-2013

Because Object declares a private method, 'registerNatives()V', note that the same scenario can easily arise if anyone uses the name 'registerNatives' in their interface. interface I { void registerNatives(); } abstract class A implements I {} class B extends A { public void registerNatives() { System.out.println("B"); } } invokevirtual A.registerNatives()V See also JDK-8024804, which illustrates how the Object method can even interfere with invokeinterface.
13-09-2013

Note that a compiler-only change is not very friendly to binary compatibility: generally, there shouldn't be any risk associated with introducing a private method in, say, a library class. (The specified compatibility rules (JLS 13) are kind of a mess here, though, so I hesitate to whether this is absolutely a problem or not.)
11-09-2013