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

AnnotationsScanner throws NPE due to because AnnotatedElement proxy is not cloning arrays #22655

Closed
Tracked by #22560
schauder opened this issue Mar 25, 2019 · 9 comments
Assignees
Labels
in: core Issues in core modules (aop, beans, core, context, expression) type: regression A bug that is also a regression
Milestone

Comments

@schauder
Copy link
Contributor

schauder commented Mar 25, 2019

This affects 5.2.0BUILD-SNAPSHOT, specifically 5.2.0-BUILD-20190325.085048

Spring Data JDBC integration tests started failing with the stack trace below.

The reason is that this line: https://github.com/spring-projects/spring-framework/blob/master/spring-core/src/main/java/org/springframework/core/annotation/AnnotationsScanner.java#L448

throws an NPE because the array annotations contains a null element.

The issue can be reproduced by running mvn clean install on master of spring-data-jdbc.
Sorry, couldn't pin the issue down more precisely so far.

CI builds show that it worked last Friday. (20190322...)

java.lang.IllegalStateException: Failed to load ApplicationContext
	at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:125)
	at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:108)
	at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.injectDependencies(DependencyInjectionTestExecutionListener.java:118)
	at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.prepareTestInstance(DependencyInjectionTestExecutionListener.java:83)
	at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:244)
	at org.springframework.test.context.junit4.statements.RunPrepareTestInstanceCallbacks.evaluate(RunPrepareTestInstanceCallbacks.java:63)
	at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
	at org.springframework.test.context.junit4.statements.SpringFailOnTimeout.evaluate(SpringFailOnTimeout.java:87)
	at org.springframework.test.context.junit4.statements.ProfileValueChecker.evaluate(ProfileValueChecker.java:103)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
	at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
	at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
	at org.springframework.test.context.junit4.statements.ProfileValueChecker.evaluate(ProfileValueChecker.java:103)
	at org.springframework.test.context.junit4.rules.SpringClassRule$TestContextManagerCacheEvictor.evaluate(SpringClassRule.java:190)
	at org.junit.rules.RunRules.evaluate(RunRules.java:20)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
	at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
	at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
	at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
	at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
	at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jdbcRepositoryManipulateDbActionsIntegrationTests.DummyEntityRepository': Injection of autowired dependencies failed; nested exception is java.lang.NullPointerException
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:391)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1415)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:592)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:515)
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:840)
	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:877)
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:549)
	at org.springframework.test.context.support.AbstractGenericContextLoader.loadContext(AbstractGenericContextLoader.java:128)
	at org.springframework.test.context.support.AbstractGenericContextLoader.loadContext(AbstractGenericContextLoader.java:60)
	at org.springframework.test.context.support.AbstractDelegatingSmartContextLoader.delegateLoading(AbstractDelegatingSmartContextLoader.java:275)
	at org.springframework.test.context.support.AbstractDelegatingSmartContextLoader.loadContext(AbstractDelegatingSmartContextLoader.java:243)
	at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:99)
	at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:117)
	... 27 common frames omitted
Caused by: java.lang.NullPointerException: null
	at org.springframework.core.annotation.AnnotationsScanner.getDeclaredAnnotations(AnnotationsScanner.java:448)
	at org.springframework.core.annotation.AnnotationsScanner.getDeclaredAnnotations(AnnotationsScanner.java:421)
	at org.springframework.core.annotation.AnnotationsScanner.processElement(AnnotationsScanner.java:409)
	at org.springframework.core.annotation.AnnotationsScanner.process(AnnotationsScanner.java:111)
	at org.springframework.core.annotation.AnnotationsScanner.scan(AnnotationsScanner.java:96)
	at org.springframework.core.annotation.AnnotationsScanner.scan(AnnotationsScanner.java:77)
	at org.springframework.core.annotation.TypeMappedAnnotations.scan(TypeMappedAnnotations.java:246)
	at org.springframework.core.annotation.TypeMappedAnnotations.get(TypeMappedAnnotations.java:148)
	at org.springframework.core.annotation.AnnotatedElementUtils.getMergedAnnotationAttributes(AnnotatedElementUtils.java:258)
	at org.springframework.beans.factory.annotation.QualifierAnnotationAutowireCandidateResolver.findValue(QualifierAnnotationAutowireCandidateResolver.java:366)
	at org.springframework.beans.factory.annotation.QualifierAnnotationAutowireCandidateResolver.getSuggestedValue(QualifierAnnotationAutowireCandidateResolver.java:354)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1195)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1177)
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement.inject(AutowiredAnnotationBeanPostProcessor.java:697)
	at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:116)
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:385)
	... 43 common frames omitted

