JDK-8013085 : Default method bridges have incorrect class loading constraints
  • Type: Bug
  • Component: hotspot
  • Sub-Component: runtime
  • Priority: P3
  • Status: Closed
  • Resolution: Duplicate
  • Submitted: 2013-04-23
  • Updated: 2013-09-24
  • Resolved: 2013-07-23
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.
Related Reports
Duplicate :  
The JVM is required to ensure that, in certain circumstances, types mentioned in classes handled be different loaders mean the same thing (JVMS 5.3.4).  For methods, there are two kinds of constraints:
- Given a Methodref or InterfaceMethodref appearing in a class file C: at resolution time, where the resolved method m1 is declared in class file D, the types mentioned in m1's descriptor must mean the same thing in C's and D's class loaders (,
- Given a method declaration m1 appearing in a class file C: at preparation time, where m1 overrides a method m2 in a class file D, the types mentioned by m1's descriptor must mean the same thing in C's and D's class loaders (5.4.2).

Because the VM generates bridges ("overpasses") in a class that is distinct from C or D, the timing and meaning of class loading constraints is different than if the VM were to directly implement default methods (with adjustments to method selection, etc.)



Let L1, L2, and L3 be class loaders with possibly-different interpretations of the type "Foo".

Loaded by L1:
public interface I { public default Foo m() { return null; } }

Loaded by L2:
public class A implements I {}

Loaded by L3:
public class Test { static {
new A()
invokevirtual A.m()Foo
} }

Expected (specified) behavior: 'I' and 'A' can be loaded cleanly (no constraints).  In 'Test', 'A.m' resolves to 'I.m'.  Loading constraint upon resolution of the invocation: Foo[L3]=Foo[L1].

Actual behavior: 'A.m' contains a bridge that redeclares 'm()Foo'.  Loading constraint on preparation of A: Foo[L2]=Foo[L1].  In 'Test', 'A.m' resolves to 'A.m'.  Loading constraint upon resolution of the invocation: Foo[L3]=Foo[L2].

What can go wrong?
- If Foo[L3]=Foo[L1], but Foo[L1/L3]!=Foo[L2], there should be no error, but one will be reported.
- If Foo[L2]!=Foo[L1], an error can occur upon preparation of A, even though no error should occur until the invocation is resolved.

The attached sources can be used to generate various tests that illustrate this second point.  Instructions and outcomes (in each case, uncomment the noted items, compile/run, then restore the comments before the next test):

declare A.m: success
declare A.m, declare B.m: overriding error (B vs. A)
declare A.m, invoke A.m: resolution error (Task vs. A)
declare A.m, invoke B.m: resolution error (Task vs. A)

declare I.n: success (separately compiled)
declare I.n, declare B.n: overriding error (B vs. I)
declare I.n, invoke I.n: resolution error (Task vs. I) (separately compiled)
declare I.n, invoke B.n: AME (separately compiled)

declare I.o: overriding error (B vs. I)
declare I.o, declare B.o: overriding error (B vs. I)
declare I.o, invoke I.o: (never reaches invocation)
declare I.o, invoke B.o: (never reaches invocation)

Behavior for 'm' and 'n' is mostly* correct, but in the case of 'o':
1) Simply declaring I.o causes an overriding error; this is in contrast to simply declaring A.m or I.n, which cause no error.
2) If we somehow turn off the overriding error, we still need a Task vs. I test to occur when either I.o or B.o is invoked (even though the VM sees the resolved method as the B.o bridge in the latter case).

(* I believe "declare I.n, invoke B.n" should trigger a resolution error, as in "declare A.m, invoke B.m", not an AME...)
Closing as a duplicate of 8009130. Now that we have agreed that the JVM is not generating bridges, and is not handling generic signatures and covariant returns, the JVM no longer needs to create overpass methods to contain the appropriate check casts. The fix is for the JVM to determine the appropriate default methods and to track those in inheriting class, and include them in the vtable inheritance, maintaining the original methods and the method-holder (i.e. the interface which defines the default method). This means that both the access checking and the loader constraint checking will be done against the correct method and method-holder, i.e. class. The JVM does create overpasses to handle the error conditions, for example if there are conflicting default methods, then we need a new method to enable us to throw the appropriate error and error message.

The class loading constraints are specified clearly. The problem is that "overpasses" do not really exist from the specification's point of view. The requirements of the specification are (referring to my initial example): - No constraints are introduced by 'A' (specifically, there is no error if Foo[L2]!=Foo[L1]) - 'Test' introduces the constraint Foo[L3]=Foo[L1] (there is no error if Foo[L3]!=Foo[L2]) - When the method is actually invoked, it should run successfully _even if_ Foo[L2]!=Foo[L1] It seems likely that getting this right will require some relatively deep changes to the core VM behavior, treating "overpasses" differently than other methods. (Or alternatively, this bug could be viewed as tolerable until, sometime in the near future, a different strategy for default methods is implemented.)

So if the implementation is to special-case these then the constraint rules need to be fully specified anyway. Who can write that specification?

"Overpasses" are an implementation detail. If we were willing to make them a specification-level concept, then this would be no problem -- we could specify the class loading constraints accordingly. But we want the specification to be abstract enough that the implementation strategy can be changed, meaning that the use of "overpasses" needs to be completely transparent.

Has this notion of an "overpass" been fully spec'd out? Is this something the JVMS is aware of or purely an implementation artifact?