From ea3573176a5c80e5a7b2bd48a6d5d5ab5cfa4285 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Fri, 16 Feb 2024 11:15:02 +0100 Subject: [PATCH] Avoid infinite recursion for self-referencing generic type Closes gh-32282 See gh-30079 --- .../springframework/core/ResolvableType.java | 28 ++++++++++++++++--- .../core/ResolvableTypeTests.java | 24 +++++++++++++++- 2 files changed, 47 insertions(+), 5 deletions(-) diff --git a/spring-core/src/main/java/org/springframework/core/ResolvableType.java b/spring-core/src/main/java/org/springframework/core/ResolvableType.java index 037c280d43b..9734b2a61d8 100644 --- a/spring-core/src/main/java/org/springframework/core/ResolvableType.java +++ b/spring-core/src/main/java/org/springframework/core/ResolvableType.java @@ -28,8 +28,10 @@ import java.lang.reflect.TypeVariable; import java.lang.reflect.WildcardType; import java.util.Arrays; import java.util.Collection; +import java.util.HashSet; import java.util.IdentityHashMap; import java.util.Map; +import java.util.Set; import java.util.StringJoiner; import org.springframework.core.SerializableTypeWrapper.FieldTypeProvider; @@ -588,18 +590,28 @@ public class ResolvableType implements Serializable { if (this == NONE) { return false; } + return hasUnresolvableGenerics(null); + } + + private boolean hasUnresolvableGenerics(@Nullable Set alreadySeen) { Boolean unresolvableGenerics = this.unresolvableGenerics; if (unresolvableGenerics == null) { - unresolvableGenerics = determineUnresolvableGenerics(); + unresolvableGenerics = determineUnresolvableGenerics(alreadySeen); this.unresolvableGenerics = unresolvableGenerics; } return unresolvableGenerics; } - private boolean determineUnresolvableGenerics() { + private boolean determineUnresolvableGenerics(@Nullable Set alreadySeen) { + if (alreadySeen != null && alreadySeen.contains(this.type)) { + // Self-referencing generic -> not unresolvable + return false; + } + ResolvableType[] generics = getGenerics(); for (ResolvableType generic : generics) { - if (generic.isUnresolvableTypeVariable() || generic.isWildcardWithoutBounds() || generic.hasUnresolvableGenerics()) { + if (generic.isUnresolvableTypeVariable() || generic.isWildcardWithoutBounds() || + generic.hasUnresolvableGenerics(currentTypeSeen(alreadySeen))) { return true; } } @@ -619,12 +631,20 @@ public class ResolvableType implements Serializable { } Class superclass = resolved.getSuperclass(); if (superclass != null && superclass != Object.class) { - return getSuperType().hasUnresolvableGenerics(); + return getSuperType().hasUnresolvableGenerics(currentTypeSeen(alreadySeen)); } } return false; } + private Set currentTypeSeen(@Nullable Set alreadySeen) { + if (alreadySeen == null) { + alreadySeen = new HashSet<>(4); + } + alreadySeen.add(this.type); + return alreadySeen; + } + /** * Determine whether the underlying type is a type variable that * cannot be resolved through the associated variable resolver. diff --git a/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java b/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java index 64c0ee5ee45..5f4c1f0a816 100644 --- a/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java +++ b/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java @@ -1321,6 +1321,18 @@ class ResolvableTypeTests { assertThat(type.hasUnresolvableGenerics()).isTrue(); } + @Test + void hasUnresolvableGenericsWhenSelfReferring() { + ResolvableType type = ResolvableType.forInstance(new Bar()); + assertThat(type.hasUnresolvableGenerics()).isFalse(); + } + + @Test + void hasUnresolvableGenericsWithEnum() { + ResolvableType type = ResolvableType.forType(SimpleEnum.class.getGenericSuperclass()); + assertThat(type.hasUnresolvableGenerics()).isFalse(); + } + @Test void spr11219() throws Exception { ResolvableType type = ResolvableType.forField(BaseProvider.class.getField("stuff"), BaseProvider.class); @@ -1624,12 +1636,22 @@ class ResolvableTypeTests { } - public interface ListOfListSupplier { + interface ListOfListSupplier { List> get(); + } + + + class Foo> { + } + class Bar extends Foo { } + + enum SimpleEnum { VALUE } + + static class EnclosedInParameterizedType { static class InnerRaw {