1 /*
   2  * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 package org.graalvm.compiler.nodeinfo.processor;
  24 
  25 import static java.util.Collections.reverse;
  26 
  27 import java.io.PrintWriter;
  28 import java.io.StringWriter;
  29 import java.util.ArrayList;
  30 import java.util.List;
  31 import java.util.Set;
  32 import java.util.stream.Collectors;
  33 
  34 import javax.annotation.processing.AbstractProcessor;
  35 import javax.annotation.processing.FilerException;
  36 import javax.annotation.processing.ProcessingEnvironment;
  37 import javax.annotation.processing.RoundEnvironment;
  38 import javax.annotation.processing.SupportedAnnotationTypes;
  39 import javax.annotation.processing.SupportedSourceVersion;
  40 import javax.lang.model.SourceVersion;
  41 import javax.lang.model.element.Element;
  42 import javax.lang.model.element.ElementKind;
  43 import javax.lang.model.element.Modifier;
  44 import javax.lang.model.element.TypeElement;
  45 import javax.lang.model.util.Types;
  46 import javax.tools.Diagnostic.Kind;
  47 
  48 import org.graalvm.compiler.nodeinfo.NodeInfo;
  49 
  50 @SupportedSourceVersion(SourceVersion.RELEASE_8)
  51 @SupportedAnnotationTypes({"org.graalvm.compiler.nodeinfo.NodeInfo"})
  52 public class GraphNodeProcessor extends AbstractProcessor {
  53     @Override
  54     public SourceVersion getSupportedSourceVersion() {
  55         return SourceVersion.latest();
  56     }
  57 
  58     /**
  59      * Node class currently being processed.
  60      */
  61     private Element scope;
  62 
  63     public static boolean isEnclosedIn(Element e, Element scopeElement) {
  64         List<Element> elementHierarchy = getElementHierarchy(e);
  65         return elementHierarchy.contains(scopeElement);
  66     }
  67 
  68     void errorMessage(Element element, String format, Object... args) {
  69         message(Kind.ERROR, element, format, args);
  70     }
  71 
  72     void message(Kind kind, Element element, String format, Object... args) {
  73         if (scope != null && !isEnclosedIn(element, scope)) {
  74             // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=428357#c1
  75             List<Element> elementHierarchy = getElementHierarchy(element);
  76             reverse(elementHierarchy);
  77             String loc = elementHierarchy.stream().filter(e -> e.getKind() != ElementKind.PACKAGE).map(Object::toString).collect(Collectors.joining("."));
  78             processingEnv.getMessager().printMessage(kind, String.format(loc + ": " + format, args), scope);
  79         } else {
  80             processingEnv.getMessager().printMessage(kind, String.format(format, args), element);
  81         }
  82     }
  83 
  84     private static List<Element> getElementHierarchy(Element e) {
  85         List<Element> elements = new ArrayList<>();
  86         elements.add(e);
  87 
  88         Element enclosing = e.getEnclosingElement();
  89         while (enclosing != null && enclosing.getKind() != ElementKind.PACKAGE) {
  90             elements.add(enclosing);
  91             enclosing = enclosing.getEnclosingElement();
  92         }
  93         if (enclosing != null) {
  94             elements.add(enclosing);
  95         }
  96         return elements;
  97     }
  98 
  99     /**
 100      * Bugs in an annotation processor can cause silent failure so try to report any exception
 101      * throws as errors.
 102      */
 103     private void reportException(Kind kind, Element element, Throwable t) {
 104         StringWriter buf = new StringWriter();
 105         t.printStackTrace(new PrintWriter(buf));
 106         buf.toString();
 107         message(kind, element, "Exception thrown during processing: %s", buf.toString());
 108     }
 109 
 110     ProcessingEnvironment getProcessingEnv() {
 111         return processingEnv;
 112     }
 113 
 114     boolean isNodeType(Element element) {
 115         if (element.getKind() != ElementKind.CLASS) {
 116             return false;
 117         }
 118         TypeElement type = (TypeElement) element;
 119         Types types = processingEnv.getTypeUtils();
 120 
 121         while (type != null) {
 122             if (type.toString().equals("org.graalvm.compiler.graph.Node")) {
 123                 return true;
 124             }
 125             type = (TypeElement) types.asElement(type.getSuperclass());
 126         }
 127         return false;
 128     }
 129 
 130     @Override
 131     public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
 132         if (roundEnv.processingOver()) {
 133             return false;
 134         }
 135 
 136         GraphNodeVerifier verifier = new GraphNodeVerifier(this);
 137 
 138         for (Element element : roundEnv.getElementsAnnotatedWith(NodeInfo.class)) {
 139             scope = element;
 140             try {
 141                 if (!isNodeType(element)) {
 142                     errorMessage(element, "%s can only be applied to Node subclasses", NodeInfo.class.getSimpleName());
 143                     continue;
 144                 }
 145 
 146                 NodeInfo nodeInfo = element.getAnnotation(NodeInfo.class);
 147                 if (nodeInfo == null) {
 148                     errorMessage(element, "Cannot get %s annotation from annotated element", NodeInfo.class.getSimpleName());
 149                     continue;
 150                 }
 151 
 152                 TypeElement typeElement = (TypeElement) element;
 153 
 154                 Set<Modifier> modifiers = typeElement.getModifiers();
 155                 if (!modifiers.contains(Modifier.FINAL) && !modifiers.contains(Modifier.ABSTRACT)) {
 156                     // TODO(thomaswue): Reenable this check.
 157                     // errorMessage(element, "%s annotated class must be either final or abstract",
 158                     // NodeInfo.class.getSimpleName());
 159                     // continue;
 160                 }
 161                 boolean found = false;
 162                 for (Element e : typeElement.getEnclosedElements()) {
 163                     if (e.getKind() == ElementKind.FIELD) {
 164                         if (e.getSimpleName().toString().equals("TYPE")) {
 165                             found = true;
 166                             break;
 167                         }
 168                     }
 169                 }
 170                 if (!found) {
 171                     errorMessage(element, "%s annotated class must have a field named TYPE", NodeInfo.class.getSimpleName());
 172                 }
 173 
 174                 if (!typeElement.equals(verifier.Node) && !modifiers.contains(Modifier.ABSTRACT)) {
 175                     verifier.verify(typeElement);
 176                 }
 177             } catch (ElementException ee) {
 178                 errorMessage(ee.element, ee.getMessage());
 179             } catch (Throwable t) {
 180                 reportException(isBug367599(t) ? Kind.NOTE : Kind.ERROR, element, t);
 181             } finally {
 182                 scope = null;
 183             }
 184         }
 185         return false;
 186     }
 187 
 188     /**
 189      * Determines if a given exception is (most likely) caused by
 190      * <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=367599">Bug 367599</a>.
 191      */
 192     public static boolean isBug367599(Throwable t) {
 193         if (t instanceof FilerException) {
 194             for (StackTraceElement ste : t.getStackTrace()) {
 195                 if (ste.toString().contains("org.eclipse.jdt.internal.apt.pluggable.core.filer.IdeFilerImpl.create")) {
 196                     // See: https://bugs.eclipse.org/bugs/show_bug.cgi?id=367599
 197                     return true;
 198                 }
 199             }
 200         }
 201         if (t.getCause() != null) {
 202             return isBug367599(t.getCause());
 203         }
 204         return false;
 205     }
 206 }