RFR: 8173056: Add test that captures current behavior of annotations with invalid annotation types

Peter Levart peter.levart at gmail.com
Thu Jan 19 15:58:08 UTC 2017


Hi Claes,

On 01/18/2017 12:01 PM, Claes Redestad wrote:
> Hi Peter,
>
> On 01/17/2017 03:10 PM, Peter Levart wrote:
>>
>> As for the failure reporting: requesting an annotation for invalid 
>> annotation type now always produces AnnotationFormatError. 
>> Previously, some invalid annotations were simply skipped, some 
>> produced AnnotationFormatError and for some of them, 
>> AnnotationFormatError was raised only when evaluating Annotation's 
>> equals() method.
>>
>> I would like to discuss this failure behavior more in-depth.
>
> I don't have an example, but if there are applications that have invalid
> annotations which are currently ignored completely or until someone
> calls equals on them, this behavior change could result in some
> application breaking in unexpected ways.
>
> Although this change might be well-intended and align implementation
> better with specified intent, I fear this kind this behavior change 
> might be
> too late for 9 (maybe even for later releases), and think it'd be 
> easier to
> accept a patch that took great care not to change observable behavior
> and discuss this separately.
>
> Thanks!

Mind you that we are talking about the behavior of invalid annotations 
types (class files containing annotation interfaces which are invalid 
according to JLS) and not about the behavior of invalid annotation data 
(annotation attributes in the class files containing annotated 
types/members/...). The later behavior is not changed with the presented 
patch, just the behavior when an annotation type is encountered which 
must have been created by some other tool - not javac - and violates JLS 
for annotation types.

Anyway, the behavior of such invalid annotation types has already 
changed in the past (with a patch for the issue: 8035781: Improve 
equality for annotations) which introduced additional checks into the 
AnnotationInvocationHandler constructor and the equalsImpl() method 
which implements annotation's equals() method.

I extended the AnnotationVerifier test with additional cases that cover 
various "wrongs" in annotation types:

http://cr.openjdk.java.net/~plevart/jdk9-dev/8173056_AnnotationVerifier/webrev.01/

Running this on current jdk9 shows all the shades of behavior for 
different "wrongs":

HolderA.class.getAnnotations() = [@GoodAnnotation()]
test AnnotationVerifier.holderA_annotations(): success

HolderA.class.getAnnotation(GoodAnnotation.class) = @GoodAnnotation()
test AnnotationVerifier.holderA_goodAnnotation(): success

HolderB.class.getAnnotation(AnnotationWithParameter.class) = null
test AnnotationVerifier.holderB_annotationWithParameter(): success

HolderB.class.getAnnotations() = [@GoodAnnotation()]
test AnnotationVerifier.holderB_annotations(): success

HolderB.class.getAnnotation(GoodAnnotation.class) = @GoodAnnotation()
test AnnotationVerifier.holderB_goodAnnotation(): success

HolderC.class.getAnnotation(AnnotationWithVoidReturn.class) = 
java.lang.annotation.AnnotationFormatError: Invalid default: public 
abstract void AnnotationWithVoidReturn.m()
test AnnotationVerifier.holderC_annotationWithVoidReturn(): success

HolderC.class.getAnnotations() = 
java.lang.annotation.AnnotationFormatError: Invalid default: public 
abstract void AnnotationWithVoidReturn.m()
test AnnotationVerifier.holderC_annotations(): success

HolderC.class.getAnnotation(GoodAnnotation.class) = 
java.lang.annotation.AnnotationFormatError: Invalid default: public 
abstract void AnnotationWithVoidReturn.m()
test AnnotationVerifier.holderC_goodAnnotation(): success

HolderD.class.getAnnotation(AnnotationWithExtraInterface.class) = 
java.lang.annotation.AnnotationFormatError: Attempt to create proxy for 
a non-annotation type.
test AnnotationVerifier.holderD_annotationWithExtraInterface(): success

HolderD.class.getAnnotations() = 
java.lang.annotation.AnnotationFormatError: Attempt to create proxy for 
a non-annotation type.
test AnnotationVerifier.holderD_annotations(): success

HolderD.class.getAnnotation(GoodAnnotation.class) = 
java.lang.annotation.AnnotationFormatError: Attempt to create proxy for 
a non-annotation type.
test AnnotationVerifier.holderD_goodAnnotation(): success

HolderE.class.getAnnotation(AnnotationWithException.class) = 
@AnnotationWithException(m=1)
test AnnotationVerifier.holderE_annotationWithException(): success

@AnnotationWithException(m=1).equals(@AnnotationWithException(m=1)) = 
java.lang.annotation.AnnotationFormatError: Malformed method on an 
annotation type
test AnnotationVerifier.holderE_annotationWithException_equals(): success

HolderE.class.getAnnotations() = [@GoodAnnotation(), 
@AnnotationWithException(m=1)]
test AnnotationVerifier.holderE_annotations(): success

HolderE.class.getAnnotation(GoodAnnotation.class) = @GoodAnnotation()
test AnnotationVerifier.holderE_goodAnnotation(): success

HolderF.class.getAnnotation(AnnotationWithHashCode.class) = 
@AnnotationWithHashCode(hashCode=1)
test AnnotationVerifier.holderF_annotationWithHashCode(): success

