Hi Peter,

On 3/06/2021 6:52 pm, Peter Levart wrote:
On Thu, 3 Jun 2021 07:31:14 GMT, David Holmes <david.hol...@oracle.com> wrote:

The code is confusing because it gives no indication that it is aware
that runtime invisible annotations could be present:
/**
* Parses the annotations described by the specified byte array.
* resolving constant references in the specified constant pool.
* The array must contain an array of annotations as described
* in the RuntimeVisibleAnnotations_attribute:
but at the same time it checks for the RUNTIME retention, which means it
must have recognised the possibility there could be something else
there.


Yes, there could be a CLASS retention annotation there (even though 
`-XX+PreserveAllAnnotations` was not used at runtime, so rawAnnotations 
contains the content of `RuntimeVisibleAnnotations` only). Again, such 
annotation was RUNTIME retention when its use was compiled into a class, but at 
runtime such annotation may be updated to have CLASS or even SOURCE retention. 
Such annotation use is filtered out.

Sorry Peter I'm not following you. I am only talking about runtime here.
The VM loads a class at runtime and examines the annotation attributes
that exist in that classfile.

Right, but into which annotation attribute (`RuntimeVisibleAnnotations` vs. 
`RuntimeInvisibleAnnotations`) of that class the annotation use was encoded 
depends on what retention the annotation had at compile time, because those 
attributes are created by `javac` compiler.

Okay

Some will be visible annotations (which
implies RUNTIME retention), others may be invisible (which implies
!RUNTIME which I assume means CLASS). It provides access to these via
the "raw annotations" and the reflection code then processes that
through the AnnotationProcessor.

Right, so reflection code at runtime accesses the annotations that were 
compiled into the class annotation attributes by parsing them and filtering out 
all but RUNTIME annotations. But this filtering is done at runtime and uses 
annotation's current set of meta-annotations values (i.e. `@Retention`) which 
can differ from what this same annotation had at class compile time. So this is 
how current RUNTIME annotations can end up in the `RuntimeInvisibleAnnotations` 
class attribute and how current CLASS annotations can end up in 
`RuntimeVisibleAnnotations` class attribute. It's the consequence of separate 
compilation.

Okay I see what you are talking about in regards to separate compilation.


I don't know exactly what you mean by an annotation use being compiled
into a class, but I assume it is something like the way compile-time
constants are compiled in. But I don't see how that relates to the
current discussion.

An annotation use is the use of annotation in the source code to annotate 
something (Class, Method, Field, ...). For example:


@AnnA
public class Use { ... }


`javac` decides into which class attribute (`RuntimeVisibleAnnotations` vs. 
`RuntimeInvisibleAnnotations`) of Use.class file it will encode the `@AnnA` use 
by examining `AnnA`'s `@Retention` meta-annotation at that time. Say that at 
that time the annotation was this:


@Retention(CLASS)
public @interface AnnA {}


`javac` would encode the `Use`'s use of that annotation into the 
`RuntimeInvisibleAnnotations` attribute of `Use.class`.


Now comes runtime. Annotation maintainer decides to lift the retention of 
`AnnA` annotation to RUNTIME and now it looks like this:


@Retention(RUNTIME)
public @interface AnnA {}


He tries to run some new code to extract the annotation value from `Use` class 
at runtime via reflection, because now at runtime the annotation is updated to 
have RUNTIME retention. But he also doesn't have access to `Use.java` to 
recompile it with updated annotation. He just has access to `Use.class` that 
was compiled with an old version of the same annotation. As we said, 
`Use.class` has encoded the annotation use in its `RuntimeInvisibleAnnotations` 
attribute. Voila, here comes `-XX+PreserveAllAnnotations` option to enable 
passing `RuntimeInvisibleAnnotations` attribute with encoded annotations to the 
reflection runtime and annotation parser which would return such annotation use.

I see the picture you are painting and that things could work that way, but I don't know if I agree that this was an intention or that they should work that way.

The separate compilation story with annotations is somewhat different to other separate compilation issues as the JVMS does not really provide any guidance here. The RuntimeInvisibleAnnotations are not even parsed by the VM so it know nothing about which annotation types are referred to, never loads those types or does any checking to see if the properties of that type (like Retention) remain the same. Even RuntimeVisibleAnnotations are only structurally parsed to see of there are annotations the VM has to be aware of (ie @Contended) but those can never be out-of-sync through separate compilation.

So in essence these annotations are transparent to the VM and it simply hands them up to the JDK for interpretation. The code in AnnotationParser uses the current definition of the annotation type to determine things like Retention policy and uses that when checking what the VM has returned.

Now imagine whole libraries with classes that use an annotation which began its 
life as CLASS annotation because it was paired with AnnotationProcessor(s) that 
processed those annotation uses at compile time, but now some runtime tool 
emerges that would like to see those annotations too. The maintainer accepts 
the proposal to promote annotation's retention from CLASS to RUNTIME in new 
version of annotation. But who is going to re-compile all those libraries?

Yes good point. I don't have an answer. I don't know what the evolution philosophy is/was with annotations.

Cheers,
David
-----


David

Regards, Peter

-------------

PR: https://git.openjdk.java.net/jdk/pull/4280

Reply via email to