Throwable#addSuppressed is used for combining exceptions when two
independent exceptions can be thrown from the same code block. Typical case
is try-with-resources where one exception is from try block, and another
from closing resource.
#addSuppressed internally verifies that suppressed exception and current
exception are not the same. That is a reasonable condition to check, but it
doesn't seem to work well with fast throw optimization when exceptions are
being replaced with the same class instance, and that condition starts to
fail.
Consider the example below: it finishes successfully with
-XX:-OmitStackTraceInFastThrow flag, and fails with
IllegalArgumentException "Self-suppression not permitted" if optimization
is enabled.
public class Test {
static class SomeCloseable implements AutoCloseable {
@Override
public void close() {
throwsNPE();
}
}
static void throwsNPE() {
((Object) null).getClass();
}
//run with and without -XX:-OmitStackTraceInFastThrow
public static void main(String[] args) {
for (int i = 0; i < 100_000; i++) {
try (SomeCloseable c = new SomeCloseable()) {
throwsNPE();
} catch (NullPointerException expectedException) {
assert expectedException.getSuppressed().length == 1;
}
}
}
}
Could self match be no-op instead of exception in Throwable#addSuppressed?
See: https://mail.openjdk.java.net/pipermail/jdk-dev/2022-May/006718.html