8029376: Full attribution of unresolvable annotations
authorjlahoda
Fri, 10 Jan 2014 11:31:09 +0100
changeset 22439 4785d87cd2d6
parent 22438 a41a8bfccea7
child 22440 d40c30326317
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
langtools/src/share/classes/com/sun/tools/javac/comp/Annotate.java
langtools/test/tools/javac/annotations/neg/8022765/ErroneousAnnotations.java
langtools/test/tools/javac/annotations/neg/8022765/ErroneousAnnotations.out
langtools/test/tools/javac/annotations/neg/8022765/T8022765.out
langtools/test/tools/javac/annotations/neg/8022765/VerifyErroneousAnnotationsAttributed.java
langtools/test/tools/javac/annotations/testCrashNestedAnnos/TestCrashNestedAnnos.out
langtools/test/tools/javac/annotations/typeAnnotations/failures/common/parambounds/BrokenAnnotation.out
langtools/test/tools/javac/diags/examples/AnnotationMustBeNameValue.java
--- 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";