1 /*
2  * Copyright (c) 2010, 2019, 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 import java.io.*;
25 import java.net.URL;
26 import java.net.URLClassLoader;
27 import java.nio.file.Files;
28 import java.nio.file.Path;
29 import java.util.*;
30 import java.util.Map.Entry;
31 import java.util.jar.JarFile;
32 import java.util.jar.JarOutputStream;
33 import java.util.logging.Level;
34 import java.util.logging.Logger;
35 import java.util.regex.*;
36 import java.util.stream.Collectors;
37 import java.util.zip.ZipEntry;
38 
39 import javax.annotation.processing.Processor;
40 import javax.tools.Diagnostic;
41 import javax.tools.DiagnosticCollector;
42 import javax.tools.JavaCompiler;
43 import javax.tools.JavaCompiler.CompilationTask;
44 import javax.tools.JavaFileManager;
45 import javax.tools.JavaFileObject;
46 import javax.tools.StandardJavaFileManager;
47 import javax.tools.ToolProvider;
48 
49 // The following two classes are both used, but cannot be imported directly
50 // import com.sun.tools.javac.Main
51 // import com.sun.tools.javac.main.Main
52 
53 import com.sun.tools.javac.api.ClientCodeWrapper;
54 import com.sun.tools.javac.file.JavacFileManager;
55 import com.sun.tools.javac.main.Main;
56 import com.sun.tools.javac.util.Context;
57 import com.sun.tools.javac.util.JavacMessages;
58 import com.sun.tools.javac.util.JCDiagnostic;
59 
60 /**
61  * Class to handle example code designed to illustrate javac diagnostic messages.
62  */
63 class Example implements Comparable<Example> {
64     /* Create an Example from the files found at path.
65      * The head of the file, up to the first Java code, is scanned
66      * for information about the test, such as what resource keys it
67      * generates when run, what options are required to run it, and so on.
68      */
Example(File file)69     Example(File file) {
70         this.file = file;
71         declaredKeys = new TreeSet<String>();
72         srcFiles = new ArrayList<File>();
73         procFiles = new ArrayList<File>();
74         srcPathFiles = new ArrayList<File>();
75         moduleSourcePathFiles = new ArrayList<File>();
76         patchModulePathFiles = new ArrayList<File>();
77         modulePathFiles = new ArrayList<File>();
78         classPathFiles = new ArrayList<File>();
79         additionalFiles = new ArrayList<File>();
80         nonEmptySrcFiles = new ArrayList<File>();
81 
82         findFiles(file, srcFiles);
83         for (File f: srcFiles) {
84             parse(f);
85         }
86 
87         if (infoFile == null)
88             throw new Error("Example " + file + " has no info file");
89     }
90 
findFiles(File f, List<File> files)91     private void findFiles(File f, List<File> files) {
92         if (f.isDirectory()) {
93             for (File c: f.listFiles()) {
94                 if (files == srcFiles && c.getName().equals("processors"))
95                     findFiles(c, procFiles);
96                 else if (files == srcFiles && c.getName().equals("sourcepath")) {
97                     srcPathDir = c;
98                     findFiles(c, srcPathFiles);
99                 } else if (files == srcFiles && c.getName().equals("modulesourcepath")) {
100                     moduleSourcePathDir = c;
101                     findFiles(c, moduleSourcePathFiles);
102                 } else if (files == srcFiles && c.getName().equals("patchmodule")) {
103                     patchModulePathDir = c;
104                     findFiles(c, patchModulePathFiles);
105                 } else if (files == srcFiles && c.getName().equals("additional")) {
106                     additionalFilesDir = c;
107                     findFiles(c, additionalFiles);
108                 } else if (files == srcFiles && c.getName().equals("modulepath")) {
109                     findFiles(c, modulePathFiles);
110                 } else if (files == srcFiles && c.getName().equals("classpath")) {
111                     findFiles(c, classPathFiles);
112                 } else {
113                     findFiles(c, files);
114                 }
115             }
116         } else if (f.isFile()) {
117             if (f.getName().endsWith(".java")) {
118                 files.add(f);
119             } else if (f.getName().equals("modulesourcepath")) {
120                 moduleSourcePathDir = f;
121             }
122         }
123     }
124 
parse(File f)125     private void parse(File f) {
126         Pattern keyPat = Pattern.compile(" *// *key: *([^ ]+) *");
127         Pattern optPat = Pattern.compile(" *// *options: *(.*)");
128         Pattern runPat = Pattern.compile(" *// *run: *(.*)");
129         Pattern javaPat = Pattern.compile(" *@?[A-Za-z].*");
130         try {
131             String[] lines = read(f).split("[\r\n]+");
132             for (String line: lines) {
133                 Matcher keyMatch = keyPat.matcher(line);
134                 if (keyMatch.matches()) {
135                     foundInfo(f);
136                     declaredKeys.add(keyMatch.group(1));
137                     continue;
138                 }
139                 Matcher optMatch = optPat.matcher(line);
140                 if (optMatch.matches()) {
141                     foundInfo(f);
142                     options = Arrays.asList(optMatch.group(1).trim().split(" +"));
143                     continue;
144                 }
145                 Matcher runMatch = runPat.matcher(line);
146                 if (runMatch.matches()) {
147                     foundInfo(f);
148                     runOpts = Arrays.asList(runMatch.group(1).trim().split(" +"));
149                 }
150                 if (javaPat.matcher(line).matches()) {
151                     nonEmptySrcFiles.add(f);
152                     break;
153                 }
154             }
155         } catch (IOException e) {
156             throw new Error(e);
157         }
158     }
159 
foundInfo(File file)160     private void foundInfo(File file) {
161         if (infoFile != null && !infoFile.equals(file))
162             throw new Error("multiple info files found: " + infoFile + ", " + file);
163         infoFile = file;
164     }
165 
getName()166     String getName() {
167         return file.getName();
168     }
169 
170     /**
171      * Get the set of resource keys that this test declares it will generate
172      * when it is run.
173      */
getDeclaredKeys()174     Set<String> getDeclaredKeys() {
175         return declaredKeys;
176     }
177 
178     /**
179      * Get the set of resource keys that this test generates when it is run.
180      * The test will be run if it has not already been run.
181      */
getActualKeys()182     Set<String> getActualKeys() {
183         if (actualKeys == null)
184             actualKeys = run(false);
185         return actualKeys;
186     }
187 
188     /**
189      * Run the test.  Information in the test header is used to determine
190      * how to run the test.
191      */
run(PrintWriter out, boolean raw, boolean verbose)192     void run(PrintWriter out, boolean raw, boolean verbose) {
193         if (out == null)
194             throw new NullPointerException();
195         try {
196             run(out, null, raw, verbose);
197         } catch (IOException e) {
198             e.printStackTrace(out);
199         }
200     }
201 
run(boolean verbose)202     Set<String> run(boolean verbose) {
203         Set<String> keys = new TreeSet<String>();
204         try {
205             run(null, keys, true, verbose);
206         } catch (IOException e) {
207             e.printStackTrace(System.err);
208         }
209         return keys;
210     }
211 
212     /**
213      * Run the test.  Information in the test header is used to determine
214      * how to run the test.
215      */
run(PrintWriter out, Set<String> keys, boolean raw, boolean verbose)216     private void run(PrintWriter out, Set<String> keys, boolean raw, boolean verbose)
217             throws IOException {
218         List<String> opts = new ArrayList<String>();
219         if (!modulePathFiles.isEmpty()) {
220             File modulepathDir = new File(tempDir, "modulepath");
221             modulepathDir.mkdirs();
222             clean(modulepathDir);
223             boolean hasModuleInfo =
224                     modulePathFiles.stream()
225                                    .anyMatch(f -> f.getName().equalsIgnoreCase("module-info.java"));
226             Path modulePath = new File(file, "modulepath").toPath().toAbsolutePath();
227             if (hasModuleInfo) {
228                 //ordinary modules
229                 List<String> sOpts =
230                         Arrays.asList("-d", modulepathDir.getPath(),
231                                       "--module-source-path", modulePath.toString());
232                 new Jsr199Compiler(verbose).run(null, null, false, sOpts, modulePathFiles);
233             } else {
234                 //automatic modules:
235                 Map<String, List<Path>> module2Files =
236                         modulePathFiles.stream()
237                                        .map(f -> f.toPath())
238                                        .collect(Collectors.groupingBy(p -> modulePath.relativize(p)
239                                                                             .getName(0)
240                                                                             .toString()));
241                 for (Entry<String, List<Path>> e : module2Files.entrySet()) {
242                     File scratchDir = new File(tempDir, "scratch");
243                     scratchDir.mkdirs();
244                     clean(scratchDir);
245                     List<String> sOpts =
246                             Arrays.asList("-d", scratchDir.getPath());
247                     new Jsr199Compiler(verbose).run(null,
248                                                     null,
249                                                     false,
250                                                     sOpts,
251                                                     e.getValue().stream()
252                                                                 .map(p -> p.toFile())
253                                                                 .collect(Collectors.toList()));
254                     try (JarOutputStream jarOut =
255                             new JarOutputStream(new FileOutputStream(new File(modulepathDir, e.getKey() + ".jar")))) {
256                         Files.find(scratchDir.toPath(), Integer.MAX_VALUE, (p, attr) -> attr.isRegularFile())
257                                 .forEach(p -> {
258                                     try (InputStream in = Files.newInputStream(p)) {
259                                         jarOut.putNextEntry(new ZipEntry(scratchDir.toPath()
260                                                                                    .relativize(p)
261                                                                                    .toString()));
262                                         jarOut.write(in.readAllBytes());
263                                     } catch (IOException ex) {
264                                         throw new IllegalStateException(ex);
265                                     }
266                                 });
267                     }
268                 }
269             }
270             opts.add("--module-path");
271             opts.add(modulepathDir.getAbsolutePath());
272         }
273 
274         if (!classPathFiles.isEmpty()) {
275             File classpathDir = new File(tempDir, "classpath");
276             classpathDir.mkdirs();
277             clean(classpathDir);
278             List<String> sOpts = Arrays.asList("-d", classpathDir.getPath());
279             new Jsr199Compiler(verbose).run(null, null, false, sOpts, classPathFiles);
280             opts.add("--class-path");
281             opts.add(classpathDir.getAbsolutePath());
282         }
283 
284         File classesDir = new File(tempDir, "classes");
285         classesDir.mkdirs();
286         clean(classesDir);
287 
288         opts.add("-d");
289         opts.add(classesDir.getPath());
290         if (options != null)
291             opts.addAll(evalProperties(options));
292 
293         if (procFiles.size() > 0) {
294             List<String> pOpts = new ArrayList<>(Arrays.asList("-d", classesDir.getPath()));
295 
296             // hack to automatically add exports; a better solution would be to grep the
297             // source for import statements or a magic comment
298             for (File pf: procFiles) {
299                 if (pf.getName().equals("CreateBadClassFile.java")) {
300                     pOpts.add("--add-modules=jdk.jdeps");
301                     pOpts.add("--add-exports=jdk.jdeps/com.sun.tools.classfile=ALL-UNNAMED");
302                 }
303             }
304 
305             new Jsr199Compiler(verbose).run(null, null, false, pOpts, procFiles);
306             opts.add("-classpath"); // avoid using -processorpath for now
307             opts.add(classesDir.getPath());
308             createAnnotationServicesFile(classesDir, procFiles);
309         } else if (options != null) {
310             int i = options.indexOf("-processor");
311             // check for built-in anno-processor(s)
312             if (i != -1 && options.get(i + 1).equals("DocCommentProcessor")) {
313                 opts.add("-classpath");
314                 opts.add(System.getProperty("test.classes"));
315             }
316         }
317 
318         List<File> files = srcFiles;
319 
320         if (srcPathDir != null) {
321             opts.add("-sourcepath");
322             opts.add(srcPathDir.getPath());
323         }
324 
325         if (moduleSourcePathDir != null) {
326             opts.add("--module-source-path");
327             opts.add(moduleSourcePathDir.getPath());
328             files = new ArrayList<>();
329             files.addAll(moduleSourcePathFiles);
330             files.addAll(nonEmptySrcFiles); // srcFiles containing declarations
331         }
332 
333         if (patchModulePathDir != null) {
334             for (File mod : patchModulePathDir.listFiles()) {
335                 opts.add("--patch-module");
336                 opts.add(mod.getName() + "=" + mod.getPath());
337             }
338             files = new ArrayList<>();
339             files.addAll(patchModulePathFiles);
340             files.addAll(nonEmptySrcFiles); // srcFiles containing declarations
341         }
342 
343         if (additionalFiles.size() > 0) {
344             List<String> sOpts = Arrays.asList("-d", classesDir.getPath());
345             new Jsr199Compiler(verbose).run(null, null, false, sOpts, additionalFiles);
346         }
347 
348         try {
349             Compiler c = Compiler.getCompiler(runOpts, verbose);
350             c.run(out, keys, raw, opts, files);
351         } catch (IllegalArgumentException e) {
352             if (out != null) {
353                 out.println("Invalid value for run tag: " + runOpts);
354             }
355         }
356     }
357 
evalProperties(List<String> args)358     private static List<String> evalProperties(List<String> args) {
359         boolean fast = true;
360         for (String arg : args) {
361             fast = fast && (arg.indexOf("${") == -1);
362         }
363         if (fast) {
364             return args;
365         }
366         List<String> newArgs = new ArrayList<>();
367         for (String arg : args) {
368             newArgs.add(evalProperties(arg));
369         }
370         return newArgs;
371     }
372 
373     private static final Pattern namePattern = Pattern.compile("\\$\\{([A-Za-z0-9._]+)\\}");
374     private static final String jdkVersion = Integer.toString(Runtime.version().feature());
375 
evalProperties(String arg)376     private static String evalProperties(String arg) {
377         Matcher m = namePattern.matcher(arg);
378         StringBuilder sb = null;
379         while (m.find()) {
380             if (sb == null) {
381                 sb = new StringBuilder();
382             }
383             String propName = m.group(1);
384             String propValue;
385             switch (propName) {
386                 case "jdk.version":
387                     propValue = jdkVersion;
388                     break;
389                 default:
390                     propValue = System.getProperty(propName);
391                     break;
392             }
393             m.appendReplacement(sb, propValue != null ? propValue : m.group(0).replace("$", "\\$"));
394         }
395         if (sb == null) {
396             return arg;
397         } else {
398             m.appendTail(sb);
399             return sb.toString();
400         }
401     }
402 
createAnnotationServicesFile(File dir, List<File> procFiles)403     void createAnnotationServicesFile(File dir, List<File> procFiles) throws IOException {
404         File servicesDir = new File(new File(dir, "META-INF"), "services");
405         servicesDir.mkdirs();
406         File annoServices = new File(servicesDir, Processor.class.getName());
407         Writer out = new FileWriter(annoServices);
408         try {
409             for (File f: procFiles) {
410                 out.write(f.getName().toString().replace(".java", ""));
411             }
412         } finally {
413             out.close();
414         }
415     }
416 
417     @Override
compareTo(Example e)418     public int compareTo(Example e) {
419         return file.compareTo(e.file);
420     }
421 
422     @Override
toString()423     public String toString() {
424         return file.getPath();
425     }
426 
427     /**
428      * Read the contents of a file.
429      */
read(File f)430     private String read(File f) throws IOException {
431         byte[] bytes = new byte[(int) f.length()];
432         DataInputStream in = new DataInputStream(new FileInputStream(f));
433         try {
434             in.readFully(bytes);
435         } finally {
436             in.close();
437         }
438         return new String(bytes);
439     }
440 
441     /**
442      * Clean the contents of a directory.
443      */
clean(File dir)444     boolean clean(File dir) {
445         boolean ok = true;
446         for (File f: dir.listFiles()) {
447             if (f.isDirectory())
448                 ok &= clean(f);
449             ok &= f.delete();
450         }
451         return ok;
452     }
453 
454     File file;
455     List<File> srcFiles;
456     List<File> procFiles;
457     File srcPathDir;
458     File moduleSourcePathDir;
459     File patchModulePathDir;
460     File additionalFilesDir;
461     List<File> srcPathFiles;
462     List<File> moduleSourcePathFiles;
463     List<File> patchModulePathFiles;
464     List<File> modulePathFiles;
465     List<File> classPathFiles;
466     List<File> additionalFiles;
467     List<File> nonEmptySrcFiles;
468     File infoFile;
469     private List<String> runOpts;
470     private List<String> options;
471     private Set<String> actualKeys;
472     private Set<String> declaredKeys;
473 
474     static File tempDir = (System.getProperty("test.src") != null) ?
475             new File(System.getProperty("user.dir")):
476             new File(System.getProperty("java.io.tmpdir"));
477 
setTempDir(File tempDir)478     static void setTempDir(File tempDir) {
479         Example.tempDir = tempDir;
480     }
481 
482     abstract static class Compiler {
483         interface Factory {
getCompiler(List<String> opts, boolean verbose)484             Compiler getCompiler(List<String> opts, boolean verbose);
485         }
486 
487         static class DefaultFactory implements Factory {
getCompiler(List<String> opts, boolean verbose)488             public Compiler getCompiler(List<String> opts, boolean verbose) {
489                 String first;
490                 String[] rest;
491                     if (opts == null || opts.isEmpty()) {
492                     first = null;
493                     rest = new String[0];
494                 } else {
495                     first = opts.get(0);
496                     rest = opts.subList(1, opts.size()).toArray(new String[opts.size() - 1]);
497                 }
498                 // For more details on the different compilers,
499                 // see their respective class doc comments.
500                 // See also README.examples.txt in this directory.
501                 if (first == null || first.equals("jsr199"))
502                     return new Jsr199Compiler(verbose, rest);
503                 else if (first.equals("simple"))
504                     return new SimpleCompiler(verbose);
505                 else if (first.equals("backdoor"))
506                     return new BackdoorCompiler(verbose);
507                 else if (first.equals("exec"))
508                     return new ExecCompiler(verbose, rest);
509                 else
510                     throw new IllegalArgumentException(first);
511             }
512         }
513 
514         static Factory factory;
515 
getCompiler(List<String> opts, boolean verbose)516         static Compiler getCompiler(List<String> opts, boolean verbose) {
517             if (factory == null)
518                 factory = new DefaultFactory();
519 
520             return factory.getCompiler(opts, verbose);
521         }
522 
Compiler(boolean verbose)523         protected Compiler(boolean verbose) {
524             this.verbose = verbose;
525         }
526 
run(PrintWriter out, Set<String> keys, boolean raw, List<String> opts, List<File> files)527         abstract boolean run(PrintWriter out, Set<String> keys, boolean raw,
528                 List<String> opts,  List<File> files);
529 
setSupportClassLoader(ClassLoader cl)530         void setSupportClassLoader(ClassLoader cl) {
531             loader = cl;
532         }
533 
close(JavaFileManager fm)534         protected void close(JavaFileManager fm) {
535             try {
536                 fm.close();
537             } catch (IOException e) {
538                 throw new Error(e);
539             }
540         }
541 
542         protected ClassLoader loader;
543         protected boolean verbose;
544     }
545 
546     /**
547      * Compile using the JSR 199 API.  The diagnostics generated are
548      * scanned for resource keys.   Not all diagnostic keys are generated
549      * via the JSR 199 API -- for example, rich diagnostics are not directly
550      * accessible, and some diagnostics generated by the file manager may
551      * not be generated (for example, the JSR 199 file manager does not see
552      * -Xlint:path).
553      */
554     static class Jsr199Compiler extends Compiler {
555         List<String> fmOpts;
556 
Jsr199Compiler(boolean verbose, String... args)557         Jsr199Compiler(boolean verbose, String... args) {
558             super(verbose);
559             for (int i = 0; i < args.length; i++) {
560                 String arg = args[i];
561                 if (arg.equals("-filemanager") && (i + 1 < args.length)) {
562                     fmOpts = Arrays.asList(args[++i].split(","));
563                 } else
564                     throw new IllegalArgumentException(arg);
565             }
566         }
567 
568         @Override
run(PrintWriter out, Set<String> keys, boolean raw, List<String> opts, List<File> files)569         boolean run(PrintWriter out, Set<String> keys, boolean raw, List<String> opts, List<File> files) {
570             if (out != null && keys != null)
571                 throw new IllegalArgumentException();
572 
573             if (verbose)
574                 System.err.println("run_jsr199: " + opts + " " + files);
575 
576             DiagnosticCollector<JavaFileObject> dc = null;
577             if (keys != null)
578                 dc = new DiagnosticCollector<JavaFileObject>();
579 
580             if (raw) {
581                 List<String> newOpts = new ArrayList<String>();
582                 newOpts.add("-XDrawDiagnostics");
583                 newOpts.addAll(opts);
584                 opts = newOpts;
585             }
586 
587             JavaCompiler c = ToolProvider.getSystemJavaCompiler();
588 
589             StandardJavaFileManager fm = c.getStandardFileManager(dc, null, null);
590             try {
591                 if (fmOpts != null)
592                     fm = new FileManager(fm, fmOpts);
593 
594                 Iterable<? extends JavaFileObject> fos = fm.getJavaFileObjectsFromFiles(files);
595 
596                 CompilationTask t = c.getTask(out, fm, dc, opts, null, fos);
597                 Boolean ok = t.call();
598 
599                 if (keys != null) {
600                     for (Diagnostic<? extends JavaFileObject> d: dc.getDiagnostics()) {
601                         scanForKeys(unwrap(d), keys);
602                     }
603                 }
604 
605                 return ok;
606             } finally {
607                 close(fm);
608             }
609         }
610 
611         /**
612          * Scan a diagnostic for resource keys.  This will not detect additional
613          * sub diagnostics that might be generated by a rich diagnostic formatter.
614          */
scanForKeys(JCDiagnostic d, Set<String> keys)615         private static void scanForKeys(JCDiagnostic d, Set<String> keys) {
616             keys.add(d.getCode());
617             for (Object o: d.getArgs()) {
618                 if (o instanceof JCDiagnostic) {
619                     scanForKeys((JCDiagnostic) o, keys);
620                 }
621             }
622             for (JCDiagnostic sd: d.getSubdiagnostics())
623                 scanForKeys(sd, keys);
624         }
625 
unwrap(Diagnostic<? extends JavaFileObject> diagnostic)626         private JCDiagnostic unwrap(Diagnostic<? extends JavaFileObject> diagnostic) {
627             if (diagnostic instanceof JCDiagnostic)
628                 return (JCDiagnostic) diagnostic;
629             if (diagnostic instanceof ClientCodeWrapper.DiagnosticSourceUnwrapper)
630                 return ((ClientCodeWrapper.DiagnosticSourceUnwrapper)diagnostic).d;
631             throw new IllegalArgumentException();
632         }
633     }
634 
635     /**
636      * Run the test using the standard simple entry point.
637      */
638     static class SimpleCompiler extends Compiler {
SimpleCompiler(boolean verbose)639         SimpleCompiler(boolean verbose) {
640             super(verbose);
641         }
642 
643         @Override
run(PrintWriter out, Set<String> keys, boolean raw, List<String> opts, List<File> files)644         boolean run(PrintWriter out, Set<String> keys, boolean raw, List<String> opts, List<File> files) {
645             if (out != null && keys != null)
646                 throw new IllegalArgumentException();
647 
648             if (verbose)
649                 System.err.println("run_simple: " + opts + " " + files);
650 
651             List<String> args = new ArrayList<String>();
652 
653             if (keys != null || raw)
654                 args.add("-XDrawDiagnostics");
655 
656             args.addAll(opts);
657             for (File f: files)
658                 args.add(f.getPath());
659 
660             StringWriter sw = null;
661             PrintWriter pw;
662             if (keys != null) {
663                 sw = new StringWriter();
664                 pw = new PrintWriter(sw);
665             } else
666                 pw = out;
667 
668             int rc = com.sun.tools.javac.Main.compile(args.toArray(new String[args.size()]), pw);
669 
670             if (keys != null) {
671                 pw.close();
672                 scanForKeys(sw.toString(), keys);
673             }
674 
675             return (rc == 0);
676         }
677 
scanForKeys(String text, Set<String> keys)678         private static void scanForKeys(String text, Set<String> keys) {
679             StringTokenizer st = new StringTokenizer(text, " ,\r\n():");
680             while (st.hasMoreElements()) {
681                 String t = st.nextToken();
682                 if (t.startsWith("compiler."))
683                     keys.add(t);
684             }
685         }
686     }
687 
688     /**
689      * Run the test in a separate process.
690      */
691     static class ExecCompiler extends Compiler {
692         List<String> vmOpts;
693 
ExecCompiler(boolean verbose, String... args)694         ExecCompiler(boolean verbose, String... args) {
695             super(verbose);
696             vmOpts = Arrays.asList(args);
697         }
698 
699         @Override
run(PrintWriter out, Set<String> keys, boolean raw, List<String> opts, List<File> files)700         boolean run(PrintWriter out, Set<String> keys, boolean raw, List<String> opts, List<File> files) {
701             if (out != null && keys != null)
702                 throw new IllegalArgumentException();
703 
704             if (verbose)
705                 System.err.println("run_exec: " + vmOpts + " " + opts + " " + files);
706 
707             List<String> args = new ArrayList<String>();
708 
709             File javaHome = new File(System.getProperty("java.home"));
710             if (javaHome.getName().equals("jre"))
711                 javaHome = javaHome.getParentFile();
712             File javaExe = new File(new File(javaHome, "bin"), "java");
713             args.add(javaExe.getPath());
714 
715             File toolsJar = new File(new File(javaHome, "lib"), "tools.jar");
716             if (toolsJar.exists()) {
717                 args.add("-classpath");
718                 args.add(toolsJar.getPath());
719             }
720 
721             args.addAll(vmOpts);
722             addOpts(args, "test.vm.opts");
723             addOpts(args, "test.java.opts");
724             args.add(com.sun.tools.javac.Main.class.getName());
725 
726             if (keys != null || raw)
727                 args.add("-XDrawDiagnostics");
728 
729             args.addAll(opts);
730             for (File f: files)
731                 args.add(f.getPath());
732 
733             try {
734                 ProcessBuilder pb = new ProcessBuilder(args);
735                 pb.redirectErrorStream(true);
736                 Process p = pb.start();
737                 BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream()));
738                 String line;
739                 while ((line = in.readLine()) != null) {
740                     if (keys != null)
741                         scanForKeys(line, keys);
742                 }
743                 int rc = p.waitFor();
744 
745                 return (rc == 0);
746             } catch (IOException | InterruptedException e) {
747                 System.err.println("Exception execing javac" + e);
748                 System.err.println("Command line: " + opts);
749                 return false;
750             }
751         }
752 
scanForKeys(String text, Set<String> keys)753         private static void scanForKeys(String text, Set<String> keys) {
754             StringTokenizer st = new StringTokenizer(text, " ,\r\n():");
755             while (st.hasMoreElements()) {
756                 String t = st.nextToken();
757                 if (t.startsWith("compiler."))
758                     keys.add(t);
759             }
760         }
761 
addOpts(List<String> args, String propName)762         private static void addOpts(List<String> args, String propName) {
763             String propValue = System.getProperty(propName);
764             if (propValue == null || propValue.isEmpty())
765                 return;
766             args.addAll(Arrays.asList(propValue.split(" +", 0)));
767         }
768     }
769 
770     static class BackdoorCompiler extends Compiler {
BackdoorCompiler(boolean verbose)771         BackdoorCompiler(boolean verbose) {
772             super(verbose);
773         }
774 
775         @Override
run(PrintWriter out, Set<String> keys, boolean raw, List<String> opts, List<File> files)776         boolean run(PrintWriter out, Set<String> keys, boolean raw, List<String> opts, List<File> files) {
777             if (out != null && keys != null)
778                 throw new IllegalArgumentException();
779 
780             if (verbose)
781                 System.err.println("run_simple: " + opts + " " + files);
782 
783             List<String> args = new ArrayList<String>();
784 
785             if (out != null && raw)
786                 args.add("-XDrawDiagnostics");
787 
788             args.addAll(opts);
789             for (File f: files)
790                 args.add(f.getPath());
791 
792             StringWriter sw = null;
793             PrintWriter pw;
794             if (keys != null) {
795                 sw = new StringWriter();
796                 pw = new PrintWriter(sw);
797             } else
798                 pw = out;
799 
800             Context c = new Context();
801             JavacFileManager.preRegister(c); // can't create it until Log has been set up
802             MessageTracker.preRegister(c, keys);
803 
804             try {
805                 Main m = new Main("javac", pw);
806                 Main.Result rc = m.compile(args.toArray(new String[args.size()]), c);
807 
808                 if (keys != null) {
809                     pw.close();
810                 }
811 
812                 return rc.isOK();
813             } finally {
814                 close(c.get(JavaFileManager.class));
815             }
816         }
817 
818         static class MessageTracker extends JavacMessages {
819 
MessageTracker(Context context)820             MessageTracker(Context context) {
821                 super(context);
822             }
823 
preRegister(Context c, final Set<String> keys)824             static void preRegister(Context c, final Set<String> keys) {
825                 if (keys != null) {
826                     c.put(JavacMessages.messagesKey, new Context.Factory<JavacMessages>() {
827                         public JavacMessages make(Context c) {
828                             return new MessageTracker(c) {
829                                 @Override
830                                 public String getLocalizedString(Locale l, String key, Object... args) {
831                                     keys.add(key);
832                                     return super.getLocalizedString(l, key, args);
833                                 }
834                             };
835                         }
836                     });
837                 }
838             }
839         }
840 
841     }
842 }
843