The AbstractProcessor below processes greghmerrill's @Unsafe annotation and emits warnings on method calls to @Unsafe annotated methods.
It is a slight modification of greghmerrills own answer, which was great, but I had some problems getting my IDEs incremental compiler (I am using Netbeans) to detect the warnings/errors etc emitted from the plugin - only those I printed from the processor was shown, though the behaviour was as expected when I ran 'mvn clean compile' ( I am using Maven). Whether this is due to some problem from my hand, or a points to difference between Plugins and AbstractProcessors/the phases of the compilation process, I do not know.
Anyway:
package com.hervian.annotationutils.target;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.util.*;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.TreeInfo;
import java.util.Set;
import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.*;
import javax.tools.Diagnostic;
@SupportedAnnotationTypes({"com.hervian.annotationutils.target.Unsafe"})
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class UnsafeAnnotationProcessor extends AbstractProcessor implements TaskListener {
Trees trees;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
trees = Trees.instance(processingEnv);
JavacTask.instance(processingEnv).setTaskListener(this);
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
//Process @Unsafe annotated methods if needed
return true;
}
@Override public void finished(TaskEvent taskEvt) {
if (taskEvt.getKind() == TaskEvent.Kind.ANALYZE) {
taskEvt.getCompilationUnit().accept(new TreeScanner<Void, Void>() {
@Override
public Void visitMethodInvocation(MethodInvocationTree methodInv, Void v) {
Element method = TreeInfo.symbol((JCTree) methodInv.getMethodSelect());
Unsafe unsafe = method.getAnnotation(Unsafe.class);
if (unsafe != null) {
JCTree jcTree = (JCTree) methodInv.getMethodSelect();
trees.printMessage(Diagnostic.Kind.WARNING, "Call to unsafe method.", jcTree, taskEvt.getCompilationUnit());
}
return super.visitMethodInvocation(methodInv, v);
}
}, null);
}
}
@Override public void started(TaskEvent taskEvt) { } }
When using the annotation and making calls to the annotated method it will look like this:

One needs to remember to add the fully qualified class name of the annotation processor to a META-INF/service file named javax.annotation.processing.Processor. This makes it available to the ServiceLoader framework.
Maven users having trouble with the com.sun** imports may find this answer from AnimeshSharma helpful.
I keep my annotation + annotation processor in a separate project. I had to disable annotation processing by adding the following to the pom:
<build>
<pluginManagement>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<compilerArgument>-proc:none</compilerArgument>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
Using the annotation and having the processor do its work was simple: In my other project (the one where the screenshot of method foo() is from) I simply added a dependency to the project containing the annotation and processor.
Lastly it should be mentioned that I am new to AbstractProcessors and TaskListeners. I do, fx, not have an overview of the performance or robustness of the code. The goal was simply to "get it to work" and provide a stub for similar projects.