You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by pa...@apache.org on 2026/05/02 05:47:50 UTC

(groovy) branch master updated: GROOVY-11770: StackOverflowError processing generics for kubernetes-client library (cycle guard instead of try/catch)

This is an automated email from the ASF dual-hosted git repository.

paulk-asert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/groovy.git


The following commit(s) were added to refs/heads/master by this push:
     new 42b9d17edf GROOVY-11770: StackOverflowError processing generics for kubernetes-client library (cycle guard instead of try/catch)
42b9d17edf is described below

commit 42b9d17edfbccb8d8af261683c487a5fac0eeca9
Author: Paul King <pa...@asert.com.au>
AuthorDate: Tue Apr 28 16:25:01 2026 +1000

    GROOVY-11770: StackOverflowError processing generics for kubernetes-client library (cycle guard instead of try/catch)
---
 .../groovy/ast/tools/WideningCategories.java       |  75 ++++++++-
 .../groovy/transform/stc/GenericsSTCTest.groovy    | 175 ++++++++++++---------
 2 files changed, 169 insertions(+), 81 deletions(-)

diff --git a/src/main/java/org/codehaus/groovy/ast/tools/WideningCategories.java b/src/main/java/org/codehaus/groovy/ast/tools/WideningCategories.java
index 3c5ae2c807..1d5d38c25c 100644
--- a/src/main/java/org/codehaus/groovy/ast/tools/WideningCategories.java
+++ b/src/main/java/org/codehaus/groovy/ast/tools/WideningCategories.java
@@ -207,6 +207,10 @@ public class WideningCategories {
      * @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 class WideningCategories {
             // 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 class WideningCategories {
      *
      * For example, if LUB is Set&lt;T&gt; and a is Set&lt;String&gt; and b is Set&lt;StringBuffer&gt;, this
      * will return a LUB which parameterized type matches Set&lt;? extends CharSequence&gt;
+     * @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,16 @@ public class WideningCategories {
             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: recursion guard for an already-expanding type pair, or depth cap reached
+                // (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);
+                } finally {
+                    ctx.exit(t1, t2);
                 }
             }
             if (agt[i].isWildcard() || bgt[i].isWildcard() || !t1.equals(t2)) {
@@ -289,6 +299,57 @@ public class WideningCategories {
         return GenericsUtils.makeClassSafe0(lub, lubGTs);
     }
 
+    /**
+     * Tracks pairs of types whose LUB is currently being computed by
+     * {@link #lowestUpperBound(ClassNode, ClassNode)}, so the recursion can
+     * break cycles caused by F-bounded type parameters that route a subtype
+     * back through itself (GROOVY-11770). Pair equality is identity-based and
+     * order-insensitive, since LUB is symmetric. A depth cap acts as a
+     * backstop in case generics rewriting yields fresh {@code ClassNode}
+     * instances for logically identical types, which would defeat identity
+     * tracking.
+     */
+    private static final class LowestUpperBoundContext {
+        private static final int MAX_DEPTH = 64;
+        private final Set<TypePair> inflight = new HashSet<>();
+        private int depth;
+
+        boolean isExpanding(final ClassNode a, final ClassNode b) {
+            return depth >= MAX_DEPTH || inflight.contains(new TypePair(a, b));
+        }
+
+        void enter(final ClassNode a, final ClassNode b) {
+            inflight.add(new TypePair(a, b));
+            depth++;
+        }
+
+        void exit(final ClassNode a, final ClassNode b) {
+            inflight.remove(new TypePair(a, b));
+            depth--;
+        }
+
+        private static final class TypePair {
+            final ClassNode a, b;
+
+            TypePair(final ClassNode a, final ClassNode b) {
+                this.a = a;
+                this.b = b;
+            }
+
+            @Override
+            public boolean equals(final Object o) {
+                if (!(o instanceof TypePair)) return false;
+                TypePair p = (TypePair) o;
+                return (a == p.a && b == p.b) || (a == p.b && b == p.a);
+            }
+
+            @Override
+            public int hashCode() {
+                return System.identityHashCode(a) ^ System.identityHashCode(b);
+            }
+        }
+    }
+
     private static ClassNode findGenericsTypeHolderForClass(ClassNode source, final ClassNode target) {
         if (isPrimitiveType(source)) source = getWrapper(source);
         if (source.equals(target)) {
diff --git a/src/test/groovy/groovy/transform/stc/GenericsSTCTest.groovy b/src/test/groovy/groovy/transform/stc/GenericsSTCTest.groovy
index 59dceeb46d..4c7f9b8625 100644
--- a/src/test/groovy/groovy/transform/stc/GenericsSTCTest.groovy
+++ b/src/test/groovy/groovy/transform/stc/GenericsSTCTest.groovy
@@ -48,7 +48,7 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
         shouldFailWithMessages '''
             Map<String,String> obj = new HashMap<String,Integer>()
         ''',
-        'Incompatible generic argument types. Cannot assign java.util.HashMap<java.lang.String, java.lang.Integer> to: java.util.Map<java.lang.String, java.lang.String>'
+            'Incompatible generic argument types. Cannot assign java.util.HashMap<java.lang.String, java.lang.Integer> to: java.util.Map<java.lang.String, java.lang.String>'
     }
 
     @Test
@@ -57,7 +57,7 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
         shouldFailWithMessages '''
             Map<? extends CharSequence,String> obj = new HashMap<String,Integer>()
         ''',
-        'Incompatible generic argument types. Cannot assign java.util.HashMap<java.lang.String, java.lang.Integer> to: java.util.Map<? extends java.lang.CharSequence, java.lang.String>'
+            'Incompatible generic argument types. Cannot assign java.util.HashMap<java.lang.String, java.lang.Integer> to: java.util.Map<? extends java.lang.CharSequence, java.lang.String>'
     }
 
     @Test
@@ -87,7 +87,7 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
             List<String> list = []
             list.add(1)
         ''',
-        'Cannot call java.util.ArrayList#add(java.lang.String) with arguments [int]'
+            'Cannot call java.util.ArrayList#add(java.lang.String) with arguments [int]'
     }
 
     // GROOVY-9074
@@ -97,13 +97,13 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
             List<?> list = []
             list.add(1)
         ''',
-        'Cannot call java.util.ArrayList#add(capture-of ?) with arguments [int]'
+            'Cannot call java.util.ArrayList#add(capture-of ?) with arguments [int]'
 
         shouldFailWithMessages '''
             List<? extends Number> list = []
             list.add(1)
         ''',
-        'Cannot call java.util.ArrayList#add(capture-of ? extends java.lang.Number) with arguments [int]'
+            'Cannot call java.util.ArrayList#add(capture-of ? extends java.lang.Number) with arguments [int]'
     }
 
     @Test
@@ -125,7 +125,7 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
             List<String> list = []
             list << 1
         ''',
-        'Cannot call <T> org.codehaus.groovy.runtime.DefaultGroovyMethods#leftShift(java.util.List<T>, T) with arguments [java.util.ArrayList<java.lang.String>, int]'
+            'Cannot call <T> org.codehaus.groovy.runtime.DefaultGroovyMethods#leftShift(java.util.List<T>, T) with arguments [java.util.ArrayList<java.lang.String>, int]'
     }
 
     @Test
@@ -134,7 +134,7 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
             List<? extends Number> list = []
             list << 1
         ''',
-        'Cannot call <T> org.codehaus.groovy.runtime.DefaultGroovyMethods#leftShift(java.util.List<T>, T) with arguments [java.util.ArrayList<? extends java.lang.Number>, int]'
+            'Cannot call <T> org.codehaus.groovy.runtime.DefaultGroovyMethods#leftShift(java.util.List<T>, T) with arguments [java.util.ArrayList<? extends java.lang.Number>, int]'
     }
 
     @Test
