[MNG-8066] Default exception handler does not handle recursion (#1558)

If there is a recursion in throwable causes, Maven will hang forever, instead to return.

---

https://issues.apache.org/jira/browse/MNG-8066
This commit is contained in:
Tamas Cservenak 2024-06-06 12:22:06 +02:00 committed by GitHub
parent df00ea5c10
commit 865072025d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 34 additions and 6 deletions

View File

@ -22,7 +22,10 @@ import java.io.IOException;
import java.net.ConnectException; import java.net.ConnectException;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.List; import java.util.List;
import java.util.Set;
import org.apache.maven.lifecycle.LifecycleExecutionException; import org.apache.maven.lifecycle.LifecycleExecutionException;
import org.apache.maven.model.building.ModelProblem; import org.apache.maven.model.building.ModelProblem;
@ -87,13 +90,13 @@ Plugins:
*/ */
@Component(role = ExceptionHandler.class) @Component(role = ExceptionHandler.class)
public class DefaultExceptionHandler implements ExceptionHandler { public class DefaultExceptionHandler implements ExceptionHandler {
@Override
public ExceptionSummary handleException(Throwable exception) { public ExceptionSummary handleException(Throwable exception) {
return handle("", exception); return handle("", exception);
} }
private ExceptionSummary handle(String message, Throwable exception) { private ExceptionSummary handle(String message, Throwable exception) {
String reference = getReference(exception); String reference = getReference(Collections.newSetFromMap(new IdentityHashMap<>()), exception);
List<ExceptionSummary> children = null; List<ExceptionSummary> children = null;
@ -153,8 +156,11 @@ public class DefaultExceptionHandler implements ExceptionHandler {
} }
} }
private String getReference(Throwable exception) { private String getReference(Set<Throwable> dejaVu, Throwable exception) {
String reference = ""; String reference = "";
if (!dejaVu.add(exception)) {
return reference;
}
if (exception != null) { if (exception != null) {
if (exception instanceof MojoExecutionException) { if (exception instanceof MojoExecutionException) {
@ -186,14 +192,14 @@ public class DefaultExceptionHandler implements ExceptionHandler {
} }
if (StringUtils.isEmpty(reference)) { if (StringUtils.isEmpty(reference)) {
reference = getReference(cause); reference = getReference(dejaVu, cause);
} }
if (StringUtils.isEmpty(reference)) { if (StringUtils.isEmpty(reference)) {
reference = exception.getClass().getSimpleName(); reference = exception.getClass().getSimpleName();
} }
} else if (exception instanceof LifecycleExecutionException) { } else if (exception instanceof LifecycleExecutionException) {
reference = getReference(exception.getCause()); reference = getReference(dejaVu, exception.getCause());
} else if (isNoteworthyException(exception)) { } else if (isNoteworthyException(exception)) {
reference = exception.getClass().getSimpleName(); reference = exception.getClass().getSimpleName();
} }
@ -222,7 +228,8 @@ public class DefaultExceptionHandler implements ExceptionHandler {
private String getMessage(String message, Throwable exception) { private String getMessage(String message, Throwable exception) {
String fullMessage = (message != null) ? message : ""; String fullMessage = (message != null) ? message : "";
// To break out of possible endless loop when getCause returns "this" // To break out of possible endless loop when getCause returns "this", or dejaVu for n-level recursion (n>1)
Set<Throwable> dejaVu = Collections.newSetFromMap(new IdentityHashMap<>());
for (Throwable t = exception; t != null && t != t.getCause(); t = t.getCause()) { for (Throwable t = exception; t != null && t != t.getCause(); t = t.getCause()) {
String exceptionMessage = t.getMessage(); String exceptionMessage = t.getMessage();
@ -246,6 +253,11 @@ public class DefaultExceptionHandler implements ExceptionHandler {
} else if (!fullMessage.contains(exceptionMessage)) { } else if (!fullMessage.contains(exceptionMessage)) {
fullMessage = join(fullMessage, exceptionMessage); fullMessage = join(fullMessage, exceptionMessage);
} }
if (!dejaVu.add(t)) {
fullMessage = join(fullMessage, "[CIRCULAR REFERENCE]");
break;
}
} }
return fullMessage.trim(); return fullMessage.trim();

View File

@ -124,4 +124,20 @@ public class DefaultExceptionHandlerTest {
String expectedReference = "http://cwiki.apache.org/confluence/display/MAVEN/PluginContainerException"; String expectedReference = "http://cwiki.apache.org/confluence/display/MAVEN/PluginContainerException";
assertEquals(expectedReference, summary.getReference()); assertEquals(expectedReference, summary.getReference());
} }
@Test
public void testHandleExceptionSelfReferencing() {
RuntimeException boom3 = new RuntimeException("BOOM3");
RuntimeException boom2 = new RuntimeException("BOOM2", boom3);
RuntimeException boom1 = new RuntimeException("BOOM1", boom2);
boom3.initCause(boom1);
DefaultExceptionHandler handler = new DefaultExceptionHandler();
ExceptionSummary summary = handler.handleException(boom1);
assertEquals("BOOM1: BOOM2: BOOM3: [CIRCULAR REFERENCE]", summary.getMessage());
assertEquals("", summary.getReference());
assertEquals(0, summary.getChildren().size());
assertEquals(boom1, summary.getException());
}
} }