Skip to content

Commit 61e9aa9

Browse files
committed
Fix StackOverflowError in BindingReflectionHintsRegistrar
This commit fixes the cycle detection in BindingReflectionHintsRegistrar. See spring-projectsgh-28683
1 parent a4ccec1 commit 61e9aa9

File tree

2 files changed

+74
-42
lines changed

2 files changed

+74
-42
lines changed

spring-core/src/main/java/org/springframework/aot/hint/support/BindingReflectionHintsRegistrar.java

Lines changed: 54 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
import java.beans.PropertyDescriptor;
2323
import java.lang.reflect.Method;
2424
import java.lang.reflect.Type;
25-
import java.util.HashSet;
2625
import java.util.LinkedHashSet;
2726
import java.util.Set;
2827
import java.util.function.Consumer;
@@ -34,7 +33,6 @@
3433
import org.springframework.aot.hint.ExecutableMode;
3534
import org.springframework.aot.hint.MemberCategory;
3635
import org.springframework.aot.hint.ReflectionHints;
37-
import org.springframework.aot.hint.TypeHint.Builder;
3836
import org.springframework.core.KotlinDetector;
3937
import org.springframework.core.MethodParameter;
4038
import org.springframework.core.ResolvableType;
@@ -66,16 +64,9 @@ public class BindingReflectionHintsRegistrar {
6664
* @param types the types to bind
6765
*/
6866
public void registerReflectionHints(ReflectionHints hints, Type... types) {
67+
Set<Type> seen = new LinkedHashSet<>();
6968
for (Type type : types) {
70-
Set<Class<?>> referencedTypes = new LinkedHashSet<>();
71-
collectReferencedTypes(new HashSet<>(), referencedTypes, type);
72-
for (Class<?> referencedType : referencedTypes) {
73-
hints.registerType(referencedType, builder -> {
74-
if (shouldRegisterMembers(referencedType)) {
75-
registerMembers(hints, referencedType, builder);
76-
}
77-
});
78-
}
69+
registerReflectionHints(hints, seen, type);
7970
}
8071
}
8172

@@ -88,40 +79,60 @@ protected boolean shouldRegisterMembers(Class<?> type) {
8879
return !type.getCanonicalName().startsWith("java.");
8980
}
9081

91-
private void registerMembers(ReflectionHints hints, Class<?> type, Builder builder) {
92-
builder.withMembers(MemberCategory.DECLARED_FIELDS,
93-
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
94-
try {
95-
BeanInfo beanInfo = Introspector.getBeanInfo(type);
96-
PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
97-
for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
98-
Method writeMethod = propertyDescriptor.getWriteMethod();
99-
if (writeMethod != null && writeMethod.getDeclaringClass() != Object.class) {
100-
hints.registerMethod(writeMethod, INVOKE);
101-
MethodParameter methodParameter = MethodParameter.forExecutable(writeMethod, 0);
102-
registerReflectionHints(hints, methodParameter.getGenericParameterType());
103-
}
104-
Method readMethod = propertyDescriptor.getReadMethod();
105-
if (readMethod != null && readMethod.getDeclaringClass() != Object.class) {
106-
hints.registerMethod(readMethod, INVOKE);
107-
MethodParameter methodParameter = MethodParameter.forExecutable(readMethod, -1);
108-
registerReflectionHints(hints, methodParameter.getGenericParameterType());
82+
private void registerReflectionHints(ReflectionHints hints, Set<Type> seen, Type type) {
83+
if (type instanceof Class<?> clazz) {
84+
hints.registerType(clazz, builder -> {
85+
if (seen.contains(type)) {
86+
return;
10987
}
110-
}
111-
String companionClassName = type.getCanonicalName() + KOTLIN_COMPANION_SUFFIX;
112-
if (KotlinDetector.isKotlinType(type) && ClassUtils.isPresent(companionClassName, null)) {
113-
Class<?> companionClass = ClassUtils.resolveClassName(companionClassName, null);
114-
Method serializerMethod = ClassUtils.getMethodIfAvailable(companionClass, "serializer");
115-
if (serializerMethod != null) {
116-
hints.registerMethod(serializerMethod);
88+
seen.add(type);
89+
if (shouldRegisterMembers(clazz)) {
90+
builder.withMembers(
91+
MemberCategory.DECLARED_FIELDS,
92+
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
93+
try {
94+
BeanInfo beanInfo = Introspector.getBeanInfo(clazz);
95+
PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
96+
for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
97+
Method writeMethod = propertyDescriptor.getWriteMethod();
98+
if (writeMethod != null && writeMethod.getDeclaringClass() != Object.class) {
99+
hints.registerMethod(writeMethod, INVOKE);
100+
MethodParameter methodParameter = MethodParameter.forExecutable(writeMethod, 0);
101+
Type methodParameterType = methodParameter.getGenericParameterType();
102+
if (!seen.contains(methodParameterType)) {
103+
registerReflectionHints(hints, seen, methodParameterType);
104+
}
105+
}
106+
Method readMethod = propertyDescriptor.getReadMethod();
107+
if (readMethod != null && readMethod.getDeclaringClass() != Object.class) {
108+
hints.registerMethod(readMethod, INVOKE);
109+
MethodParameter methodParameter = MethodParameter.forExecutable(readMethod, -1);
110+
Type methodParameterType = methodParameter.getGenericParameterType();
111+
if (!seen.contains(methodParameterType)) {
112+
registerReflectionHints(hints, seen, methodParameterType);
113+
}
114+
}
115+
}
116+
String companionClassName = clazz.getCanonicalName() + KOTLIN_COMPANION_SUFFIX;
117+
if (KotlinDetector.isKotlinType(clazz) && ClassUtils.isPresent(companionClassName, null)) {
118+
Class<?> companionClass = ClassUtils.resolveClassName(companionClassName, null);
119+
Method serializerMethod = ClassUtils.getMethodIfAvailable(companionClass, "serializer");
120+
if (serializerMethod != null) {
121+
hints.registerMethod(serializerMethod);
122+
}
123+
}
124+
}
125+
catch (IntrospectionException ex) {
126+
if (logger.isDebugEnabled()) {
127+
logger.debug("Ignoring referenced type [" + clazz.getName() + "]: " + ex.getMessage());
128+
}
129+
}
117130
}
118-
}
119-
}
120-
catch (IntrospectionException ex) {
121-
if (logger.isDebugEnabled()) {
122-
logger.debug("Ignoring referenced type [" + type.getName() + "]: " + ex.getMessage());
123-
}
131+
});
124132
}
133+
Set<Class<?>> referencedTypes = new LinkedHashSet<>();
134+
collectReferencedTypes(seen, referencedTypes, type);
135+
referencedTypes.forEach(referencedType -> registerReflectionHints(hints, seen, referencedType));
125136
}
126137

127138
private void collectReferencedTypes(Set<Type> seen, Set<Class<?>> types, @Nullable Type type) {
@@ -138,4 +149,5 @@ private void collectReferencedTypes(Set<Type> seen, Set<Class<?>> types, @Nullab
138149
}
139150
}
140151
}
152+
141153
}

spring-core/src/test/java/org/springframework/aot/hint/support/BindingReflectionHintsRegistrarTests.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,15 @@ void registerTypeForSerializationWithListProperty() {
131131
});
132132
}
133133

134+
@Test
135+
void registerTypeForSerializationWithCycles() {
136+
bindingRegistrar.registerReflectionHints(this.hints.reflection(), SampleClassWithCycles.class);
137+
assertThat(this.hints.reflection().typeHints()).satisfiesExactlyInAnyOrder(
138+
typeHint -> assertThat(typeHint.getType()).isEqualTo(TypeReference.of(SampleClassWithCycles.class)),
139+
typeHint -> assertThat(typeHint.getType()).isEqualTo(TypeReference.of(List.class)));
140+
}
141+
142+
134143
static class SampleEmptyClass {
135144
}
136145

@@ -172,4 +181,15 @@ public void setNames(List<String> names) {
172181
}
173182
}
174183

184+
static class SampleClassWithCycles {
185+
186+
public SampleClassWithCycles getSampleClassWithCycles() {
187+
return null;
188+
}
189+
190+
public List<SampleClassWithCycles> getSampleClassWithCyclesList() {
191+
return null;
192+
}
193+
}
194+
175195
}

0 commit comments

Comments
 (0)