From a9f5a6b1886992b35795fe5a3388bcaa53a1df5a Mon Sep 17 00:00:00 2001 From: Stephen Colebourne Date: Wed, 29 Mar 2006 22:07:53 +0000 Subject: [PATCH] Fix infinite loops leading to OutOfMemory with circular cause chain bug 37038 git-svn-id: https://svn.apache.org/repos/asf/jakarta/commons/proper/lang/trunk@389900 13f79535-47bb-0310-9956-ffa450edef68 --- .../lang/exception/ExceptionUtils.java | 65 ++++++--- .../exception/ExceptionUtilsTestCase.java | 133 ++++++++++++++++-- 2 files changed, 169 insertions(+), 29 deletions(-) diff --git a/src/java/org/apache/commons/lang/exception/ExceptionUtils.java b/src/java/org/apache/commons/lang/exception/ExceptionUtils.java index 1e576ba9b..9d36217f9 100644 --- a/src/java/org/apache/commons/lang/exception/ExceptionUtils.java +++ b/src/java/org/apache/commons/lang/exception/ExceptionUtils.java @@ -233,6 +233,7 @@ public static boolean isCauseMethodName(String methodName) { return ArrayUtils.indexOf(CAUSE_METHOD_NAMES, methodName) >= 0; } + //----------------------------------------------------------------------- /** *

Introspects the Throwable to obtain the cause.

