What on earth is “Self-suppression not permitted” and why is Javac generating code which results in this error?

StackOverflow https://stackoverflow.com/questions/12103126

Domanda

This new Java 7 try-with-resources construct is quite nice. Or at least, it was nice until an exception came along and ruined my day.

I've finally managed to boil it down to a reproducible test which uses nothing but JUnit+jMock.

@Test
public void testAddSuppressedIssue() throws Exception {
    Mockery mockery = new Mockery();
    final Dependency dependency = mockery.mock(Dependency.class);

    mockery.checking(new Expectations() {{
        allowing(dependency).expectedCall();
        allowing(dependency).close();
    }});

    try (DependencyUser user = new DependencyUser(dependency)) {
        user.doStuff();
    }
}

// A class we're testing.
private static class DependencyUser implements Closeable {
    private final Dependency dependency;

    private DependencyUser(Dependency dependency) {
        this.dependency = dependency;
    }

    public void doStuff() {
        dependency.unexpectedCall(); // bug
    }

    @Override
    public void close() throws IOException {
        dependency.close();
    }
}

// Interface for its dependent component.
private static interface Dependency extends Closeable {
    void expectedCall();
    void unexpectedCall();
}

Running this example, I get:

java.lang.IllegalArgumentException: Self-suppression not permitted
    at java.lang.Throwable.addSuppressed(Throwable.java:1042)
    at com.acme.Java7FeaturesTest.testTryWithResources(Java7FeaturesTest.java:35)

Reading the documentation, they seem to be saying that if you were to add a suppressed exception back to itself, that is what triggers this error. But I'm not doing that, I'm just using a try-with-resources block. The Java compiler then generates what would seem to be illegal code, which makes the feature effectively unusable.

Of course, when the test passes, no problem occurs. And when the test fails, an exception occurs. So now that I have fixed the problem I originally discovered I have reverted to using try-with-resources. But next time an exception occurs, I would much rather the exception be the expectation failure, instead of one Java itself has emitted for seemingly no good reason.

So... is there a way to get proper error reporting here, without giving up on try-with-resources?

È stato utile?

Soluzione

It looks like jMock throws the same instance of exception from the both methods. That's how it can be reproduced without jMock:

public class Test implements Closeable {
    private RuntimeException ex = new RuntimeException();

    public void doStuff() {
        throw ex;
    }

    public void close() {
        throw ex;
    }
}

try (Test t = new Test()) {
    t.doStuff();
}

If so, I think it's a problem of jMock rather than of Java compiler.

Altri suggerimenti

I had a problem in Apache Commons VFS (Unit Test failed on Java 8, see VFS-521). And it turns out that java.io.FilterOutputStream is using the try-with-resource (suppressed exception) feature in a way that it cannot deal with flush+close throwing the same exception.

And what is even worse, before Java 8 it just silently swallows exceptions from the flush() call, see JDK-6335274).

I fixed it, by avoiding super.close() at all. Currently discussing this on the corelibs-dev openjdk mailingl ist: http://openjdk.5641.n7.nabble.com/FilterOutputStream-close-throws-exception-from-flush-td187617.html

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top