Can I use Java annotations to define compile time checks?
Asked Answered
O

1

16

For example, I wanted to create the annotation @Out to target parameters. Then I would somehow use the compiler to check if the parameter value is set before the function returns. Is this possible?

Also was thinking about a @Immutable annotation that would not allow any method not annotaded with @Const to be invoked or access to any public fields. (compile time and probably runtime?)

So far I have this:

//I'm assuming Class retention is a subset of Runtime retention
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface Out
{
    //no idea what to go in here.
}

this is the other annotation. again, I have no complete definition for it:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Immutable
{

}

I think I can begin to devise a strategy to implement that at runtime using reflection, but I wanted to instruct the compiler or pre-processor to check that stuff for me instead, so my annotations would have zero overhead.

This is one of those things that you think "if this could've been done, it would already be out there, and if it is, where can I grab it".

Edit: After further thought about @Const and @Immutable and after remembering java passes pointers to objects by value, I expanded the definition of @Const, got rid of @Immutable, and altered the definition of @Out, as follows bellow:

/**
* When Applied to a method, ensures the method doesn't change in any
* way the state of the object used to invoke it, i.e., all the fields
* of the object must remain the same, and no field may be returned,
* unless the field itself is marked as {@code @Const}. A method 
* annotated with {@code @Const} can only invoke other {@code @Const}
* methods of its class, can only use the class's fields to invoke
* {@code @Const} methods of the fields classes and can only pass fields
* as parameters to methods that annotate that formal parameter as
* {@code @Const}.
*
* When applied to a formal parameter, ensures the method will not
* modify the value referenced by the formal parameter. A formal   
* parameter annotated as {@code @Const} will not be aliased inside the
* body of the method. The method is not allowed to invoke another 
* method and pass the annotated parameter, save if the other method 
* also annotates the formal parameter as {@code @Const}. The method is 
* not allowed to use the parameter to invoke any of its type's methods,
* unless the method being invoked is also annotated as {@code @Const}
* 
* When applied to a field, ensures the field cannot be aliased and that
* no code can alter the state of that field, either from inside the   
* class that owns the field or from outside it. Any constructor in any
* derived class is allowed to set the value of the field and invoke any
* methods using it. As for methods, only those annotated as
* {@code @Const} may be invoked using the field. The field may only be
* passed as a parameter to a method if the method annotates the 
* corresponding formal parameter as {@code @Const}
* 
* When applied to a local variable, ensures neither the block where the
* variable is declared or any nested block will alter the value of that 
* local variable. The local variable may be defined only once, at any
* point where it is in scope. Only methods annotated as
* {@code @Const} may be invoked using this variable, and the variable 
* may only be passed as a parameter to another method if said method
* annotates its corresponding formal parameter as {@code @Const}
*
*/
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD,
ElementType.LOCAL_VARIABLE})
@Inherited
public @interface Const
{

}

now the @Out:

/**
* The formal parameter annotated with {@code @Out} must be undefined in 
* the scope of the caller, and it's the responsibility of the method to
* define it. If allowNull is true, the parameter can be explicitly set
* to null in the body of the method.
*/
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.PARAMETER)
public @interface Out
{
    boolean allowNull() default false;
}

Edit: I'm trying to implement this as an eclipse plugin, but I'm completely lost reading the manual. I wrote a plugin with the basic logic for accessing the AST and visiting methods and fields. I then made a bunch of dummy annotations that my plugin should detect, then I try to print the results, but I'm not even sure what to expect. My plugin is a "Incremental Build" plugin. Here's the code for it, If someone could take a look and just explain a few things to me. I'm completely lost in this API.

https://github.com/Starless2001/Plugin-for-Eclipse

