Details
-
Bug
-
Status: Closed
-
Major
-
Resolution: Fixed
-
None
-
None
Description
If the code executed by Maven throws a self-referencing exception, such as
RuntimeException selfReferencingException = new RuntimeException("BOOM self"); selfReferencingException.initCause(new Exception("BOOM cause", selfReferencingException)); throw selfReferencingException;
For instance, if this code is added to an `AbstractExecutionListener`, which is added to the running build:
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; } }
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
Set<Throwable> dejaVu = Collections.newSetFromMap(new IdentityHashMap<>());
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
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; } }
Attachments
Issue Links
- links to