javaannotationsdeprecated

Make the java compiler warn when an annotated method is used (like @deprecated)


Let's say I define a custom annotation called @Unsafe.

I'd like to provide an annotation processor which will detect references to methods annotated with @Unsafe and print a warning.

For example, given this code ...

public class Foo {
  @Unsafe
  public void doSomething() { ... }
}

public class Bar {
  public static void main(String[] args) {
    new Foo().doSomething();
  }
}

... I want the compiler to print something like:

WARN > Bar.java, line 3 : Call to Unsafe API - Foo.doSomething()

It is very similar in spirit to @Deprecated, but my annotation is communicating something different, so I can't use @Deprecated directly. Is there a way to achieve this with an annotation processor? The annotation processor API seems to be more focused on the entities applying the annotations (Foo.java in my example) than entities which reference annotated members.

This question provides a technique to achieve it as a separate build step using ASM. But I'm wondering if I can do it in a more natural way with javac & annotation processing?


Solution

  • I think I could have technically achieved my goal using the response from @mernst, so I appreciate the suggestion. However, I found another route that worked better for me as I'm working on a commercial product and cannot incoporate the Checker Framework (its GPL license is incompatible with ours).

    In my solution, I use my own "standard" java annotation processor to build a listing of all the methods annotated with @Unsafe.

    Then, I developed a javac plugin. The Plugin API makes it easy to find every invocation of any method in the AST. By using some tips from this question, I was able to determine the class and method name from the MethodInvocationTree AST node. Then I compare those method invocations with the earlier "listing" I created containing methods annotated with @Unsafe and issue warnings where required.

    Here is an abbreviated version of my javac Plugin.

    import javax.lang.model.element.Element;
    import javax.lang.model.element.TypeElement;
    
    import com.sun.source.tree.MethodInvocationTree;
    import com.sun.source.util.JavacTask;
    import com.sun.source.util.Plugin;
    import com.sun.source.util.TaskEvent;
    import com.sun.source.util.TaskEvent.Kind;
    import com.sun.tools.javac.tree.JCTree;
    import com.sun.tools.javac.tree.TreeInfo;
    import com.sun.source.util.TaskListener;
    import com.sun.source.util.TreeScanner;
    
    public class UnsafePlugin implements Plugin, TaskListener {
    
      @Override
      public String getName() {
        return "UnsafePlugin";
      }
    
      @Override
      public void init(JavacTask task, String... args) {
        task.addTaskListener(this);
      }
    
      @Override
      public void finished(TaskEvent taskEvt) {
        if (taskEvt.getKind() == Kind.ANALYZE) {
          taskEvt.getCompilationUnit().accept(new TreeScanner<Void, Void>() {
            @Override
            public Void visitMethodInvocation(MethodInvocationTree methodInv, Void v) {
              Element method = TreeInfo.symbol((JCTree) methodInv.getMethodSelect());
              TypeElement invokedClass = (TypeElement) method.getEnclosingElement();
              String className = invokedClass.toString();
              String methodName = methodInv.getMethodSelect().toString().replaceAll(".*\\.", "");
              System.out.println("Method Invocation: " + className + " : " + methodName);
              return super.visitMethodInvocation(methodInv, v);
            }
          }, null);
        }
      }
    
      @Override
      public void started(TaskEvent taskEvt) {
      }
    
    }
    

    Note - in order for the javac plugin to be invoked, you must provide arguments on the command line:

    javac -processorpath build/unsafe-plugin.jar -Xplugin:UnsafePlugin
    

    Also, you must have a file META-INF/services/com.sun.source.util.Plugin in unsafe-plugin.jar containing the fully qualified name of the plugin:

    com.unsafetest.javac.UnsafePlugin