-
Notifications
You must be signed in to change notification settings - Fork 1.9k
GROOVY-11770: StackOverflowError processing generics for kubernetes-c… #2498
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -207,6 +207,10 @@ public static ClassNode lowestUpperBound(final List<ClassNode> nodes) { | |||||||||||||||
| * @since 2.0.0 | ||||||||||||||||
| */ | ||||||||||||||||
| public static ClassNode lowestUpperBound(final ClassNode a, final ClassNode b) { | ||||||||||||||||
| return lowestUpperBound(new LowestUpperBoundContext(), a, b); | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| private static ClassNode lowestUpperBound(final LowestUpperBoundContext ctx, final ClassNode a, final ClassNode b) { | ||||||||||||||||
| ClassNode lub = lowestUpperBound(a, b, null, null); | ||||||||||||||||
| if (lub == null || !lub.isUsingGenerics() | ||||||||||||||||
| || lub.isGenericsPlaceHolder()) { // GROOVY-10330 | ||||||||||||||||
|
|
@@ -222,20 +226,20 @@ public static ClassNode lowestUpperBound(final ClassNode a, final ClassNode b) { | |||||||||||||||
| // plus the interfaces | ||||||||||||||||
| ClassNode superClass = lub.getSuperClass(); | ||||||||||||||||
| if (superClass.redirect().getGenericsTypes() != null) { | ||||||||||||||||
| superClass = parameterizeLowestUpperBound(superClass, a, b, lub); | ||||||||||||||||
| superClass = parameterizeLowestUpperBound(ctx, superClass, a, b, lub); | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| ClassNode[] interfaces = lub.getInterfaces().clone(); | ||||||||||||||||
| for (int i = 0, n = interfaces.length; i < n; i += 1) { | ||||||||||||||||
| ClassNode icn = interfaces[i]; | ||||||||||||||||
| if (icn.redirect().getGenericsTypes() != null) { | ||||||||||||||||
| interfaces[i] = parameterizeLowestUpperBound(icn, a, b, lub); | ||||||||||||||||
| interfaces[i] = parameterizeLowestUpperBound(ctx, icn, a, b, lub); | ||||||||||||||||
| } | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| return new LowestUpperBoundClassNode(lub.getUnresolvedName(), superClass, interfaces); | ||||||||||||||||
| } else { | ||||||||||||||||
| return parameterizeLowestUpperBound(lub, a, b, lub); | ||||||||||||||||
| return parameterizeLowestUpperBound(ctx, lub, a, b, lub); | ||||||||||||||||
| } | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
|
|
@@ -246,13 +250,14 @@ public static ClassNode lowestUpperBound(final ClassNode a, final ClassNode b) { | |||||||||||||||
| * | ||||||||||||||||
| * For example, if LUB is Set<T> and a is Set<String> and b is Set<StringBuffer>, this | ||||||||||||||||
| * will return a LUB which parameterized type matches Set<? extends CharSequence> | ||||||||||||||||
| * @param ctx tracks (t1, t2) pairs whose LUB is currently being computed, so this method can detect recursive calls (GROOVY-11770) | ||||||||||||||||
| * @param lub the type to be parameterized | ||||||||||||||||
| * @param a parameterized type a | ||||||||||||||||
| * @param b parameterized type b | ||||||||||||||||
| * @param fallback if we detect a recursive call, use this LUB as the parameterized type instead of computing a value | ||||||||||||||||
| * @return the class node representing the parameterized lowest upper bound | ||||||||||||||||
| */ | ||||||||||||||||
| private static ClassNode parameterizeLowestUpperBound(final ClassNode lub, final ClassNode a, final ClassNode b, final ClassNode fallback) { | ||||||||||||||||
| private static ClassNode parameterizeLowestUpperBound(final LowestUpperBoundContext ctx, final ClassNode lub, final ClassNode a, final ClassNode b, final ClassNode fallback) { | ||||||||||||||||
| if (a.toString(false).equals(b.toString(false))) return lub; | ||||||||||||||||
| // a common super type exists, all we have to do is to parameterize | ||||||||||||||||
| // it according to the types provided by the two class nodes | ||||||||||||||||
|
|
@@ -273,11 +278,15 @@ private static ClassNode parameterizeLowestUpperBound(final ClassNode lub, final | |||||||||||||||
| if (areEqualWithGenerics(t1, isPrimitiveType(a)?getWrapper(a):a) && areEqualWithGenerics(t2, isPrimitiveType(b)?getWrapper(b):b)) { | ||||||||||||||||
| // "String implements Comparable<String>" and "StringBuffer implements Comparable<StringBuffer>" | ||||||||||||||||
| basicType = fallback; // do not loop | ||||||||||||||||
| } else if (ctx.isExpanding(t1, t2)) { | ||||||||||||||||
| // GROOVY-11770: structural cycle (e.g. LUB(B, D) where B extends A<W<B>>, D extends A<W<D>>) | ||||||||||||||||
| basicType = fallback; | ||||||||||||||||
| } else { | ||||||||||||||||
| ctx.enter(t1, t2); | ||||||||||||||||
| try { | ||||||||||||||||
| basicType = lowestUpperBound(t1, t2); | ||||||||||||||||
| } catch (StackOverflowError ignore) { | ||||||||||||||||
| basicType = fallback; // best we can do for now | ||||||||||||||||
| basicType = lowestUpperBound(ctx, t1, t2); | ||||||||||||||||
|
||||||||||||||||
| basicType = lowestUpperBound(ctx, t1, t2); | |
| basicType = lowestUpperBound(ctx, t1, t2); | |
| } catch (StackOverflowError ignore) { | |
| // Some generics-rewriting paths can create fresh ClassNode instances for the same | |
| // logical type, which may defeat identity-based cycle tracking in the context. | |
| // Fall back rather than allowing an unbounded recursive expansion to overflow. | |
| basicType = fallback; |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -4616,6 +4616,26 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase { | |||||
| } | ||||||
| } | ||||||
|
|
||||||
| // GROOVY-11770 | ||||||
| @Test | ||||||
| void testNoStackOverflow3() { | ||||||
| // Cycle through a wrapper type: LUB(B, D) needs LUB(W<B>, W<D>), | ||||||
| // which needs LUB(B, D) again. Caught by structural cycle guard. | ||||||
|
||||||
| // which needs LUB(B, D) again. Caught by structural cycle guard. | |
| // which needs LUB(B, D) again. Caught by the in-flight LUB pair guard. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment says “structural cycle”, but the guard currently detects cycles using identity (
TypePairuses==). Either adjust the wording to reflect identity-based detection, or change the keying strategy to something structural/stable so the comment matches the behavior.