JDK-6531725 : Throwable.initCause(Throwable) does not prevent circular chaining
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.lang
  • Affected Version: 6
  • Priority: P5
  • Status: Closed
  • Resolution: Cannot Reproduce
  • OS: windows_xp
  • CPU: x86
  • Submitted: 2007-03-06
  • Updated: 2013-05-11
  • Resolved: 2013-05-11
Related Reports
Relates :  
Description
FULL PRODUCT VERSION :
java version "1.6.0"
Java (TM) SE Runtime Environment (build 1.6.0-b105)
Java HotSpot(TM) Client VM (build 1.6.0-b105, mixed mode, sharing)

ADDITIONAL OS VERSION INFORMATION :
Windows

A DESCRIPTION OF THE PROBLEM :
Throwable.initCause(Throwable) cannot prevent circular exception-chaining from occuring.

The specification for Throwable.initCause(Throwable) says that a Throwable can't be it's own cause. However the implementation only checks if the direct cause is itself, it does not perform a deep checking.

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
See the attached code to reproduce the problem. Just compile and run.

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The line "t2.initCause(t1);" should throw IllegalArgumentException.
ACTUAL -
printStackTrace loops forever.

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
/* This simple code reproduce the bug. It makes Throwable.printStackTrace() looping forever. */

public class ThrowableTrouble {
    public static void main(String[] args) {
        Throwable t1 = new Throwable();
        Throwable t2 = new Throwable();
        t1.initCause(t2);
        t2.initCause(t1);
        try {
          throw t1;
        } catch (Throwable t) {
          t.printStackTrace();
        }
    }
}
---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
The workaround is simple. The programmer should be careful to not do something erroneous like showed in the example code.

However, it is very simple and straight-forward to patch Throwable.initCause(Throwable):

    public synchronized Throwable initCause(Throwable cause) {
        Throwable deepCause = cause;
        if (this.cause != this)
            throw new IllegalStateException("Can't overwrite cause");
        while (deepCause != null) {
            if (deepCause == this)
                throw new IllegalArgumentException("Self-causation not permitted");
            deepCause = deepCause.getCause();
        }
        this.cause = cause;
        return this;
    }

Comments
As of consequence of the work on Throwable done in JDK 7, this situation is detected a a printout like java.lang.Throwable at ThrowableTrouble.main(ThrowableTrouble.java:3) Caused by: java.lang.Throwable at ThrowableTrouble.main(ThrowableTrouble.java:4) [CIRCULAR REFERENCE:java.lang.Throwable] occurs instead of a stack overflow. Closing as not reproducible.
11-05-2013