Java 8 javac wrong return type for a generic method

Maurizio Cimadamore maurizio.cimadamore at oracle.com
Thu Nov 13 13:26:11 UTC 2014


This is a javac bug - when multiple applicable abstract methods are 
found during overload, javac is supposed to return the one with the most 
specific return type; unfortunately, when javac merges the two 
signatures, it uses the 'instantiated' return type (String) instead of 
the bare one (T). This leads to the missing checkcast.

I filed a new issue:

https://bugs.openjdk.java.net/browse/JDK-8064803

Thanks for the detailed report.

Maurizio

On 13/11/14 12:59, Mehmet Dogan wrote:
> Hi,
>
> I faced a weird compiler issue (or at least I looked weird to me) 
> while compiling & running a unit test on Java8. It was running on Java 
> 6 & 7 without a problem. Then I created a very basic sample to 
> reproduce it locally.
>
> Test includes 3 interfaces, 1 implementation and 1 test class.
>
> /public interface ParentA<T> {/
> /    T process() throws Exception;/
> /}/
> /
> /
> /public interface ParentB<T> {/
> /    T process() throws Exception;/
> /}/
>
> /public interface Child<T> extends ParentA<T>, ParentB<T> {/
> /* // *//*everything works fine *//*when this line is uncommented */
> /   // T process() throws Exception;/
> /}/
> /
> /
> /public class ChildImpl<T> implements Child<T> {/
> /    @Override/
> /    public T process() {/
> /        return null;/
> /    }/
> /}/
> /
> /
> /public class Test {/
> /    public static void main(String[] args) throws Exception {/
> /        Child<String> child = new ChildImpl<String>();/
> /        String result = child.process();/
> /        System.err.println(result);/
> /    }/
> /}/
>
>
> 1 - When I compile these with javac 8 and run the main method in Test 
> class, it fails with the following exception:
>
> /Exception in thread "main" java.lang.NoSuchMethodError: 
> Child.process()Ljava/lang/String;/
> /at Test.main(Test.java:5)/
>
>
> 2 - When I uncomment overriding method definition in `/Child/` 
> interface, then it works fine.
> /
> /
> /public interface Child<T> extends ParentA<T>, ParentB<T> {/
> /   T process() throws Exception;/
> /}/
>
> 3 - When I remove `/throws Exception/` declaration from `/process/` 
> method, then it works fine again.
>
>
> Then I looked at the disassembled bytecode of the Test.class and saw 
> in Java 6 and 7 versions there is an additional `checkcast` 
> instruction after `Child.process()` method call. See;
>
> - *With Java8:*
>
> /public class Test {/
> /  public Test();/
> /    Code:/
> /       0: aload_0/
> /       1: invokespecial #1                  // Method 
> java/lang/Object."<init>":()V/
> /       4: return/
> /
> /
> /  public static void main(java.lang.String[]) throws 
> java.lang.Exception;/
> /    Code:/
> /       0: new           #2                  // class ChildImpl/
> /       3: dup/
> /       4: invokespecial #3                  // Method 
> ChildImpl."<init>":()V/
> /       7: astore_1/
> /       8: aload_1/
> /*       9: invokeinterface #4,  1            // InterfaceMethod 
> Child.process:()Ljava/lang/String;*/
> /      14: astore_2/
> /      15: getstatic     #5                  // Field 
> java/lang/System.err:Ljava/io/PrintStream;/
> /      18: aload_2/
> /      19: invokevirtual #6                  // Method 
> java/io/PrintStream.println:(Ljava/lang/String;)V/
> /      22: return/
> /}/
>
> *- With Java7:*
> /
> /
> /Compiled from "Test.java"/
> /public class Test {/
> /  public Test();/
> /    Code:/
> /       0: aload_0/
> /       1: invokespecial #1                  // Method 
> java/lang/Object."<init>":()V/
> /       4: return/
> /
> /
> /  public static void main(java.lang.String[]) throws 
> java.lang.Exception;/
> /    Code:/
> /       0: new           #2                  // class ChildImpl/
> /       3: dup/
> /       4: invokespecial #3                  // Method 
> ChildImpl."<init>":()V/
> /       7: astore_1/
> /       8: aload_1/
> /*       9: invokeinterface #4,  1            // InterfaceMethod 
> Child.process:()Ljava/lang/Object;*/
> /*      14: checkcast     #5                  // class java/lang/String*/
> /      17: astore_2/
> /      18: getstatic     #6                  // Field 
> java/lang/System.err:Ljava/io/PrintStream;/
> /      21: aload_2/
> /      22: invokevirtual #7                  // Method 
> java/io/PrintStream.println:(Ljava/lang/String;)V/
> /      25: return/
> /}/
>
>
> Javac 8 is expecting to call a method with return type of String 
> (/InterfaceMethod Child.process:()Ljava/lang/String)/, but on Java 7 
> version is expecting a method with return type of Object 
> (/InterfaceMethod Child.process:()Ljava/lang/Object/) which is 
> expected because of type-erasure then it's checking type of returning 
> value with `checkcast` instruction.
>
>
> Test environment:
>
> Centos 6.6 & OSX 10.10
>
> javac 1.8.0_25
> openjdk version "1.8.0_25"
> OpenJDK Runtime Environment (build 1.8.0_25-b17)
> OpenJDK 64-Bit Server VM (build 25.20-b23, mixed mode)
>
> javac 1.7.0_71
> java version "1.7.0_71"
> OpenJDK Runtime Environment (rhel-2.5.3.1.el6-x86_64 u71-b14)
> OpenJDK 64-Bit Server VM (build 24.65-b04, mixed mode)
>
> Is this an expected situation, is there something I'm 
> understanding/using wrong? Or is this a bug/problem in Java 8 compiler?
>
> Thanks.
>
> *Mehmet Dogan
> *mehmet at hazelcast.com <mailto:mehmet at hazelcast.com>
> @mmdogan

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.openjdk.java.net/pipermail/compiler-dev/attachments/20141113/f465df4a/attachment.html>


More information about the compiler-dev mailing list