@@ -176,7 +176,7 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
             List<Integer> list = new LinkedList<>()
             list.add 'Hello'
         ''',
-        'Cannot call java.util.LinkedList#add(java.lang.Integer) with arguments [java.lang.String]'
+            'Cannot call java.util.LinkedList#add(java.lang.Integer) with arguments [java.lang.String]'
     }
 
     @Test
@@ -185,7 +185,7 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
             List<Integer> list = new LinkedList<>()
             list << 'Hello'
         ''',
-        'Cannot call <T> org.codehaus.groovy.runtime.DefaultGroovyMethods#leftShift(java.util.List<T>, T) with arguments [java.util.LinkedList<java.lang.Integer>, java.lang.String]'
+            'Cannot call <T> org.codehaus.groovy.runtime.DefaultGroovyMethods#leftShift(java.util.List<T>, T) with arguments [java.util.LinkedList<java.lang.Integer>, java.lang.String]'
     }
 
     @Test
@@ -209,7 +209,7 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
             int a = 42
             def b = (a < new Object())
         ''',
-        'Cannot call java.lang.Integer#compareTo(java.lang.Integer) with arguments [java.lang.Object]'
+            'Cannot call java.lang.Integer#compareTo(java.lang.Integer) with arguments [java.lang.Object]'
     }
 
     @Test
@@ -333,7 +333,7 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
             Number number = Optional.of(42).orElse(Double.NaN)
             assert number.intValue() == 42
         ''',