@AnnotationWithHashCode(hashCode=1).equals(@AnnotationWithHashCode(hashCode=1)) 
= java.lang.annotation.AnnotationFormatError: Malformed method on an 
annotation type
test AnnotationVerifier.holderF_annotationWithHashCode_equals(): success

HolderF.class.getAnnotations() = [@GoodAnnotation(), 
@AnnotationWithHashCode(hashCode=1)]
test AnnotationVerifier.holderF_annotations(): success

HolderF.class.getAnnotation(GoodAnnotation.class) = @GoodAnnotation()
test AnnotationVerifier.holderF_goodAnnotation(): success

HolderG.class.getAnnotation(AnnotationWithDefaultMember.class) = 
@AnnotationWithDefaultMember(m=1)
test AnnotationVerifier.holderG_annotationWithDefaultMember(): success

@AnnotationWithDefaultMember(m=1).equals(@AnnotationWithDefaultMember(m=1)) 
= java.lang.annotation.AnnotationFormatError: Malformed method on an 
annotation type
test AnnotationVerifier.holderG_annotationWithDefaultMember_equals(): 
success

HolderG.class.getAnnotations() = [@GoodAnnotation(), 
@AnnotationWithDefaultMember(m=1)]
test AnnotationVerifier.holderG_annotations(): success

HolderG.class.getAnnotation(GoodAnnotation.class) = @GoodAnnotation()
test AnnotationVerifier.holderG_goodAnnotation(): success

HolderX.class.getAnnotation(AnnotationWithoutAnnotationAccessModifier.class) 
= null
test 
AnnotationVerifier.holderX_annotationWithoutAnnotationAccessModifier(): 
success

HolderX.class.getAnnotations() = [@GoodAnnotation()]
test AnnotationVerifier.holderX_annotations(): success

HolderX.class.getAnnotation(GoodAnnotation.class) = @GoodAnnotation()
test AnnotationVerifier.holderX_goodAnnotation(): success


Cases that throw "AnnotationFormatError: Malformed method on an 
annotation type" and "AnnotationFormatError: Attempt to create proxy for 
a non-annotation type" are the cases that changed behavior with a patch 
for 8035781 and nobody complained. This patch causes 
AnnotationFormatError even for some valid cases (see bellow).

When AnnotationType.getInstance() throws IllegalArgumentException, the 
annotation is skipped while parsing it. This currently happens only when:
- the annotation interface is not marked with ACC_ANNOTATION access 
modifier (Class.isAnnotation() == false).
- the annotation interface contains a non-synthetic public abstract 
method with parameters.

But AnnotationType.getInstance() can also throw AnnotationFormatError 
which is propagated out of parsing logic and out of public 
annotation-obtaining method(s). This currently happens only when:
- the annotation interface contains a non-synthetic public abstract 
method with invalid return type (such as void) because 
Method.getDefaultValue() throws such error which is propagated out of 
AnnotationType constructor.

AnnotationInvocationHandler constructor throws AnnotationFormatError 
which is propagated out of parsing logic and out of public 
annotation-obtaining method(s). This currently happens only when:
- the annotation interface is marked with ACC_ANNOTATION access modifier 
but it either doesn't extend java.lang.annotation.Annotation or it 
extends any additional interfaces.

In all other "wrong annotation type" cases (and some valid cases too), 
the annotation is successfully constructed and returned, but such 
annotation later fails with AnnotationFormatError when .equals() method 
is evaluated on it. Those cases are:

- the annotation interface contains a method (of any kind: synthetic, 
abstract, default, static, private) that declares a thrown exception or 
when it contains a non-abstract or non-public method. The perfectly 
valid situation when this happens is the following example:

public class AnnotationWithLambda {

     @Retention(RetentionPolicy.RUNTIME)
     @interface Ann {
         Callable<Void> callable = () -> {throw new Exception();};
     }

     @Ann
     static class Holder1 {}

     @Ann
     static class Holder2 {}

     public static void main(String[] args) {
         Ann ann1 = Holder1.class.getAnnotation(Ann.class);
         Ann ann2 = Holder1.class.getAnnotation(Ann.class);

         // throws AnnotationFormatError: Malformed method on an 
annotation type
         ann1.equals(ann2);
     }
}

This happens as a consequence of fix for 8035781 which introduced a very 
rigorous validation in annotation's equals method which is more rigorous 
than it should be.

- the annotation interface contains a method (of any kind: synthetic, 
abstract, default, static, private) that has an invalid return type
- the annotation interface contains a method (of any kind: synthetic, 
abstract, default, static, private) which is override equivalent with 
public/protected methods in Object class or Annotation interface.


Do we really want to keep this behavior?

I suggest that when an annotation type is not marked with ACC_ANNOTATION 
modifier (Class.isAnnotation() == false) such annotations are skipped 
when parsing. This can happen when a former annotation type is changed 
to be a normal interface and separately compiled. In all other cases the 
invalid annotation type should be reported when annotations are parsed.

Perhaps this could be performed lazily. An idea:

During parsing of annotations, if an annotation for invalid annotation 
type that has ACC_ANNOTATION modifier is being parsed, the parsing skips 
such annotation and returns a special Proxy implementation implementing 
the invalid annotation type, but this proxy throws AnnotationFormatError 
on any method being invoked on it. This would not prevent other valid 
annotations to be obtained on annotated elements just because they are 
also annotated with invalid annotations.

What do you think?

Regards, Peter



More information about the core-libs-dev mailing list