Skip to content

AnnotationScanner scanning leads to StackOverflowError with recursive annotation #31400

@JanCizmar

Description

@JanCizmar

Affects: 6.0.12

I am trying to update from Spring boot 2.7.13 to 3.1.4.
We are using this custom annotation to document configuration properties.

@Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY)
annotation class DocProperty(
  val name: String = "",
  val displayName: String = "",
  val description: String = "",
  val defaultValue: String = "",
  val defaultExplanation: String = "",
  val children: Array<DocProperty> = [],
  val prefix: String = "",
  val removedIn: String = "",
  val removalReason: String = "",
  val hidden: Boolean = false
)

When I run the app, it fails:

2023-10-10T17:17:04.743+02:00 ERROR 22027 --- [  restartedMain] o.s.boot.SpringApplication               : Application run failed

org.springframework.beans.factory.BeanDefinitionStoreException: Failed to read candidate component class: URL [jar:file:/Users/jenik/IdeaProjects/tolgee-server/public/backend/data/build/libs/data-local-plain.jar!/io/tolgee/configuration/tolgee/S3Settings.class]
	at org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider.scanCandidateComponents(ClassPathScanningCandidateComponentProvider.java:463) ~[spring-context-6.0.12.jar:6.0.12]
	at org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider.findCandidateComponents(ClassPathScanningCandidateComponentProvider.java:317) ~[spring-context-6.0.12.jar:6.0.12]
	at org.springframework.context.annotation.ClassPathBeanDefinitionScanner.doScan(ClassPathBeanDefinitionScanner.java:276) ~[spring-context-6.0.12.jar:6.0.12]
	at org.springframework.context.annotation.ComponentScanAnnotationParser.parse(ComponentScanAnnotationParser.java:128) ~[spring-context-6.0.12.jar:6.0.12]
	at org.springframework.context.annotation.ConfigurationClassParser.doProcessConfigurationClass(ConfigurationClassParser.java:289) ~[spring-context-6.0.12.jar:6.0.12]
	at org.springframework.context.annotation.ConfigurationClassParser.processConfigurationClass(ConfigurationClassParser.java:243) ~[spring-context-6.0.12.jar:6.0.12]
	at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:196) ~[spring-context-6.0.12.jar:6.0.12]
	at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:164) ~[spring-context-6.0.12.jar:6.0.12]
	at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:415) ~[spring-context-6.0.12.jar:6.0.12]
	at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:287) ~[spring-context-6.0.12.jar:6.0.12]
	at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:344) ~[spring-context-6.0.12.jar:6.0.12]
	at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:115) ~[spring-context-6.0.12.jar:6.0.12]
	at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:771) ~[spring-context-6.0.12.jar:6.0.12]
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:589) ~[spring-context-6.0.12.jar:6.0.12]
	at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) ~[spring-boot-3.1.4.jar:3.1.4]
	at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:737) ~[spring-boot-3.1.4.jar:3.1.4]
	at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:439) ~[spring-boot-3.1.4.jar:3.1.4]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:315) ~[spring-boot-3.1.4.jar:3.1.4]
	at io.tolgee.Application$Companion.main(Application.kt:26) ~[main/:na]
	at io.tolgee.Application.main(Application.kt) ~[main/:na]
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
	at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
	at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:50) ~[spring-boot-devtools-3.1.4.jar:3.1.4]
Caused by: java.lang.StackOverflowError: null
	at java.base/java.lang.ref.ReferenceQueue.poll(ReferenceQueue.java:117) ~[na:na]
