Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AnnotationScanner scanning leads to StackOverflowError with recursive annotation #31400

Closed
JanCizmar opened this issue Oct 10, 2023 · 4 comments
Assignees
Labels
in: core Issues in core modules (aop, beans, core, context, expression) theme: kotlin An issue related to Kotlin support type: regression A bug that is also a regression
Milestone

Comments

@JanCizmar
Copy link

JanCizmar commented Oct 10, 2023

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)

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Oct 10, 2023
@jhoeller
Copy link
Contributor

It looks like #28618 removed the support for cyclic annotation definitions introduced in #28012 but unfortunately also removed the ability to handle nested references to the same annotation type.

@jhoeller jhoeller added in: core Issues in core modules (aop, beans, core, context, expression) type: regression A bug that is also a regression and removed status: waiting-for-triage An issue we've not yet triaged or decided on labels Oct 10, 2023
@jhoeller jhoeller self-assigned this Oct 10, 2023
@jhoeller jhoeller added this to the 6.0.13 milestone Oct 10, 2023
@JanCizmar
Copy link
Author

Is there any workaround like ignoring the annotation?

@jhoeller
Copy link
Contributor

I'm afraid there isn't an obvious workaround. We'll have to address this in our AnnotationTypeMapping implementation.

Note that Java annotations are not allowed to refer to themselves in their attributes, so this remains Kotlin specific. Kotlin prohibits deep cycles (X -> Y -> X) in annotation declarations as of Kotlin 1.9 (aligned with Java) but still allows self references (in contrast to Java)... unfortunately we missed the latter part there.

@jhoeller jhoeller added the theme: kotlin An issue related to Kotlin support label Oct 10, 2023
@lorenzsimon
Copy link
Contributor

lorenzsimon commented Oct 21, 2023

@jhoeller The fix does not work for the following legal Kotlin code:

@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,
    val externalMap: Array<DocPropertyMapEntry> = []
)

annotation class DocPropertyMapEntry(
    val docProperty: DocProperty = DocProperty()
)

see the externalMap property. Can Spring add support for this valid use-case please 🙏

This is my use-case: https://github.com/OpenFolder/kotlin-asyncapi/blob/master/kotlin-asyncapi-annotation/src/main/kotlin/org/openfolder/kotlinasyncapi/annotation/Schema.kt

sbrannen pushed a commit to sbrannen/spring-framework that referenced this issue Nov 8, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: core Issues in core modules (aop, beans, core, context, expression) theme: kotlin An issue related to Kotlin support type: regression A bug that is also a regression
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants