Search code examples
javaeclipsesuppression

Exception self suppression error in try-with-resources not occuring from Eclipse


I have come across a troubling situation where I expect Java to complain (via an IllegalArgumentException from Throwable.addSuppressed) about throwing the same exception twice, once from within a try-with-resources block and once from the AutoCloseable class's close() routine. I have created a simple test case below which highlights the problem.

I am running JDK 1.7.0_65 with the following code:

public class TestDoubleThrow {
    public static void main(String[] args) {
        class TestA implements AutoCloseable {
            RuntimeException e;
            public TestA(RuntimeException e) { this.e = e; }
            @Override public void close() { throw e; }
        }

        RuntimeException e = new RuntimeException("My Exception");
        try (TestA A = new TestA(e)) {
            throw e;
        }
    }
}

When I compile and run the code above via the command line I get the expected result, an error indicating I have tried to self-suppress and exception:

[coreys terminal]$ java TestDoubleThrow.java ; java TestDoubleThrow
Exception in thread "main" java.lang.IllegalArgumentException: Self-suppression not permitted
    at java.lang.Throwable.addSuppressed(Throwable.java:1043)
    at TestDoubleThrow.main(TestDoubleThrow.java:12)
Caused by: java.lang.RuntimeException: My Exception
    at TestDoubleThrow.main(TestDoubleThrow.java:9)

However, when I build and run the same code from Eclipse I do not get the same result, I get the following:

Exception in thread "main" java.lang.RuntimeException: My Exception
    at TestDoubleThrow.main(TestDoubleThrow.java:9)

I removed the .class path after building from the command line to ensure that Eclipse rebuilt it. Running from a debugger I noticed that from Eclipse the code never enters java.lang.Throwable.addSuppressed().

Interestingly, if I build the class from Eclipse, then run it from the command line I DO NOT SEE the self suppression error. Similarly if I build the class from the command line and run it from Eclipse (without building from Eclipse) then I DO SEE the error. This suggests that it is something funny about how eclipse is building the class.

I would like to know how to ensure Eclipse can build the class in such a way that I do get the error, because for my purposes it is an error and I want to be able to detect it when I build and run from Eclipse.


Solution

  • Eclipse has it's own compiler and is producing different output. Code compiled by Eclipse checks to see if a suppressed exception equals itself before invoking Throwable.addSuppressed. You can see this using the javap tool.

    See the if_acmpeq line in the Eclipse compiler output.

    The JDK behaviour more closely adheres to the example in the specification. You could raise a defect with the Eclipse team.

    JDK/javac output:

      public static void main(java.lang.String[]);
        Code:
           0: new           #2                  // class java/lang/RuntimeException
           3: dup           
           4: ldc           #3                  // String My Exception
           6: invokespecial #4                  // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
           9: astore_1      
          10: new           #5                  // class TestDoubleThrow$1TestA
          13: dup           
          14: aload_1       
          15: invokespecial #6                  // Method TestDoubleThrow$1TestA."<init>":(Ljava/lang/RuntimeException;)V
          18: astore_2      
          19: aconst_null   
          20: astore_3      
          21: aload_1       
          22: athrow        
          23: astore        4
          25: aload         4
          27: astore_3      
          28: aload         4
          30: athrow        
          31: astore        5
          33: aload_2       
          34: ifnull        63
          37: aload_3       
          38: ifnull        59
          41: aload_2       
          42: invokevirtual #8                  // Method TestDoubleThrow$1TestA.close:()V
          45: goto          63
          48: astore        6
          50: aload_3       
          51: aload         6
          53: invokevirtual #9                  // Method java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
          56: goto          63
          59: aload_2       
          60: invokevirtual #8                  // Method TestDoubleThrow$1TestA.close:()V
          63: aload         5
          65: athrow        
        Exception table:
           from    to  target type
              21    23    23   Class java/lang/Throwable
              41    45    48   Class java/lang/Throwable
              21    33    31   any
    

    Eclipse output:

      public static void main(java.lang.String[]);
        Code:
           0: new           #16                 // class java/lang/RuntimeException
           3: dup           
           4: ldc           #18                 // String My Exception
           6: invokespecial #20                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
           9: astore_1      
          10: aconst_null   
          11: astore_2      
          12: aconst_null   
          13: astore_3      
          14: new           #23                 // class TestDoubleThrow$1TestA
          17: dup           
          18: aload_1       
          19: invokespecial #25                 // Method TestDoubleThrow$1TestA."<init>":(Ljava/lang/RuntimeException;)V
          22: astore        4
          24: aload_1       
          25: athrow        
          26: astore_2      
          27: aload         4
          29: ifnull        37
          32: aload         4
          34: invokevirtual #28                 // Method TestDoubleThrow$1TestA.close:()V
          37: aload_2       
          38: athrow        
          39: astore_3      
          40: aload_2       
          41: ifnonnull     49
          44: aload_3       
          45: astore_2      
          46: goto          59
          49: aload_2       
          50: aload_3       
          51: if_acmpeq     59
          54: aload_2       
          55: aload_3       
          56: invokevirtual #31                 // Method java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
          59: aload_2       
          60: athrow        
        Exception table:
           from    to  target type
              24    26    26   any
              14    39    39   any
    

    I used JDK 8 and Eclipse 4.4.