8027310: Annotation Processor crashes with NPE
Summary: JCAnnotation.attribute is null when annotation type is unavailable
Reviewed-by: jjg, jfranck
--- a/langtools/src/share/classes/com/sun/tools/javac/comp/Attr.java Fri Nov 01 19:08:56 2013 +0000
+++ b/langtools/src/share/classes/com/sun/tools/javac/comp/Attr.java Fri Nov 01 21:43:27 2013 +0100
@@ -2254,7 +2254,8 @@
// empty annotations, if only declaration annotations were given.
// This method will raise an error for such a type.
for (JCAnnotation ai : annotations) {
- if (typeAnnotations.annotationType(ai.attribute, sym) == TypeAnnotations.AnnotationType.DECLARATION) {
+ if (!ai.type.isErroneous() &&
+ typeAnnotations.annotationType(ai.attribute, sym) == TypeAnnotations.AnnotationType.DECLARATION) {
log.error(ai.pos(), "annotation.type.not.applicable");
}
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/javac/processing/errors/CrashOnNonExistingAnnotation/Processor.java Fri Nov 01 21:43:27 2013 +0100
@@ -0,0 +1,208 @@
+/*
+ * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.io.File;
+import java.io.IOException;
+import java.io.Writer;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.TreeMap;
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.RoundEnvironment;
+import javax.annotation.processing.SupportedAnnotationTypes;
+import javax.annotation.processing.SupportedOptions;
+import javax.lang.model.element.TypeElement;
+import javax.tools.Diagnostic;
+import javax.tools.DiagnosticCollector;
+import javax.tools.DiagnosticListener;
+import javax.tools.FileObject;
+import javax.tools.ForwardingJavaFileManager;
+import javax.tools.JavaFileManager;
+import javax.tools.JavaFileObject;
+import javax.tools.JavaFileObject.Kind;
+import javax.tools.SimpleJavaFileObject;
+import com.sun.source.tree.AnnotationTree;
+import com.sun.source.tree.CompilationUnitTree;
+import com.sun.source.tree.LiteralTree;
+import com.sun.source.util.JavacTask;
+import com.sun.source.util.TreeScanner;
+import com.sun.source.util.Trees;
+import com.sun.tools.javac.api.JavacTool;
+import com.sun.tools.javac.file.JavacFileManager;
+import com.sun.tools.javac.util.Assert;
+
+@SupportedAnnotationTypes("*")
+@SupportedOptions("target")
+public class Processor extends AbstractProcessor {
+
+ private int round = 0;
+ @Override
+ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+ if (round++ == 0) {
+ try (Writer out = processingEnv.getFiler()
+ .createSourceFile("Anno.java")
+ .openWriter()) {
+ String target = processingEnv.getOptions().get("target");
+ String code = "import java.lang.annotation.ElementType;\n" +
+ "import java.lang.annotation.Target;\n" +
+ "@Target(ElementType." + target + ")\n" +
+ "@interface Anno { public String value(); }\n";
+ out.write(code);
+ } catch (IOException exc) {
+ throw new IllegalStateException(exc);
+ }
+ }
+ return true;
+ }
+
+ public static void main(String... args) throws IOException, URISyntaxException {
+ if (args.length != 1) throw new IllegalStateException("Must provide class name!");
+ String testContent = null;
+ File testSrc = new File(System.getProperty("test.src"));
+ File testFile = new File(testSrc, args[0]);
+ if (!testFile.canRead()) throw new IllegalStateException("Cannot read the test source");
+ JavacTool compiler = JavacTool.create();
+ JavacFileManager fm = compiler.getStandardFileManager(null, null, null);
+ testContent = fm.getRegularFile(testFile).getCharContent(true).toString();
+ JavaFileObject testFileObject = new TestFO(new URI("mem://" + args[0]), testContent);
+ TestFM testFileManager = new TestFM(fm);
+ JavacTask task = compiler.getTask(null,
+ testFileManager,
+ new DiagnosticCollector<JavaFileObject>(),
+ null,
+ null,
+ Arrays.asList(testFileObject));
+ final Trees trees = Trees.instance(task);
+ final CompilationUnitTree cut = task.parse().iterator().next();
+
+ final Map<int[], String> annotation2Target = new TreeMap<>(new Comparator<int[]>() {
+ @Override public int compare(int[] o1, int[] o2) {
+ return o2[0] - o1[0];
+ }
+ });
+
+ new TreeScanner<Void, Void>() {
+ @Override
+ public Void visitAnnotation(AnnotationTree node, Void p) {
+ int endPos = (int) trees.getSourcePositions().getEndPosition(cut, node);
+
+ Assert.check(endPos >= 0);
+
+ int startPos = (int) trees.getSourcePositions().getStartPosition(cut, node);
+ String target = ((LiteralTree) node.getArguments().get(0)).getValue().toString();
+
+ annotation2Target.put(new int[] {startPos, endPos}, target);
+
+ return super.visitAnnotation(node, p);
+ }
+ }.scan(cut.getTypeDecls().get(0), null);
+
+ DiagnosticListener<JavaFileObject> noErrors = new DiagnosticListener<JavaFileObject>() {
+ @Override public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
+ if (diagnostic.getKind() == Diagnostic.Kind.ERROR) {
+ throw new IllegalStateException(diagnostic.toString());
+ }
+ }
+ };
+
+ for (Entry<int[], String> e : annotation2Target.entrySet()) {
+ StringBuilder updatedContent = new StringBuilder();
+ int last = testContent.length();
+
+ for (int[] toRemove : annotation2Target.keySet()) {
+ if (toRemove == e.getKey()) continue;
+ updatedContent.insert(0, testContent.substring(toRemove[1], last));
+ last = toRemove[0];
+ }
+
+ updatedContent.insert(0, testContent.substring(0, last));
+
+ JavaFileObject updatedFile = new TestFO(new URI("mem://" + args[0]),
+ updatedContent.toString());
+ JavacTask testTask = compiler.getTask(null,
+ testFileManager,
+ noErrors,
+ Arrays.asList("-processor", "Processor",
+ "-Atarget=" + e.getValue()),
+ null,
+ Arrays.asList(updatedFile));
+
+ try {
+ testTask.analyze();
+ } catch (Throwable exc) {
+ System.out.println("error while processing:");
+ System.out.println(updatedContent);
+ throw exc;
+ }
+
+ JavacTask testTask2 = compiler.getTask(null,
+ testFileManager,
+ new DiagnosticCollector<JavaFileObject>(),
+ null,
+ null,
+ Arrays.asList(updatedFile));
+
+ try {
+ testTask2.analyze();
+ } catch (Throwable exc) {
+ System.out.println("error while processing:");
+ System.out.println(updatedContent);
+ throw exc;
+ }
+ }
+ }
+
+ private static final class TestFO extends SimpleJavaFileObject {
+ private final String content;
+ public TestFO(URI uri, String content) {
+ super(uri, Kind.SOURCE);
+ this.content = content;
+ }
+
+ @Override public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
+ return content;
+ }
+
+ @Override public boolean isNameCompatible(String simpleName, Kind kind) {
+ return true;
+ }
+ }
+
+ private static final class TestFM extends ForwardingJavaFileManager<JavaFileManager> {
+
+ public TestFM(JavaFileManager fileManager) {
+ super(fileManager);
+ }
+
+ @Override
+ public boolean isSameFile(FileObject a, FileObject b) {
+ return a.equals(b);
+ }
+
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/javac/processing/errors/CrashOnNonExistingAnnotation/Source.java Fri Nov 01 21:43:27 2013 +0100
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test /nodynamiccopyright/
+ * @bug 8027310
+ * @summary Ensure no exceptions on unresolvable annotations
+ * @build Processor
+ * @run main Processor Source.java
+ */
+
+import java.util.List;
+
+@Anno("TYPE")
+public class Source {
+ @Anno("TYPE")
+ class Inner {
+ class InnerInner {
+ public @Anno("CONSTRUCTOR") InnerInner(@Anno("TYPE_USE") Source. @Anno("TYPE_USE") Inner Inner.this,
+ @Anno("PARAMETER") java.lang. @Anno("TYPE_USE") Runnable p) {
+ Runnable r = () -> {
+ @Anno("TYPE_USE") Object tested = null;
+ @Anno("TYPE_USE") boolean isAnnotated = tested instanceof @Anno("TYPE_USE") String;
+ };
+
+ @Anno("TYPE_USE") Object tested = (@Anno("TYPE_USE") String @Anno("TYPE_USE") []) null;
+ @Anno("TYPE_USE") boolean isAnnotated = tested instanceof@Anno("TYPE_USE") String;
+
+ tested = new java.lang. @Anno("TYPE_USE") Object();
+ tested = new @Anno("TYPE_USE") Object();
+ }
+ }
+ }
+
+ {
+ Runnable r = () -> {
+ @Anno("TYPE_USE") Object tested = null;
+ @Anno("TYPE_USE") boolean isAnnotated = tested instanceof @Anno("TYPE_USE") String;
+ };
+
+ @Anno("TYPE_USE") Object tested = (@Anno("TYPE_USE") String @Anno("TYPE_USE") []) null;
+ @Anno("TYPE_USE") boolean isAnnotated = tested instanceof@Anno("TYPE_USE") String;
+
+ tested = new java.lang. @Anno("TYPE_USE") Object();
+ tested = new @Anno("TYPE_USE") Object();
+ }
+
+ @Anno("TYPE")
+ @Anno("ANNOTATION_TYPE")
+ @interface A { }
+ abstract class Parameterized<@Anno("TYPE_PARAMETER") T extends @Anno("TYPE_USE") CharSequence &
+ @Anno("TYPE_USE") Runnable>
+ implements @Anno("TYPE_USE") List<@Anno("TYPE_USE") Runnable> { }
+}