-        'Cannot call java.util.Optional#orElse(java.lang.Integer) with arguments [double]'
+            'Cannot call java.util.Optional#orElse(java.lang.Integer) with arguments [double]'
     }
 
     @Test
@@ -351,7 +351,7 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
             Number number = Optional.ofNullable((Integer) null).orElse(42d)
             assert number.intValue() == 42
         ''',
-        'Cannot call java.util.Optional#orElse(java.lang.Integer) with arguments [double]'
+            'Cannot call java.util.Optional#orElse(java.lang.Integer) with arguments [double]'
     }
 
     @Test
@@ -372,7 +372,8 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
               return x // List<E>
             }
         ''',
-        'Incompatible generic argument types.' // Cannot assign java.util.ArrayList<java.lang.Object> to: java.util.List<java.lang.String>
+            'Incompatible generic argument types.'
+        // Cannot assign java.util.ArrayList<java.lang.Object> to: java.util.List<java.lang.String>
 
         assertScript '''
             @ASTTest(phase=INSTRUCTION_SELECTION, value={
@@ -642,18 +643,18 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
         shouldFailWithMessages '''
             List<CharSequence> list = ['x'].collect()
         ''',
-        'Incompatible generic argument types. Cannot assign java.util.List<java.lang.String> to: java.util.List<java.lang.CharSequence>'
+            'Incompatible generic argument types. Cannot assign java.util.List<java.lang.String> to: java.util.List<java.lang.CharSequence>'
 
         shouldFailWithMessages '''
             List<CharSequence> list = ['x'].stream().toList()
         ''',
-        'Incompatible generic argument types. Cannot assign java.util.List<java.lang.String> to: java.util.List<java.lang.CharSequence>'
+            'Incompatible generic argument types. Cannot assign java.util.List<java.lang.String> to: java.util.List<java.lang.CharSequence>'
 
         shouldFailWithMessages '''
             import static java.util.stream.Collectors.toList
             List<CharSequence> list = ['x'].stream().collect(toList())
         ''',
-        'Incompatible generic argument types. Cannot assign java.util.List<java.lang.String> to: java.util.List<java.lang.CharSequence>'
+            'Incompatible generic argument types. Cannot assign java.util.List<java.lang.String> to: java.util.List<java.lang.CharSequence>'
 
         assertScript '''
             import static java.util.stream.Collectors.toList
@@ -676,7 +677,7 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
                 chars()
             }
         ''',
-        'Cannot return value of type java.lang.CharSequence for method returning java.util.List'
+            'Cannot return value of type java.lang.CharSequence for method returning java.util.List'
     }
 
     // GROOVY-10098
@@ -722,7 +723,7 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
             def mmap = (ListMultimap<String, Integer>) ArrayListMultimap.create()
             Map<String, Set<Integer>> map = mmap.asMap()
         ''',
-        'Cannot assign java.util.Map<java.lang.String, java.util.Collection<java.lang.Integer>> to: java.util.Map<java.lang.String, java.util.Set<java.lang.Integer>>'
+            'Cannot assign java.util.Map<java.lang.String, java.util.Collection<java.lang.Integer>> to: java.util.Map<java.lang.String, java.util.Set<java.lang.Integer>>'
     }
 
     // GROOVY-10120
