Some Eclipse Foundation services are deprecated, or will be soon. Please ensure you've read this important communication.

Bug 502350

Summary: Eclipse compiler gets stuck in infinite loop
Product: [Eclipse Project] JDT Reporter: Stefan Xenos <sxenos>
Component: CoreAssignee: Till Brychcy <register.eclipse>
Status: VERIFIED FIXED QA Contact: Stephan Herrmann <stephan.herrmann>
Severity: normal    
Priority: P3 CC: daniel_megert, dominik.stadler, jarthana, manoj.palat, register.eclipse, stephan.herrmann
Version: 4.6Flags: daniel_megert: pmc_approved+
stephan.herrmann: review+
Target Milestone: 4.6.2   
Hardware: PC   
OS: Linux   
See Also: https://git.eclipse.org/r/82139
https://git.eclipse.org/c/jdt/eclipse.jdt.core.git/commit/?id=372105883c90f57f2c7a8992de070fba3f4f7974
https://git.eclipse.org/r/85227
https://git.eclipse.org/c/jdt/eclipse.jdt.core.git/commit/?id=0cdebe3b9ca49ce7d54cc31b9fee2a075a55b264
https://bugs.eclipse.org/bugs/show_bug.cgi?id=527742
Whiteboard:

Description Stefan Xenos CLA 2016-09-28 00:41:21 EDT
Create the following class, turn on autobuild, and observe that the compiler and indexer get stuck in an infinite loop:


package makeCompilerFreeze;

import java.util.Collection;
import java.util.Comparator;

class Stuff {
  public static <T, S extends T> Comparator<Iterable<S>> func(Comparator<T> comparator) {
    return null;
  }
}

public class EclipseJava8Bug {
  class Thing {
    Collection<Object> getStuff() { return null; }
  }

  static final Comparator<Thing> BORKED =
      Comparator.comparing(Thing::getStuff, Stuff.func(Comparator.naturalOrder()));
}
Comment 1 Stefan Xenos CLA 2016-09-28 00:42:58 EDT
Here is a stack trace from the livelocked indexer thread:

	at org.eclipse.jdt.internal.compiler.lookup.TypeBinding.findSuperTypeOriginatingFrom(TypeBinding.java:422)
	at org.eclipse.jdt.internal.compiler.lookup.CaptureBinding18.findSuperTypeOriginatingFrom(CaptureBinding18.java:188)
	at org.eclipse.jdt.internal.compiler.lookup.CaptureBinding18.findSuperTypeOriginatingFrom(CaptureBinding18.java:181)
	at org.eclipse.jdt.internal.compiler.lookup.ConstraintTypeFormula.addConstraintsFromTypeParameters(ConstraintTypeFormula.java:368)
	at org.eclipse.jdt.internal.compiler.lookup.ConstraintTypeFormula.reduceSubType(ConstraintTypeFormula.java:250)
	at org.eclipse.jdt.internal.compiler.lookup.ConstraintTypeFormula.reduce(ConstraintTypeFormula.java:95)
	at org.eclipse.jdt.internal.compiler.lookup.BoundSet.reduceOneConstraint(BoundSet.java:949)
	at org.eclipse.jdt.internal.compiler.lookup.BoundSet.incorporate(BoundSet.java:669)
	at org.eclipse.jdt.internal.compiler.lookup.BoundSet.incorporate(BoundSet.java:570)
	at org.eclipse.jdt.internal.compiler.lookup.InferenceContext18.resolve(InferenceContext18.java:1076)
	at org.eclipse.jdt.internal.compiler.lookup.InferenceContext18.solve(InferenceContext18.java:843)
	at org.eclipse.jdt.internal.compiler.lookup.ParameterizedGenericMethodBinding.computeCompatibleMethod18(ParameterizedGenericMethodBinding.java:238)
	at org.eclipse.jdt.internal.compiler.lookup.ParameterizedGenericMethodBinding.computeCompatibleMethod(ParameterizedGenericMethodBinding.java:84)
	at org.eclipse.jdt.internal.compiler.lookup.Scope.computeCompatibleMethod(Scope.java:743)
	at org.eclipse.jdt.internal.compiler.lookup.Scope.computeCompatibleMethod(Scope.java:700)
	at org.eclipse.jdt.internal.compiler.lookup.Scope.findMethod0(Scope.java:1656)
	at org.eclipse.jdt.internal.compiler.lookup.Scope.findMethod(Scope.java:1557)
	at org.eclipse.jdt.internal.compiler.lookup.Scope.getMethod(Scope.java:2840)
	at org.eclipse.jdt.internal.compiler.ast.MessageSend.findMethodBinding(MessageSend.java:915)
	at org.eclipse.jdt.internal.compiler.ast.MessageSend.resolveType(MessageSend.java:729)
	at org.eclipse.jdt.internal.compiler.ast.MessageSend.resolveType(MessageSend.java:684)
	at org.eclipse.jdt.internal.compiler.ast.FieldDeclaration.resolve(FieldDeclaration.java:266)
	at org.eclipse.jdt.internal.compiler.ast.TypeDeclaration.resolve(TypeDeclaration.java:1131)
	at org.eclipse.jdt.internal.compiler.ast.TypeDeclaration.resolve(TypeDeclaration.java:1308)
	at org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration.resolve(CompilationUnitDeclaration.java:593)
	at org.eclipse.jdt.internal.core.search.indexing.SourceIndexer.resolveDocument(SourceIndexer.java:165)
	at org.eclipse.jdt.internal.core.search.JavaSearchParticipant.resolveDocument(JavaSearchParticipant.java:102)
	at org.eclipse.jdt.internal.core.search.indexing.IndexManager.indexResolvedDocument(IndexManager.java:510)
	at org.eclipse.jdt.internal.core.search.indexing.IndexManager$1.execute(IndexManager.java:989)
	at org.eclipse.jdt.internal.core.search.processing.JobManager.run(JobManager.java:394)
	at java.lang.Thread.run(Thread.java:745)
