François Guillot created MNG-8066: ------------------------------------- Summary: Maven hangs on self-referencing exceptions Key: MNG-8066 URL: https://issues.apache.org/jira/browse/MNG-8066 Project: Maven Issue Type: Bug Reporter: François Guillot
If the code executed by Maven throws a self-referencing exception, such as {code:java} RuntimeException selfReferencingException = new RuntimeException("BOOM self"); selfReferencingException.initCause(new Exception("BOOM cause", selfReferencingException)); throw selfReferencingException; {code} For instance, if this code is added to an `AbstractExecutionListener`, which is added to the running build: {code:java} import org.apache.maven.execution.AbstractExecutionListener; import org.apache.maven.execution.ExecutionListener; import org.apache.maven.execution.ExecutionEvent; public class FailingExecutionListener extends AbstractExecutionListener { private final ExecutionListener delegate; public FailingExecutionListener(ExecutionListener delegate) { this.delegate = delegate; } @Override public void sessionStarted(ExecutionEvent event) { if (delegate != null) { delegate.sessionStarted(event); } RuntimeException selfReferencingException = new RuntimeException("BOOM self"); selfReferencingException.initCause(new Exception("BOOM cause", selfReferencingException)); throw selfReferencingException; } } {code} Maven hangs at the end of the build, in `DefaultExceptionHandler`. The code in `DefaultExceptionHandler#getMessage` iterates on a given throwable and its causes. It checks if the cause is not the same throwable, but doesn't protect against a 'two-level' recursion like shown above. Note that when printing a stacktrace, Java itself protects against this via the use of a {code:java} Set<Throwable> dejaVu = Collections.newSetFromMap(new IdentityHashMap<>()); {code} and stops the recursion if encountering an already seen throwable. A way to fix this would be to replace the offending cause with a replacement with no cause, such as in {code:java} private static Throwable patchCircularCause(Throwable current, Throwable parent) { try { Field causeField = Throwable.class.getDeclaredField("cause"); causeField.setAccessible(true); Throwable replacement = new Throwable("[CIRCULAR REFERENCE: " + current + "]"); replacement.setStackTrace(current.getStackTrace()); causeField.set(parent, replacement); return replacement; } catch (NoSuchFieldException | IllegalAccessException e) { // Couldn't replace the cause, let's return the actual exception. return current; } } {code} -- This message was sent by Atlassian Jira (v8.20.10#820010)