1 package oracle.code.samples;
  2 
  3 import jdk.incubator.code.CodeElement;
  4 import jdk.incubator.code.CodeReflection;
  5 import jdk.incubator.code.Op;
  6 import jdk.incubator.code.dialect.core.CoreOp.FuncOp;
  7 import jdk.incubator.code.dialect.java.JavaOp.InvokeOp;
  8 import jdk.incubator.code.dialect.java.JavaOp.ThrowOp;
  9 import jdk.incubator.code.dialect.java.JavaOp.TryOp;
 10 import jdk.incubator.code.dialect.java.JavaType;
 11 
 12 import javax.annotation.processing.AbstractProcessor;
 13 import javax.annotation.processing.Messager;
 14 import javax.annotation.processing.ProcessingEnvironment;
 15 import javax.annotation.processing.RoundEnvironment;
 16 import javax.annotation.processing.SupportedAnnotationTypes;
 17 import javax.annotation.processing.SupportedSourceVersion;
 18 import javax.lang.model.SourceVersion;
 19 import javax.lang.model.element.Element;
 20 import javax.lang.model.element.ExecutableElement;
 21 import javax.lang.model.element.TypeElement;
 22 import javax.lang.model.util.ElementScannerPreview;
 23 import javax.tools.Diagnostic.Kind;
 24 import java.util.List;
 25 import java.util.Map;
 26 import java.util.Optional;
 27 import java.util.Set;
 28 
 29 /**
 30  * This annotation processor can be used to inspect methods annotated with {@code CodeReflection},
 31  * and check if they conform to specific programming model restrictions. This provides an example on how
 32  * clients might control the contents of a code model via compile-time checks, which can be useful e.g. if said
 33  * models are meant for an <em>foreign</em> execution environment that might not support all the features of
 34  * a full-blown JVM.
 35  * <p>
 36  * More specifically,this annotation processor issues error messages when it detects calls to <em>complex</em>
 37  * system methods (such as {@link System#gc()}, or {@link Runtime#loadLibrary(String)}). It also issues errors
 38  * when encountering <em>unsupported</em> languages features, such as {@code try/catch} or {@code throw}.
 39  */
 40 @SupportedAnnotationTypes("jdk.incubator.code.CodeReflection")
 41 @SupportedSourceVersion(SourceVersion.RELEASE_26)
 42 public class CodeReflectionProcessor extends AbstractProcessor {
 43 
 44     @Override
 45     public synchronized void init(ProcessingEnvironment processingEnv) {
 46         super.init(processingEnv);
 47     }
 48 
 49     @Override
 50     public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
 51         for (Element e : roundEnv.getElementsAnnotatedWith(CodeReflection.class)) {
 52             ReflectableMethodScanner scanner = new ReflectableMethodScanner();
 53             scanner.scan(e);
 54         }
 55         return true;
 56     }
 57 
 58     class ReflectableMethodScanner extends ElementScannerPreview<Void, Void> {
 59 
 60         final Messager logger = processingEnv.getMessager();
 61 
 62         @Override
 63         public Void scan(Element e, Void unused) {
 64             return super.scan(e, unused);
 65         }
 66 
 67         @Override
 68         public Void visitExecutable(ExecutableElement e, Void unused) {
 69             if (e.getAnnotationsByType(CodeReflection.class) != null) {
 70                 Optional<FuncOp> funcOp = Op.ofElement(processingEnv, e);
 71                 funcOp.ifPresent(f -> processMethodModel(e, f));
 72             }
 73             return null;
 74         }
 75 
 76         void processMethodModel(ExecutableElement element, FuncOp funcOp) {
 77             funcOp.elements().forEach(ce -> processOp(element, ce));
 78         }
 79 
 80         void processOp(Element element, CodeElement<?, ?> codeElement) {
 81             switch (codeElement) {
 82                 case InvokeOp invokeOp -> {
 83                     var desc = invokeOp.invokeDescriptor();
 84                     var receiverType = (JavaType) desc.refType();
 85                     String methodName = desc.name();
 86                     List<String> unsupportedMethods = UNSUPPORTED_METHODS.getOrDefault(receiverType, List.of());
 87                     for (String unsupportedMethod : unsupportedMethods) {
 88                         if (unsupportedMethod.equals(methodName)) {
 89                             String methErrString = receiverType.toNominalDescriptor().displayName() + "." + methodName;
 90                             logger.printMessage(Kind.ERROR,  methErrString + " not supported in reflectable methods", element);
 91                             break;
 92                         }
 93                     }
 94                 }
 95                 case Op op -> {
 96                     String unsupportedOp = UNSUPPORTED_OPS.get(op.getClass());
 97                     if (unsupportedOp != null) {
 98                         logger.printMessage(Kind.ERROR, unsupportedOp + " not supported in reflectable methods", element);
 99                     }
100                 }
101                 default -> {
102                     // do nothing
103                 }
104             }
105         }
106     }
107 
108     static final Map<JavaType, List<String>> UNSUPPORTED_METHODS = Map.of(
109             JavaType.type(System.class), List.of("exit", "gc", "load", "loadLibrary"),
110             JavaType.type(Runtime.class), List.of("load", "loadLibrary"));
111 
112     static final Map<Class<?>, String> UNSUPPORTED_OPS = Map.of(
113             ThrowOp.class, "throw statement",
114             TryOp.class, "try/catch statement"
115     );
116 }