Skip to content

Commit 42b9d17

Browse files
committed
GROOVY-11770: StackOverflowError processing generics for kubernetes-client library (cycle guard instead of try/catch)
1 parent 3e45a30 commit 42b9d17

2 files changed

Lines changed: 169 additions & 81 deletions

File tree

src/main/java/org/codehaus/groovy/ast/tools/WideningCategories.java

Lines changed: 68 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,10 @@ public static ClassNode lowestUpperBound(final List<ClassNode> nodes) {
207207
* @since 2.0.0
208208
*/
209209
public static ClassNode lowestUpperBound(final ClassNode a, final ClassNode b) {
210+
return lowestUpperBound(new LowestUpperBoundContext(), a, b);
211+
}
212+
213+
private static ClassNode lowestUpperBound(final LowestUpperBoundContext ctx, final ClassNode a, final ClassNode b) {
210214
ClassNode lub = lowestUpperBound(a, b, null, null);
211215
if (lub == null || !lub.isUsingGenerics()
212216
|| lub.isGenericsPlaceHolder()) { // GROOVY-10330
@@ -222,20 +226,20 @@ public static ClassNode lowestUpperBound(final ClassNode a, final ClassNode b) {
222226
// plus the interfaces
223227
ClassNode superClass = lub.getSuperClass();
224228
if (superClass.redirect().getGenericsTypes() != null) {
225-
superClass = parameterizeLowestUpperBound(superClass, a, b, lub);
229+
superClass = parameterizeLowestUpperBound(ctx, superClass, a, b, lub);
226230
}
227231

228232
ClassNode[] interfaces = lub.getInterfaces().clone();
229233
for (int i = 0, n = interfaces.length; i < n; i += 1) {
230234
ClassNode icn = interfaces[i];
231235
if (icn.redirect().getGenericsTypes() != null) {
232-
interfaces[i] = parameterizeLowestUpperBound(icn, a, b, lub);
236+
interfaces[i] = parameterizeLowestUpperBound(ctx, icn, a, b, lub);
233237
}
234238
}
235239

236240
return new LowestUpperBoundClassNode(lub.getUnresolvedName(), superClass, interfaces);
237241
} else {
238-
return parameterizeLowestUpperBound(lub, a, b, lub);
242+
return parameterizeLowestUpperBound(ctx, lub, a, b, lub);
239243
}
240244
}
241245

@@ -246,13 +250,14 @@ public static ClassNode lowestUpperBound(final ClassNode a, final ClassNode b) {
246250
*
247251
* For example, if LUB is Set&lt;T&gt; and a is Set&lt;String&gt; and b is Set&lt;StringBuffer&gt;, this
248252
* will return a LUB which parameterized type matches Set&lt;? extends CharSequence&gt;
253+
* @param ctx tracks (t1, t2) pairs whose LUB is currently being computed, so this method can detect recursive calls (GROOVY-11770)
249254
* @param lub the type to be parameterized
250255
* @param a parameterized type a
251256
* @param b parameterized type b
252257
* @param fallback if we detect a recursive call, use this LUB as the parameterized type instead of computing a value
253258
* @return the class node representing the parameterized lowest upper bound
254259
*/
255-
private static ClassNode parameterizeLowestUpperBound(final ClassNode lub, final ClassNode a, final ClassNode b, final ClassNode fallback) {
260+
private static ClassNode parameterizeLowestUpperBound(final LowestUpperBoundContext ctx, final ClassNode lub, final ClassNode a, final ClassNode b, final ClassNode fallback) {
256261
if (a.toString(false).equals(b.toString(false))) return lub;
257262
// a common super type exists, all we have to do is to parameterize
258263
// it according to the types provided by the two class nodes
@@ -273,11 +278,16 @@ private static ClassNode parameterizeLowestUpperBound(final ClassNode lub, final
273278
if (areEqualWithGenerics(t1, isPrimitiveType(a)?getWrapper(a):a) && areEqualWithGenerics(t2, isPrimitiveType(b)?getWrapper(b):b)) {
274279
// "String implements Comparable<String>" and "StringBuffer implements Comparable<StringBuffer>"
275280
basicType = fallback; // do not loop
281+
} else if (ctx.isExpanding(t1, t2)) {
282+
// GROOVY-11770: recursion guard for an already-expanding type pair, or depth cap reached
283+
// (e.g. LUB(B, D) where B extends A<W<B>>, D extends A<W<D>>)
284+
basicType = fallback;
276285
} else {
286+
ctx.enter(t1, t2);
277287
try {
278-
basicType = lowestUpperBound(t1, t2);
279-
} catch (StackOverflowError ignore) {
280-
basicType = fallback; // best we can do for now
288+
basicType = lowestUpperBound(ctx, t1, t2);
289+
} finally {
290+
ctx.exit(t1, t2);
281291
}
282292
}
283293
if (agt[i].isWildcard() || bgt[i].isWildcard() || !t1.equals(t2)) {
@@ -289,6 +299,57 @@ private static ClassNode parameterizeLowestUpperBound(final ClassNode lub, final
289299
return GenericsUtils.makeClassSafe0(lub, lubGTs);
290300
}
291301

302+
/**
303+
* Tracks pairs of types whose LUB is currently being computed by
304+
* {@link #lowestUpperBound(ClassNode, ClassNode)}, so the recursion can
305+
* break cycles caused by F-bounded type parameters that route a subtype
306+
* back through itself (GROOVY-11770). Pair equality is identity-based and
307+
* order-insensitive, since LUB is symmetric. A depth cap acts as a
308+
* backstop in case generics rewriting yields fresh {@code ClassNode}
309+
* instances for logically identical types, which would defeat identity
310+
* tracking.
311+
*/
312+
private static final class LowestUpperBoundContext {
313+
private static final int MAX_DEPTH = 64;
314+
private final Set<TypePair> inflight = new HashSet<>();
315+
private int depth;
316+
317+
boolean isExpanding(final ClassNode a, final ClassNode b) {
318+
return depth >= MAX_DEPTH || inflight.contains(new TypePair(a, b));
319+
}
320+
321+
void enter(final ClassNode a, final ClassNode b) {
322+
inflight.add(new TypePair(a, b));
323+
depth++;
324+
}
325+
326+
void exit(final ClassNode a, final ClassNode b) {
327+
inflight.remove(new TypePair(a, b));
328+
depth--;
329+
}
330+
331+
private static final class TypePair {
332+
final ClassNode a, b;
333+
334+
TypePair(final ClassNode a, final ClassNode b) {
335+
this.a = a;
336+
this.b = b;
337+
}
338+
339+
@Override
340+
public boolean equals(final Object o) {
341+
if (!(o instanceof TypePair)) return false;
342+
TypePair p = (TypePair) o;
343+
return (a == p.a && b == p.b) || (a == p.b && b == p.a);
344+
}
345+
346+
@Override
347+
public int hashCode() {
348+
return System.identityHashCode(a) ^ System.identityHashCode(b);
349+
}
350+
}
351+
}
352+
292353
private static ClassNode findGenericsTypeHolderForClass(ClassNode source, final ClassNode target) {
293354
if (isPrimitiveType(source)) source = getWrapper(source);
294355
if (source.equals(target)) {

0 commit comments

Comments
 (0)