001/*
002 * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation.
008 *
009 * This code is distributed in the hope that it will be useful, but WITHOUT
010 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
011 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
012 * version 2 for more details (a copy is included in the LICENSE file that
013 * accompanied this code).
014 *
015 * You should have received a copy of the GNU General Public License version
016 * 2 along with this work; if not, write to the Free Software Foundation,
017 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
018 *
019 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
020 * or visit www.oracle.com if you need additional information or have any
021 * questions.
022 */
023package com.oracle.graal.nodeinfo.processor;
024
025import static java.util.Collections.*;
026
027import java.io.*;
028import java.util.*;
029import java.util.stream.*;
030
031import javax.annotation.processing.*;
032import javax.lang.model.*;
033import javax.lang.model.element.*;
034import javax.lang.model.util.*;
035import javax.tools.Diagnostic.Kind;
036
037import com.oracle.graal.nodeinfo.*;
038
039@SupportedSourceVersion(SourceVersion.RELEASE_8)
040@SupportedAnnotationTypes({"com.oracle.graal.nodeinfo.NodeInfo"})
041public class GraphNodeProcessor extends AbstractProcessor {
042    @Override
043    public SourceVersion getSupportedSourceVersion() {
044        return SourceVersion.latest();
045    }
046
047    /**
048     * Node class currently being processed.
049     */
050    private Element scope;
051
052    public static boolean isEnclosedIn(Element e, Element scopeElement) {
053        List<Element> elementHierarchy = getElementHierarchy(e);
054        return elementHierarchy.contains(scopeElement);
055    }
056
057    void errorMessage(Element element, String format, Object... args) {
058        message(Kind.ERROR, element, format, args);
059    }
060
061    void message(Kind kind, Element element, String format, Object... args) {
062        if (scope != null && !isEnclosedIn(element, scope)) {
063            // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=428357#c1
064            List<Element> elementHierarchy = getElementHierarchy(element);
065            reverse(elementHierarchy);
066            String loc = elementHierarchy.stream().filter(e -> e.getKind() != ElementKind.PACKAGE).map(Object::toString).collect(Collectors.joining("."));
067            processingEnv.getMessager().printMessage(kind, String.format(loc + ": " + format, args), scope);
068        } else {
069            processingEnv.getMessager().printMessage(kind, String.format(format, args), element);
070        }
071    }
072
073    private static List<Element> getElementHierarchy(Element e) {
074        List<Element> elements = new ArrayList<>();
075        elements.add(e);
076
077        Element enclosing = e.getEnclosingElement();
078        while (enclosing != null && enclosing.getKind() != ElementKind.PACKAGE) {
079            elements.add(enclosing);
080            enclosing = enclosing.getEnclosingElement();
081        }
082        if (enclosing != null) {
083            elements.add(enclosing);
084        }
085        return elements;
086    }
087
088    /**
089     * Bugs in an annotation processor can cause silent failure so try to report any exception
090     * throws as errors.
091     */
092    private void reportException(Kind kind, Element element, Throwable t) {
093        StringWriter buf = new StringWriter();
094        t.printStackTrace(new PrintWriter(buf));
095        buf.toString();
096        message(kind, element, "Exception thrown during processing: %s", buf.toString());
097    }
098
099    ProcessingEnvironment getProcessingEnv() {
100        return processingEnv;
101    }
102
103    boolean isNodeType(Element element) {
104        if (element.getKind() != ElementKind.CLASS) {
105            return false;
106        }
107        TypeElement type = (TypeElement) element;
108        Types types = processingEnv.getTypeUtils();
109
110        while (type != null) {
111            if (type.toString().equals("com.oracle.graal.graph.Node")) {
112                return true;
113            }
114            type = (TypeElement) types.asElement(type.getSuperclass());
115        }
116        return false;
117    }
118
119    @Override
120    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
121        if (roundEnv.processingOver()) {
122            return false;
123        }
124
125        GraphNodeVerifier verifier = new GraphNodeVerifier(this);
126
127        for (Element element : roundEnv.getElementsAnnotatedWith(NodeInfo.class)) {
128            scope = element;
129            try {
130                if (!isNodeType(element)) {
131                    errorMessage(element, "%s can only be applied to Node subclasses", NodeInfo.class.getSimpleName());
132                    continue;
133                }
134
135                NodeInfo nodeInfo = element.getAnnotation(NodeInfo.class);
136                if (nodeInfo == null) {
137                    errorMessage(element, "Cannot get %s annotation from annotated element", NodeInfo.class.getSimpleName());
138                    continue;
139                }
140
141                TypeElement typeElement = (TypeElement) element;
142
143                Set<Modifier> modifiers = typeElement.getModifiers();
144                if (!modifiers.contains(Modifier.FINAL) && !modifiers.contains(Modifier.ABSTRACT)) {
145                    // TODO(thomaswue): Reenable this check.
146                    // errorMessage(element, "%s annotated class must be either final or abstract",
147                    // NodeInfo.class.getSimpleName());
148                    // continue;
149                }
150                boolean found = false;
151                for (Element e : typeElement.getEnclosedElements()) {
152                    if (e.getKind() == ElementKind.FIELD) {
153                        if (e.getSimpleName().toString().equals("TYPE")) {
154                            found = true;
155                            break;
156                        }
157                    }
158                }
159                if (!found) {
160                    errorMessage(element, "%s annotated class must have a field named TYPE", NodeInfo.class.getSimpleName());
161                }
162
163                if (!typeElement.equals(verifier.Node) && !modifiers.contains(Modifier.ABSTRACT)) {
164                    verifier.verify(typeElement);
165                }
166            } catch (ElementException ee) {
167                errorMessage(ee.element, ee.getMessage());
168            } catch (Throwable t) {
169                reportException(isBug367599(t) ? Kind.NOTE : Kind.ERROR, element, t);
170            } finally {
171                scope = null;
172            }
173        }
174        return false;
175    }
176
177    /**
178     * Determines if a given exception is (most likely) caused by <a
179     * href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=367599">Bug 367599</a>.
180     */
181    public static boolean isBug367599(Throwable t) {
182        if (t instanceof FilerException) {
183            for (StackTraceElement ste : t.getStackTrace()) {
184                if (ste.toString().contains("org.eclipse.jdt.internal.apt.pluggable.core.filer.IdeFilerImpl.create")) {
185                    // See: https://bugs.eclipse.org/bugs/show_bug.cgi?id=367599
186                    return true;
187                }
188            }
189        }
190        if (t.getCause() != null) {
191            return isBug367599(t.getCause());
192        }
193        return false;
194    }
195}