From 61e9aa9f42b34666d94067c23f9c54eea1226a63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Deleuze?= Date: Thu, 23 Jun 2022 15:21:38 +0200 Subject: [PATCH] Fix StackOverflowError in BindingReflectionHintsRegistrar This commit fixes the cycle detection in BindingReflectionHintsRegistrar. See gh-28683 --- .../BindingReflectionHintsRegistrar.java | 96 +++++++++++-------- .../BindingReflectionHintsRegistrarTests.java | 20 ++++ 2 files changed, 74 insertions(+), 42 deletions(-) diff --git a/spring-core/src/main/java/org/springframework/aot/hint/support/BindingReflectionHintsRegistrar.java b/spring-core/src/main/java/org/springframework/aot/hint/support/BindingReflectionHintsRegistrar.java index 714e0a0abc..7b37b73a3e 100644 --- a/spring-core/src/main/java/org/springframework/aot/hint/support/BindingReflectionHintsRegistrar.java +++ b/spring-core/src/main/java/org/springframework/aot/hint/support/BindingReflectionHintsRegistrar.java @@ -22,7 +22,6 @@ import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.reflect.Method; import java.lang.reflect.Type; -import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Set; import java.util.function.Consumer; @@ -34,7 +33,6 @@ import org.springframework.aot.hint.ExecutableHint; import org.springframework.aot.hint.ExecutableMode; import org.springframework.aot.hint.MemberCategory; import org.springframework.aot.hint.ReflectionHints; -import org.springframework.aot.hint.TypeHint.Builder; import org.springframework.core.KotlinDetector; import org.springframework.core.MethodParameter; import org.springframework.core.ResolvableType; @@ -66,16 +64,9 @@ public class BindingReflectionHintsRegistrar { * @param types the types to bind */ public void registerReflectionHints(ReflectionHints hints, Type... types) { + Set seen = new LinkedHashSet<>(); for (Type type : types) { - Set> referencedTypes = new LinkedHashSet<>(); - collectReferencedTypes(new HashSet<>(), referencedTypes, type); - for (Class referencedType : referencedTypes) { - hints.registerType(referencedType, builder -> { - if (shouldRegisterMembers(referencedType)) { - registerMembers(hints, referencedType, builder); - } - }); - } + registerReflectionHints(hints, seen, type); } } @@ -88,40 +79,60 @@ public class BindingReflectionHintsRegistrar { return !type.getCanonicalName().startsWith("java."); } - private void registerMembers(ReflectionHints hints, Class type, Builder builder) { - builder.withMembers(MemberCategory.DECLARED_FIELDS, - MemberCategory.INVOKE_DECLARED_CONSTRUCTORS); - try { - BeanInfo beanInfo = Introspector.getBeanInfo(type); - PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors(); - for (PropertyDescriptor propertyDescriptor : propertyDescriptors) { - Method writeMethod = propertyDescriptor.getWriteMethod(); - if (writeMethod != null && writeMethod.getDeclaringClass() != Object.class) { - hints.registerMethod(writeMethod, INVOKE); - MethodParameter methodParameter = MethodParameter.forExecutable(writeMethod, 0); - registerReflectionHints(hints, methodParameter.getGenericParameterType()); + private void registerReflectionHints(ReflectionHints hints, Set seen, Type type) { + if (type instanceof Class clazz) { + hints.registerType(clazz, builder -> { + if (seen.contains(type)) { + return; } - Method readMethod = propertyDescriptor.getReadMethod(); - if (readMethod != null && readMethod.getDeclaringClass() != Object.class) { - hints.registerMethod(readMethod, INVOKE); - MethodParameter methodParameter = MethodParameter.forExecutable(readMethod, -1); - registerReflectionHints(hints, methodParameter.getGenericParameterType()); + seen.add(type); + if (shouldRegisterMembers(clazz)) { + builder.withMembers( + MemberCategory.DECLARED_FIELDS, + MemberCategory.INVOKE_DECLARED_CONSTRUCTORS); + try { + BeanInfo beanInfo = Introspector.getBeanInfo(clazz); + PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors(); + for (PropertyDescriptor propertyDescriptor : propertyDescriptors) { + Method writeMethod = propertyDescriptor.getWriteMethod(); + if (writeMethod != null && writeMethod.getDeclaringClass() != Object.class) { + hints.registerMethod(writeMethod, INVOKE); + MethodParameter methodParameter = MethodParameter.forExecutable(writeMethod, 0); + Type methodParameterType = methodParameter.getGenericParameterType(); + if (!seen.contains(methodParameterType)) { + registerReflectionHints(hints, seen, methodParameterType); + } + } + Method readMethod = propertyDescriptor.getReadMethod(); + if (readMethod != null && readMethod.getDeclaringClass() != Object.class) { + hints.registerMethod(readMethod, INVOKE); + MethodParameter methodParameter = MethodParameter.forExecutable(readMethod, -1); + Type methodParameterType = methodParameter.getGenericParameterType(); + if (!seen.contains(methodParameterType)) { + registerReflectionHints(hints, seen, methodParameterType); + } + } + } + String companionClassName = clazz.getCanonicalName() + KOTLIN_COMPANION_SUFFIX; + if (KotlinDetector.isKotlinType(clazz) && ClassUtils.isPresent(companionClassName, null)) { + Class companionClass = ClassUtils.resolveClassName(companionClassName, null); + Method serializerMethod = ClassUtils.getMethodIfAvailable(companionClass, "serializer"); + if (serializerMethod != null) { + hints.registerMethod(serializerMethod); + } + } + } + catch (IntrospectionException ex) { + if (logger.isDebugEnabled()) { + logger.debug("Ignoring referenced type [" + clazz.getName() + "]: " + ex.getMessage()); + } + } } - } - String companionClassName = type.getCanonicalName() + KOTLIN_COMPANION_SUFFIX; - if (KotlinDetector.isKotlinType(type) && ClassUtils.isPresent(companionClassName, null)) { - Class companionClass = ClassUtils.resolveClassName(companionClassName, null); - Method serializerMethod = ClassUtils.getMethodIfAvailable(companionClass, "serializer"); - if (serializerMethod != null) { - hints.registerMethod(serializerMethod); - } - } - } - catch (IntrospectionException ex) { - if (logger.isDebugEnabled()) { - logger.debug("Ignoring referenced type [" + type.getName() + "]: " + ex.getMessage()); - } + }); } + Set> referencedTypes = new LinkedHashSet<>(); + collectReferencedTypes(seen, referencedTypes, type); + referencedTypes.forEach(referencedType -> registerReflectionHints(hints, seen, referencedType)); } private void collectReferencedTypes(Set seen, Set> types, @Nullable Type type) { @@ -138,4 +149,5 @@ public class BindingReflectionHintsRegistrar { } } } + } diff --git a/spring-core/src/test/java/org/springframework/aot/hint/support/BindingReflectionHintsRegistrarTests.java b/spring-core/src/test/java/org/springframework/aot/hint/support/BindingReflectionHintsRegistrarTests.java index f827b24656..9d87152191 100644 --- a/spring-core/src/test/java/org/springframework/aot/hint/support/BindingReflectionHintsRegistrarTests.java +++ b/spring-core/src/test/java/org/springframework/aot/hint/support/BindingReflectionHintsRegistrarTests.java @@ -131,6 +131,15 @@ public class BindingReflectionHintsRegistrarTests { }); } + @Test + void registerTypeForSerializationWithCycles() { + bindingRegistrar.registerReflectionHints(this.hints.reflection(), SampleClassWithCycles.class); + assertThat(this.hints.reflection().typeHints()).satisfiesExactlyInAnyOrder( + typeHint -> assertThat(typeHint.getType()).isEqualTo(TypeReference.of(SampleClassWithCycles.class)), + typeHint -> assertThat(typeHint.getType()).isEqualTo(TypeReference.of(List.class))); + } + + static class SampleEmptyClass { } @@ -172,4 +181,15 @@ public class BindingReflectionHintsRegistrarTests { } } + static class SampleClassWithCycles { + + public SampleClassWithCycles getSampleClassWithCycles() { + return null; + } + + public List getSampleClassWithCyclesList() { + return null; + } + } + }