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