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:
parent
62a97240a5
commit
a9f5a6b188
|
@ -233,6 +233,7 @@ public class ExceptionUtils {
|
|||
return ArrayUtils.indexOf(CAUSE_METHOD_NAMES, methodName) >= 0;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* <p>Introspects the <code>Throwable</code> to obtain the cause.</p>
|
||||
*
|
||||
|
@ -319,19 +320,19 @@ public class ExceptionUtils {
|
|||
* "root" of the tree, using {@link #getCause(Throwable)}, and
|
||||
* 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
|
||||
* @return the root cause of the <code>Throwable</code>,
|
||||
* <code>null</code> 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 class ExceptionUtils {
|
|||
* A throwable with one cause will return <code>2</code> and so on.
|
||||
* 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
|
||||
* @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 class ExceptionUtils {
|
|||
* 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 <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
|
||||
* @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()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* <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();
|
||||
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;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
|
|
|
@ -22,6 +22,7 @@ import java.io.PrintWriter;
|
|||
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 class ExceptionUtilsTestCase extends junit.framework.TestCase {
|
|||
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 class ExceptionUtilsTestCase extends junit.framework.TestCase {
|
|||
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 class ExceptionUtilsTestCase extends junit.framework.TestCase {
|
|||
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 class ExceptionUtilsTestCase extends junit.framework.TestCase {
|
|||
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));
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
|
|
Loading…
Reference in New Issue