Author: scolebourne
Date: Wed Mar 29 14:07:53 2006
New Revision: 389900

URL: http://svn.apache.org/viewcvs?rev=389900&view=rev
Log:
Fix infinite loops leading to OutOfMemory with circular cause chain
bug 37038

Modified:
    
jakarta/commons/proper/lang/trunk/src/java/org/apache/commons/lang/exception/ExceptionUtils.java
    
jakarta/commons/proper/lang/trunk/src/test/org/apache/commons/lang/exception/ExceptionUtilsTestCase.java

Modified: 
jakarta/commons/proper/lang/trunk/src/java/org/apache/commons/lang/exception/ExceptionUtils.java
URL: 
http://svn.apache.org/viewcvs/jakarta/commons/proper/lang/trunk/src/java/org/apache/commons/lang/exception/ExceptionUtils.java?rev=389900&r1=389899&r2=389900&view=diff
==============================================================================
--- 
jakarta/commons/proper/lang/trunk/src/java/org/apache/commons/lang/exception/ExceptionUtils.java
 (original)
+++ 
jakarta/commons/proper/lang/trunk/src/java/org/apache/commons/lang/exception/ExceptionUtils.java
 Wed Mar 29 14:07:53 2006
@@ -233,6 +233,7 @@
         return ArrayUtils.indexOf(CAUSE_METHOD_NAMES, methodName) >= 0;
     }
 
+    //-----------------------------------------------------------------------
     /**
      * <p>Introspects the <code>Throwable</code> to obtain the cause.</p>
      *
@@ -319,19 +320,19 @@
      * "root" of the tree, using [EMAIL PROTECTED] #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 @@
      * 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 @@
      * 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;
     }
 
     //-----------------------------------------------------------------------

Modified: 
jakarta/commons/proper/lang/trunk/src/test/org/apache/commons/lang/exception/ExceptionUtilsTestCase.java
URL: 
http://svn.apache.org/viewcvs/jakarta/commons/proper/lang/trunk/src/test/org/apache/commons/lang/exception/ExceptionUtilsTestCase.java?rev=389900&r1=389899&r2=389900&view=diff
==============================================================================
--- 
jakarta/commons/proper/lang/trunk/src/test/org/apache/commons/lang/exception/ExceptionUtilsTestCase.java
 (original)
+++ 
jakarta/commons/proper/lang/trunk/src/test/org/apache/commons/lang/exception/ExceptionUtilsTestCase.java
 Wed Mar 29 14:07:53 2006
@@ -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 @@
     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 @@
         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 @@
         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 @@
         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 @@
         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));
     }
 
     //-----------------------------------------------------------------------



---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]

Reply via email to