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

Nested annotations no longer supported in ASM-based annotation processing #24375

Closed
AndreasKl opened this issue Jan 16, 2020 · 6 comments
Closed
Assignees
Labels
in: core Issues in core modules (aop, beans, core, context, expression) type: regression A bug that is also a regression
Milestone

Comments

@AndreasKl
Copy link

AndreasKl commented Jan 16, 2020

tl;dr

Build https://github.com/AndreasKl/spring-issue-in-TypeMappedAnnotation the test fails with the following exception:

org.springframework.beans.factory.BeanDefinitionStoreException: Failed to read candidate component class: file [.../demo/DemoApplicationTests.class]; nested exception is java.lang.ClassCastException: class org.springframework.core.annotation.TypeMappedAnnotation cannot be cast to class java.util.Map (org.springframework.core.annotation.TypeMappedAnnotation is in unnamed module of loader 'app'; java.util.Map is in module java.base of loader 'bootstrap')

This is caused by the @AliasFor("value") and @AliasFor("hallo") on public @interface Bad.

We do not see this behaviour with Spring Boot 2.1.11 (Spring Core 5.1.12.RELEASE) but can reproduce the issue on (Spring Core 5.2.2.RELEASE and 5.2.4.BUILD-SNAPSHOT).

Failing test case:

public class Gh24375Tests {

  @Target({ElementType.METHOD, ElementType.TYPE})
  @Retention(RetentionPolicy.RUNTIME)
  public @interface A {

    @AliasFor("value")
    B versus() default @B;

    @AliasFor("versus")
    B value() default @B;
  }

  @Target(ElementType.ANNOTATION_TYPE)
  @Retention(RetentionPolicy.RUNTIME)
  public @interface B {

    String name() default "";
  }

  @Test
  @A(versus = @B)
  public void gh24375() {
    new ClassPathScanningCandidateComponentProvider(true)
        .findCandidateComponents(Integer.class.getPackage().getName());
  }
}

The root cause could be in:

org.springframework.core.annotation.AnnotationTypeMapping#areEquivalent(java.lang.Object, java.lang.Object, java.util.function.BiFunction<java.lang.reflect.Method,java.lang.Object,java.lang.Object>)

and could be mitigated by adding:

if (value instanceof Annotation && extractedValue instanceof TypeMappedAnnotation) {
  return areEquivalent((Annotation) value, (TypeMappedAnnotation<?>)extractedValue, valueExtractor);
}

private static boolean areEquivalent(Annotation annotation, @Nullable TypeMappedAnnotation<?> extractedValue, BiFunction<Method, Object, Object> valueExtractor) {

    AttributeMethods attributes = AttributeMethods.forAnnotationType(annotation.annotationType());
    for (int i = 0; i < attributes.size(); i++) {
        Method attribute = attributes.get(i);
        String name = attributes.get(i).getName();
        if (!areEquivalent(ReflectionUtils.invokeMethod(attribute, annotation),
            extractedValue.getValue(name).orElse(null), valueExtractor)) {
            return false;
        }
    }
    return true;
}

As this is core framework code I'm not really feeling confident to provide a PR without breaking something else.

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Jan 16, 2020
@sbrannen sbrannen added the in: core Issues in core modules (aop, beans, core, context, expression) label Jan 16, 2020
@sbrannen
Copy link
Member

Thanks for raising the issue!

I have confirmed that the supplied test case executes without errors on 5.1.x and fails with the stated exception on master.

@sbrannen sbrannen added 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 Jan 16, 2020
@sbrannen sbrannen added this to the 5.2.4 milestone Jan 16, 2020
sbrannen added a commit that referenced this issue Jan 16, 2020
@sbrannen
Copy link
Member

Update: I've submitted a passing regression test for 5.1.x in 6699833 and a failing (though currently @Disabled) regression test for master/5.2.x in 2a2efbe.

sbrannen added a commit that referenced this issue Jan 16, 2020
@sbrannen
Copy link
Member

As this is core framework code I'm not really feeling confident to provide a PR without breaking something else.

I came to a similar conclusion about what might need to be done to provide a fix. I also looked at your code and tried it out against master, and it seems to do the job.

But let's wait on feedback from @philwebb before taking the next step.

Cheers

@sbrannen sbrannen self-assigned this Jan 16, 2020
@philwebb
Copy link
Member

I think the change looks right. I probably need to see the actual commit to be sure. The only thing that concerns me is how similar two areEquivalent method appear. @sbrannen If you have a commit lined up I'll take a look in a debugger when I get a sec.

Kvicii pushed a commit to Kvicii/spring-framework that referenced this issue Jan 18, 2020
…amework into read_yuyang

* 'master' of https://github.com/spring-projects/spring-framework:
  Update copyright date
  Hoist concatenation of two constant Strings out of loops
  Avoid setting special Content-* response headers for Tomcat
  Consistent use of AnnotationUtils.rethrowAnnotationConfigurationException()
  Fix Checkstyle violation
  Introduce @disabled regression test for spring-projectsgh-24375
  Polishing contribution
  Expose proxyPing Reactor Netty WebSocket
  Updates to Validation section in reference
  HttpWebHandlerAdapter#formatRequest is protected
  Fix issue with new line handling in StompDecoder
  Simplify getCache() implementation in CaffeineCacheManager
  Publish a build scan only if authenticated
  Update Artifactory plugin to 4.12.0
@sbrannen
Copy link
Member

sbrannen commented Jan 19, 2020

I think the change looks right. I probably need to see the actual commit to be sure. The only thing that concerns me is how similar two areEquivalent method appear. @sbrannen If you have a commit lined up I'll take a look in a debugger when I get a sec.

Indeed, I implemented the fix slightly differently by adding logic to the existing areEquivalent(Annotation, Object, BiFunction<Method, Object, Object>) method.

See this commit for details: sbrannen@625dc7b

See this commit for details: sbrannen@b55a564

@philwebb, as soon as you give a 👍 for that, I'll merge it into master.

Cheers

sbrannen added a commit to sbrannen/spring-framework that referenced this issue Feb 7, 2020
@sbrannen sbrannen changed the title ClassCastException in TypeMappedAnnotation during ClassPathScanningCandidateComponentProvider#scanCandidateComponents Nested annotations no longer supported in ASM-based annotation processing Feb 7, 2020
sbrannen added a commit to sbrannen/spring-framework that referenced this issue Feb 11, 2020
Spring Framework 5.0 introduced a regression in ASM-based annotation
processing. Specifically, nested annotations were no longer supported,
and component scanning resulted in an exception if a candidate
component was annotated with an annotation that contained nested
annotations.

This commit fixes this regression by introducing a standard "value
extractor" in TypeMappedAnnotation that supports extracting values from
objects of type Annotation, Map, or TypeMappedAnnotation.

Closes spring-projectsgh-24375
sbrannen added a commit to sbrannen/spring-framework that referenced this issue Feb 11, 2020
@sbrannen
Copy link
Member

This has been fixed in 974caca and revised in 5d4f1d9.

@AndreasKl, feel free to try it out in the next 5.2.4 snapshot build and let us know if it works for you.

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