Caused by: java.lang.StackOverflowError: null

	at org.springframework.util.ConcurrentReferenceHashMap$ReferenceManager.pollForPurge(ConcurrentReferenceHashMap.java:1006) ~[spring-core-6.0.12.jar:6.0.12]
	at org.springframework.util.ConcurrentReferenceHashMap$Segment.restructureIfNecessary(ConcurrentReferenceHashMap.java:574) ~[spring-core-6.0.12.jar:6.0.12]
	at org.springframework.util.ConcurrentReferenceHashMap$Segment.getReference(ConcurrentReferenceHashMap.java:495) ~[spring-core-6.0.12.jar:6.0.12]
	at org.springframework.util.ConcurrentReferenceHashMap.getReference(ConcurrentReferenceHashMap.java:265) ~[spring-core-6.0.12.jar:6.0.12]
	at org.springframework.util.ConcurrentReferenceHashMap.get(ConcurrentReferenceHashMap.java:235) ~[spring-core-6.0.12.jar:6.0.12]
	at org.springframework.core.annotation.AnnotationsScanner.getDeclaredAnnotations(AnnotationsScanner.java:446) ~[spring-core-6.0.12.jar:6.0.12]
	at org.springframework.core.annotation.AnnotationsScanner.getDeclaredAnnotation(AnnotationsScanner.java:435) ~[spring-core-6.0.12.jar:6.0.12]
	at org.springframework.core.annotation.AnnotationTypeMapping.resolveAliasedForTargets(AnnotationTypeMapping.java:144) ~[spring-core-6.0.12.jar:6.0.12]
	at org.springframework.core.annotation.AnnotationTypeMapping.<init>(AnnotationTypeMapping.java:122) ~[spring-core-6.0.12.jar:6.0.12]
	at org.springframework.core.annotation.AnnotationTypeMappings.addIfPossible(AnnotationTypeMappings.java:112) ~[spring-core-6.0.12.jar:6.0.12]
	at org.springframework.core.annotation.AnnotationTypeMappings.addAllMappings(AnnotationTypeMappings.java:75) ~[spring-core-6.0.12.jar:6.0.12]
	at org.springframework.core.annotation.AnnotationTypeMappings.<init>(AnnotationTypeMappings.java:68) ~[spring-core-6.0.12.jar:6.0.12]
	at org.springframework.core.annotation.AnnotationTypeMappings$Cache.createMappings(AnnotationTypeMappings.java:245) ~[spring-core-6.0.12.jar:6.0.12]
	at java.base/java.util.concurrent.ConcurrentMap.computeIfAbsent(ConcurrentMap.java:330) ~[na:na]
	at org.springframework.core.annotation.AnnotationTypeMappings$Cache.get(AnnotationTypeMappings.java:241) ~[spring-core-6.0.12.jar:6.0.12]
	at org.springframework.core.annotation.AnnotationTypeMappings.forAnnotationType(AnnotationTypeMappings.java:199) ~[spring-core-6.0.12.jar:6.0.12]
	at org.springframework.core.annotation.AnnotationTypeMappings.forAnnotationType(AnnotationTypeMappings.java:182) ~[spring-core-6.0.12.jar:6.0.12]
	at org.springframework.core.annotation.AnnotationTypeMappings.forAnnotationType(AnnotationTypeMappings.java:169) ~[spring-core-6.0.12.jar:6.0.12]
	at org.springframework.core.annotation.AnnotationTypeMapping.computeSynthesizableFlag(AnnotationTypeMapping.java:405) ~[spring-core-6.0.12.jar:6.0.12]
	at org.springframework.core.annotation.AnnotationTypeMapping.<init>(AnnotationTypeMapping.java:126) ~[spring-core-6.0.12.jar:6.0.12]
	at org.springframework.core.annotation.AnnotationTypeMappings.addIfPossible(AnnotationTypeMappings.java:112) ~[spring-core-6.0.12.jar:6.0.12]
	at org.springframework.core.annotation.AnnotationTypeMappings.addAllMappings(AnnotationTypeMappings.java:75) ~[spring-core-6.0.12.jar:6.0.12]
	at org.springframework.core.annotation.AnnotationTypeMappings.<init>(AnnotationTypeMappings.java:68) ~[spring-core-6.0.12.jar:6.0.12]
	at org.springframework.core.annotation.AnnotationTypeMappings$Cache.createMappings(AnnotationTypeMappings.java:245) ~[spring-core-6.0.12.jar:6.0.12]
	at java.base/java.util.concurrent.ConcurrentMap.computeIfAbsent(ConcurrentMap.java:330) ~[na:na]
	at org.springframework.core.annotation.AnnotationTypeMappings$Cache.get(AnnotationTypeMappings.java:241) ~[spring-core-6.0.12.jar:6.0.12]
	at org.springframework.core.annotation.AnnotationTypeMappings.forAnnotationType(AnnotationTypeMappings.java:199) ~[spring-core-6.0.12.jar:6.0.12]
	at org.springframework.core.annotation.AnnotationTypeMappings.forAnnotationType(AnnotationTypeMappings.java:182) ~[spring-core-6.0.12.jar:6.0.12]
	at org.springframework.core.annotation.AnnotationTypeMappings.forAnnotationType(AnnotationTypeMappings.java:169) ~[spring-core-6.0.12.jar:6.0.12]
	at org.springframework.core.annotation.AnnotationTypeMapping.computeSynthesizableFlag(AnnotationTypeMapping.java:405) ~[spring-core-6.0.12.jar:6.0.12]
	at org.springframework.core.annotation.AnnotationTypeMapping.<init>(AnnotationTypeMapping.java:126) ~[spring-core-6.0.12.jar:6.0.12]
	at org.springframework.core.annotation.AnnotationTypeMappings.addIfPossible(AnnotationTypeMappings.java:112) ~[spring-core-6.0.12.jar:6.0.12]
	at org.springframework.core.annotation.AnnotationTypeMappings.addAllMappings(AnnotationTypeMappings.java:75) ~[spring-core-6.0.12.jar:6.0.12]
	at org.springframework.core.annotation.AnnotationTypeMappings.<init>(AnnotationTypeMappings.java:68) ~[spring-core-6.0.12.jar:6.0.12]
	at org.springframework.core.annotation.AnnotationTypeMappings$Cache.createMappings(AnnotationTypeMappings.java:245) ~[spring-core-6.0.12.jar:6.0.12]
	at java.base/java.util.concurrent.ConcurrentMap.computeIfAbsent(ConcurrentMap.java:330) ~[na:na]
	at org.springframework.core.annotation.AnnotationTypeMappings$Cache.get(AnnotationTypeMappings.java:241) ~[spring-core-6.0.12.jar:6.0.12]
	at org.springframework.core.annotation.AnnotationTypeMappings.forAnnotationType(AnnotationTypeMappings.java:199) ~[spring-core-6.0.12.jar:6.0.12]
	at org.springframework.core.annotation.AnnotationTypeMappings.forAnnotationType(AnnotationTypeMappings.java:182) ~[spring-core-6.0.12.jar:6.0.12]
	at org.springframework.core.annotation.AnnotationTypeMappings.forAnnotationType(AnnotationTypeMappings.java:169) ~[spring-core-6.0.12.jar:6.0.12]
	at 
...

I've epxlored what happens, and the issue is that method org.springframework.core.annotation.AnnotationTypeMapping#computeSynthesizableFlag finds the children property of our DocProperty class annotation and calls the forAnnotationType, with the children's type which is DocProperty again. And that's infinite loop.

It's reproducible here: tolgee/tolgee-platform@259cd31 (the specific commit)

Metadata

Metadata

Assignees

Labels

in: coreIssues in core modules (aop, beans, core, context, expression)theme: kotlinAn issue related to Kotlin supporttype: regressionA bug that is also a regression

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions