Thu, 06 Jun 2013 15:37:23 +0100
8015648: Duplicate variable in lambda causes javac crash
Summary: Missing flag in synthetic lambda blog is causing duplicates symbol to go undetected
Reviewed-by: jjg, vromero
1 /*
2 * Copyright (c) 2010, 2013, 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 } else if (options != null) {
213 int i = options.indexOf("-processor");
214 // check for built-in anno-processor(s)
215 if (i != -1 && options.get(i + 1).equals("DocCommentProcessor")) {
216 opts.add("-classpath");
217 opts.add(System.getProperty("test.classes"));
218 }
219 }
221 if (srcPathDir != null) {
222 opts.add("-sourcepath");
223 opts.add(srcPathDir.getPath());
224 }
226 try {
227 Compiler c = Compiler.getCompiler(runOpts, verbose);
228 c.run(out, keys, raw, opts, srcFiles);
229 } catch (IllegalArgumentException e) {
230 if (out != null) {
231 out.println("Invalid value for run tag: " + runOpts);
232 }
233 }
234 }
236 void createAnnotationServicesFile(File dir, List<File> procFiles) throws IOException {
237 File servicesDir = new File(new File(dir, "META-INF"), "services");
238 servicesDir.mkdirs();
239 File annoServices = new File(servicesDir, Processor.class.getName());
240 Writer out = new FileWriter(annoServices);
241 try {
242 for (File f: procFiles) {
243 out.write(f.getName().toString().replace(".java", ""));
244 }
245 } finally {
246 out.close();
247 }
248 }
250 @Override
251 public int compareTo(Example e) {
252 return file.compareTo(e.file);
253 }
255 @Override
256 public String toString() {
257 return file.getPath();
258 }
260 /**
261 * Read the contents of a file.
262 */
263 private String read(File f) throws IOException {
264 byte[] bytes = new byte[(int) f.length()];
265 DataInputStream in = new DataInputStream(new FileInputStream(f));
266 try {
267 in.readFully(bytes);
268 } finally {
269 in.close();
270 }
271 return new String(bytes);
272 }
274 /**
275 * Clean the contents of a directory.
276 */
277 boolean clean(File dir) {
278 boolean ok = true;
279 for (File f: dir.listFiles()) {
280 if (f.isDirectory())
281 ok &= clean(f);
282 ok &= f.delete();
283 }
284 return ok;
285 }
287 File file;
288 List<File> srcFiles;
289 List<File> procFiles;
290 File srcPathDir;
291 List<File> srcPathFiles;
292 List<File> supportFiles;
293 File infoFile;
294 private List<String> runOpts;
295 private List<String> options;
296 private Set<String> actualKeys;
297 private Set<String> declaredKeys;
299 static File tempDir = (System.getProperty("test.src") != null) ?
300 new File(System.getProperty("user.dir")):
301 new File(System.getProperty("java.io.tmpdir"));
303 static void setTempDir(File tempDir) {
304 Example.tempDir = tempDir;
305 }
307 abstract static class Compiler {
308 interface Factory {
309 Compiler getCompiler(List<String> opts, boolean verbose);
310 }
312 static class DefaultFactory implements Factory {
313 public Compiler getCompiler(List<String> opts, boolean verbose) {
314 String first;
315 String[] rest;
316 if (opts == null || opts.isEmpty()) {
317 first = null;
318 rest = new String[0];
319 } else {
320 first = opts.get(0);
321 rest = opts.subList(1, opts.size()).toArray(new String[opts.size() - 1]);
322 }
323 if (first == null || first.equals("jsr199"))
324 return new Jsr199Compiler(verbose, rest);
325 else if (first.equals("simple"))
326 return new SimpleCompiler(verbose);
327 else if (first.equals("backdoor"))
328 return new BackdoorCompiler(verbose);
329 else
330 throw new IllegalArgumentException(first);
331 }
332 }
334 static Factory factory;
336 static Compiler getCompiler(List<String> opts, boolean verbose) {
337 if (factory == null)
338 factory = new DefaultFactory();
340 return factory.getCompiler(opts, verbose);
341 }
343 protected Compiler(boolean verbose) {
344 this.verbose = verbose;
345 }
347 abstract boolean run(PrintWriter out, Set<String> keys, boolean raw,
348 List<String> opts, List<File> files);
350 void setSupportClassLoader(ClassLoader cl) {
351 loader = cl;
352 }
354 protected ClassLoader loader;
355 protected boolean verbose;
356 }
358 /**
359 * Compile using the JSR 199 API. The diagnostics generated are
360 * scanned for resource keys. Not all diagnostic keys are generated
361 * via the JSR 199 API -- for example, rich diagnostics are not directly
362 * accessible, and some diagnostics generated by the file manager may
363 * not be generated (for example, the JSR 199 file manager does not see
364 * -Xlint:path).
365 */
366 static class Jsr199Compiler extends Compiler {
367 List<String> fmOpts;
369 Jsr199Compiler(boolean verbose, String... args) {
370 super(verbose);
371 for (int i = 0; i < args.length; i++) {
372 String arg = args[i];
373 if (arg.equals("-filemanager") && (i + 1 < args.length)) {
374 fmOpts = Arrays.asList(args[++i].split(","));
375 } else
376 throw new IllegalArgumentException(arg);
377 }
378 }
380 @Override
381 boolean run(PrintWriter out, Set<String> keys, boolean raw, List<String> opts, List<File> files) {
382 if (out != null && keys != null)
383 throw new IllegalArgumentException();
385 if (verbose)
386 System.err.println("run_jsr199: " + opts + " " + files);
388 DiagnosticCollector<JavaFileObject> dc = null;
389 if (keys != null)
390 dc = new DiagnosticCollector<JavaFileObject>();
392 if (raw) {
393 List<String> newOpts = new ArrayList<String>();
394 newOpts.add("-XDrawDiagnostics");
395 newOpts.addAll(opts);
396 opts = newOpts;
397 }
399 JavaCompiler c = ToolProvider.getSystemJavaCompiler();
401 StandardJavaFileManager fm = c.getStandardFileManager(dc, null, null);
402 if (fmOpts != null)
403 fm = new FileManager(fm, fmOpts);
405 Iterable<? extends JavaFileObject> fos = fm.getJavaFileObjectsFromFiles(files);
407 CompilationTask t = c.getTask(out, fm, dc, opts, null, fos);
408 Boolean ok = t.call();
410 if (keys != null) {
411 for (Diagnostic<? extends JavaFileObject> d: dc.getDiagnostics()) {
412 scanForKeys(unwrap(d), keys);
413 }
414 }
416 return ok;
417 }
419 /**
420 * Scan a diagnostic for resource keys. This will not detect additional
421 * sub diagnostics that might be generated by a rich diagnostic formatter.
422 */
423 private static void scanForKeys(JCDiagnostic d, Set<String> keys) {
424 keys.add(d.getCode());
425 for (Object o: d.getArgs()) {
426 if (o instanceof JCDiagnostic) {
427 scanForKeys((JCDiagnostic) o, keys);
428 }
429 }
430 for (JCDiagnostic sd: d.getSubdiagnostics())
431 scanForKeys(sd, keys);
432 }
434 private JCDiagnostic unwrap(Diagnostic<? extends JavaFileObject> diagnostic) {
435 if (diagnostic instanceof JCDiagnostic)
436 return (JCDiagnostic) diagnostic;
437 if (diagnostic instanceof ClientCodeWrapper.DiagnosticSourceUnwrapper)
438 return ((ClientCodeWrapper.DiagnosticSourceUnwrapper)diagnostic).d;
439 throw new IllegalArgumentException();
440 }
441 }
443 /**
444 * Run the test using the standard simple entry point.
445 */
446 static class SimpleCompiler extends Compiler {
447 SimpleCompiler(boolean verbose) {
448 super(verbose);
449 }
451 @Override
452 boolean run(PrintWriter out, Set<String> keys, boolean raw, List<String> opts, List<File> files) {
453 if (out != null && keys != null)
454 throw new IllegalArgumentException();
456 if (verbose)
457 System.err.println("run_simple: " + opts + " " + files);
459 List<String> args = new ArrayList<String>();
461 if (keys != null || raw)
462 args.add("-XDrawDiagnostics");
464 args.addAll(opts);
465 for (File f: files)
466 args.add(f.getPath());
468 StringWriter sw = null;
469 PrintWriter pw;
470 if (keys != null) {
471 sw = new StringWriter();
472 pw = new PrintWriter(sw);
473 } else
474 pw = out;
476 int rc = com.sun.tools.javac.Main.compile(args.toArray(new String[args.size()]), pw);
478 if (keys != null) {
479 pw.close();
480 scanForKeys(sw.toString(), keys);
481 }
483 return (rc == 0);
484 }
486 private static void scanForKeys(String text, Set<String> keys) {
487 StringTokenizer st = new StringTokenizer(text, " ,\r\n():");
488 while (st.hasMoreElements()) {
489 String t = st.nextToken();
490 if (t.startsWith("compiler."))
491 keys.add(t);
492 }
493 }
494 }
496 static class BackdoorCompiler extends Compiler {
497 BackdoorCompiler(boolean verbose) {
498 super(verbose);
499 }
501 @Override
502 boolean run(PrintWriter out, Set<String> keys, boolean raw, List<String> opts, List<File> files) {
503 if (out != null && keys != null)
504 throw new IllegalArgumentException();
506 if (verbose)
507 System.err.println("run_simple: " + opts + " " + files);
509 List<String> args = new ArrayList<String>();
511 if (out != null && raw)
512 args.add("-XDrawDiagnostics");
514 args.addAll(opts);
515 for (File f: files)
516 args.add(f.getPath());
518 StringWriter sw = null;
519 PrintWriter pw;
520 if (keys != null) {
521 sw = new StringWriter();
522 pw = new PrintWriter(sw);
523 } else
524 pw = out;
526 Context c = new Context();
527 JavacFileManager.preRegister(c); // can't create it until Log has been set up
528 MessageTracker.preRegister(c, keys);
529 Main m = new Main("javac", pw);
530 Main.Result rc = m.compile(args.toArray(new String[args.size()]), c);
532 if (keys != null) {
533 pw.close();
534 }
536 return rc.isOK();
537 }
539 static class MessageTracker extends JavacMessages {
541 MessageTracker(Context context) {
542 super(context);
543 }
545 static void preRegister(Context c, final Set<String> keys) {
546 if (keys != null) {
547 c.put(JavacMessages.messagesKey, new Context.Factory<JavacMessages>() {
548 public JavacMessages make(Context c) {
549 return new MessageTracker(c) {
550 @Override
551 public String getLocalizedString(Locale l, String key, Object... args) {
552 keys.add(key);
553 return super.getLocalizedString(l, key, args);
554 }
555 };
556 }
557 });
558 }
559 }
560 }
562 }
563 }