@@ -736,7 +737,7 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
             ListMultimap<String, Integer> mmap = ArrayListMultimap.create()
             Map<String, Set<Integer>> map = mmap.asMap() // ArrayListMultimap#asMap() is bridge and lacks generics
         ''',
-        'Cannot assign java.util.Map<java.lang.String, java.util.Collection<java.lang.Integer>> to: java.util.Map<java.lang.String, java.util.Set<java.lang.Integer>>'
+            'Cannot assign java.util.Map<java.lang.String, java.util.Collection<java.lang.Integer>> to: java.util.Map<java.lang.String, java.util.Set<java.lang.Integer>>'
     }
 
     // GROOVY-10222
@@ -804,7 +805,7 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
                 }
                 $type z = bar(foo(), 1)
             """,
-            'Cannot assign value of type (java.io.Serializable & java.lang.Comparable',') to variable of type java.lang.' + type
+                'Cannot assign value of type (java.io.Serializable & java.lang.Comparable', ') to variable of type java.lang.' + type
         }
     }
 
@@ -1234,12 +1235,12 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
 
             A<String> ax = new C<>(new D())
         ''',
-        'Incompatible generic argument types. Cannot assign C<D> to: A<java.lang.String>'
+            'Incompatible generic argument types. Cannot assign C<D> to: A<java.lang.String>'
 
         shouldFailWithMessages '''
             Set<List<String>> strings = new HashSet<>([new ArrayList<Number>()])
         ''',
-        'Incompatible generic argument types. Cannot assign java.util.HashSet<java.util.ArrayList<java.lang.Number>> to: java.util.Set<java.util.List<java.lang.String>>'
+            'Incompatible generic argument types. Cannot assign java.util.HashSet<java.util.ArrayList<java.lang.Number>> to: java.util.Set<java.util.List<java.lang.String>>'
     }
 
     // GROOVY-9972
@@ -1331,7 +1332,7 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
                 cd.p.f.toLowerCase()
             }
         ''',
-        'Incompatible generic argument types. Cannot assign C<? extends java.lang.Object> to: C<D>'
+            'Incompatible generic argument types. Cannot assign C<? extends java.lang.Object> to: C<D>'
     }
 
     // GROOVY-9963
@@ -1385,7 +1386,7 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
             }
             m(0, new C<>(1))
         ''',
-        'Cannot call', 'm(int, C<D>[]) with arguments [int, C<java.lang.Integer>]'
+            'Cannot call', 'm(int, C<D>[]) with arguments [int, C<java.lang.Integer>]'
     }
 
     // GROOVY-9970
@@ -1453,10 +1454,10 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
             C.m(new A<>(new Object()))
             C.m(true ? new A<>(new B()) : new A<>(new Object()))
         ''',
-        'Cannot assign A<java.lang.Object> to: A<B>',
-        'Cannot assign A<? extends java.lang.Object> to: A<B>',
-        'Cannot call C#m(A<B>) with arguments [A<java.lang.Object>]',
-        'Cannot call C#m(A<B>) with arguments [A<? extends java.lang.Object>]'
+            'Cannot assign A<java.lang.Object> to: A<B>',
+            'Cannot assign A<? extends java.lang.Object> to: A<B>',
+            'Cannot call C#m(A<B>) with arguments [A<java.lang.Object>]',
+            'Cannot call C#m(A<B>) with arguments [A<? extends java.lang.Object>]'
     }
 
     // GROOVY-10114
@@ -2059,7 +2060,7 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
         shouldFailWithMessages '''
             List<String> list = new LinkedList<String>([1,2,3])
         ''',
-        'Cannot call java.util.LinkedList#<init>(java.util.Collection<? extends java.lang.String>) with arguments [java.util.ArrayList<java.lang.Integer>]'
+            'Cannot call java.util.LinkedList#<init>(java.util.Collection<? extends java.lang.String>) with arguments [java.util.ArrayList<java.lang.Integer>]'
     }
 
     @Test
@@ -2074,7 +2075,7 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
         shouldFailWithMessages '''
             List<Integer> list = new groovy.transform.stc.GenericsSTCTest.MyList()
         ''',
-        'Incompatible generic argument types'
+            'Incompatible generic argument types'
     }
 
     @Test
@@ -2093,7 +2094,7 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
         shouldFailWithMessages '''
             List<? super Number> list = ['string']
         ''',
-        'Cannot assign java.util.ArrayList<java.lang.String> to: java.util.List<? super java.lang.Number>'
+            'Cannot assign java.util.ArrayList<java.lang.String> to: java.util.List<? super java.lang.Number>'
     }
 
     // GROOVY-10371
@@ -2108,7 +2109,7 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
             B<? extends Number> x = new B<Double>()
             A<Number> y = x
         ''',
-        'Incompatible generic argument types. Cannot assign B<java.lang.Double> to: A<java.lang.Number>'
+            'Incompatible generic argument types. Cannot assign B<java.lang.Double> to: A<java.lang.Number>'
     }
 
     // GROOVY-9914, GROOVY-10036
