Protect against stack overflow when searching meta annotations

It is legal for an annotation to be annotated with itself. Previously,
when searching for meta annotations this could lead to a stack overflow.
This was likely to occur when using Kotlin as, like Java, its Target
annotation is annotated with itself. A stack overflow doesn’t occur
with Java’s Target annotation due to some short-circuiting logic for
annotations in java.lang.

This commit updates the logic for finding meta-annotations to
short-circuit when an annotation that has already been seen is
encountered.

Closes gh-5902
This commit is contained in:
Andy Wilkinson
2016-05-10 09:39:38 +01:00
parent 699d083cec
commit 0a765e36f1
6 changed files with 94 additions and 23 deletions

View File

@@ -49,6 +49,7 @@ import org.springframework.test.context.MergedContextConfiguration;
* test classes.
*
* @author Phillip Webb
* @author Andy Wilkinson
* @see ImportsContextCustomizerFactory
*/
class ImportsContextCustomizer implements ContextCustomizer {
@@ -217,27 +218,31 @@ class ImportsContextCustomizer implements ContextCustomizer {
ContextCustomizerKey(Class<?> testClass) {
Set<Annotation> annotations = new HashSet<Annotation>();
collectClassAnnotations(testClass, annotations);
Set<Class<?>> seen = new HashSet<Class<?>>();
collectClassAnnotations(testClass, annotations, seen);
this.annotations = Collections.unmodifiableSet(annotations);
}
private void collectClassAnnotations(Class<?> classType,
Set<Annotation> annotations) {
collectElementAnnotations(classType, annotations);
for (Class<?> interfaceType : classType.getInterfaces()) {
collectClassAnnotations(interfaceType, annotations);
}
if (classType.getSuperclass() != null) {
collectClassAnnotations(classType.getSuperclass(), annotations);
Set<Annotation> annotations, Set<Class<?>> seen) {
if (seen.add(classType)) {
collectElementAnnotations(classType, annotations, seen);
for (Class<?> interfaceType : classType.getInterfaces()) {
collectClassAnnotations(interfaceType, annotations, seen);
}
if (classType.getSuperclass() != null) {
collectClassAnnotations(classType.getSuperclass(), annotations, seen);
}
}
}
private void collectElementAnnotations(AnnotatedElement element,
Set<Annotation> annotations) {
Set<Annotation> annotations, Set<Class<?>> seen) {
for (Annotation annotation : element.getDeclaredAnnotations()) {
if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotation)) {
annotations.add(annotation);
collectClassAnnotations(annotation.annotationType(), annotations);
collectClassAnnotations(annotation.annotationType(), annotations,
seen);
}
}
}

View File

@@ -37,6 +37,7 @@ import static org.mockito.Mockito.mock;
* Tests for {@link ImportsContextCustomizerFactory} and {@link ImportsContextCustomizer}.
*
* @author Phillip Webb
* @author Andy Wilkinson
*/
public class ImportsContextCustomizerFactoryTests {
@@ -101,6 +102,12 @@ public class ImportsContextCustomizerFactoryTests {
assertThat(context.getBean(ImportedBean.class)).isNotNull();
}
@Test
public void selfAnnotatingAnnotationDoesNotCauseStackOverflow() {
assertThat(this.factory.createContextCustomizer(
TestWithImportAndSelfAnnotatingAnnotation.class, null)).isNotNull();
}
static class TestWithNoImport {
}
@@ -137,6 +144,12 @@ public class ImportsContextCustomizerFactoryTests {
}
@SelfAnnotating
@Import(ImportedBean.class)
static class TestWithImportAndSelfAnnotatingAnnotation {
}
@Retention(RetentionPolicy.RUNTIME)
@Import(ImportedBean.class)
@interface MetaImport {
@@ -153,4 +166,10 @@ public class ImportsContextCustomizerFactoryTests {
}
@Retention(RetentionPolicy.RUNTIME)
@SelfAnnotating
static @interface SelfAnnotating {
}
}