Closed DieBauer closed 12 months ago
Good catch — thanks for the report!
I wonder how widely this will be encountered in the wild. The existence of a regression puts some pressure on us to get a 2.13.12 out after too much longer, but how much pressure isn't clear to me.
Also not sure whether to mention it in the 2.13.11 release notes, or just assume that anyone googling the problem will land here.
I'm not sure how common it is to do this particular kind of Java reflection on compiled Scala classes. (Like, is there one or more commonly used tool or library that does it, for example.)
JVM spec is a bit unclear. Deprecation is "optional", and attributes with the same name and length might "clash", but it doesn't specify either behavior or restrictions. I don't see any reason to fail on duplicate Deprecation. Maybe it means "very deprecated" under -Xsource:3
, for example. In any case, obviously nicer not to duplicate.
Also not sure whether to mention it in the 2.13.11 release notes, or just assume that anyone googling the problem will land here.
I'm not sure how common it is to do this particular kind of Java reflection on compiled Scala classes. (Like, is there one or more commonly used tool or library that does it, for example.)
I found this when using such a Scala class that I compiled with 2.13.11 (which apparently inherited a java interface with this @Deprecated
method) in the Spring Boot framework.
Bean instantiation failed because they apparently hit these sun.reflect.annotation.AnnotationParser
classes (for @PostConstruct
and @PreDestroy
annotations in this case as the stacktrace shows)
[error] Caused by: java.lang.annotation.AnnotationFormatError: Duplicate annotation for class: interface java.lang.Deprecated: @java.lang.Deprecated(forRemoval=false, since="")
[error] at sun.reflect.annotation.AnnotationParser.parseAnnotations2(AnnotationParser.java:126)
[error] at sun.reflect.annotation.AnnotationParser.parseAnnotations(AnnotationParser.java:73)
[error] at java.lang.reflect.Executable.declaredAnnotations(Executable.java:625)
[error] at java.lang.reflect.Executable.declaredAnnotations(Executable.java:623)
[error] at java.lang.reflect.Executable.getAnnotation(Executable.java:591)
[error] at java.lang.reflect.Method.getAnnotation(Method.java:738)
[error] at java.lang.reflect.AnnotatedElement.isAnnotationPresent(AnnotatedElement.java:292)
[error] at java.lang.reflect.AccessibleObject.isAnnotationPresent(AccessibleObject.java:518)
[error] at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.lambda$buildLifecycleMetadata$0(InitDestroyAnnotationBeanPostProcessor.java:233)
[error] at org.springframework.util.ReflectionUtils.doWithLocalMethods(ReflectionUtils.java:324)
[error] at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.buildLifecycleMetadata(InitDestroyAnnotationBeanPostProcessor.java:232)
[error] at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.findLifecycleMetadata(InitDestroyAnnotationBeanPostProcessor.java:210)
[error] at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.postProcessMergedBeanDefinition(InitDestroyAnnotationBeanPostProcessor.java:149)
[error] at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.postProcessMergedBeanDefinition(CommonAnnotationBeanPostProcessor.java:305)
[error] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyMergedBeanDefinitionPostProcessors(AbstractAutowireCapableBeanFactory.java:1116)
[error] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:594)
I see that classfile parser sees both the DeprecatedAttr and its runtime counterpart; they are not distinguished internally, so both are emitted as DeprecatedAttrs in the class file. And I had expected something weird with default methods, oh well.
By extension, the previously ignored DeprecatedAttrs were not present as runtime-retained attributes.
The Scaladoc says "Annotation classes defined in Scala are not stored in classfiles in a Java-compatible manner and therefore not visible in Java reflection. In order to achieve this, the annotation has to be written in Java." I vaguely remember the interminable discussions. Now I see that the ambiguity is that Scala wants its deprecation to interoperate with the platform, so that it must be an exception to this rule. The other weirdness about default methods, and I expected one, is that an implementing class in Java does not receive a forwarding method; in Scala, it works like a trait, with a forwarder, and it is the forwarder that in turn receives the deprecated attribute. I haven't checked how other annotations are handled. But Java-defined annotations are stored according to retention policy, so Scala should at least manage that for deprecation. (There are TODOs for annots on params, etc.) Also, recall that Java has "repeatable annotations" for that use case.
I got this problem also
Reproduction steps
Scala version: 2.13.11
Reproducer is here: run
sbt test
showcases the behavior. Changing scala version to 2.13.10 solves it.https://github.com/DieBauer/scala-reproducer/tree/master
this is compiled in a separate stage (module)
and the scala class is
then checking that the Deprecated annotation is present on the Scala class/method fails
This fails with
java.lang.annotation.AnnotationFormatError: Duplicate annotation for class: interface java.lang.Deprecated: @java.lang.Deprecated(forRemoval=false, since="")
possibly introduced by https://github.com/scala/scala/pull/10291
Problem
This issue is reproduced on Java 8, 11 and 17 (potentially higher versions as well). On 2.13.10 it does not occur.
When a Java class has a Deprecated method that is inherited in a Scala class, then checking the method through the scala class for present annotations throws a
java.lang.annotation.AnnotationFormatError: Duplicate annotation for class: interface java.lang.Deprecated: @java.lang.Deprecated(forRemoval=false, since="")
The issue is the duplicate Deprecated annotation in the bytecode when compiled with Scala 2.13.11
Details
The java class bytecode looks like:
While the scala class bytecode looks like
The same class compiled with Scala 2.13.10 gives this bytecode:
This has only 1