JDK-6245699 : Missing bridge for final method (gives AbstractMethodError at runtime)
  • Type: Bug
  • Component: tools
  • Sub-Component: javac
  • Affected Version: 5.0
  • Priority: P2
  • Status: Closed
  • Resolution: Fixed
  • OS: generic,linux,windows_2000
  • CPU: generic,x86
  • Submitted: 2005-03-24
  • Updated: 2010-04-02
  • Resolved: 2006-01-07
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.
JDK 6
6 b67Fixed
Related Reports
Duplicate :  
Duplicate :  
Relates :  
Relates :  
Description
FULL PRODUCT VERSION :
java version "1.5.0_01"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_01-b08)
Java HotSpot(TM) Client VM (build 1.5.0_01-b08, mixed mode)


ADDITIONAL OS VERSION INFORMATION :
Linux tidore 2.6.10 #1 SMP Mon Jan 10 10:43:24 CET 2005 i686 Intel(R) Pentium(R) 4 CPU 2.60GHz GenuineIntel GNU/Linux


A DESCRIPTION OF THE PROBLEM :
The test case below compiles cleanly, but when run it throws an AbstractMethodError. This is because no bridge method is generated in the class Derived.

It only happens if the method (see test code) is declared final, but this should not inhibit generating the bridge method, which does not override the base class method.

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Save test case below as TestClass.java

% javac TestClass.java
% java TestClass


EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
No output
ACTUAL -
Stack trace as reported below

ERROR MESSAGES/STACK TRACES THAT OCCUR :
Exception in thread "main" java.lang.AbstractMethodError: TestClass$Derived.method()LTestClass$Base;
        at TestClass.main(TestClass.java:5)


REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
public class TestClass {

  public static void main(String[] args) {
    Interface x = new Derived();
    x.method(); // throws AbstractMethodError
  }

  static interface Interface {
    public Base method();
  }
  
  static class Base {
    // Bug only occurs if method is declared final
    public final Derived method() { return null; }
  }
  
  static class Derived extends Base implements Interface {}
}

---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
Do not declare the method in the base class as final.
###@###.### 2005-03-24 19:36:03 GMT

Comments
SUGGESTED FIX http://sa.sfbay.sun.com/projects/langtools_data/mustang/6245699/
26-01-2006

SUGGESTED FIX The problem is in TransType.addBridgeIfNeeded. The test for final methods does not account for final methods implementing an interface in a subclass. The fix below is not correct, but pinpoints where the fix must go: Index: src/share/classes/com/sun/tools/javac/comp/TransTypes.java ========================================= @@ -268,7 +272,7 @@ if (bridge == null || bridge == meth || impl != null && !bridge.owner.isSubClass(impl.owner, types)) { // No bridge was added yet. if (impl != null && - ((impl.flags() & FINAL) == 0 || impl.owner == origin) && + // ((impl.flags() & FINAL) == 0 || impl.owner == origin) && ((impl != meth || (meth.flags() & ABSTRACT) == 0) && (!types.isSameType(erasure(types.memberType(origin.type, meth)), meth.erasure(types)) || !types.isSameType(erasure(types.memberType(origin.type, impl)), impl.erasure(types))) ###@###.### 2005-03-24 21:28:26 GMT Further discussions with Gilad suggests that there is no reason for any special cases for final methods unless to detect "bad" overrides of final methods. This should have been detected elsewhere and should be verified by adding regression tests or asserting that existing tests detect this situation. I restructured the code to make it easier to maintain in the future. Index: src/share/classes/com/sun/tools/javac/comp/TransTypes.java ================================================================= @@ -265,17 +265,11 @@ MethodSymbol meth = (MethodSymbol)sym; MethodSymbol bridge = meth.binaryImplementation(origin, types); MethodSymbol impl = meth.implementation(origin, types, true); - if (bridge == null || bridge == meth || impl != null && !bridge.owner.isSubClass(impl.owner, types)) { + if (bridge == null + || bridge == meth + || impl != null && !bridge.owner.isSubClass(impl.owner, types)) { // No bridge was added yet. - if (impl != null && - ((impl.flags() & FINAL) == 0 || impl.owner == origin) && - ((impl != meth || (meth.flags() & ABSTRACT) == 0) && - (!types.isSameType(erasure(types.memberType(origin.type, meth)), meth.erasure(types)) || - !types.isSameType(erasure(types.memberType(origin.type, impl)), impl.erasure(types))) - || - impl != meth && - !types.isSameType(impl.erasure(types).restype(), meth.erasure(types).restype()))) - { + if (impl != null && isBridgeNeeded(meth, impl, origin.type)) { addBridge(pos, meth, impl, origin, bridge==impl, bridges); } } else if ((bridge.flags() & SYNTHETIC) != 0) { @@ -300,6 +294,56 @@ } } } + // where + /** + * @param method The symbol for which a bridge might have to be added + * @param impl The implementation of method + * @param dest The type in which the bridge would go + */ + private boolean isBridgeNeeded(MethodSymbol method, + MethodSymbol impl, + Type dest) { + if (impl != method) { + // If either method or impl have different erasures as + // members of dest, a bridge is needed. + Type method_erasure = method.erasure(types); + if (!isSameMemberWhenErased(dest, method, method_erasure)) + return true; + Type impl_erasure = impl.erasure(types); + if (!isSameMemberWhenErased(dest, impl, impl_erasure)) + return true; + + // If the erasure of the return type is different, a + // bridge is needed. + return !types.isSameType(impl_erasure.restype(), + method_erasure.restype()); + } else { + if ((method.flags() & ABSTRACT) != 0) { + // Both method and impl are the same *and* + // abstract so a bridge is not needed. Concrete + // subclasses will bridge as needed. + return false; + } + + // The erasure of the return type is always the same + // for the same symbol. So this is equivalent to the + // case above when impl != method. + return !isSameMemberWhenErased(dest, method, method.erasure(types)); + } + } + /** + * Lookup the method as a member of the type. Compare the + * erasures. + * @param type the class where to look for the method + * @param method the method to look for in class + * @param erasure the erasure of method + */ + private boolean isSameMemberWhenErased(Type type, + MethodSymbol method, + Type erasure) { + return types.isSameType(erasure(types.memberType(type, method)), + erasure); + } void addBridges(int pos, TypeSymbol i, ###@###.### 2005-03-26 05:42:57 GMT
24-03-2005

EVALUATION I suspect this is a case of a missing bridge method. ###@###.### 2005-03-24 20:26:50 GMT Notice that this is not a regression since covariant returns is a Tiger feature. ###@###.### 2005-03-24 20:28:28 GMT
24-03-2005