JDK-6677785 : REGRESSION: StackOverFlowError with Cyclic Class level Type Parameters when used in constructors
  • Type: Bug
  • Component: tools
  • Sub-Component: javac
  • Affected Version: 7
  • Priority: P3
  • Status: Closed
  • Resolution: Fixed
  • OS: generic
  • CPU: generic
  • Submitted: 2008-03-20
  • Updated: 2011-05-18
  • Resolved: 2011-05-18
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 7
7 b29Fixed
Related Reports
Relates :  
Relates :  
Relates :  
Description
Description:
Compiler crashes with StackOverFlowError for the following code. This is a *REGRESSION*. Compiler throws  "cyclic inheritance involving S" in JDK 7 b24 error as it should be, but goes into infinite loop with PIT b26. Strangely, if i comment the second constructor it compiles fine.

<code>
bash-3.00$ cat CyclicTypeParameters.java
class CyclicTypeParameters<S extends T, T extends S>{
        CyclicTypeParameters(S s){
        }
        CyclicTypeParameters(){// Works fine on commenting this
        }
}
</code>
Compilation result is :
<output>
The system is out of resources.
Consult the following stack trace for details.
java.lang.StackOverflowError
        at com.sun.tools.javac.code.Type$TypeVar.accept(Type.java:952)
        at com.sun.tools.javac.code.Types$UnaryVisitor.visit(Types.java:3240)
        at com.sun.tools.javac.code.Types.erasure(Types.java:1502)
        at com.sun.tools.javac.code.Types$16.visitTypeVar(Types.java:1525)
        at com.sun.tools.javac.code.Types$16.visitTypeVar(Types.java:1505)
        at com.sun.tools.javac.code.Type$TypeVar.accept(Type.java:952)
        at com.sun.tools.javac.code.Types$UnaryVisitor.visit(Types.java:3240)
        at com.sun.tools.javac.code.Types.erasure(Types.java:1502)
        at com.sun.tools.javac.code.Types$16.visitTypeVar(Types.java:1525)
        at com.sun.tools.javac.code.Types$16.visitTypeVar(Types.java:1505)
        at com.sun.tools.javac.code.Type$TypeVar.accept(Type.java:952)
        at com.sun.tools.javac.code.Types$UnaryVisitor.visit(Types.java:3240)
        at com.sun.tools.javac.code.Types.erasure(Types.java:1502)
        at com.sun.tools.javac.code.Types$16.visitTypeVar(Types.java:1525)
        at com.sun.tools.javac.code.Types$16.visitTypeVar(Types.java:1505)
        at com.sun.tools.javac.code.Type$TypeVar.accept(Type.java:952)
        at com.sun.tools.javac.code.Types$UnaryVisitor.visit(Types.java:3240)
        at com.sun.tools.javac.code.Types.erasure(Types.java:1502)
</output>
<version>
bash-3.2$ /net/bonsai.sfbay/w/builds/jdk/7/pit/b26/solaris-i586/jdk1.7.0/bin/java -version
java version "1.7.0-internal"
Java(TM) SE Runtime Environment (build 1.7.0-internal-jprtadm_18_Mar_2008_12_26-b00)
Java HotSpot(TM) Server VM (build 12.0-b01, mixed mode)
</version>
bash-3.2$ uname -a
SunOS bonsai 5.11 snv_77 i86pc i386 i86pc

Comments
SUGGESTED FIX After a round of code reviews we decided to exploit the fix described in 2) a webrev of the fix is available at: http://sa.sfbay.sun.com/projects/langtools_data/7/6677785/
30-05-2008

