Contents:
JSR 308 extends the Java annotation mechanism to permit annotations on any occurrence of a type, such as:
Map<@NonNull String, @NonEmpty List<@Readonly Document>> files;
There is interest in extending this proposal so that annotations can be placed not only on type references, but on loops, blocks, and simple statements, as well. For example:
@LoopBound(max=10)
while (i > j) {
...
}
Indeed, the JSR 308 web page states explicitly that "non-type-related improvements to annotations are within scope and will be considered."
This document supplements the JSR 308 proposal by focusing specifically on these statement annotations. (Here, we define ''statements'' to include both simple and compound statements, such as for
loops.) It describes the benefits of statement annotations and examples of use cases. It also discusses how statement annotations might be implemented.
Annotations on statements are useful in a number of different scenarios. This section highlights a few typical use cases and provides concrete examples of how they might be used.
Note that these use cases are nothing new. They have been implemented countless times in other tools and languages. The role of JSR 308 would simply be to provide a common framework for them that is built-in to Java and allows tools to access these annotations in a standard, interoperable manner.
Verification tools for Java, such as JML and Contract4J, as well as defect detectors such as FindBugs, rely heavily on annotations. These annotations are normally implemented as stylized comments and are used to specify loop invariants, pre- and post-conditions, nullness expectations, and so on.
Unfortunately, there is no standard framework -- not even a standard syntax -- that these tools can share. Without a standard, developers are hesitant to invest time in providing annotations if they cannot be sure that their effort will be portable across tools.
Although JSR 305 aims to provide naming conventions for these tools, it is limited to Java 5's existing annotation mechanism. The tools cannot benefit from Java's annotation standard unless it is extended to allow annotations on statements. The following sub-sections demonstrate what can be done when such an extension is in place.
Verification tools require annotations on loop statements to prove the correctness of loop invariants. In JML, for example, one might wish to write:
int i;
int total = 0;
@maintaining("total == (@sum int j; 0 <= j && j < i; a[j])")
for (i = 0; i < a.length; i++) {
total += a[i];
}
where the @maintaining
annotation is attached to the while
loop, and the @sum
is not an annotation but merely a component of the string within the annotation.
Another typical use case in JML is to attach entire contracts to describe the effect of a statement (including loop statements). Such contracts aid verification tools in proving that a given piece of code follows its contract. For example:
(TODO: Is this legal syntax?) (TODO: Document what this means. An incomprehensible example will turn off readers, not motivate them.)
@following(@spec_case(requires="i < Integer.MAX_VALUE", ensures="i == @old(i + 1)"))
inc(i);
Annotations on blocks and loops can be useful for specifying assertions about concurrency, atomicity, and parallelization. These assertions can then be used for correctness checking, code generation, or hints to the virtual machine about when to parallelize a code block.
A construct like this:
synchronized(mutex) {
for(Item item : items) {
...
}
}
(TODO: What does "safe" mean in this context?)
can be either safe or unsafe, depending on whether the code in the loop changes items or not.
(TODO: Motivate why one might wish to annotate the block as a read or write lock. And I don't think the block itself is a lock, right?)
One might wish to annotate this block as a read or write lock, optimistic or eager lock, etc. These are too coarsely grained to express on the method level only. (One could argue that we could mark Item
as @Immutable
or @ThreadSafe
, assuming annotations on local variables are allowed, but it won't have exactly the same meaning. Also, for the read lock, multiple readers are allowed, while an eager write lock implies a single owner.)
Note that existing tools, such as JBoss Cache and Terracotta, already support such use cases, but they do so using a proprietary annotation syntax and framework, as described here.
A new java.util.concurrent
subpackage is in development that will support very lightweight parallelism and will be helpful in allowing people to exploit 32/64/+/way multicores that will soon enough be very common. This framework has good performance even for surprisingly small computations.
Programming the framework directly requires various refinements of a parallel-recursive Cilk style that most people will not want to learn. However, the framework can be simplified for a large number of common usages, including applyToAll
for arrays and lists, reduce, map-reduce, find-any, find-all, etc.
Even the simplifications suffer from clunky inner class constructions. For example, if you'd like to sum the number of credits taken by all the students in some list, using current draft APIs you'd write something like this to arrange the corresponding map-reduce:
int sum = InParallel.reduce(students,
new Mapper<Student, Integer> {
public Integer map(Student s) {
return s.credits;
}
},
new Reducer<Integer> {
public Integer combine(Integer a,
Integer b) {
return a + b;
}
});
This is awkward and inelegant. Better closure syntax may help, but even the nicest closure syntax might still be too foreign for people to regularly use. It is preferable to write:
@ParallelAccumulator int sum = 0;
@Parallelizable for (Student s : students) sum += s.credit;
...which a javac plugin (i.e., an annotation processor) could transform into the reduce call at compile time.
Statement annotations would be useful in aspect-oriented programming. For instance, they would allow fine-grained advising in AspectJ:
class BankAccount {
public void transferFundsTo(float amount, BankAccount destination) {
// Trace a call made at a specific call site
@Trace destination.deposit(amount);
...
aspect TraceAspect {
around : call(@Trace * *(..)) {
long startTime = System.currentTimeMillis();
try {
return proceed();
} finally {
long endTime = System.currentTimeMillis();
System.out.println("Executed: " + thisJoinPoint + " " + (endTime-startTime));
}
}
}
Worst-case execution time (WCET) is the maximum time a task may execute on a specific hardware platform. Determining this bound is of prime importance for the timing analysis of hard real-time systems.
Analysis tools for determining WCET may suffer from limited reasoning capabilities or lack of knowledge about the execution context. For example, a tool may not be able to establish that percent
is a value between 0 and 100 in order to bound the number of times that this loop is executed:
for (int i=0; i<percent; i++) { ... }
A developer can supply information to the WCET analysis, allowing a static analyzer to compute a better bound on the WCET. For example:
@LoopBound(max=100)
for (int i=0; i<percent; i++) { ... }
C-based tools, such as aiT and Bound-T, have relied on annotations of this type for many years. (See the Heptane documentation for a code example.)
Now that Java is becoming viable for hard real-time systems, a number of WCET analyzers have been created for it, as well.
However,
Java annotations are simply too limited. For instance, the @LoopBound
example shown above is currently impossible because Java annotations simply aren't allowed on loops. An extension of the annotation mechanism to support statements is therefore necessary.
The @SuppressWarnings annotation causes a Java compiler such as javac not to issue specific warnings. This reduces clutter for spurious warnings.
Per-method @SuppressWarnings
annotations are too coarse: for example, a programmer may wish to indicate that (only) one method call to a deprecated method is acceptable, but be informed of other uses of deprecated methods. Per-statement annotations permit the programmer finer-grained control. This follows the @SuppressWarnings
documentation's recommendation: "programmers should always use this annotation on the most deeply nested element where it is effective."
(TODO: This seems to be an expression annotation, not a statement annotation. Therefore, it does not belong in this document.)
Annotations can be used to convey edit-time hints about a program as it is being written. For example, a development tool could render the following code as a mathematical equation instead of a Java statement. The code could then be edited in graphical equation editor. This higher level of abstraction would improve legibility of the code and make the editing process simpler. (For screenshots of this behavior, refer to the references below.)
public double mean ( final List < Integer > x) {
@Equation(DOUBLE)
return Equ.div (1, x. size ()) *
new Summer () {
public int loop ( int i) {
return x. get (i);
}
}.sum(0,x. size () -1);
}
This particular example could be implemented using type annotations alone, but statement annotations are necessary in other cases, such as providing special presentations for closures or "use" blocks.
More details of this concept can be found in the following papers:
The benefits of annotations on statements have led to multiple independent efforts to provide similar functionality for specific tools. Unfortunately, these tools use different syntax (and also different naming conventions) for annotations. For example, here are examples for WCET analyzers:
WCET Tool | Annotation Example |
---|---|
Skånerost | /*$ loop-bound 100 */ |
XAC | //@ Loopcount(100) |
WCA | //@ loop=100 |
Annotations written for one tool cannot be analyzed by another. This limits uptake of any of the tools and leads to maintenance problems for developers who use multiple tools.
Without a standard mechanism for statement annotations, there is little motivation to standardize the naming conventions. (Also, tool developers must re-implement parse algorithms.)
Since declarations are statements in Java, developers can put annotations on some but not all statements (only the ones that are declarations). A programmer may wonder why the @Peer
annotation is currently permitted but the @LoopCount
annotation is not:
public Object clone ()
{
@Peer // legal in Java 6
BoundedStack retValue = new BoundedStack(maxSize);
retValue.nextFree = nextFree;
@LoopCount(100) // illegal in Java 6
for (int k = 0; k < nextFree; k++) {
retValue.theItems[k] = theItems[k];
}
return retValue;
}
Thus, allowing annotations on statements may in fact decrease the perceived complexity of Java when using verification tools.
However, this also demonstrates why allowing annotations on statements may be syntactically ambiguous, since declarations are also statements. There is thus a risk of grammatical ambiguity if a declaration statement can be annotated for two grammatical reasons.
Like Java, C# does not permit metadata on elements within a method body. Several extensions to the C# language support statement annotations.
[a]C#
Here is an example from [a]C#
(see article), an annotation mechanism for C#:
public void m() {
Console.WriteLine("Parallelable code sample");
[Parallel("Begin of a parallelizable block")] {
Console.WriteLine("Code executed by the main thread");
[Process("First process")] { /* Computation here */ }
[Process] { /* Computation here */ }
}
Console.WriteLine("Here is sequential");
}
Wicca# extends C# to support statement annotations. It supports statement-level advising, as demonstrated in the following example.
//[Concern("Input")]
inputMapper.Update(elapsedTime);
Like the original JSR 308 proposal, annotations on statements can be built on top of the existing framework for Java annotations.
The proposal can be fully implemented without any changes to the virtual machine or class file format. All that is needed is additional compiler support and new class file attributes.
Here is possible non-ambiguous change for the Java Language Grammar
Statement:
Annotations Statement
... // current body of "Statement" grammar production
Note, that this change won't allow to annotate the following production without introducing ambiguity (i.e. annotation on local variable declaration statement vs. type annotation on the variable type):
BlockStatement :
LocalVariableDeclarationStatement
ClassOrInterfaceDeclaration
[Identifier :] Statement
(TODO: Bytecode ranges are required even for simple statements: an annotation on a statement (such as indicating locks, for example) must be applied to every instruction generated from it, so incidating only the initial instruction of a statement is inadequate. Because of instruction reordering, a set of ranges is required. This section needs to be restructured to present that from the beginning, and not to suggest that blocks would require any differences in implementation strategy.)
Neil Gafter states, "javac and other compilers generate multiple live ranges for the same statement. For example, if you compile the following class (in JDK 1.6 or later) you'll find the invocation of g() repeated multiple times in the code. This is forced on compilers by the structure of the bytecode."
class J {
public static void f(boolean b1, boolean b2) {
try {
if (b1) return;
h();
if (b2) return;
h();
} finally {
g();
}
}
public static void g() {
}
public static void h() {
}
}
The major change necessary to implement statement annotations is in the compiler. Java compilers must be modified to accept annotations in the proposed locations. The relevant syntax tree classes (e.g., for the javac compiler: JCWhileLoop
, JCEnhancedForLoop
, etc.) must also be modified to store these annotations.
If generating code for a statement annotation (i.e., if the annotation retention policy is CLASS
or RUNTIME
), these compilers must emit attributes that describe the statement annotations. This section proposes a format for these attributes.
New attribute names, RuntimeVisibleStatementAnnotations
and RuntimeInvisibleStatementAnnotations
, could be reserved for statement annotations. These attributes would be attached to the Code
attribute. Their format would be identical to the existing RuntimeVisibleAnnotations
and RuntimeInvisibleAnnotations
attributes, except that an additional field is added:
u4 pc; // Program counter
This field indicates the address in the bytecode at which the statement begins.
For example, consider the following code, which has a statement annotation on a while
loop:
void foo()
{
int i = 0;
@LoopBound(max=10)
while (i < 10)
{
i++;
}
}
A Java compiler that has been modified to support statement annotations would therefore generate the following attribute:
RuntimeInvisibleStatementAnnotations_attribute {
u2 attribute_name_index = 10 (RuntimeInvisibleStatementAnnotations);
u2 attribute_length = 15;
u2 num_annotations = 1;
annotations =
{
u2 type_index = 11 (LLoopBound;);
u4 pc = 2;
u2 num_element_value_pairs = 1;
element_value_pairs =
{
u2 element_name_index = 12 (max);
element_value value = {
u1 tag = 73 (I);
u2 const_value_index = 13 (10);
}
}
}
}
There is some disagreement about whether run-time visible statement annotations are necessary. It has been suggested that all statement annotations be placed in the RuntimeInvisibleStatementAnnotations
attribute and leaving out the RuntimeVisibleStatementAnnotations
entirely.
The primary reason for this is that run-time visible statement annotations are unusable. Java has no standard mechanism (e.g., a reflection API) of exposing such annotations at run-time.
However, there may be future APIs that provide such exposure, and removing support for run-time visible annotations would complicate such APIs. There is also the question of what a Java compiler should do when it encounters a statement annotation marked as Retention.RUNTIME
. Should it silently change such annotations to Retention.CLASS
? Should it signal an error? The best choice of action is not clear. Furthermore, supporting the distinction between run-time visible and run-time invisible annotations is trivial, so there is nothing to gain by leaving out this feature.
Supporting annotations on blocks would require a more complicated attribute structure. Consider, for example, the following annotation:
@ReadOnly
{
...
}
There is no representation of this source block in the bytecode, and therefore the annotation data must include information about where it begins and where it ends. One solution is to add a reference_info
structure:
{
u2 start_pc;
u2 end_pc;
} reference_info;
where start_pc
and end_pc
indicate a range in the code array where the annotation is active. (This is similar to the exception_table
structure in the Code
attribute.)
This section describes proof-of-concept tools for working with statement annotations.
The OpenJDK javac compiler has been modified to support statement annotations on while
loops, do-while
loops, for
loops, and for-each
loops. The modifications are simplistic; just enough code has been changed to get loop annotations working. In particular, the modified compiler handles writing out the loop annotations as attribute data somewhat awkwardly. (Part of the problem is that javac seems to have been designed without anticipating future annotation formats. It may need to be refactored substantially in order to support statement annotations.) Still, the resulting compiler is entirely useable and should be helpful to anyone who wants to experiment with statement annotations on loops. It is available in the util/compiler
directory of the Volta distribution.
The Volta project also provides a utility called Dump Annotations. As its name implies, it will dump statement annotations to the console. It displays annotation data in the same struct
-like format as the Java Virtual Machine Specification, making the output easy to read.
The utility can be extended without much effort to dump any annotation format, so it should be useful to anyone working on JSR 308. To use it with statement annotations on loops, first install the modified javac compiler (described above), specify the location of this compiler in the build file, then run ant dump
. You should see the results of a simple test case.
This paper discusses the necessity of standard Java annotations for worst-case execution time analysis tools. It includes various use cases showing the need for annotations on statements (loops in particular).
This is JSR 308's home on the web. In addition to an overview and status of the proposal, it also declares that statement annotations are within the scope of JSR 308.
This mailing list is now inactive, but it hosted a discussion of annotations on statements.
The following individuals have helped make this document possible: