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
This commit is contained in:
Stephen Colebourne 2006-03-29 22:07:53 +00:00
parent 62a97240a5
commit a9f5a6b188
2 changed files with 169 additions and 29 deletions

View File

@ -233,6 +233,7 @@ public static boolean isCauseMethodName(String methodName) {
return ArrayUtils.indexOf(CAUSE_METHOD_NAMES, methodName) >= 0; return ArrayUtils.indexOf(CAUSE_METHOD_NAMES, methodName) >= 0;
} }
//-----------------------------------------------------------------------
/** /**
* <p>Introspects the <code>Throwable</code> to obtain the cause.</p> * <p>Introspects the <code>Throwable</code> to obtain the cause.</p>
* *
@ -319,19 +320,19 @@ public static Throwable getCause(Throwable throwable, String[] methodNames) {
* "root" of the tree, using {@link #getCause(Throwable)}, and * "root" of the tree, using {@link #getCause(Throwable)}, and
* returns that exception.</p> * returns that exception.</p>
* *
* <p>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.</p>
*
* @param throwable the throwable to get the root cause for, may be null * @param throwable the throwable to get the root cause for, may be null
* @return the root cause of the <code>Throwable</code>, * @return the root cause of the <code>Throwable</code>,
* <code>null</code> if none found or null throwable input * <code>null</code> if none found or null throwable input
*/ */
public static Throwable getRootCause(Throwable throwable) { public static Throwable getRootCause(Throwable throwable) {
Throwable cause = getCause(throwable); List list = getThrowableList(throwable);
if (cause != null) { return (list.size() < 2 ? null : (Throwable)list.get(list.size() - 1));
throwable = cause;
while ((throwable = getCause(throwable)) != null) {
cause = throwable;
}
}
return cause;
} }
/** /**
@ -490,16 +491,16 @@ public static boolean isNestedThrowable(Throwable throwable) {
* A throwable with one cause will return <code>2</code> and so on. * A throwable with one cause will return <code>2</code> and so on.
* A <code>null</code> throwable will return <code>0</code>.</p> * A <code>null</code> throwable will return <code>0</code>.</p>
* *
* <p>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.</p>
*
* @param throwable the throwable to inspect, may be null * @param throwable the throwable to inspect, may be null
* @return the count of throwables, zero if null input * @return the count of throwables, zero if null input
*/ */
public static int getThrowableCount(Throwable throwable) { public static int getThrowableCount(Throwable throwable) {
int count = 0; return getThrowableList(throwable).size();
while (throwable != null) {
count++;
throwable = ExceptionUtils.getCause(throwable);
}
return count;
} }
/** /**
@ -510,18 +511,48 @@ public static int getThrowableCount(Throwable throwable) {
* one element - the input throwable. * one element - the input throwable.
* A throwable with one cause will return an array containing * A throwable with one cause will return an array containing
* two elements. - the input throwable and the cause throwable. * two elements. - the input throwable and the cause throwable.
* A <code>null</code> throwable will return an array size zero.</p> * A <code>null</code> throwable will return an array of size zero.</p>
* *
* <p>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.</p>
*
* @see #getThrowableList(Throwable)
* @param throwable the throwable to inspect, may be null * @param throwable the throwable to inspect, may be null
* @return the array of throwables, never null * @return the array of throwables, never null
*/ */
public static Throwable[] getThrowables(Throwable throwable) { public static Throwable[] getThrowables(Throwable throwable) {
List list = getThrowableList(throwable);
return (Throwable[]) list.toArray(new Throwable[list.size()]);
}
/**
* <p>Returns the list of <code>Throwable</code> objects in the
* exception chain.</p>
*
* <p>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 <code>null</code> throwable will return a list of size zero.</p>
*
* <p>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.</p>
*
* @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(); List list = new ArrayList();
while (throwable != null) { while (throwable != null && list.contains(throwable) == false) {
list.add(throwable); list.add(throwable);
throwable = ExceptionUtils.getCause(throwable); throwable = ExceptionUtils.getCause(throwable);
} }
return (Throwable[]) list.toArray(new Throwable[list.size()]); return list;
} }
//----------------------------------------------------------------------- //-----------------------------------------------------------------------

View File

@ -22,6 +22,7 @@
import java.io.StringWriter; import java.io.StringWriter;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.List;
import junit.framework.Assert; import junit.framework.Assert;
import junit.framework.Test; import junit.framework.Test;
@ -43,6 +44,9 @@ public class ExceptionUtilsTestCase extends junit.framework.TestCase {
private NestableException nested; private NestableException nested;
private Throwable withCause; private Throwable withCause;
private Throwable withoutCause; private Throwable withoutCause;
private Throwable jdkNoCause;
private ExceptionWithCause selfCause;
private ExceptionWithCause recursiveCause;
public ExceptionUtilsTestCase(String name) { public ExceptionUtilsTestCase(String name) {
super(name); super(name);
@ -56,6 +60,22 @@ public void setUp() {
withoutCause = createExceptionWithoutCause(); withoutCause = createExceptionWithoutCause();
nested = new NestableException(withoutCause); nested = new NestableException(withoutCause);
withCause = new ExceptionWithCause(nested); 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(null, ExceptionUtils.getCause(withoutCause));
assertSame(withoutCause, ExceptionUtils.getCause(nested)); assertSame(withoutCause, ExceptionUtils.getCause(nested));
assertSame(nested, ExceptionUtils.getCause(withCause)); 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() { public void testGetCause_ThrowableArray() {
@ -139,6 +164,9 @@ public void testGetRootCause_Throwable() {
assertSame(null, ExceptionUtils.getRootCause(withoutCause)); assertSame(null, ExceptionUtils.getRootCause(withoutCause));
assertSame(withoutCause, ExceptionUtils.getRootCause(nested)); assertSame(withoutCause, ExceptionUtils.getRootCause(nested));
assertSame(withoutCause, ExceptionUtils.getRootCause(withCause)); 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() { public void testSetCause() {
@ -190,21 +218,102 @@ public void testGetThrowableCount_Throwable() {
assertEquals(1, ExceptionUtils.getThrowableCount(withoutCause)); assertEquals(1, ExceptionUtils.getThrowableCount(withoutCause));
assertEquals(2, ExceptionUtils.getThrowableCount(nested)); assertEquals(2, ExceptionUtils.getThrowableCount(nested));
assertEquals(3, ExceptionUtils.getThrowableCount(withCause)); 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(0, ExceptionUtils.getThrowables(null).length);
assertEquals(1, ExceptionUtils.getThrowables(withoutCause).length); }
assertSame(withoutCause, ExceptionUtils.getThrowables(withoutCause)[0]);
public void testGetThrowables_Throwable_withoutCause() {
assertEquals(2, ExceptionUtils.getThrowables(nested).length); Throwable[] throwables = ExceptionUtils.getThrowables(withoutCause);
assertSame(nested, ExceptionUtils.getThrowables(nested)[0]); assertEquals(1, throwables.length);
assertSame(withoutCause, ExceptionUtils.getThrowables(nested)[1]); assertSame(withoutCause, throwables[0]);
}
assertEquals(3, ExceptionUtils.getThrowables(withCause).length);
assertSame(withCause, ExceptionUtils.getThrowables(withCause)[0]); public void testGetThrowables_Throwable_nested() {
assertSame(nested, ExceptionUtils.getThrowables(withCause)[1]); Throwable[] throwables = ExceptionUtils.getThrowables(nested);
assertSame(withoutCause, ExceptionUtils.getThrowables(withCause)[2]); 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));
} }
//----------------------------------------------------------------------- //-----------------------------------------------------------------------