@sbrannen sbrannen added type: bug A general bug type: regression A bug that is also a regression in: core Issues in core modules (aop, beans, core, context, expression) labels Mar 25, 2019
@sbrannen sbrannen added this to the 5.2 M1 milestone Mar 25, 2019
@sbrannen
Copy link
Member

@philwebb, do you have time to look into this one as well?

@sbrannen
Copy link
Member

Similar issued reported by another user:

Caused by: java.lang.NullPointerException
    at org.springframework.beans.factory.annotation.QualifierAnnotationAutowireCandidateResolver.checkQualifiers(QualifierAnnotationAutowireCandidateResolver.java:172)
    at org.springframework.beans.factory.annotation.QualifierAnnotationAutowireCandidateResolver.isAutowireCandidate(QualifierAnnotationAutowireCandidateResolver.java:149)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.isAutowireCandidate(DefaultListableBeanFactory.java:774)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.isAutowireCandidate(DefaultListableBeanFactory.java:737)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.isAutowireCandidate(DefaultListableBeanFactory.java:721)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAutowireCandidates(DefaultListableBeanFactory.java:1436)

@jhoeller
Copy link
Contributor

This has been bugging me in my review of the new arrangement already: Ignored annotations are nulled out in the annotations array. I left it in place for the time being but really prefer this being replaced with a correspondingly shrinked new array which never contains null entries. @philwebb, unless there was some specific reasoning behind reusing the original arrays, a shrinked array seems much safer, in particular for externally exposed annotation arrays?

@jhoeller jhoeller removed the type: bug A general bug label Mar 25, 2019
@philwebb
Copy link
Member

@jhoeller The main reason was to reduce GC pressure and improve performance. I figured it would be best not to need the array copy (but I admit I've not done testing with a copy in place).

@philwebb philwebb self-assigned this Mar 25, 2019
@jhoeller
Copy link
Contributor

jhoeller commented Mar 25, 2019

@philwebb On a related note, in my immediate revision that came with the merge, I've changed the rule for ignoring annotations to include java meta-annotations in general, not just FunctionalInterface as it originally said. This might lead to further null entries exposed down the line which haven't been seen before? In any case, for public exposure of annotation arrays, we should never expose null entries to be on the safe side. For internal arrays, iterating them and leniently skipping them might be worthwhile.

@philwebb
Copy link
Member

Indeed, the null entries should never escape in internal code.

@philwebb
Copy link
Member

Actually, this one is a little odd. The array in question isn't ours, it's a result of calling source.getDeclaredAnnotations() (where source is an AnnotatedElement). I wasn't aware that could return null elements.

@philwebb
Copy link
Member

I've pushed a fix for master. Although it doesn't surface an NPE, the issue also exists in previous branches so I'll reopen for @jhoeller to consider a backport.

@philwebb philwebb reopened this Mar 25, 2019
@philwebb philwebb changed the title AnnotationsScanner throws NPE because source.getDeclaredAnnotations() returns an array with null elements Potential NPE due to AnnotatedElementUtils.forAnnotations not cloning arrays Mar 26, 2019
@philwebb philwebb changed the title Potential NPE due to AnnotatedElementUtils.forAnnotations not cloning arrays AnnotationsScanner throws NPE due to because AnnotatedElement proxy is not cloning arrays Mar 26, 2019
@jhoeller
Copy link
Contributor

Since the user-level recommendation is to not modify those arrays and we're not defensively copying them in DependendyDescriptor and co either, I'd rather leave this as-is for our purposes in the older branches. After all, it avoids extra array creation within QualifierAnnotationAutowireCandidateResolver where we know that the annotation array input comes from a DependendyDescriptor anyway.

That said, generally speaking for an AnnotatedElement implementation, the defensive copy is indeed correct there, and as of 5.2 we're relying on this behavior ourselves now.

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) type: regression A bug that is also a regression
Projects
None yet
Development

No branches or pull requests

4 participants