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