@@ -2191,7 +2192,7 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
                 N n = m()
             }
         ''',
-        'Cannot assign value of type java.lang.CharSequence to variable of type N'
+            'Cannot assign value of type java.lang.CharSequence to variable of type N'
     }
 
     // GROOVY-10754
@@ -2371,7 +2372,7 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
             }
             new Foo()
         ''',
-        'Cannot find matching method FooWithGenerics#say(java.lang.Object)'
+            'Cannot find matching method FooWithGenerics#say(java.lang.Object)'
     }
 
     // GROOVY-5237
@@ -2502,7 +2503,7 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
             A<String, Integer> a = new B()
             a.test()
         ''',
-        'Cannot call A#<init>(java.lang.Class<java.lang.String>, java.lang.Class<java.lang.Integer>) with arguments [java.lang.Class<java.lang.Integer>, java.lang.Class<java.lang.String>]'
+            'Cannot call A#<init>(java.lang.Class<java.lang.String>, java.lang.Class<java.lang.Integer>) with arguments [java.lang.Class<java.lang.Integer>, java.lang.Class<java.lang.String>]'
     }
 
     @Test
@@ -2555,7 +2556,7 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
             def map = new HashMap<String, Integer>()
             map.put('hello', new Object())
         ''',
-        'Cannot call java.util.HashMap#put(java.lang.String, java.lang.Integer) with arguments [java.lang.String, java.lang.Object]'
+            'Cannot call java.util.HashMap#put(java.lang.String, java.lang.Integer) with arguments [java.lang.String, java.lang.Object]'
     }
 
     @Test
@@ -2564,7 +2565,7 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
             def map = new HashMap<Integer, Integer>()
             map[123] = new Object()
         ''',
-        'Cannot call <K,V> org.codehaus.groovy.runtime.DefaultGroovyMethods#putAt(java.util.Map<K, V>, K, V) with arguments [java.util.HashMap<java.lang.Integer, java.lang.Integer>, int, java.lang.Object]'
+            'Cannot call <K,V> org.codehaus.groovy.runtime.DefaultGroovyMethods#putAt(java.util.Map<K, V>, K, V) with arguments [java.util.HashMap<java.lang.Integer, java.lang.Integer>, int, java.lang.Object]'
     }
 
     // GROOVY-8788
@@ -2574,7 +2575,7 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
             def map = new HashMap<String, Integer>()
             map['x'] = new Object()
         ''',
-        'Cannot assign value of type java.lang.Object to variable of type java.lang.Integer'
+            'Cannot assign value of type java.lang.Object to variable of type java.lang.Integer'
     }
 
     // GROOVY-9069
@@ -2590,8 +2591,8 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
                 }
             }
         ''',
-        'Cannot call java.util.Map#put(java.lang.String, java.util.Map<java.lang.String, java.util.List<java.lang.String>>) with arguments [java.lang.String, java.util.LinkedHashMap<java.lang.String, java.util.List<ConfigAttribute>>]',
-        'Cannot assign java.util.LinkedHashMap<java.lang.String, java.util.List<ConfigAttribute>> to: java.util.Map<java.lang.String, java.util.List<java.lang.String>>'
+            'Cannot call java.util.Map#put(java.lang.String, java.util.Map<java.lang.String, java.util.List<java.lang.String>>) with arguments [java.lang.String, java.util.LinkedHashMap<java.lang.String, java.util.List<ConfigAttribute>>]',
+            'Cannot assign java.util.LinkedHashMap<java.lang.String, java.util.List<ConfigAttribute>> to: java.util.Map<java.lang.String, java.util.List<java.lang.String>>'
 
         assertScript '''
             void test(Map<String, Map<String, List<String>>> maps) {
@@ -2686,8 +2687,8 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
             }
             new Test()
         ''',
-        'Cannot find matching method java.lang.Object#getAt(int)',
-        'Cannot find matching method java.lang.Object#toInteger()'
+            'Cannot find matching method java.lang.Object#getAt(int)',
+            'Cannot find matching method java.lang.Object#toInteger()'
     }
 
     @Test
@@ -2731,7 +2732,7 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
         }
         new ClassB()
         ''',