SUGGESTED FIX The solution is to detect any cycle involving type-variable bounds right at the beginning of the step 2) of the completion phase carried out by MemberEnter. If we do so we ensure that: 1) the bound of each type variable has already been completed (necessary in order to avoid CR 660289) 2) Each operation involving type-variable bounds can occur only AFTER the type-variable bounds have been checked for circularity (necessary in order to avoid CR 6677785). In order to show 2) we must consider the execution flow starting from the call to Attr.attribTypeVariables() in MemberEnter.complete() until the end of phase 1 of completion (the line before 'if (wasFirst)'). In this fragment of code, the only dangerous piece of code is the call to Types.isMemberTypes(). This call is dangerous since Types.isMemberTypes() might rely upon Types.erasure(). However, this piece of code is only executed when completing the symbol of anonymous inner classes (see the if condition, checking that the name of the class is empty). Since anonymous inner class declarations cannot have type variable declarations, no cycle can arise here, so the proposed patch is safe (under all other circumstances, this call never gets executed). Cycles are now detected at the very beginning of MemberEnter.finishClass().
28-03-2008

SUGGESTED FIX As an alternative, in principle we could change the code that checks for cycles inside type-variable bounds. In particular, when a null bound is encountered, it seems quite safe to skip the check since no cyclicity can arise in this case. Consider the follwoing code: class Outer<S extends Inner> {// for cyclicity we should have written Outer<S extends Inner.T> which is not legal Java code! class Inner<T extends S> {} } In order to have unattributed bounds, the outer type-variable bound must refer some inner class that hasn't been completed yet. When completing the inner class, if we have another type variable referring the former one, we have an NPE (since completion of the outer class in some sense relies upon the competion of the inner). In such circumstances we could in principle skip the null values. No circularity can arise between type-variables of mutually referring outer/inner classes - this is because the inner symbol can refer the type variable of its enclosing symbol, while the outer cannot refer type-variables declared by the inner --> no cycles. However I don't trust this fix very much: the fact that we have type-variables with null bounds around can be a potential source of errors.
28-03-2008

EVALUATION The problem is due to the fact that detection of cycles in type variable declaration has been moved from phase 1 of symbol completion to the attribution phase of a given class. The reason of this choice is CR 660289, where we have the following code: class Outer<T extends Inner<?>> { class Inner<S extends T> {} } In this example the attribution of the type-variable bound of Outer.T (namely Inner<?>) triggers the completion of the class symbol Inner (because of multiple calls to the method flags() of ClassSymbol). When javac tries to complete Inner, it tries to attribute the type variables of Inner, namely S. Unfortunately, since S's bound is T (whose attribution is waiting for S's attribution), a NPE is thrown when detecting possible cycles in the bound declaration of S. In fact we have that bound(S) = T, bound(T) == null. In order to solve this problem I decided to postpone the circularity check involving type variables so that a given class symbol C is completed first, then any cycle is detected at a later point when C is being attributed. This sounded good at first, but this CR points out that executing the check during attribution it's too late; in fact there is a slot of time in which the class symbol has been completed but not yet attributed. Any call to either Types.erasure(tv) or Types.getUpperBound(tv) where tv is a type variable whose bound is cyclic lead javac to crash, since we have that (e.g. referring to the example above): erasure(S) === Types.getUpperBound(S) Types.getUpperBound(S) === Types.getUpperBound(bound(S)) === Types.getUpperBound(T) Types.getUpperBound(T) === Types.getUpperBound(bound(T)) === Types.getUpperBound(S) this means that we cannot compute the erasure of a type-variable whose bound is cyclic. In this example the erasure operation is executed while finishing the class symbol C. In fact, MemberEnter completes class symbols in two steps: 1) supertype, interfaces, annotation types are attributed 2) all members of the class are entered into the scope of the class Step 1 does not depend on type-variable types and bounds, but step 2 does. As an example, consider that, in the example given above, when the symbol CyclicTypeParameters(S s) is entered into the scope of the class, javac checks that no other method exists with the same name and the same signature. This is done by invoking isOverrideEquivalent that, in turn, calls types.erasure(). Since the erasure of S cannot be computed (the bound of S is cyclic) javac then crashes, as shown above.
28-03-2008