* @@ -319,19 +320,19 @@ public static Throwable getCause(Throwable throwable, String[] methodNames) { * "root" of the tree, using {@link #getCause(Throwable)}, and * returns that exception.

* + *

From version 2.2, this method handles recursive cause structures + * that might otherwise cause infinite loops. If the throwable parameter + * has a cause of itself, then null will be returned. If the throwable + * parameter cause chain loops, the last element in the chain before the + * loop is returned.

+ * * @param throwable the throwable to get the root cause for, may be null * @return the root cause of the Throwable, * null if none found or null throwable input */ public static Throwable getRootCause(Throwable throwable) { - Throwable cause = getCause(throwable); - if (cause != null) { - throwable = cause; - while ((throwable = getCause(throwable)) != null) { - cause = throwable; - } - } - return cause; + List list = getThrowableList(throwable); + return (list.size() < 2 ? null : (Throwable)list.get(list.size() - 1)); } /** @@ -490,16 +491,16 @@ public static boolean isNestedThrowable(Throwable throwable) { * A throwable with one cause will return 2 and so on. * A null throwable will return 0.

* + *

From version 2.2, this method handles recursive cause structures + * that might otherwise cause infinite loops. The cause chain is + * processed until the end is reached, or until the next item in the + * chain is already in the result set.

+ * * @param throwable the throwable to inspect, may be null * @return the count of throwables, zero if null input */ public static int getThrowableCount(Throwable throwable) { - int count = 0; - while (throwable != null) { - count++; - throwable = ExceptionUtils.getCause(throwable); - } - return count; + return getThrowableList(throwable).size(); } /** @@ -510,18 +511,48 @@ public static int getThrowableCount(Throwable throwable) { * one element - the input throwable. * A throwable with one cause will return an array containing * two elements. - the input throwable and the cause throwable. - * A null throwable will return an array size zero.

+ * A null throwable will return an array of size zero.

* + *

From version 2.2, this method handles recursive cause structures + * that might otherwise cause infinite loops. The cause chain is + * processed until the end is reached, or until the next item in the + * chain is already in the result set.

+ * + * @see #getThrowableList(Throwable) * @param throwable the throwable to inspect, may be null * @return the array of throwables, never null */ public static Throwable[] getThrowables(Throwable throwable) { + List list = getThrowableList(throwable); + return (Throwable[]) list.toArray(new Throwable[list.size()]); + } + + /** + *

Returns the list of Throwable objects in the + * exception chain.

+ * + *

A throwable without cause will return a list containing + * one element - the input throwable. + * A throwable with one cause will return a list containing + * two elements. - the input throwable and the cause throwable. + * A null throwable will return a list of size zero.

+ * + *

This method handles recursive cause structures that might + * otherwise cause infinite loops. The cause chain is processed until + * the end is reached, or until the next item in the chain is already + * in the result set.

+ * + * @param throwable the throwable to inspect, may be null + * @return the list of throwables, never null + * @since Commons Lang 2.2 + */ + public static List getThrowableList(Throwable throwable) { List list = new ArrayList(); - while (throwable != null) { + while (throwable != null && list.contains(throwable) == false) { list.add(throwable); throwable = ExceptionUtils.getCause(throwable); } - return (Throwable[]) list.toArray(new Throwable[list.size()]); + return list; } //----------------------------------------------------------------------- diff --git a/src/test/org/apache/commons/lang/exception/ExceptionUtilsTestCase.java b/src/test/org/apache/commons/lang/exception/ExceptionUtilsTestCase.java index 3b1cf32ab..f1d6035a0 100644 --- a/src/test/org/apache/commons/lang/exception/ExceptionUtilsTestCase.java +++ b/src/test/org/apache/commons/lang/exception/ExceptionUtilsTestCase.java @@ -22,6 +22,7 @@ import java.io.StringWriter; import java.lang.reflect.InvocationTargetException; import java.sql.SQLException; +import java.util.List; import junit.framework.Assert; import junit.framework.Test; @@ -43,6 +44,9 @@ public class ExceptionUtilsTestCase extends junit.framework.TestCase { private NestableException nested; private Throwable withCause; private Throwable withoutCause; + private Throwable jdkNoCause; + private ExceptionWithCause selfCause; + private ExceptionWithCause recursiveCause; public ExceptionUtilsTestCase(String name) { super(name); @@ -56,6 +60,22 @@ public void setUp() { withoutCause = createExceptionWithoutCause(); nested = new NestableException(withoutCause); withCause = new ExceptionWithCause(nested); + jdkNoCause = new NullPointerException(); + selfCause = new ExceptionWithCause(null); + selfCause.setCause(selfCause); + ExceptionWithCause a = new ExceptionWithCause(null); + ExceptionWithCause b = new ExceptionWithCause(a); + a.setCause(b); + recursiveCause = new ExceptionWithCause(a); + } + + protected void tearDown() throws Exception { + withoutCause = null; + nested = null; + withCause = null; + jdkNoCause = null; + selfCause = null; + recursiveCause = null; } //----------------------------------------------------------------------- @@ -109,6 +129,11 @@ public void testGetCause_Throwable() { assertSame(null, ExceptionUtils.getCause(withoutCause)); assertSame(withoutCause, ExceptionUtils.getCause(nested)); assertSame(nested, ExceptionUtils.getCause(withCause)); + assertSame(null, ExceptionUtils.getCause(jdkNoCause)); + assertSame(selfCause, ExceptionUtils.getCause(selfCause)); + assertSame(recursiveCause.getCause(), ExceptionUtils.getCause(recursiveCause)); + assertSame(recursiveCause.getCause().getCause(), ExceptionUtils.getCause(recursiveCause.getCause())); + assertSame(recursiveCause.getCause(), ExceptionUtils.getCause(recursiveCause.getCause().getCause())); } public void testGetCause_ThrowableArray() { @@ -139,6 +164,9 @@ public void testGetRootCause_Throwable() { assertSame(null, ExceptionUtils.getRootCause(withoutCause)); assertSame(withoutCause, ExceptionUtils.getRootCause(nested)); assertSame(withoutCause, ExceptionUtils.getRootCause(withCause)); + assertSame(null, ExceptionUtils.getRootCause(jdkNoCause)); + assertSame(null, ExceptionUtils.getRootCause(selfCause)); + assertSame(recursiveCause.getCause().getCause(), ExceptionUtils.getRootCause(recursiveCause)); } public void testSetCause() { @@ -190,21 +218,102 @@ public void testGetThrowableCount_Throwable() { assertEquals(1, ExceptionUtils.getThrowableCount(withoutCause)); assertEquals(2, ExceptionUtils.getThrowableCount(nested)); assertEquals(3, ExceptionUtils.getThrowableCount(withCause)); + assertEquals(1, ExceptionUtils.getThrowableCount(jdkNoCause)); + assertEquals(1, ExceptionUtils.getThrowableCount(selfCause)); + assertEquals(3, ExceptionUtils.getThrowableCount(recursiveCause)); } - public void testGetThrowables_Throwable() { + //----------------------------------------------------------------------- + public void testGetThrowables_Throwable_null() { assertEquals(0, ExceptionUtils.getThrowables(null).length); - assertEquals(1, ExceptionUtils.getThrowables(withoutCause).length); - assertSame(withoutCause, ExceptionUtils.getThrowables(withoutCause)[0]); - - assertEquals(2, ExceptionUtils.getThrowables(nested).length); - assertSame(nested, ExceptionUtils.getThrowables(nested)[0]); - assertSame(withoutCause, ExceptionUtils.getThrowables(nested)[1]); - - assertEquals(3, ExceptionUtils.getThrowables(withCause).length); - assertSame(withCause, ExceptionUtils.getThrowables(withCause)[0]); - assertSame(nested, ExceptionUtils.getThrowables(withCause)[1]); - assertSame(withoutCause, ExceptionUtils.getThrowables(withCause)[2]); + } + + public void testGetThrowables_Throwable_withoutCause() { + Throwable[] throwables = ExceptionUtils.getThrowables(withoutCause); + assertEquals(1, throwables.length); + assertSame(withoutCause, throwables[0]); + } + + public void testGetThrowables_Throwable_nested() { + Throwable[] throwables = ExceptionUtils.getThrowables(nested); + assertEquals(2, throwables.length); + assertSame(nested, throwables[0]); + assertSame(withoutCause, throwables[1]); + } + + public void testGetThrowables_Throwable_withCause() { + Throwable[] throwables = ExceptionUtils.getThrowables(withCause); + assertEquals(3, throwables.length); + assertSame(withCause, throwables[0]); + assertSame(nested, throwables[1]); + assertSame(withoutCause, throwables[2]); + } + + public void testGetThrowables_Throwable_jdkNoCause() { + Throwable[] throwables = ExceptionUtils.getThrowables(jdkNoCause); + assertEquals(1, throwables.length); + assertSame(jdkNoCause, throwables[0]); + } + + public void testGetThrowables_Throwable_selfCause() { + Throwable[] throwables = ExceptionUtils.getThrowables(selfCause); + assertEquals(1, throwables.length); + assertSame(selfCause, throwables[0]); + } + + public void testGetThrowables_Throwable_recursiveCause() { + Throwable[] throwables = ExceptionUtils.getThrowables(recursiveCause); + assertEquals(3, throwables.length); + assertSame(recursiveCause, throwables[0]); + assertSame(recursiveCause.getCause(), throwables[1]); + assertSame(recursiveCause.getCause().getCause(), throwables[2]); + } + + //----------------------------------------------------------------------- + public void testGetThrowableList_Throwable_null() { + List throwables = ExceptionUtils.getThrowableList(null); + assertEquals(0, throwables.size()); + } + + public void testGetThrowableList_Throwable_withoutCause() { + List throwables = ExceptionUtils.getThrowableList(withoutCause); + assertEquals(1, throwables.size()); + assertSame(withoutCause, throwables.get(0)); + } + + public void testGetThrowableList_Throwable_nested() { + List throwables = ExceptionUtils.getThrowableList(nested); + assertEquals(2, throwables.size()); + assertSame(nested, throwables.get(0)); + assertSame(withoutCause, throwables.get(1)); + } + + public void testGetThrowableList_Throwable_withCause() { + List throwables = ExceptionUtils.getThrowableList(withCause); + assertEquals(3, throwables.size()); + assertSame(withCause, throwables.get(0)); + assertSame(nested, throwables.get(1)); + assertSame(withoutCause, throwables.get(2)); + } + + public void testGetThrowableList_Throwable_jdkNoCause() { + List throwables = ExceptionUtils.getThrowableList(jdkNoCause); + assertEquals(1, throwables.size()); + assertSame(jdkNoCause, throwables.get(0)); + } + + public void testGetThrowableList_Throwable_selfCause() { + List throwables = ExceptionUtils.getThrowableList(selfCause); + assertEquals(1, throwables.size()); + assertSame(selfCause, throwables.get(0)); + } + + public void testGetThrowableList_Throwable_recursiveCause() { + List throwables = ExceptionUtils.getThrowableList(recursiveCause); + assertEquals(3, throwables.size()); + assertSame(recursiveCause, throwables.get(0)); + assertSame(recursiveCause.getCause(), throwables.get(1)); + assertSame(recursiveCause.getCause().getCause(), throwables.get(2)); } //-----------------------------------------------------------------------