Thu, 03 Feb 2011 09:36:28 +0000
5017953: spurious cascaded diagnostics when name not found
Summary: when an operator is applied to one or more erroneous operands, spurious diagnostics are generated
Reviewed-by: jjg
1 /*
2 * Copyright (c) 2010, 2011, 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 */
24 import com.sun.tools.javac.file.JavacFileManager;
25 import java.io.*;
26 import java.util.*;
27 import java.util.regex.*;
28 import javax.tools.Diagnostic;
29 import javax.tools.DiagnosticCollector;
30 import javax.tools.JavaCompiler;
31 import javax.tools.JavaCompiler.CompilationTask;
32 import javax.tools.JavaFileObject;
33 import javax.tools.StandardJavaFileManager;
34 import javax.tools.ToolProvider;
36 // The following two classes are both used, but cannot be imported directly
37 // import com.sun.tools.javac.Main
38 // import com.sun.tools.javac.main.Main
40 import com.sun.tools.javac.util.Context;
41 import com.sun.tools.javac.util.JavacMessages;
42 import com.sun.tools.javac.util.JCDiagnostic;
43 import java.net.URL;
44 import java.net.URLClassLoader;
45 import javax.annotation.processing.Processor;
47 /**
48 * Class to handle example code designed to illustrate javac diagnostic messages.
49 */
50 class Example implements Comparable<Example> {
51 /* Create an Example from the files found at path.
52 * The head of the file, up to the first Java code, is scanned
53 * for information about the test, such as what resource keys it
54 * generates when run, what options are required to run it, and so on.
55 */
56 Example(File file) {
57 this.file = file;
58 declaredKeys = new TreeSet<String>();
59 srcFiles = new ArrayList<File>();
60 procFiles = new ArrayList<File>();
61 supportFiles = new ArrayList<File>();
62 srcPathFiles = new ArrayList<File>();
64 findFiles(file, srcFiles);
65 for (File f: srcFiles) {
66 parse(f);
67 }
69 if (infoFile == null)
70 throw new Error("Example " + file + " has no info file");
71 }
73 private void findFiles(File f, List<File> files) {
74 if (f.isDirectory()) {
75 for (File c: f.listFiles()) {
76 if (files == srcFiles && c.getName().equals("processors"))
77 findFiles(c, procFiles);
78 else if (files == srcFiles && c.getName().equals("sourcepath")) {
79 srcPathDir = c;
80 findFiles(c, srcPathFiles);
81 } else if (files == srcFiles && c.getName().equals("support"))
82 findFiles(c, supportFiles);
83 else
84 findFiles(c, files);
85 }
86 } else if (f.isFile() && f.getName().endsWith(".java")) {
87 files.add(f);
88 }
89 }
91 private void parse(File f) {
92 Pattern keyPat = Pattern.compile(" *// *key: *([^ ]+) *");
93 Pattern optPat = Pattern.compile(" *// *options: *(.*)");
94 Pattern runPat = Pattern.compile(" *// *run: *(.*)");
95 Pattern javaPat = Pattern.compile(" *@?[A-Za-z].*");
96 try {
97 String[] lines = read(f).split("[\r\n]+");
98 for (String line: lines) {
99 Matcher keyMatch = keyPat.matcher(line);
100 if (keyMatch.matches()) {
101 foundInfo(f);
102 declaredKeys.add(keyMatch.group(1));
103 continue;
104 }
105 Matcher optMatch = optPat.matcher(line);
106 if (optMatch.matches()) {
107 foundInfo(f);
108 options = Arrays.asList(optMatch.group(1).trim().split(" +"));
109 continue;
110 }
111 Matcher runMatch = runPat.matcher(line);
112 if (runMatch.matches()) {
113 foundInfo(f);
114 runOpts = Arrays.asList(runMatch.group(1).trim().split(" +"));
115 }
116 if (javaPat.matcher(line).matches())
117 break;
118 }
119 } catch (IOException e) {
120 throw new Error(e);
121 }
122 }
124 private void foundInfo(File file) {
125 if (infoFile != null && !infoFile.equals(file))
126 throw new Error("multiple info files found: " + infoFile + ", " + file);
127 infoFile = file;
128 }
130 String getName() {
131 return file.getName();
132 }
134 /**
135 * Get the set of resource keys that this test declares it will generate
136 * when it is run.
137 */
138 Set<String> getDeclaredKeys() {
139 return declaredKeys;
140 }
142 /**
143 * Get the set of resource keys that this test generates when it is run.
144 * The test will be run if it has not already been run.
145 */
146 Set<String> getActualKeys() {
147 if (actualKeys == null)
148 actualKeys = run(false);
149 return actualKeys;
150 }
152 /**
153 * Run the test. Information in the test header is used to determine
154 * how to run the test.
155 */
156 void run(PrintWriter out, boolean raw, boolean verbose) {
157 if (out == null)
158 throw new NullPointerException();
159 try {
160 run(out, null, raw, verbose);
161 } catch (IOException e) {
162 e.printStackTrace(out);
163 }
164 }
166 Set<String> run(boolean verbose) {
167 Set<String> keys = new TreeSet<String>();
168 try {
169 run(null, keys, true, verbose);
170 } catch (IOException e) {
171 e.printStackTrace(System.err);
172 }
173 return keys;
174 }
176 /**
177 * Run the test. Information in the test header is used to determine
178 * how to run the test.
179 */
180 private void run(PrintWriter out, Set<String> keys, boolean raw, boolean verbose)
181 throws IOException {
182 ClassLoader loader = getClass().getClassLoader();
183 if (supportFiles.size() > 0) {
184 File supportDir = new File(tempDir, "support");
185 supportDir.mkdirs();
186 clean(supportDir);
187 List<String> sOpts = Arrays.asList("-d", supportDir.getPath());
188 new Jsr199Compiler(verbose).run(null, null, false, sOpts, procFiles);
189 URLClassLoader ucl =
190 new URLClassLoader(new URL[] { supportDir.toURI().toURL() }, loader);
191 loader = ucl;
192 }
194 File classesDir = new File(tempDir, "classes");
195 classesDir.mkdirs();
196 clean(classesDir);
198 List<String> opts = new ArrayList<String>();
199 opts.add("-d");
200 opts.add(classesDir.getPath());
201 if (options != null)
202 opts.addAll(options);
204 if (procFiles.size() > 0) {
205 List<String> pOpts = Arrays.asList("-d", classesDir.getPath());
206 new Jsr199Compiler(verbose).run(null, null, false, pOpts, procFiles);
207 opts.add("-classpath"); // avoid using -processorpath for now
208 opts.add(classesDir.getPath());
209 createAnnotationServicesFile(classesDir, procFiles);
210 }
212 if (srcPathDir != null) {
213 opts.add("-sourcepath");
214 opts.add(srcPathDir.getPath());
215 }
217 try {
218 Compiler c = Compiler.getCompiler(runOpts, verbose);
219 c.run(out, keys, raw, opts, srcFiles);
220 } catch (IllegalArgumentException e) {
221 if (out != null) {
222 out.println("Invalid value for run tag: " + runOpts);
223 }
224 }
225 }
227 void createAnnotationServicesFile(File dir, List<File> procFiles) throws IOException {
228 File servicesDir = new File(new File(dir, "META-INF"), "services");
229 servicesDir.mkdirs();
230 File annoServices = new File(servicesDir, Processor.class.getName());
231 Writer out = new FileWriter(annoServices);
232 try {
233 for (File f: procFiles) {
234 out.write(f.getName().toString().replace(".java", ""));
235 }
236 } finally {
237 out.close();
238 }
239 }
241 @Override
242 public int compareTo(Example e) {
243 return file.compareTo(e.file);
244 }
246 @Override
247 public String toString() {
248 return file.getPath();
249 }
251 /**
252 * Read the contents of a file.
253 */
254 private String read(File f) throws IOException {
255 byte[] bytes = new byte[(int) f.length()];
256 DataInputStream in = new DataInputStream(new FileInputStream(f));
257 try {
258 in.readFully(bytes);
259 } finally {
260 in.close();
261 }
262 return new String(bytes);
263 }
265 /**
266 * Clean the contents of a directory.
267 */
268 boolean clean(File dir) {
269 boolean ok = true;
270 for (File f: dir.listFiles()) {
271 if (f.isDirectory())
272 ok &= clean(f);
273 ok &= f.delete();
274 }
275 return ok;
276 }
278 File file;
279 List<File> srcFiles;
280 List<File> procFiles;
281 File srcPathDir;
282 List<File> srcPathFiles;
283 List<File> supportFiles;
284 File infoFile;
285 private List<String> runOpts;
286 private List<String> options;
287 private Set<String> actualKeys;
288 private Set<String> declaredKeys;
290 static File tempDir = new File(System.getProperty("java.io.tmpdir"));
291 static void setTempDir(File tempDir) {
292 Example.tempDir = tempDir;
293 }
295 abstract static class Compiler {
296 interface Factory {
297 Compiler getCompiler(List<String> opts, boolean verbose);
298 }
300 static class DefaultFactory implements Factory {
301 public Compiler getCompiler(List<String> opts, boolean verbose) {
302 String first;
303 String[] rest;
304 if (opts == null || opts.isEmpty()) {
305 first = null;
306 rest = new String[0];
307 } else {
308 first = opts.get(0);
309 rest = opts.subList(1, opts.size()).toArray(new String[opts.size() - 1]);
310 }
311 if (first == null || first.equals("jsr199"))
312 return new Jsr199Compiler(verbose, rest);
313 else if (first.equals("simple"))
314 return new SimpleCompiler(verbose);
315 else if (first.equals("backdoor"))
316 return new BackdoorCompiler(verbose);
317 else
318 throw new IllegalArgumentException(first);
319 }
320 }
322 static Factory factory;
324 static Compiler getCompiler(List<String> opts, boolean verbose) {
325 if (factory == null)
326 factory = new DefaultFactory();
328 return factory.getCompiler(opts, verbose);
329 }
331 protected Compiler(boolean verbose) {
332 this.verbose = verbose;
333 }
335 abstract boolean run(PrintWriter out, Set<String> keys, boolean raw,
336 List<String> opts, List<File> files);
338 void setSupportClassLoader(ClassLoader cl) {
339 loader = cl;
340 }
342 protected ClassLoader loader;
343 protected boolean verbose;
344 }
346 /**
347 * Compile using the JSR 199 API. The diagnostics generated are
348 * scanned for resource keys. Not all diagnostic keys are generated
349 * via the JSR 199 API -- for example, rich diagnostics are not directly
350 * accessible, and some diagnostics generated by the file manager may
351 * not be generated (for example, the JSR 199 file manager does not see
352 * -Xlint:path).
353 */
354 static class Jsr199Compiler extends Compiler {
355 List<String> fmOpts;
357 Jsr199Compiler(boolean verbose, String... args) {
358 super(verbose);
359 for (int i = 0; i < args.length; i++) {
360 String arg = args[i];
361 if (arg.equals("-filemanager") && (i + 1 < args.length)) {
362 fmOpts = Arrays.asList(args[++i].split(","));
363 } else
364 throw new IllegalArgumentException(arg);
365 }
366 }
368 @Override
369 boolean run(PrintWriter out, Set<String> keys, boolean raw, List<String> opts, List<File> files) {
370 if (out != null && keys != null)
371 throw new IllegalArgumentException();
373 if (verbose)
374 System.err.println("run_jsr199: " + opts + " " + files);
376 DiagnosticCollector<JavaFileObject> dc = null;
377 if (keys != null)
378 dc = new DiagnosticCollector<JavaFileObject>();
380 if (raw) {
381 List<String> newOpts = new ArrayList<String>();
382 newOpts.add("-XDrawDiagnostics");
383 newOpts.addAll(opts);
384 opts = newOpts;
385 }
387 JavaCompiler c = ToolProvider.getSystemJavaCompiler();
389 StandardJavaFileManager fm = c.getStandardFileManager(dc, null, null);
390 if (fmOpts != null)
391 fm = new FileManager(fm, fmOpts);
393 Iterable<? extends JavaFileObject> fos = fm.getJavaFileObjectsFromFiles(files);
395 CompilationTask t = c.getTask(out, fm, dc, opts, null, fos);
396 Boolean ok = t.call();
398 if (keys != null) {
399 for (Diagnostic<? extends JavaFileObject> d: dc.getDiagnostics()) {
400 scanForKeys((JCDiagnostic) d, keys);
401 }
402 }
404 return ok;
405 }
407 /**
408 * Scan a diagnostic for resource keys. This will not detect additional
409 * sub diagnostics that might be generated by a rich diagnostic formatter.
410 */
411 private static void scanForKeys(JCDiagnostic d, Set<String> keys) {
412 keys.add(d.getCode());
413 for (Object o: d.getArgs()) {
414 if (o instanceof JCDiagnostic) {
415 scanForKeys((JCDiagnostic) o, keys);
416 }
417 }
418 for (JCDiagnostic sd: d.getSubdiagnostics())
419 scanForKeys(sd, keys);
420 }
421 }
423 /**
424 * Run the test using the standard simple entry point.
425 */
426 static class SimpleCompiler extends Compiler {
427 SimpleCompiler(boolean verbose) {
428 super(verbose);
429 }
431 @Override
432 boolean run(PrintWriter out, Set<String> keys, boolean raw, List<String> opts, List<File> files) {
433 if (out != null && keys != null)
434 throw new IllegalArgumentException();
436 if (verbose)
437 System.err.println("run_simple: " + opts + " " + files);
439 List<String> args = new ArrayList<String>();
441 if (keys != null || raw)
442 args.add("-XDrawDiagnostics");
444 args.addAll(opts);
445 for (File f: files)
446 args.add(f.getPath());
448 StringWriter sw = null;
449 PrintWriter pw;
450 if (keys != null) {
451 sw = new StringWriter();
452 pw = new PrintWriter(sw);
453 } else
454 pw = out;
456 int rc = com.sun.tools.javac.Main.compile(args.toArray(new String[args.size()]), pw);
458 if (keys != null) {
459 pw.close();
460 scanForKeys(sw.toString(), keys);
461 }
463 return (rc == 0);
464 }
466 private static void scanForKeys(String text, Set<String> keys) {
467 StringTokenizer st = new StringTokenizer(text, " ,\r\n():");
468 while (st.hasMoreElements()) {
469 String t = st.nextToken();
470 if (t.startsWith("compiler."))
471 keys.add(t);
472 }
473 }
474 }
476 static class BackdoorCompiler extends Compiler {
477 BackdoorCompiler(boolean verbose) {
478 super(verbose);
479 }
481 @Override
482 boolean run(PrintWriter out, Set<String> keys, boolean raw, List<String> opts, List<File> files) {
483 if (out != null && keys != null)
484 throw new IllegalArgumentException();
486 if (verbose)
487 System.err.println("run_simple: " + opts + " " + files);
489 List<String> args = new ArrayList<String>(opts);
491 if (out != null && raw)
492 args.add("-XDrawDiagnostics");
494 args.addAll(opts);
495 for (File f: files)
496 args.add(f.getPath());
498 StringWriter sw = null;
499 PrintWriter pw;
500 if (keys != null) {
501 sw = new StringWriter();
502 pw = new PrintWriter(sw);
503 } else
504 pw = out;
506 Context c = new Context();
507 JavacFileManager.preRegister(c); // can't create it until Log has been set up
508 MessageTracker.preRegister(c, keys);
509 com.sun.tools.javac.main.Main m = new com.sun.tools.javac.main.Main("javac", pw);
510 int rc = m.compile(args.toArray(new String[args.size()]), c);
512 if (keys != null) {
513 pw.close();
514 }
516 return (rc == 0);
517 }
519 static class MessageTracker extends JavacMessages {
521 MessageTracker(Context context) {
522 super(context);
523 }
525 static void preRegister(final Context c, final Set<String> keys) {
526 if (keys != null) {
527 c.put(JavacMessages.messagesKey, new Context.Factory<JavacMessages>() {
528 public JavacMessages make() {
529 return new MessageTracker(c) {
530 @Override
531 public String getLocalizedString(Locale l, String key, Object... args) {
532 keys.add(key);
533 return super.getLocalizedString(l, key, args);
534 }
535 };
536 }
537 });
538 }
539 }
540 }
542 }
543 }