From fad65c667f972571fb3bda26a1f26962bcd50a06 Mon Sep 17 00:00:00 2001 From: Paul King Date: Tue, 28 Apr 2026 15:09:41 +1000 Subject: [PATCH] GROOVY-11022: StackOverflowError when having parameterized function with recursive bounds --- .../stc/StaticTypeCheckingSupport.java | 24 ++++++++++-- .../transform/stc/GenericsSTCTest.groovy | 37 +++++++++++++++++++ 2 files changed, 58 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java index 2122c917787..cdc341686b4 100644 --- a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java +++ b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java @@ -55,11 +55,13 @@ import org.codehaus.groovy.transform.trait.Traits; import org.objectweb.asm.Opcodes; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; +import java.util.Deque; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; @@ -1769,6 +1771,9 @@ static GenericsType[] getGenericsWithoutArray(final ClassNode type) { return type.getGenericsTypes(); } + private static final ThreadLocal> EXPANDING_BOUNDS = + ThreadLocal.withInitial(ArrayDeque::new); + static GenericsType[] applyGenericsContext(final Map spec, final GenericsType[] gts) { if (gts == null || spec == null || spec.isEmpty()) return gts; @@ -1789,9 +1794,22 @@ private static GenericsType applyGenericsContext(final Map>`) + Deque expanding = EXPANDING_BOUNDS.get(); + if (expanding.contains(name)) return gt; + expanding.push(name); + try { + GenericsType newGT = new GenericsType(type, applyGenericsContext(spec, gt.getUpperBounds()), applyGenericsContext(spec, gt.getLowerBound())); + newGT.setPlaceholder(true); + return newGT; + } finally { + expanding.pop(); + if (expanding.isEmpty()) { + EXPANDING_BOUNDS.remove(); + } + } } return gt; } diff --git a/src/test/groovy/groovy/transform/stc/GenericsSTCTest.groovy b/src/test/groovy/groovy/transform/stc/GenericsSTCTest.groovy index e58c44cc506..59dceeb46d1 100644 --- a/src/test/groovy/groovy/transform/stc/GenericsSTCTest.groovy +++ b/src/test/groovy/groovy/transform/stc/GenericsSTCTest.groovy @@ -4616,6 +4616,43 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase { } } + // GROOVY-11022 + @Test + void testNoStackOverflow3() { + // F-bounded type parameter with self-reference inside a `? super` wildcard + assertScript ''' + class C { + public static , V> C m(K k, V v) { + new C() + } + } + class Main { + @groovy.transform.TypeChecked + static test() { + C.m(null, 1) + } + } + Main.test() + ''' + + // self-reference inside a `? extends` wildcard + assertScript ''' + class Self implements Iterable { + Iterator iterator() { Collections.emptyIterator() } + } + class C { + public static > C m(K k) { + new C() + } + } + @groovy.transform.TypeChecked + void test() { + C.m(null) + } + test() + ''' + } + @Test void testRegressionInConstructorCheck() { assertScript '''