8029376: Full attribution of unresolvable annotations
8029161: javac crashing when processing broken annotations
Summary: Attributing values of annotation attributes regardless how broken the annotation is.
Reviewed-by: jjg, jfranck
--- a/langtools/src/share/classes/com/sun/tools/javac/comp/Annotate.java Thu Jan 09 15:00:33 2014 +0100
+++ b/langtools/src/share/classes/com/sun/tools/javac/comp/Annotate.java Fri Jan 10 11:31:09 2014 +0100
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2003, 2014, 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
@@ -37,7 +37,6 @@
import static com.sun.tools.javac.code.TypeTag.ARRAY;
import static com.sun.tools.javac.code.TypeTag.CLASS;
import static com.sun.tools.javac.tree.JCTree.Tag.*;
-import javax.lang.model.type.ErrorType;
/** Enter annotations on symbols. Annotations accumulate in a queue,
* which is processed at the top level of any set of recursive calls
@@ -253,29 +252,11 @@
Type at = (a.annotationType.type != null ? a.annotationType.type
: attr.attribType(a.annotationType, env));
a.type = chk.checkType(a.annotationType.pos(), at, expected);
- if (a.type.isErroneous()) {
- // Need to make sure nested (anno)trees does not have null as .type
- attr.postAttr(a);
-
- if (typeAnnotation) {
- return new Attribute.TypeCompound(a.type, List.<Pair<MethodSymbol,Attribute>>nil(),
- new TypeAnnotationPosition());
- } else {
- return new Attribute.Compound(a.type, List.<Pair<MethodSymbol,Attribute>>nil());
- }
- }
- if ((a.type.tsym.flags() & Flags.ANNOTATION) == 0) {
+ boolean isError = a.type.isErroneous();
+ if ((a.type.tsym.flags() & Flags.ANNOTATION) == 0 && !isError) {
log.error(a.annotationType.pos(),
"not.annotation.type", a.type.toString());
-
- // Need to make sure nested (anno)trees does not have null as .type
- attr.postAttr(a);
-
- if (typeAnnotation) {
- return new Attribute.TypeCompound(a.type, List.<Pair<MethodSymbol,Attribute>>nil(), null);
- } else {
- return new Attribute.Compound(a.type, List.<Pair<MethodSymbol,Attribute>>nil());
- }
+ isError = true;
}
List<JCExpression> args = a.args;
if (args.length() == 1 && !args.head.hasTag(ASSIGN)) {
@@ -289,11 +270,13 @@
JCExpression t = tl.head;
if (!t.hasTag(ASSIGN)) {
log.error(t.pos(), "annotation.value.must.be.name.value");
+ enterAttributeValue(t.type = syms.errType, t, env);
continue;
}
JCAssign assign = (JCAssign)t;
if (!assign.lhs.hasTag(IDENT)) {
log.error(t.pos(), "annotation.value.must.be.name.value");
+ enterAttributeValue(t.type = syms.errType, t, env);
continue;
}
JCIdent left = (JCIdent)assign.lhs;
@@ -305,7 +288,7 @@
null);
left.sym = method;
left.type = method.type;
- if (method.owner != a.type.tsym)
+ if (method.owner != a.type.tsym && !isError)
log.error(left.pos(), "no.annotation.member", left.name, a.type);
Type result = method.type.getReturnType();
Attribute value = enterAttributeValue(result, assign.rhs, env);
@@ -389,7 +372,8 @@
enterAnnotation((JCAnnotation)tree, syms.errType, env);
return new Attribute.Error(((JCAnnotation)tree).annotationType.type);
}
- if (expected.isPrimitive() || types.isSameType(expected, syms.stringType)) {
+ if (expected.isPrimitive() ||
+ (types.isSameType(expected, syms.stringType) && !expected.hasTag(TypeTag.ERROR))) {
Type result = attr.attribExpr(tree, env, expected);
if (result.isErroneous())
return new Attribute.Error(result.getOriginalType());
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/javac/annotations/neg/8022765/ErroneousAnnotations.java Fri Jan 10 11:31:09 2014 +0100
@@ -0,0 +1,38 @@
+/**
+ * @test /nodynamiccopyright/
+ * @bug 8029376
+ * @summary Verify reasonable errors for erroneous annotations, and incorrectly used types
+ * @compile/fail/ref=ErroneousAnnotations.out -XDrawDiagnostics ErroneousAnnotations.java
+ */
+class ErroneousAnnotations {
+ @Undefined //no "is not an annotation type error"
+ private int f1;
+ @String //produce "is not an annotation type error"
+ private int f2;
+ @Annot(@Undefined)
+ private int f3;
+ @Annot(@String)
+ private int f4;
+ @Primitive(@Undefined)
+ private int f5;
+ @Primitive(@String)
+ private int f6;
+ @PrimitiveWrap(@PrimitiveImpl)
+ private int f7;
+
+ @interface Annot {
+ Undefined value();
+ }
+
+ @interface PrimitiveWrap {
+ Primitive value();
+ }
+
+ @interface Primitive {
+ int value();
+ }
+
+ interface PrimitiveImpl extends Primitive {
+ }
+}
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/javac/annotations/neg/8022765/ErroneousAnnotations.out Fri Jan 10 11:31:09 2014 +0100
@@ -0,0 +1,9 @@
+ErroneousAnnotations.java:24:9: compiler.err.cant.resolve.location: kindname.class, Undefined, , , (compiler.misc.location: kindname.annotation, ErroneousAnnotations.Annot, null)
+ErroneousAnnotations.java:8:6: compiler.err.cant.resolve.location: kindname.class, Undefined, , , (compiler.misc.location: kindname.class, ErroneousAnnotations, null)
+ErroneousAnnotations.java:10:6: compiler.err.prob.found.req: (compiler.misc.inconvertible.types: java.lang.String, java.lang.annotation.Annotation)
+ErroneousAnnotations.java:12:13: compiler.err.cant.resolve.location: kindname.class, Undefined, , , (compiler.misc.location: kindname.class, ErroneousAnnotations, null)
+ErroneousAnnotations.java:16:16: compiler.err.annotation.not.valid.for.type: int
+ErroneousAnnotations.java:16:17: compiler.err.cant.resolve.location: kindname.class, Undefined, , , (compiler.misc.location: kindname.class, ErroneousAnnotations, null)
+ErroneousAnnotations.java:18:16: compiler.err.annotation.not.valid.for.type: int
+ErroneousAnnotations.java:20:21: compiler.err.not.annotation.type: ErroneousAnnotations.PrimitiveImpl
+8 errors
--- a/langtools/test/tools/javac/annotations/neg/8022765/T8022765.out Thu Jan 09 15:00:33 2014 +0100
+++ b/langtools/test/tools/javac/annotations/neg/8022765/T8022765.out Fri Jan 10 11:31:09 2014 +0100
@@ -44,14 +44,10 @@
T8022765.java:90:20: compiler.err.prob.found.req: (compiler.misc.inconvertible.types: java.lang.String, int)
T8022765.java:92:13: compiler.err.prob.found.req: (compiler.misc.inconvertible.types: java.lang.String, E)
T8022765.java:97:6: compiler.err.annotation.value.not.allowable.type
-T8022765.java:97:8: compiler.err.attribute.value.must.be.constant
T8022765.java:98:12: compiler.err.annotation.value.not.allowable.type
-T8022765.java:98:14: compiler.err.attribute.value.must.be.constant
T8022765.java:99:6: compiler.err.annotation.value.not.allowable.type
-T8022765.java:99:8: compiler.err.attribute.value.must.be.constant
T8022765.java:100:5: compiler.err.annotation.value.not.allowable.type
-T8022765.java:100:7: compiler.err.attribute.value.must.be.constant
T8022765.java:101:11: compiler.err.annotation.value.must.be.annotation
T8022765.java:102:17: compiler.err.prob.found.req: (compiler.misc.inconvertible.types: E, int)
T8022765.java:103:11: compiler.err.prob.found.req: (compiler.misc.inconvertible.types: E, java.lang.String)
-56 errors
\ No newline at end of file
+52 errors
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/javac/annotations/neg/8022765/VerifyErroneousAnnotationsAttributed.java Fri Jan 10 11:31:09 2014 +0100
@@ -0,0 +1,276 @@
+/*
+ * Copyright (c) 2014, 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
+ * @bug 8029161 8029376
+ */
+
+import com.sun.source.tree.CompilationUnitTree;
+import com.sun.source.tree.IdentifierTree;
+import com.sun.source.tree.MemberSelectTree;
+import com.sun.source.util.JavacTask;
+import com.sun.source.util.TreePathScanner;
+import com.sun.source.util.Trees;
+import com.sun.tools.javac.api.JavacTool;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.type.TypeKind;
+import javax.tools.Diagnostic;
+import javax.tools.DiagnosticListener;
+import javax.tools.JavaFileObject;
+import javax.tools.SimpleJavaFileObject;
+
+public class VerifyErroneousAnnotationsAttributed {
+ public static void main(String... args) throws IOException, URISyntaxException {
+ new VerifyErroneousAnnotationsAttributed().run();
+ }
+
+ void run() {
+ int failCount = 0;
+ for (String ann : generateAnnotations()) {
+ String code = PATTERN.replace("PLACEHOLDER", ann);
+ try {
+ validate(code);
+ } catch (Throwable t) {
+ System.out.println("Failed for: ");
+ System.out.println(code);
+ t.printStackTrace(System.out);
+ failCount++;
+ }
+ }
+
+ if (failCount > 0) {
+ throw new IllegalStateException("failed sub-tests: " + failCount);
+ }
+ }
+
+ List<String> generateAnnotations() {
+ List<String> result = new ArrayList<>();
+
+ result.addAll(Kind.ANNOTATION.generateValue(2, true));
+ result.addAll(Kind.ANNOTATION.generateValue(2, false));
+
+ return result;
+ }
+
+ enum Kind {
+ INT("i", "ValueInt") {
+ @Override public List<String> generateValue(int depth, boolean valid) {
+ if (valid) {
+ return Arrays.asList("INT_VALUE");
+ } else {
+ return Arrays.asList("BROKEN_INT_VALUE");
+ }
+ }
+ },
+ ANNOTATION("a", "ValueAnnotation") {
+ @Override public List<String> generateValue(int depth, boolean valid) {
+ String ad = "@Annotation" + depth + (valid ? "" : "Unknown");
+
+ if (depth <= 0) {
+ return Arrays.asList(ad);
+ }
+
+ List<String> result = new ArrayList<>();
+ final Kind[][] generateKindCombinations = new Kind[][] {
+ new Kind[] {Kind.INT},
+ new Kind[] {Kind.ANNOTATION},
+ new Kind[] {Kind.INT, Kind.ANNOTATION}
+ };
+
+ for (boolean generateAssignment : new boolean[] {false, true}) {
+ for (boolean generateValid : new boolean[] {false, true}) {
+ for (Kind[] generateKinds : generateKindCombinations) {
+ if (generateKinds.length > 1 && generateValid && !generateAssignment) {
+ //skip: the same code is generated for generateValid == false.
+ continue;
+ }
+ List<String> attributes = generateAttributes(generateAssignment,
+ generateValid, depth, generateKinds);
+ String annotation;
+ if (generateAssignment) {
+ annotation = ad;
+ } else {
+ annotation = ad + generateKinds[0].annotationWithValueSuffix;
+ }
+ for (String attr : attributes) {
+ result.add(annotation + "(" + attr + ")");
+ }
+ }
+ }
+ }
+
+ return result;
+ }
+
+ List<String> generateAttributes(boolean generateAssignment, boolean valid, int depth,
+ Kind... kinds) {
+ List<List<String>> combinations = new ArrayList<>();
+
+ for (boolean subValid : new boolean[] {false, true}) {
+ for (Kind k : kinds) {
+ String prefix;
+
+ if (generateAssignment) {
+ if (valid) {
+ prefix = k.validAttributeName + "=";
+ } else {
+ prefix = "invalid" + k.validAttributeName + "=";
+ }
+ } else {
+ prefix = "";
+ }
+
+ List<String> combination = new ArrayList<>();
+
+ combinations.add(combination);
+
+ for (String val : k.generateValue(depth - 1, subValid)) {
+ combination.add(prefix + val);
+ }
+ }
+ }
+
+ List<String> result = new ArrayList<>();
+
+ combine(combinations, new StringBuilder(), result);
+
+ return result;
+ }
+
+ void combine(List<List<String>> combinations, StringBuilder current, List<String> to) {
+ if (combinations.isEmpty()) {
+ to.add(current.toString());
+ return ;
+ }
+
+ int currLen = current.length();
+
+ for (String str : combinations.get(0)) {
+ if (current.length() > 0) current.append(", ");
+ current.append(str);
+
+ combine(combinations.subList(1, combinations.size()), current, to);
+
+ current.delete(currLen, current.length());
+ }
+ }
+ };
+ String validAttributeName;
+ String annotationWithValueSuffix;
+
+ private Kind(String validAttributeName, String annotationWithValueSuffix) {
+ this.validAttributeName = validAttributeName;
+ this.annotationWithValueSuffix = annotationWithValueSuffix;
+ }
+
+ public abstract List<String> generateValue(int depth, boolean valid);
+
+ }
+
+ private static final String PATTERN =
+ "public class Test {\n" +
+ " public static final int INT_VALUE = 1;\n" +
+ " @interface Annotation0 {}\n" +
+ " @interface Annotation1 {int i() default 0; Annotation0 a() default @Annotation0; }\n" +
+ " @interface Annotation2 {int i() default 0; Annotation1 a() default @Annotation1; }\n" +
+ " @interface Annotation1ValueInt {int value() default 0; }\n" +
+ " @interface Annotation2ValueInt {int value() default 0; }\n" +
+ " @interface Annotation1ValueAnnotation {Annotation0 a() default @Annotation0; }\n" +
+ " @interface Annotation2ValueAnnotation {Annotation1 a() default @Annotation1; }\n" +
+ " PLACEHOLDER\n" +
+ " private void test() { }\n" +
+ "}";
+
+ static final class TestCase {
+ final String code;
+ final boolean valid;
+
+ public TestCase(String code, boolean valid) {
+ this.code = code;
+ this.valid = valid;
+ }
+
+ }
+
+ final JavacTool tool = JavacTool.create();
+ final DiagnosticListener<JavaFileObject> devNull = new DiagnosticListener<JavaFileObject>() {
+ @Override public void report(Diagnostic<? extends JavaFileObject> diagnostic) {}
+ };
+
+ void validate(String code) throws IOException, URISyntaxException {
+ JavacTask task = tool.getTask(null,
+ null,
+ devNull,
+ Arrays.asList("-XDshouldStopPolicy=FLOW"),
+ null,
+ Arrays.asList(new MyFileObject(code)));
+
+ final Trees trees = Trees.instance(task);
+ final CompilationUnitTree cut = task.parse().iterator().next();
+ task.analyze();
+
+ //ensure all the annotation attributes are annotated meaningfully
+ //all the attributes in the test file should contain either an identifier
+ //or a select, so only checking those for a reasonable Element/Symbol.
+ new TreePathScanner<Void, Void>() {
+ @Override
+ public Void visitIdentifier(IdentifierTree node, Void p) {
+ verifyAttributedMeaningfully();
+ return super.visitIdentifier(node, p);
+ }
+ @Override
+ public Void visitMemberSelect(MemberSelectTree node, Void p) {
+ verifyAttributedMeaningfully();
+ return super.visitMemberSelect(node, p);
+ }
+ private void verifyAttributedMeaningfully() {
+ Element el = trees.getElement(getCurrentPath());
+
+ if (el == null || el.getKind() == ElementKind.OTHER ||
+ el.asType().getKind() == TypeKind.OTHER) {
+ throw new IllegalStateException("Not attributed properly: " +
+ getCurrentPath().getParentPath().getLeaf());
+ }
+ }
+ }.scan(cut, null);
+ }
+ static class MyFileObject extends SimpleJavaFileObject {
+ private final String text;
+ public MyFileObject(String text) {
+ super(URI.create("myfo:/Test.java"), JavaFileObject.Kind.SOURCE);
+ this.text = text;
+ }
+ @Override
+ public CharSequence getCharContent(boolean ignoreEncodingErrors) {
+ return text;
+ }
+ }
+}
--- a/langtools/test/tools/javac/annotations/testCrashNestedAnnos/TestCrashNestedAnnos.out Thu Jan 09 15:00:33 2014 +0100
+++ b/langtools/test/tools/javac/annotations/testCrashNestedAnnos/TestCrashNestedAnnos.out Fri Jan 10 11:31:09 2014 +0100
@@ -1,3 +1,4 @@
TestCrashNestedAnnos.java:9:6: compiler.err.cant.resolve.location: kindname.class, A, , , (compiler.misc.location: kindname.class, TestCrashNestedAnnos, null)
+TestCrashNestedAnnos.java:9:9: compiler.err.cant.resolve.location: kindname.class, A1, , , (compiler.misc.location: kindname.class, TestCrashNestedAnnos, null)
TestCrashNestedAnnos.java:10:6: compiler.err.prob.found.req: (compiler.misc.inconvertible.types: B, java.lang.annotation.Annotation)
-2 errors
+3 errors
--- a/langtools/test/tools/javac/annotations/typeAnnotations/failures/common/parambounds/BrokenAnnotation.out Thu Jan 09 15:00:33 2014 +0100
+++ b/langtools/test/tools/javac/annotations/typeAnnotations/failures/common/parambounds/BrokenAnnotation.out Fri Jan 10 11:31:09 2014 +0100
@@ -1,3 +1,5 @@
BrokenAnnotation.java:16:6: compiler.err.cant.resolve.location: kindname.class, Target, , , (compiler.misc.location: kindname.class, BrokenAnnotation<T>, null)
+BrokenAnnotation.java:16:14: compiler.err.cant.resolve.location: kindname.variable, ElementType, , , (compiler.misc.location: kindname.class, BrokenAnnotation<T>, null)
+BrokenAnnotation.java:16:36: compiler.err.cant.resolve.location: kindname.variable, ElementType, , , (compiler.misc.location: kindname.class, BrokenAnnotation<T>, null)
BrokenAnnotation.java:15:34: compiler.err.annotation.type.not.applicable
-2 errors
\ No newline at end of file
+4 errors
--- a/langtools/test/tools/javac/diags/examples/AnnotationMustBeNameValue.java Thu Jan 09 15:00:33 2014 +0100
+++ b/langtools/test/tools/javac/diags/examples/AnnotationMustBeNameValue.java Fri Jan 10 11:31:09 2014 +0100
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2010, 2014, 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
@@ -22,6 +22,7 @@
*/
// key: compiler.err.annotation.value.must.be.name.value
+// key: compiler.err.cant.resolve
@interface Anno {
String name() default "anon";