Overprize answered 11/4, 2016 at 0:41 Comment(15)
Seriously, I think that to do what you want, you'd need a compiler that somehow supports user-definable plugins. And I don't know of any out there. It sounds like a very difficult thing to provide, from a compiler-writer's standpoint.Leucocyte
@Leucocyte that's disappointing. annotations should work as language extensions.Overprize
I might build something like a compiler. a pre-compiler, pehaps?Overprize
You could define an annotation processor. The retention for your annotation would be SOURCE. You would then create a Processor and attach it to your project by defining the fully qualified name of your processor impl in META-INF/services/javax.annotation.processing.Processor. You'll also have to enable annotation processing. The downside is that you must save your files before the errors/warnings appear, rather than them appearing instantly like built-in compile time checksPashto
I'd write an answer, but I'm not sure what you mean by "check if the parameter value is set before the function returns". Mind giving an example of what you are trying to achieve?Pashto
This blog post describes how Project Lombok hooks into the compilation process: notatube.blogspot.com/2010/11/…Prince
As @VinceEmigh mentioned, the correct solution is to write an annotation processor. It performs compile-time checks and issues warnings for code that does not conform to your rules. The implementation of these checks goes in the annotation processor, not in the annotation definition. A framework that makes it easy to write such compile-time compiler plug-ins is the Checker Framework, and it contains definitions for annotations like @NonNull and @Immutable.Ambroseambrosi
I still have not seen an example or tutorial where an annotation processor is used to read and validate code. I've only seen it adding code.Overprize
Check out Annotation Processor, generating a compiler error for an example of using annotation processing for compile time checks. Here is another post that I wrote a while back wondering why the warnings/errors weren't being displayed until after saving the files. It contains a small example of a compile-time annotation processorPashto
Here are two tutorials in which annotation processors validate code: tutorial 1, tutorial 2.Ambroseambrosi
@VinceEmigh But will I have access to the whole project's AST using an annotation processor? Because they seem like they only act on one file at a time. I don't want to (and I'm certain I wouldn't be capable to) build an AST myself. In order to make my idea work, I would have to check if something annotated with @Const will remain immutable even if accessed from other files or dependencies.Overprize
You don't need access to the whole project's AST all at once. Your checker should be modular, working one class at a time. It's enough to have access to the AST of the current class, plus the signatures (including annotations) of all classes that it uses. Annotation processing gives you that information.Ambroseambrosi
@Ambroseambrosi well, I didn't know that. about the inclusion of signatures and annotations of code foreign to the class. Thank you very much, I think I can work with that!Overprize
@Ambroseambrosi if you could put that into a nice answer I would be able to accept it.Overprize
I guess, your @Const is fine, but you've lost me with @Out. There are no out parameters in Java, actually, all you get in a method call is a local variable initialized in the caller. It may be a primitive or a reference, but it's always call by value, i.e., a pure "@In" and nothing goes "@Out".Rossuck
A
13

The javac compiler supports user-definable plugins, called annotation processors, that accomplish exactly what you want. You can think of annotations as language extensions.

The definition public @interface Immutable { ... } defines the syntax: the @Immutable annotation that you can write in your program. The annotation processor (the compiler plug-in) defines the semantics: it enforces the semantic rules and issues compiler warnings when your program violates the rules.

One framework that makes it easy to write such annotation processors is the Checker Framework, and it contains definitions for annotations like @NonNull and @Immutable. Here are two tutorials about how to use the Checker Framework to validate code: tutorial 1, tutorial 2.

Ordinary Java annotation processing is invoked on each declaration, such as classes, fields, methods, and method parameters, and ordinary Java gives the annotation processor no access to the program's full AST. You can think of the Checker Framework as a library that extends the power of Java annotation processing. It gives you access to the full AST of each class, and it lets you define rules for every statement in your program. Thus, your annotation processor can issue warnings when a statement invokes a non-@Const method on an @Immutable object.

Your annotation processor should be modular, working one class at a time. The annotation processor has access to the AST of the current class, plus the signatures, including annotations, of all classes that it uses. Annotation processing gives you that information (but not to the whole project's AST all at once).

Ambroseambrosi answered 15/4, 2016 at 13:17 Comment(2)
I will implement it this way. Thanks for all the help. I don't like checker framework because it's slow and incomplete. you need multiple "checkers" to define just immutability. Once it's ready I'll publish, who knows, someone may find it useful.Overprize
That sounds great. Please do share your results.Ambroseambrosi

© 2022 - 2024 — McMap. All rights reserved.