Background: It is bad style for an annotation to apply to both
declarations and types. We have never encountered a reason to do this.
But, such annotations are possible: if the @Target meta-annotation is
missing, or if it perversely specifies both declaration and type targets.
This note is about the classfile representation for such annotations.
They're bad style, but we have to decide how the classfile represents them.
The JSR 308 specification (section 2.3) states that if an annotation
applies to both declarations and types, then the annotation is treated as
applying to both the declaration and the type. For example, suppose that
the @Foo annotation lacks a @Target annotation. Then, in this code:
@Foo int m() { return 0; }
the @Foo annotation applies to both the m method, and the int data type.
During annotation processing, a query of either the m method or its
return type would return the @Foo annotation. A reflective query of either
the declaration or the type would return the @Foo annotation. A tool that
reads a classfile would present an interface to its clients indicating that
the @Foo annotation appears both on the m method and on its return type.
That abstraction -- the annotation appears in two distinct locations -- is
clear enough. But how is the abstraction implemented? Here are two
possible representations for the classfile format.
1. Actually have two copies of the @Foo annotation in the classfile. One
would appear in the RuntimeVisibleAnnotations attribute, and one would
appear in the RuntimeVisibleTypeAnnotations attribute.
2. Have only one copy of the annotation in the classfile, but
classfile-reading tools present the illusion of two copies to clients.
The annotation would appear only in the RuntimeVisibleAnnotations
attribute.
Each of these designs has its own merits and disadvantages.
1. Having two copies of the annotation in the classfile: The key
disadvantage is for tools that take a .class file as input and produce
a Java-like file as output. Examples of such tools are Javadoc or a
decompiler. The tool must take care not to write an annotation twice
in the decompiled code, as in "@Foo @Foo int m()...". The tool must
recognize when two copies of an annotation arose from one syntactic
form in the Java program.
2. Having one copy of the annotation in the classfile: The key
disadvantage is for certain classfile processing tools.
* If the tool uses an internal representation with only one copy of
the annotation, then each query for a type annotation must check
both the type annotations, and also check the corresponding
declaration annotations to determine if one of them applies to types
as well as to declarations.
* If the tool uses an internal representation with two copies of the
annotation, then it must duplicate the annotation when reading a
classfile and de-duplicate the annotation when writing a classfile.
A minor disadvantage is that bytecode analyzers must load an annotation
to parse its Target to know if an Runtime[In]visibleAnnotation is a
declaration annotation or a declaration-and-type annotation. In many
cases, the annotation must be read in anyway.
A minor disadvantage is that tool writers may forget to implement the
abstraction that the annotation appears in two places.
A minor advantage is that a Java 6 and a Java 7 compiler would produce
identical attributes; but I don't see why this would matter.
Again, each of these (dis)advantages occurs only for bad annotations that
apply to both declarations and types.
The javac AST used to use approach #2, but we found it clumsy and switched
to approach #1. Now, we are trying to decide which approach to use for the
classfile format. We welcome comments.
Michael Ernst wrote:
> Background: It is bad style for an annotation to apply to both
> declarations and types. We have never encountered a reason to do this.
> But, such annotations are possible: if the @Target meta-annotation is
> missing, or if it perversely specifies both declaration and type targets.
> This note is about the classfile representation for such annotations.
> They're bad style, but we have to decide how the classfile represents them.
> The JSR 308 specification (section 2.3) states that if an annotation
> applies to both declarations and types, then the annotation is treated as
> applying to both the declaration and the type. For example, suppose that
> the @Foo annotation lacks a @Target annotation. Then, in this code:
> @Foo int m() { return 0; }
> the @Foo annotation applies to both the m method, and the int data type.
> During annotation processing, a query of either the m method or its
> return type would return the @Foo annotation. A reflective query of either
> the declaration or the type would return the @Foo annotation. A tool that
> reads a classfile would present an interface to its clients indicating that
> the @Foo annotation appears both on the m method and on its return type.
> That abstraction -- the annotation appears in two distinct locations -- is
> clear enough. But how is the abstraction implemented? Here are two
> possible representations for the classfile format.
> 1. Actually have two copies of the @Foo annotation in the classfile. One
> would appear in the RuntimeVisibleAnnotations attribute, and one would
> appear in the RuntimeVisibleTypeAnnotations attribute.
> 2. Have only one copy of the annotation in the classfile, but
> classfile-reading tools present the illusion of two copies to clients.
> The annotation would appear only in the RuntimeVisibleAnnotations
> attribute.
> Each of these designs has its own merits and disadvantages.
> 1. Having two copies of the annotation in the classfile: The key
> disadvantage is for tools that take a .class file as input and produce
> a Java-like file as output. Examples of such tools are Javadoc or a
> decompiler. The tool must take care not to write an annotation twice
> in the decompiled code, as in "@Foo @Foo int m()...". The tool must
> recognize when two copies of an annotation arose from one syntactic
> form in the Java program.
> 2. Having one copy of the annotation in the classfile: The key
> disadvantage is for certain classfile processing tools.
> * If the tool uses an internal representation with only one copy of
> the annotation, then each query for a type annotation must check
> both the type annotations, and also check the corresponding
> declaration annotations to determine if one of them applies to types
> as well as to declarations.
> * If the tool uses an internal representation with two copies of the
> annotation, then it must duplicate the annotation when reading a
> classfile and de-duplicate the annotation when writing a classfile.
> A minor disadvantage is that bytecode analyzers must load an annotation
> to parse its Target to know if an Runtime[In]visibleAnnotation is a
> declaration annotation or a declaration-and-type annotation. In many
> cases, the annotation must be read in anyway.
> A minor disadvantage is that tool writers may forget to implement the
> abstraction that the annotation appears in two places.
> A minor advantage is that a Java 6 and a Java 7 compiler would produce
> identical attributes; but I don't see why this would matter.
> Again, each of these (dis)advantages occurs only for bad annotations that
> apply to both declarations and types.
> The javac AST used to use approach #2, but we found it clumsy and switched
> to approach #1. Now, we are trying to decide which approach to use for the
> classfile format. We welcome comments.
> -Mike
I would think approach #1 is better. It is simpler, in that there are two annotations in two places, so one should expect two different representations in the classfile. It is also somewhat more "failsafe" to expect selected disassembly tools to handle duplicates than it is to expect all reflective tools to know about this quirk in the classfile format.
I doubt that the duplicated annotation is likely to significantly affect classfile size. If you think that might be an issue, I would suggest a compromise #1.5, which is to put one copy of the full annotation and a separate reference attribute where the other annotation might be expected to be found -- but I can't believe the optimization is worthwhile.
@Version("2.0") could mean that the createWidget method only appears in the
2.0 version, or it could mean that the returned Widget should only be used
by code that uses the 2.0 API of Widget, in addition to being a 2.0 method.
E.g.
could be a 2.0 method that returns a value which allows the 1.0 API method
invocations.
The checker would ensure that versions of widgets don't get assigned to
incompatible variables, and that older code does not call newer code (to
avoid problems when backporting).
On Thu, Dec 3, 2009 at 7:24 PM, Michael Ernst <mer...@cs.washington.edu>wrote:
> Background: It is bad style for an annotation to apply to both
> declarations and types. We have never encountered a reason to do this.
> But, such annotations are possible: if the @Target meta-annotation is
> missing, or if it perversely specifies both declaration and type targets.
@Version("2.0") could mean that the createWidget method only appears in the
2.0 version, or it could mean that the returned Widget should only be used
by code that uses the 2.0 API of Widget, in addition to being a 2.0 method.
E.g.
could be a 2.0 method that returns a value which allows the 1.0 API method
invocations.
The checker would ensure that versions of widgets don't get assigned to
incompatible variables, and that older code does not call newer code (to
avoid problems when backporting).
On Thu, Dec 3, 2009 at 7:24 PM, Michael Ernst <mer...@cs.washington.edu>wrote:
> Background: It is bad style for an annotation to apply to both
> declarations and types. We have never encountered a reason to do this.
> But, such annotations are possible: if the @Target meta-annotation is
> missing, or if it perversely specifies both declaration and type targets.
> @Version("2.0") could mean that the createWidget method only appears in the
> 2.0 version, or it could mean that the returned Widget should only be used
> by code that uses the 2.0 API of Widget, in addition to being a 2.0 method.
There are two questions here:
1. How do we determine the target element of Version? Is it the
method or return type?
The answer here is as before, it's determined by the Version
annotation declaration. If it's meta-annotated with @Target(METHOD),
then it's assumed to target the method, if it's meta-annotated with
@Target(TYPE_USE), then it's assumed to target the return type.
The problem that Mike is presenting here, is what if the annotation
contains no Target meta-annotation (bad style!), how should this
annotation be treated? Both proposals here state that this annotation
should be treated as both: a declaration annotation (i.e. targets the
method) and a type annotation (i.e. targets the return type). The
proposals differ however, on whether the annotation should be written
twice in the classpath or only once.
2. What does the annotation Version mean? Is it restricting where the
method appears or how the returned value is used?
The JSR 308 specification is only concerned about which syntactical
Java element the annotation is targeting. Any meaning beyond that is
defined by the annotation/checker writer. In other words, the
specification and compiler makes no assumption on what the annotation
actually means.
I assume that @Version is an illustrated example here, but not
actually found in the JDK or particular library. Do you know of
libraries defining annotations without Target meta-annotation?
> @Version("2.0") could mean that the createWidget method only appears in the
> 2.0 version, or it could mean that the returned Widget should only be used
> by code that uses the 2.0 API of Widget, in addition to being a 2.0 method.
I don't think this is a good design.
I've followed up to the checker-framework-discuss@googlegroups.com mailing
list.