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)

Reply via email to