1/* 2 * Copyright (c) 2014, 2016, 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 24/* 25 * @test 26 * @bug 8029161 8029376 27 * @modules jdk.compiler/com.sun.tools.javac.api 28 * jdk.compiler/com.sun.tools.javac.file 29 */ 30 31import com.sun.source.tree.CompilationUnitTree; 32import com.sun.source.tree.IdentifierTree; 33import com.sun.source.tree.MemberSelectTree; 34import com.sun.source.util.JavacTask; 35import com.sun.source.util.TreePathScanner; 36import com.sun.source.util.Trees; 37import com.sun.tools.javac.api.JavacTool; 38import java.io.IOException; 39import java.net.URI; 40import java.net.URISyntaxException; 41import java.util.ArrayList; 42import java.util.Arrays; 43import java.util.List; 44import javax.lang.model.element.Element; 45import javax.lang.model.element.ElementKind; 46import javax.lang.model.type.TypeKind; 47import javax.tools.Diagnostic; 48import javax.tools.DiagnosticListener; 49import javax.tools.JavaFileManager; 50import javax.tools.JavaFileObject; 51import javax.tools.SimpleJavaFileObject; 52 53public class VerifyErroneousAnnotationsAttributed { 54 public static void main(String... args) throws IOException, URISyntaxException { 55 new VerifyErroneousAnnotationsAttributed().run(); 56 } 57 58 void run() throws IOException { 59 try { 60 int failCount = 0; 61 for (String ann : generateAnnotations()) { 62 String code = PATTERN.replace("PLACEHOLDER", ann); 63 try { 64 validate(code); 65 } catch (Throwable t) { 66 System.out.println("Failed for: "); 67 System.out.println(code); 68 t.printStackTrace(System.out); 69 failCount++; 70 } 71 } 72 73 if (failCount > 0) { 74 throw new IllegalStateException("failed sub-tests: " + failCount); 75 } 76 } finally { 77 fm.close(); 78 } 79 } 80 81 List<String> generateAnnotations() { 82 List<String> result = new ArrayList<>(); 83 84 result.addAll(Kind.ANNOTATION.generateValue(2, true)); 85 result.addAll(Kind.ANNOTATION.generateValue(2, false)); 86 87 return result; 88 } 89 90 enum Kind { 91 INT("i", "ValueInt") { 92 @Override public List<String> generateValue(int depth, boolean valid) { 93 if (valid) { 94 return Arrays.asList("INT_VALUE"); 95 } else { 96 return Arrays.asList("BROKEN_INT_VALUE"); 97 } 98 } 99 }, 100 ANNOTATION("a", "ValueAnnotation") { 101 @Override public List<String> generateValue(int depth, boolean valid) { 102 String ad = "@Annotation" + depth + (valid ? "" : "Unknown"); 103 104 if (depth <= 0) { 105 return Arrays.asList(ad); 106 } 107 108 List<String> result = new ArrayList<>(); 109 final Kind[][] generateKindCombinations = new Kind[][] { 110 new Kind[] {Kind.INT}, 111 new Kind[] {Kind.ANNOTATION}, 112 new Kind[] {Kind.INT, Kind.ANNOTATION} 113 }; 114 115 for (boolean generateAssignment : new boolean[] {false, true}) { 116 for (boolean generateValid : new boolean[] {false, true}) { 117 for (Kind[] generateKinds : generateKindCombinations) { 118 if (generateKinds.length > 1 && generateValid && !generateAssignment) { 119 //skip: the same code is generated for generateValid == false. 120 continue; 121 } 122 List<String> attributes = generateAttributes(generateAssignment, 123 generateValid, depth, generateKinds); 124 String annotation; 125 if (generateAssignment) { 126 annotation = ad; 127 } else { 128 annotation = ad + generateKinds[0].annotationWithValueSuffix; 129 } 130 for (String attr : attributes) { 131 result.add(annotation + "(" + attr + ")"); 132 } 133 } 134 } 135 } 136 137 return result; 138 } 139 140 List<String> generateAttributes(boolean generateAssignment, boolean valid, int depth, 141 Kind... kinds) { 142 List<List<String>> combinations = new ArrayList<>(); 143 144 for (boolean subValid : new boolean[] {false, true}) { 145 for (Kind k : kinds) { 146 String prefix; 147 148 if (generateAssignment) { 149 if (valid) { 150 prefix = k.validAttributeName + "="; 151 } else { 152 prefix = "invalid" + k.validAttributeName + "="; 153 } 154 } else { 155 prefix = ""; 156 } 157 158 List<String> combination = new ArrayList<>(); 159 160 combinations.add(combination); 161 162 for (String val : k.generateValue(depth - 1, subValid)) { 163 combination.add(prefix + val); 164 } 165 } 166 } 167 168 List<String> result = new ArrayList<>(); 169 170 combine(combinations, new StringBuilder(), result); 171 172 return result; 173 } 174 175 void combine(List<List<String>> combinations, StringBuilder current, List<String> to) { 176 if (combinations.isEmpty()) { 177 to.add(current.toString()); 178 return ; 179 } 180 181 int currLen = current.length(); 182 183 for (String str : combinations.get(0)) { 184 if (current.length() > 0) current.append(", "); 185 current.append(str); 186 187 combine(combinations.subList(1, combinations.size()), current, to); 188 189 current.delete(currLen, current.length()); 190 } 191 } 192 }; 193 String validAttributeName; 194 String annotationWithValueSuffix; 195 196 private Kind(String validAttributeName, String annotationWithValueSuffix) { 197 this.validAttributeName = validAttributeName; 198 this.annotationWithValueSuffix = annotationWithValueSuffix; 199 } 200 201 public abstract List<String> generateValue(int depth, boolean valid); 202 203 } 204 205 private static final String PATTERN = 206 "public class Test {\n" + 207 " public static final int INT_VALUE = 1;\n" + 208 " @interface Annotation0 {}\n" + 209 " @interface Annotation1 {int i() default 0; Annotation0 a() default @Annotation0; }\n" + 210 " @interface Annotation2 {int i() default 0; Annotation1 a() default @Annotation1; }\n" + 211 " @interface Annotation1ValueInt {int value() default 0; }\n" + 212 " @interface Annotation2ValueInt {int value() default 0; }\n" + 213 " @interface Annotation1ValueAnnotation {Annotation0 a() default @Annotation0; }\n" + 214 " @interface Annotation2ValueAnnotation {Annotation1 a() default @Annotation1; }\n" + 215 " PLACEHOLDER\n" + 216 " private void test() { }\n" + 217 "}"; 218 219 static final class TestCase { 220 final String code; 221 final boolean valid; 222 223 public TestCase(String code, boolean valid) { 224 this.code = code; 225 this.valid = valid; 226 } 227 228 } 229 230 final JavacTool tool = JavacTool.create(); 231 final JavaFileManager fm = tool.getStandardFileManager(null, null, null); 232 final DiagnosticListener<JavaFileObject> devNull = new DiagnosticListener<JavaFileObject>() { 233 @Override public void report(Diagnostic<? extends JavaFileObject> diagnostic) {} 234 }; 235 236 void validate(String code) throws IOException, URISyntaxException { 237 JavacTask task = tool.getTask(null, 238 fm, 239 devNull, 240 Arrays.asList("--should-stop:at=FLOW"), 241 null, 242 Arrays.asList(new MyFileObject(code))); 243 244 final Trees trees = Trees.instance(task); 245 final CompilationUnitTree cut = task.parse().iterator().next(); 246 task.analyze(); 247 248 //ensure all the annotation attributes are annotated meaningfully 249 //all the attributes in the test file should contain either an identifier 250 //or a select, so only checking those for a reasonable Element/Symbol. 251 new TreePathScanner<Void, Void>() { 252 @Override 253 public Void visitIdentifier(IdentifierTree node, Void p) { 254 verifyAttributedMeaningfully(); 255 return super.visitIdentifier(node, p); 256 } 257 @Override 258 public Void visitMemberSelect(MemberSelectTree node, Void p) { 259 verifyAttributedMeaningfully(); 260 return super.visitMemberSelect(node, p); 261 } 262 private void verifyAttributedMeaningfully() { 263 Element el = trees.getElement(getCurrentPath()); 264 265 if (el == null || el.getKind() == ElementKind.OTHER || 266 el.asType().getKind() == TypeKind.OTHER) { 267 throw new IllegalStateException("Not attributed properly: " + 268 getCurrentPath().getParentPath().getLeaf()); 269 } 270 } 271 }.scan(cut, null); 272 } 273 static class MyFileObject extends SimpleJavaFileObject { 274 private final String text; 275 public MyFileObject(String text) { 276 super(URI.create("myfo:/Test.java"), JavaFileObject.Kind.SOURCE); 277 this.text = text; 278 } 279 @Override 280 public CharSequence getCharContent(boolean ignoreEncodingErrors) { 281 return text; 282 } 283 } 284} 285