-        'Cannot call <X> groovy.transform.stc.GenericsSTCTest$ClassA#bar(java.lang.Class<java.lang.Long>) with arguments [java.lang.Class<? extends java.lang.Object>]'
+            'Cannot call <X> groovy.transform.stc.GenericsSTCTest$ClassA#bar(java.lang.Class<java.lang.Long>) with arguments [java.lang.Class<? extends java.lang.Object>]'
     }
 
     // GROOVY-8961, GROOVY-9915
@@ -2759,7 +2760,7 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
                 }
                 x = Collections.<Integer>emptyList()
             """,
-            'Incompatible generic argument types. Cannot assign java.util.List<java.lang.Integer> to: java.util.List<java.lang.String>'
+                'Incompatible generic argument types. Cannot assign java.util.List<java.lang.Integer> to: java.util.List<java.lang.String>'
         }
     }
 
@@ -2986,7 +2987,7 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
 
             new D<Number>().p = 'x'
         ''',
-        'Cannot assign value of type java.lang.String to variable of type java.lang.Number'
+            'Cannot assign value of type java.lang.String to variable of type java.lang.Number'
     }
 
     // GROOVY-10225
@@ -3094,7 +3095,7 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
             Collection<Integer> numbers = (Collection<Integer>) [1,2,3]
             boolean r = list.addAll(numbers)
         ''',
-        'Cannot call java.util.ArrayList#addAll(java.util.Collection<? extends java.lang.String>) with arguments [java.util.Collection<java.lang.Integer>]'
+            'Cannot call java.util.ArrayList#addAll(java.util.Collection<? extends java.lang.String>) with arguments [java.util.Collection<java.lang.Integer>]'
     }
 
     // GROOVY-5528
@@ -3128,7 +3129,7 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
             })
             List<String> list = ["foo", "$bar"]
         ''',
-        'You are trying to use a GString in place of a String'
+            'You are trying to use a GString in place of a String'
 
         shouldFailWithMessages base + '''
             @ASTTest(phase=INSTRUCTION_SELECTION, value={
@@ -3137,13 +3138,13 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
             })
             List<String> list = ["$bar"]
         ''',
-        'You are trying to use a GString in place of a String'
+            'You are trying to use a GString in place of a String'
 
         shouldFailWithMessages base + '''
             void m(List<String> list) {}
             m(["foo", "$bar"])
         ''',
-        'You are trying to use a GString in place of a String'
+            'You are trying to use a GString in place of a String'
     }
 
     // GROOVY-5559, GROOVY-10010
@@ -3154,19 +3155,19 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
         shouldFailWithMessages base + '''
             Map<String,String> map = [x:"foo", y:"$bar"]
         ''',
-        'You are trying to use a GString in place of a String'
+            'You are trying to use a GString in place of a String'
 
         shouldFailWithMessages base + '''
             void m(Map<?,String> map) {}
             m([x:"foo", y:"$bar"])
         ''',
-        'You are trying to use a GString in place of a String'
+            'You are trying to use a GString in place of a String'
 
         shouldFailWithMessages base + '''
             void m(Map<?,String> map) {}
             m(x:"foo", y:"$bar")
         ''',
-        'You are trying to use a GString in place of a String'
+            'You are trying to use a GString in place of a String'
     }
 
     // GROOVY-5559: related behaviour
@@ -3313,7 +3314,7 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
         shouldFailWithMessages '''
             Map<String,Date> map = new HashMap<>() {}
         ''',
-        'Cannot use diamond <> with anonymous inner classes'
+            'Cannot use diamond <> with anonymous inner classes'
     }
 
     // GROOVY-5614
@@ -3348,6 +3349,7 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
             r.bindings2.put('b', 'B')
         '''
     }
+
     @Test
     void testInferDiamondForAssignment() {
         assertScript '''
@@ -3366,6 +3368,7 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
             Map<String, String> map = new HashMap<>()
         '''
     }
+
     @Test
     void testInferDiamondForAssignmentWithDates() {
         assertScript '''
@@ -3385,6 +3388,7 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
             Map<Date, Date> map = new HashMap<>()
         '''
     }
+
     @Test
     void testInferDiamondForAssignmentWithDatesAndIllegalKeyUsingPut() {
         shouldFailWithMessages '''
@@ -3404,8 +3408,9 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
             Map<Date, Date> map = new HashMap<>()
             map.put(123, new Date())
         ''',
-        'Cannot call java.util.HashMap#put(java.util.Date, java.util.Date) with arguments [int, java.util.Date]'
+            'Cannot call java.util.HashMap#put(java.util.Date, java.util.Date) with arguments [int, java.util.Date]'
     }
+
     @Test
     void testInferDiamondForAssignmentWithDatesAndIllegalKeyUsingSquareBracket() {
         shouldFailWithMessages '''
@@ -3425,8 +3430,9 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
             Map<Date, Date> map = new HashMap<>()
             map[123] = new Date()
         ''',
-        'Cannot call <K,V> org.codehaus.groovy.runtime.DefaultGroovyMethods#putAt(java.util.Map<K, V>, K, V) with arguments [java.util.HashMap<java.util.Date, java.util.Date>, int, java.util.Date]'
+            'Cannot call <K,V> org.codehaus.groovy.runtime.DefaultGroovyMethods#putAt(java.util.Map<K, V>, K, V) with arguments [java.util.HashMap<java.util.Date, java.util.Date>, int, java.util.Date]'
     }
+
     @Test
     void testInferDiamondForAssignmentWithDatesAndIllegalValueUsingPut() {
         shouldFailWithMessages '''
@@ -3446,8 +3452,9 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
             Map<Date, Date> map = new HashMap<>()
             map.put(new Date(), 'foo')
         ''',
-        'Cannot call java.util.HashMap#put(java.util.Date, java.util.Date) with arguments [java.util.Date, java.lang.String]'
+            'Cannot call java.util.HashMap#put(java.util.Date, java.util.Date) with arguments [java.util.Date, java.lang.String]'
     }
+
     @Test
     void testInferDiamondForAssignmentWithDatesAndIllegalValueUsingSquareBracket() {
         shouldFailWithMessages '''
@@ -3467,7 +3474,7 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
             Map<Date, Date> map = new HashMap<>()
             map[new Date()] = 'foo'
         ''',
-        'Cannot call <K,V> org.codehaus.groovy.runtime.DefaultGroovyMethods#putAt(java.util.Map<K, V>, K, V) with arguments [java.util.HashMap<java.util.Date, java.util.Date>, java.util.Date, java.lang.String]'
+            'Cannot call <K,V> org.codehaus.groovy.runtime.DefaultGroovyMethods#putAt(java.util.Map<K, V>, K, V) with arguments [java.util.HashMap<java.util.Date, java.util.Date>, java.util.Date, java.lang.String]'
     }
 
     @Test
@@ -3826,7 +3833,7 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
             def <T extends Number> T test(T one, T two) { }
             test(1,"II")
         ''',
-        '#test(int, java.lang.String). Please check if the declared type is correct and if the method exists.'
+            '#test(int, java.lang.String). Please check if the declared type is correct and if the method exists.'
     }
 
     // GROOVY-7720
@@ -3840,7 +3847,7 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
                 }
             }
         ''',
-        'Cannot assign value of type U to variable of type T'
+            'Cannot assign value of type U to variable of type T'
 
         shouldFailWithMessages '''
             class Converter<T, U> {
@@ -3849,7 +3856,7 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
                 }
             }
         ''',
-        'Cannot return value of type T for method returning U'
+            'Cannot return value of type T for method returning U'
 
         shouldFailWithMessages '''
             class C<X, Y> {
@@ -3858,7 +3865,7 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
                 new X[]{new C<Object, String>()}
             }
         ''',
-        'Cannot convert from C<java.lang.Object, java.lang.String> to X'
+            'Cannot convert from C<java.lang.Object, java.lang.String> to X'
     }
 
     // GROOVY-9902: incomplete generics should not stop type checking
@@ -3897,9 +3904,9 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
 
             test(new Holder<Object>())
         ''',
-        'Cannot call TypedProperty#eq(java.lang.String) with arguments [groovy.lang.GString]',
-        'Cannot call TypedProperty#eq(java.lang.String) with arguments [int]',
-        'Cannot call TypedProperty#eq(java.lang.Number) with arguments [java.lang.String]'
+            'Cannot call TypedProperty#eq(java.lang.String) with arguments [groovy.lang.GString]',
+            'Cannot call TypedProperty#eq(java.lang.String) with arguments [int]',
+            'Cannot call TypedProperty#eq(java.lang.Number) with arguments [java.lang.String]'
     }
 
     // GROOVY-5748
@@ -4162,8 +4169,8 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
             Map<String,?> getTwo() { [:] }
             def one_plus_two = one + two // <K,V> Map<K,V> plus(Map<K,V> left, Map<K,V> right)
         ''',
-        'Cannot call <K,V> org.codehaus.groovy.runtime.DefaultGroovyMethods#plus(java.util.Map<K, V>, java.util.Map<K, V>)' +
-        ' with arguments [java.util.LinkedHashMap<java.lang.String, ?>, java.util.Map<java.lang.String, ? extends java.lang.Object>]'
+            'Cannot call <K,V> org.codehaus.groovy.runtime.DefaultGroovyMethods#plus(java.util.Map<K, V>, java.util.Map<K, V>)' +
+                ' with arguments [java.util.LinkedHashMap<java.lang.String, ?>, java.util.Map<java.lang.String, ? extends java.lang.Object>]'
     }
 
     @Test
@@ -4174,7 +4181,7 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
             }
             Foo.bar([new Object()])
         ''',
-        'Cannot call <T extends java.util.List<? extends java.lang.CharSequence>> Foo#bar(T) with arguments [java.util.ArrayList<java.lang.Object>]'
+            'Cannot call <T extends java.util.List<? extends java.lang.CharSequence>> Foo#bar(T) with arguments [java.util.ArrayList<java.lang.Object>]'
     }
 
     @Test
@@ -4187,7 +4194,7 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
                 Foo.bar(list)
             }
         ''',
-        'Cannot call <T extends java.util.List<? extends java.lang.CharSequence>> Foo#bar(T) with arguments [U]'
+            'Cannot call <T extends java.util.List<? extends java.lang.CharSequence>> Foo#bar(T) with arguments [U]'
     }
 
     // GROOVY-8084
@@ -4201,8 +4208,8 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
             }
             Foo.bar(new ArrayList<Integer>())
         ''',
-        'Cannot call java.util.List#add(capture-of ? extends java.io.Serializable) with arguments [java.lang.String]',
-        'Cannot find matching method java.lang.Class#bar(java.util.ArrayList<java.lang.Integer>) or static method Foo#bar(java.util.ArrayList<java.lang.Integer>)'
+            'Cannot call java.util.List#add(capture-of ? extends java.io.Serializable) with arguments [java.lang.String]',
+            'Cannot find matching method java.lang.Class#bar(java.util.ArrayList<java.lang.Integer>) or static method Foo#bar(java.util.ArrayList<java.lang.Integer>)'
     }
 
     // GROOVY-9074
@@ -4226,7 +4233,7 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
                 shapes.add(0, new Rectangle())
             }
         ''',
-        'Cannot call java.util.List#add(int, capture-of ? extends Shape) with arguments [int, Rectangle]'
+            'Cannot call java.util.List#add(int, capture-of ? extends Shape) with arguments [int, Rectangle]'
     }
 
     @Test
@@ -4265,7 +4272,7 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
             }
             Foo.bar(['abc'])
         ''',
-        'Cannot call <T extends java.util.List<? super java.lang.CharSequence>> Foo#bar(T) with arguments [java.util.ArrayList<java.lang.String>]'
+            'Cannot call <T extends java.util.List<? super java.lang.CharSequence>> Foo#bar(T) with arguments [java.util.ArrayList<java.lang.String>]'
     }
 
     @Test
@@ -4278,7 +4285,7 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
                 Foo.bar(list)
             }
         ''',
-        'Cannot call <T extends java.util.List<? super java.lang.CharSequence>> Foo#bar(T) with arguments [U]'
+            'Cannot call <T extends java.util.List<? super java.lang.CharSequence>> Foo#bar(T) with arguments [U]'
     }
 
     // GROOVY-5721
@@ -4616,9 +4623,29 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
         }
     }
 
-    // GROOVY-11022
+    // 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 the in-flight LUB pair guard.
+        assertScript '''
+            class A<T> {}
+            class W<T> {}
+            class B extends A<W<B>> {}
+            class D extends A<W<D>> {}
+
+            @groovy.transform.TypeChecked
+            def test(boolean f) {
+                def x = f ? new B() : new D()
+                x.class.name
+            }
+            assert test(true) == 'B'
+        '''
+    }
+
+    // GROOVY-11022
+    @Test
+    void testNoStackOverflow4() {
         // F-bounded type parameter with self-reference inside a `? super` wildcard
         assertScript '''
             class C<K, V> {