Comment 2 Stefan Xenos CLA 2016-09-28 00:45:27 EDT
The loop is entirely within findSuperTypeOriginatingFrom. It is traversing the supertype hierarchy, and gets stuck in a cycle due to the following bindings which each point to one another as their parent binding:

<Z#1-T#2 extends Z#0-T#0>
<Z#0-T#0 extends Z#1-T#2 & Comparable<? super Z#1-T#2>>
Comment 3 Stephan Herrmann CLA 2016-09-28 12:24:15 EDT
(In reply to Stefan Xenos from comment #2)
> The loop is entirely within findSuperTypeOriginatingFrom. It is traversing
> the supertype hierarchy, and gets stuck in a cycle due to the following
> bindings which each point to one another as their parent binding:
> 
> <Z#1-T#2 extends Z#0-T#0>
> <Z#0-T#0 extends Z#1-T#2 & Comparable<? super Z#1-T#2>>

Thanks for the analysis. Wow! 
I'm not even sure such cyclic type variables are forbidden ...

As you assigned this to yourself, do you have a strategy for fixing this?
Comment 4 Stefan Xenos CLA 2016-09-28 13:46:29 EDT
> do you have a strategy for fixing this?

No. I just have a need to have it fixed. :-)

I've been reading Section 18 JLS all morning trying to understand the intended behavior better, and was hoping to figure things out from there.

You probably understand Java's type inferencing better than I do, so please feel free to reassign to yourself if you have time to look into this.
Comment 5 Till Brychcy CLA 2016-09-28 16:41:46 EDT
To make debugging easier, I've made the example self-contained (no jdk types), renamed all type parameters so they have distinct names and reduced it further:

package makeCompilerFreeze;

interface Comparable<E> {} 
interface Function<Q, R> {}

interface Comparator<A> {
  public static <B extends Comparable<? super B>> Comparator<B> naturalOrder() {
    return null;
  }
  public static <C, D> Comparator<C> comparing(Function<? super C, ? extends D> f1, Comparator<? super D> f2) {
    return null;
  }
}


class Stuff {
  public static <T, S extends T> Comparator<Iterable<S>> func(Comparator<T> comparator) {
    return null;
  }
}

public class EclipseJava8Bug {
  static final Comparator<Object> BORKED =
      Comparator.comparing(null, Stuff.func(Comparator.naturalOrder()));
}

With these names the cycle is:
<Z#1-T#0 extends Z#0-B#2>
<Z#0-B#2 extends Z#1-T#0 & Comparable<? super Z#0-B#2>>
Comment 6 Till Brychcy CLA 2016-09-28 16:54:08 EDT
Further reduced:

package makeCompilerFreeze;

interface Comparable<E> {} 

interface Comparator<A> {
  public static <B extends Comparable<? super B>> Comparator<B> naturalOrder() {
    return null;
  }
}


class Stuff {
  public static <T, S extends T> Comparator<Iterable<S>> func(Comparator<T> comparator) {
    return null;
  }
}

public class EclipseJava8Bug {
  static final Object BORKED =
      Stuff.func(Comparator.naturalOrder());
}

bwt, javac says:

makeCompilerFreeze/EclipseJava8Bug.java:20: error: method func in class Stuff cannot be applied to given types;
      Stuff.func(Comparator.naturalOrder());
           ^
  required: Comparator<T>
  found: Comparator<B>
  reason: inferred type does not conform to upper bound(s)
    inferred: Object
    upper bound(s): Comparable<? super Object>,Object
  where T,S,B are type-variables:
    T extends Object declared in method <T,S>func(Comparator<T>)
    S extends T declared in method <T,S>func(Comparator<T>)
    B extends Comparable<? super B>
1 error
Comment 7 Till Brychcy CLA 2016-09-28 17:13:07 EDT
I think this is the minimal example. S is not used at all, but if you remove it, the endless loop doesn't happen:

package makeCompilerFreeze;

interface Comparable<E> {} 

interface Comparator<A> {
  public static <B extends Comparable<? super B>> Comparator<B> naturalOrder() {
    return null;
  }
}


class Stuff {
  public static <T, S extends T> Object func(Comparator<T> comparator) {
    return null;
  }
}

public class EclipseJava8Bug {
  static final Object BORKED =
      Stuff.func(Comparator.naturalOrder());
}
Comment 8 Eclipse Genie CLA 2016-09-28 22:49:22 EDT
New Gerrit change created: https://git.eclipse.org/r/82127
Comment 9 Stefan Xenos CLA 2016-09-28 22:53:42 EDT
I've attached a patch that seems to fix this specific example. It uses Brent's algorithm to detect cycles in the superclass list and breaks out of the iteration before visiting any class twice.

However, after searching for all callers of the superclass() method, it looks like none of the others can handle cycles in the superclass list either.

Should this pattern be applied more widely? Or should the cycle not have been there in the first place?
Comment 10 Stefan Xenos CLA 2016-09-28 22:57:34 EDT
FYI, this patch was written with a very limited understanding of the type inferencing algorithm. It looked to me like the findSuperTypeOriginatingFrom method only needed to visit each superclass once and could otherwise tolerate cycles... but I'd really like someone with a deeper understanding of type inferencing to offer their opinion on whether or not tolerating the cycle is appropriate in this case.
Comment 11 Eclipse Genie CLA 2016-09-29 04:02:44 EDT
New Gerrit change created: https://git.eclipse.org/r/82139
Comment 12 Stephan Herrmann CLA 2016-09-29 09:58:58 EDT
(In reply to Eclipse Genie from comment #11)
> New Gerrit change created: https://git.eclipse.org/r/82139

Cool! Someone understood that code deep inside the inference engine.
And found the tiny mistake hidden inside.
Well spotted!

I'll quickly amend the patch with a tiny refactoring that should demonstrate why the proposed correction is - correct.
Comment 13 Stephan Herrmann CLA 2016-09-29 10:14:45 EDT
Bookkeeping: Since we could choose between a broken and a working proposal, I simply removed the review requests, abandoned the failed attempt in gerrit, resubmitted the winning gerrit with my tiny refactoring and assigned to Till. Thanks.
Comment 14 Stefan Xenos CLA 2016-09-29 10:47:40 EDT
> I simply removed the review requests, abandoned the failed attempt in gerrit,

No worries. Thanks for looking at this, Till.
Comment 15 Till Brychcy CLA 2016-09-29 15:32:40 EDT
(In reply to Stephan Herrmann from comment #12)
> (In reply to Eclipse Genie from comment #11)
> > New Gerrit change created: https://git.eclipse.org/r/82139
> 
> Cool! Someone understood that code deep inside the inference engine.
> And found the tiny mistake hidden inside.
> Well spotted!
> 

Thanks for the nice words. The inference engine is really impressive.

In case  you wondered why the commit is dated March, 21, 2016:
I came across that code when I looked if I can find a solution for what is now bug 493151. I suspected it was wrong and made an experimental fix in a branch, but could not construct an example that led to an observable bug, and was busy with other bugs, so I forgot about it.
When I stepped throught the code for this bug, it felt familiar and I found my experimental fix.
Next time I'll file a bug immediately if I see something like that :-)

> I'll quickly amend the patch with a tiny refactoring that should demonstrate
> why the proposed correction is - correct.

Good!
Comment 17 Till Brychcy CLA 2016-09-29 15:34:26 EDT
(In reply to Eclipse Genie from comment #16)
> Gerrit change https://git.eclipse.org/r/82139 was merged to [master].
> Commit:
> http://git.eclipse.org/c/jdt/eclipse.jdt.core.git/commit/
> ?id=372105883c90f57f2c7a8992de070fba3f4f7974

Released for 4.7M3
Comment 18 Jay Arthanareeswaran CLA 2016-10-26 06:14:29 EDT
Verified for 4.7 M3 with build I20161024-2000
Comment 19 Dominik Stadler CLA 2016-11-17 06:56:55 EST
I am hit by this also, compilation reproducibly locks up for a large project in one specific package. The same code is compiled fine in CI by JDK and locally by IntelliJ, so I cannot use Eclipse any longer for that project. 

Is there any way to get this fix earlier than with 4.7 which is due only in July 2017?
Comment 20 Stephan Herrmann CLA 2016-11-17 07:28:39 EST
Proposing for 4.6.3, the mistake is obvious, the effect is really bad and the fix is straight forward.

If people insist, we _could_ even ship this with 4.6.2 RC3.
Comment 21 Eclipse Genie CLA 2016-11-17 13:17:52 EST
New Gerrit change created: https://git.eclipse.org/r/85227
Comment 22 Till Brychcy CLA 2016-11-17 13:18:46 EST
(In reply to Eclipse Genie from comment #21)
> New Gerrit change created: https://git.eclipse.org/r/85227

Gerrit for a possible backport (now or later)
Comment 23 Till Brychcy CLA 2016-11-17 13:26:14 EST
(In reply to Stephan Herrmann from comment #20)
> Proposing for 4.6.3, the mistake is obvious, the effect is really bad and
> the fix is straight forward.
+1

> 
> If people insist, we _could_ even ship this with 4.6.2 RC3.

(In reply to Dominik Stadler from comment #19)
> I am hit by this also, compilation reproducibly locks up for a large project
> in one specific package. The same code is compiled fine in CI by JDK and
> locally by IntelliJ, so I cannot use Eclipse any longer for that project. 
> 
> Is there any way to get this fix earlier than with 4.7 which is due only in
> July 2017?

If we backport this: Neon.3 (4.6.3) will be out in March 23, 2017.

Maybe you could use 4.7 M3?
Comment 24 Jay Arthanareeswaran CLA 2016-11-17 23:24:41 EST
(In reply to Stephan Herrmann from comment #20)
> Proposing for 4.6.3, the mistake is obvious, the effect is really bad and
> the fix is straight forward.
> 
> If people insist, we _could_ even ship this with 4.6.2 RC3.

Agree, it will be good to have this in 4.6.2 itself. 


Requesting PMC to approve this change for RC3.
Comment 25 Dani Megert CLA 2016-11-18 10:54:02 EST
(In reply to Jay Arthanareeswaran from comment #24)
> (In reply to Stephan Herrmann from comment #20)
> Requesting PMC to approve this change for RC3.

+1
Comment 26 Till Brychcy CLA 2016-11-18 12:05:27 EST
reopened for back port to 4.6.2
Comment 27 Eclipse Genie CLA 2016-11-19 15:35:21 EST
Gerrit change https://git.eclipse.org/r/85227 was merged to [R4_6_maintenance].
Commit: http://git.eclipse.org/c/jdt/eclipse.jdt.core.git/commit/?id=0cdebe3b9ca49ce7d54cc31b9fee2a075a55b264
Comment 28 Till Brychcy CLA 2016-11-19 15:36:08 EST
(In reply to Eclipse Genie from comment #27)
> Gerrit change https://git.eclipse.org/r/85227 was merged to
> [R4_6_maintenance].
> Commit:
> http://git.eclipse.org/c/jdt/eclipse.jdt.core.git/commit/
> ?id=0cdebe3b9ca49ce7d54cc31b9fee2a075a55b264

Released for 4.6.2
Comment 29 Jay Arthanareeswaran CLA 2016-11-23 00:37:01 EST
Verified for 4.6.2 with build M20161122-0800