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