Search code examples
javaexceptionlambdajava-8multi-catch

Java bug when combining lambdas and multi-catch clauses?


import java.io.*;             
import java.net.*;            

public class Test {           
    public static void main(String[] arguments) throws Exception {
        Runnable runnable = () -> {
            try {
                throwException();
            }
            catch (SocketException|EOFException exception) {
                System.err.println("wrong");
            }
            catch (IOException exception) {
                System.err.println("right");
            }
        };

        runnable.run();
    }                         

    private static void throwException() throws IOException {
        throw new NotSerializableException();
    }                         
}

Why does this program print "wrong"? If I remove the lambda, or if I break apart the multi-catch clause, then it prints "right".

$ javac -version
javac 1.8.0_11
$ java -version
java version "1.8.0_11"
Java(TM) SE Runtime Environment (build 1.8.0_11-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.11-b03, mixed mode)

Solution

  • This was a fixed bug in 1.8.0_20, from 1.8.0_11:

    Area: tools/javac
    Synopsis: javac generates incorrect exception table for multi-catch statements inside a lambda

    Handling of try-catch with multiple catches inside a lambda has been corrected.

    The actual bug report is JDK-8036942

    What actually went wrong was a supposed type information loss within the compiler:

    LTM makes a heavy use of erasure() during translations and mapping of variables. These erasure operations may be correct in most cases but it may lead to an information loss as this case shows. It's also arguably that such an intense use of erasure() is needed here as LTM is applied after TransTypes which is supposed to erase most / all types, so I wonder if this may be a bug in TransTypes. I think that it should be evaluated by Robert Field, which is current maintainer of LTM, what is the best approach here, I will thus reassigning[sic] it to him.

    What I see on 8u20 (I forgot to give a command-line parameter and no longer have 8u20 to do it correctly):

    wlan1-loopback% /usr/lib/jvm/java-8-oracle/bin/javap -c Test
    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: invokedynamic #2,  0              // InvokeDynamic #0:run:()Ljava/lang/Runnable;
           5: astore_1
           6: aload_1
           7: invokeinterface #3,  1            // InterfaceMethod java/lang/Runnable.run:()V
          12: return
    }
    wlan1-loopback% java Test 
    right
    wlan1-loopback% java -version
    java version "1.8.0_20"
    Java(TM) SE Runtime Environment (build 1.8.0_20-b26)
    Java HotSpot(TM) 64-Bit Server VM (build 25.20-b23, mixed mode)
    wlan1-loopback% 
    

    Correct:

    public class Test {
      public Test();
        descriptor: ()V
        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;
        descriptor: ([Ljava/lang/String;)V
        Code:
           0: invokedynamic #2,  0              // InvokeDynamic #0:run:()Ljava/lang/Runnable;
           5: astore_1
           6: aload_1
           7: invokeinterface #3,  1            // InterfaceMethod java/lang/Runnable.run:()V
          12: return
    
      private static void throwException() throws java.io.IOException;
        descriptor: ()V
        Code:
           0: new           #4                  // class java/io/NotSerializableException
           3: dup
           4: invokespecial #5                  // Method java/io/NotSerializableException."<init>":()V
           7: athrow
    
      private static void lambda$main$0();
        descriptor: ()V
        Code:
           0: invokestatic  #6                  // Method throwException:()V
           3: goto          27
           6: astore_0
           7: getstatic     #9                  // Field java/lang/System.err:Ljava/io/PrintStream;
          10: ldc           #10                 // String wrong
          12: invokevirtual #11                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
          15: goto          27
          18: astore_0
          19: getstatic     #9                  // Field java/lang/System.err:Ljava/io/PrintStream;
          22: ldc           #13                 // String right
          24: invokevirtual #11                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
          27: return
        Exception table:
           from    to  target type
               0     3     6   Class java/net/SocketException
               0     3     6   Class java/io/EOFException
               0     3    18   Class java/io/IOException
    }