¿Qué demonios es "No se permite la autosupresión" y por qué Javac genera un código que da como resultado este error?

Esta nueva construcción de prueba con recursos de Java 7 es bastante agradable. O al menos, lo fue agradable hasta que llegó una excepción y arruinó mi día.

Finalmente logré reducirlo a una prueba reproducible que usa nada más que 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();
}

Ejecutando este ejemplo, obtengo:

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

Al leer la documentación, parecen estar diciendo que si tuviera que volver a agregar una excepción suprimida, eso es lo que desencadena este error. Pero estoy no hacer eso, estoy simplemente usando un bloque de prueba con recursos. los Compilador de Java luego genera lo que parece ser un código ilegal, lo que hace que la función sea efectivamente inutilizable.

Por supuesto, cuando pasa la prueba, no ocurre ningún problema. Y cuando la prueba falla, ocurre una excepción. Entonces, ahora que he solucionado el problema que descubrí originalmente, he vuelto a usar probar con recursos. Pero la próxima vez que ocurra una excepción, preferiría que la excepción fuera la falla de la expectativa, en lugar de una que Java mismo haya emitido aparentemente sin una buena razón.

Entonces... ¿hay alguna manera de obtener un informe de error adecuado aquí, sin renunciar a probar con los recursos?

preguntado el 24 de agosto de 12 a las 05:08

¿Comprobaste si dependency.close() arroja algo? -

¿Se puede reproducir esto? sin jMock? -

Mi primera reacción sería culpar a jMock en lugar del compilador de Java. -

El ejemplo en la respuesta a continuación lo reproduce sin jMock. -

Por lo que vale, creo que hay una buena posibilidad de que esto se mejore pronto: abrí un hilo de discusión que conducir a un parche propuesto. Solo mejora los diagnósticos, el problema subyacente aún deberá solucionarse. El parche aún no está fusionado, pero eventualmente se filtrará. -

2 Respuestas

Parece que jMock arroja la misma instancia de excepción de ambos métodos. Así es como se puede reproducir sin 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();
}

Si es así, creo que es un problema de jMock y no del compilador de Java.

Respondido 24 ago 12, 06:08

Sí, esto es exactamente. La expectativa jMock falla cuando unexpectedCall() se invoca en el objeto simulado, y Mockery.dispatch() vuelve a lanzar la misma instancia de excepción en llamadas posteriores como la que se hace para close(). - Tim Stone

Pude reproducirlo de esta manera también. Pero no pude encontrar el bit en el JLS donde dice que era ilegal reutilizar instancias de excepción. Supongo que este es el caso? - Hakanai

stackoverflow.com/questions/11473263 sugiere que compartir instancias de excepción está bien. - Hakanai

@Trejkaz No hay nada de malo en reutilizar la instancia en general, simplemente no puede pasar una autorreferencia a addSuppressed(). Porque la excepción en close() no porque la excepción dentro del bloque try-with-resources, el close() en cambio, la excepción se agrega a la lista de supresión de la excepción de unexpectedCall(). Debido a la reutilización aquí, esto hace que la excepción intente suprimirse, lo que no está permitido según la descripción del método. - Tim Stone

Derecha. Entonces, este es un problema del compilador de Java, ya que no verifica si es la misma instancia antes de agregarla como la excepción suprimida. :( - Hakanai

Tuve un problema en Apache Commons VFS (La prueba unitaria falló en Java 8, consulte VFS 521). Y resulta que java.io.FilterOutputStream está utilizando la función de prueba con recursos (excepción suprimida) en cierto sentido que no puede lidiar con flush+close lanzando la misma excepción.

Y lo que es aún peor, antes de Java 8, se traga silenciosamente las excepciones del flush() llamar, ver JDK-6335274).

Lo arreglé evitando super.close() en absoluto. Actualmente discutiendo esto en la lista de correo corelibs-dev openjdk: http://openjdk.5641.n7.nabble.com/FilterOutputStream-close-throws-exception-from-flush-td187617.html

contestado el 03 de mayo de 14 a las 02:05

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.