Fix StackOverflowError in BindingReflectionHintsRegistrar
This commit fixes the cycle detection in BindingReflectionHintsRegistrar. See gh-28683
This commit is contained in:
@@ -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<Type> seen = new LinkedHashSet<>();
|
||||
for (Type type : types) {
|
||||
Set<Class<?>> 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<Type> 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<Class<?>> referencedTypes = new LinkedHashSet<>();
|
||||
collectReferencedTypes(seen, referencedTypes, type);
|
||||
referencedTypes.forEach(referencedType -> registerReflectionHints(hints, seen, referencedType));
|
||||
}
|
||||
|
||||
private void collectReferencedTypes(Set<Type> seen, Set<Class<?>> types, @Nullable Type type) {
|
||||
@@ -138,4 +149,5 @@ public class BindingReflectionHintsRegistrar {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<SampleClassWithCycles> getSampleClassWithCyclesList() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user