1 /*
   2  * Copyright (c) 2011, 2015, 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 /*
  25  * @test
  26  * @bug 6437138 6482554
  27  * @summary JSR 199: Compiler doesn't diagnose crash in user code
  28  * @library ../lib
  29  * @modules jdk.compiler/com.sun.tools.javac.api
  30  * @build JavacTestingAbstractProcessor TestClientCodeWrapper
  31  * @run main TestClientCodeWrapper
  32  */
  33 
  34 import java.io.*;
  35 import java.lang.reflect.Method;
  36 import java.net.URI;
  37 import java.util.*;
  38 import javax.annotation.processing.*;
  39 import javax.lang.model.*;
  40 import javax.lang.model.element.*;
  41 import javax.tools.*;
  42 import javax.tools.JavaFileObject.Kind;
  43 
  44 import com.sun.source.util.*;
  45 import com.sun.tools.javac.api.*;
  46 
  47 public class TestClientCodeWrapper extends JavacTestingAbstractProcessor {
  48     public static void main(String... args) throws Exception {
  49         new TestClientCodeWrapper().run();
  50     }
  51 
  52     /**
  53      * Run a series of compilations, each with a different user-provided object
  54      * configured to throw an exception when a specific method is invoked.
  55      * Then, verify the exception is thrown as expected.
  56      *
  57      * Some methods are not invoked from the compiler, and are excluded from the test.
  58      */
  59     void run() throws Exception {
  60         JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
  61         try (StandardJavaFileManager fm = compiler.getStandardFileManager(null, null, null)) {
  62             defaultFileManager = fm;
  63 
  64             for (Method m: getMethodsExcept(JavaFileManager.class,
  65                         "close", "getJavaFileForInput", "getModuleLocation", "getServiceLoader")) {
  66                 test(m);
  67             }
  68 
  69             for (Method m: getMethodsExcept(FileObject.class, "delete")) {
  70                 test(m);
  71             }
  72 
  73             for (Method m: getMethods(JavaFileObject.class)) {
  74                 test(m);
  75             }
  76 
  77             for (Method m: getMethodsExcept(Processor.class, "getCompletions")) {
  78                 test(m);
  79             }
  80 
  81             for (Method m: DiagnosticListener.class.getDeclaredMethods()) {
  82                 test(m);
  83             }
  84 
  85             for (Method m: TaskListener.class.getDeclaredMethods()) {
  86                 test(m);
  87             }
  88 
  89             if (errors > 0)
  90                 throw new Exception(errors + " errors occurred");
  91         }
  92     }
  93 
  94     /** Get a sorted set of the methods declared on a class. */
  95     Set<Method> getMethods(Class<?> clazz) {
  96         return getMethodsExcept(clazz, new String[0]);
  97     }
  98 
  99     /** Get a sorted set of the methods declared on a class, excluding
 100      *  specified methods by name. */
 101     Set<Method> getMethodsExcept(Class<?> clazz, String... exclude) {
 102         Set<Method> methods = new TreeSet<Method>(new Comparator<Method>() {
 103             public int compare(Method m1, Method m2) {
 104                 return m1.toString().compareTo(m2.toString());
 105             }
 106         });
 107         Set<String> e = new HashSet<String>(Arrays.asList(exclude));
 108         for (Method m: clazz.getDeclaredMethods()) {
 109             if (!e.contains(m.getName()))
 110                 methods.add(m);
 111         }
 112         return methods;
 113     }
 114 
 115     /**
 116      * Test a method in a user supplied component, to verify javac's handling
 117      * of any exceptions thrown by that method.
 118      */
 119     void test(Method m) throws Exception {
 120         testNum++;
 121 
 122         File extDirs = new File("empty-extdirs");
 123         extDirs.mkdirs();
 124 
 125         File testClasses = new File("test" + testNum);
 126         testClasses.mkdirs();
 127         defaultFileManager.setLocation(StandardLocation.CLASS_OUTPUT, Arrays.asList(testClasses));
 128 
 129         System.err.println("test " + testNum + ": "
 130                 + m.getDeclaringClass().getSimpleName() + "." + m.getName());
 131 
 132         StringWriter sw = new StringWriter();
 133         PrintWriter pw = new PrintWriter(sw);
 134 
 135         List<String> javacOptions = Arrays.asList(
 136                 "-XaddExports:jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED",
 137                 "-extdirs", extDirs.getPath(), // for use by filemanager handleOption
 138                 "-processor", TestClientCodeWrapper.class.getName()
 139                 );
 140 
 141         List<String> classes = Collections.emptyList();
 142 
 143         JavacTool tool = JavacTool.create();
 144         try {
 145             JavacTask task = tool.getTask(pw,
 146                     getFileManager(m, defaultFileManager),
 147                     getDiagnosticListener(m, pw),
 148                     javacOptions,
 149                     classes,
 150                     getCompilationUnits(m));
 151 
 152             if (isDeclaredIn(m, Processor.class))
 153                 task.setProcessors(getProcessors(m));
 154 
 155             if (isDeclaredIn(m, TaskListener.class))
 156                 task.setTaskListener(getTaskListener(m, pw));
 157 
 158             boolean ok = task.call();
 159             error("compilation " + (ok ? "succeeded" : "failed") + " unexpectedly");
 160         } catch (RuntimeException e) {
 161             System.err.println("caught " + e);
 162             if (e.getClass() == RuntimeException.class) {
 163                 Throwable cause = e.getCause();
 164                 if (cause instanceof UserError) {
 165                     String expect = m.getName();
 166                     String found = cause.getMessage();
 167                     checkEqual("exception messaqe", expect, found);
 168                 } else {
 169                     cause.printStackTrace(System.err);
 170                     error("Unexpected exception: " + cause);
 171                 }
 172             } else {
 173                 e.printStackTrace(System.err);
 174                 error("Unexpected exception: " + e);
 175             }
 176         }
 177 
 178         pw.close();
 179         String out = sw.toString();
 180         System.err.println(out);
 181     }
 182 
 183     /** Get a file manager to use for the test compilation. */
 184     JavaFileManager getFileManager(Method m, JavaFileManager defaultFileManager) {
 185         return isDeclaredIn(m, JavaFileManager.class, FileObject.class, JavaFileObject.class)
 186                 ? new UserFileManager(m, defaultFileManager)
 187                 : defaultFileManager;
 188     }
 189 
 190     /** Get a diagnostic listener to use for the test compilation. */
 191     DiagnosticListener<JavaFileObject> getDiagnosticListener(Method m, PrintWriter out) {
 192         return isDeclaredIn(m, DiagnosticListener.class)
 193                 ? new UserDiagnosticListener(m, out)
 194                 : null;
 195     }
 196 
 197     /** Get a set of file objects to use for the test compilation. */
 198     Iterable<? extends JavaFileObject> getCompilationUnits(Method m) {
 199         File testSrc = new File(System.getProperty("test.src"));
 200         File thisSrc = new File(testSrc, TestClientCodeWrapper.class.getName() + ".java");
 201         Iterable<? extends JavaFileObject> files = defaultFileManager.getJavaFileObjects(thisSrc);
 202         if (isDeclaredIn(m, FileObject.class, JavaFileObject.class))
 203             return Arrays.asList(new UserFileObject(m, files.iterator().next()));
 204         else
 205             return files;
 206     }
 207 
 208     /** Get a set of annotation processors to use for the test compilation. */
 209     Iterable<? extends Processor> getProcessors(Method m) {
 210         return Arrays.asList(new UserProcessor(m));
 211     }
 212 
 213     /** Get a task listener to use for the test compilation. */
 214     TaskListener getTaskListener(Method m, PrintWriter out) {
 215         return new UserTaskListener(m, out);
 216     }
 217 
 218     /** Check if two values are .equal, and report an error if not. */
 219     <T> void checkEqual(String label, T expect, T found) {
 220         if (!expect.equals(found))
 221             error("Unexpected value for " + label + ": " + found + "; expected: " + expect);
 222     }
 223 
 224     /** Report an error. */
 225     void error(String msg) {
 226         System.err.println("Error: " + msg);
 227         errors++;
 228     }
 229 
 230     /** Check if a method is declared in any of a set of classes */
 231     static boolean isDeclaredIn(Method m, Class<?>... classes) {
 232         Class<?> dc = m.getDeclaringClass();
 233         for (Class<?> c: classes) {
 234             if (c == dc) return true;
 235         }
 236         return false;
 237     }
 238 
 239     /** Throw an intentional error if the method has a given name. */
 240     static void throwUserExceptionIfNeeded(Method m, String name) {
 241         if (m != null && m.getName().equals(name))
 242             throw new UserError(name);
 243     }
 244 
 245     StandardJavaFileManager defaultFileManager;
 246     int testNum;
 247     int errors;
 248 
 249     //--------------------------------------------------------------------------
 250 
 251     /**
 252      * Processor used to trigger use of methods not normally used by javac.
 253      */
 254     @Override
 255     public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
 256         boolean firstRound = false;
 257         for (Element e: roundEnv.getRootElements()) {
 258             if (e.getSimpleName().contentEquals(TestClientCodeWrapper.class.getSimpleName()))
 259                 firstRound = true;
 260         }
 261         if (firstRound) {
 262             try {
 263                 FileObject f1 = filer.getResource(StandardLocation.CLASS_PATH, "",
 264                     TestClientCodeWrapper.class.getName() + ".java");
 265                 f1.openInputStream().close();
 266                 f1.openReader(false).close();
 267 
 268                 FileObject f2 = filer.createResource(
 269                         StandardLocation.CLASS_OUTPUT, "", "f2.txt", (Element[]) null);
 270                 f2.openOutputStream().close();
 271 
 272                 FileObject f3 = filer.createResource(
 273                         StandardLocation.CLASS_OUTPUT, "", "f3.txt", (Element[]) null);
 274                 f3.openWriter().close();
 275 
 276                 JavaFileObject f4 = filer.createSourceFile("f4", (Element[]) null);
 277                 f4.openWriter().close();
 278                 f4.getNestingKind();
 279                 f4.getAccessLevel();
 280 
 281                 messager.printMessage(Diagnostic.Kind.NOTE, "informational note",
 282                         roundEnv.getRootElements().iterator().next());
 283 
 284             } catch (IOException e) {
 285                 throw new UserError(e);
 286             }
 287         }
 288         return true;
 289     }
 290 
 291     //--------------------------------------------------------------------------
 292 
 293     // <editor-fold defaultstate="collapsed" desc="User classes">
 294 
 295     static class UserError extends Error {
 296         private static final long serialVersionUID = 1L;
 297         UserError(String msg) {
 298             super(msg);
 299         }
 300         UserError(Throwable t) {
 301             super(t);
 302         }
 303     }
 304 
 305     static class UserFileManager extends ForwardingJavaFileManager<JavaFileManager> {
 306         Method fileManagerMethod;
 307         Method fileObjectMethod;
 308 
 309         UserFileManager(Method m, JavaFileManager delegate) {
 310             super(delegate);
 311             if (isDeclaredIn(m, JavaFileManager.class)) {
 312                 fileManagerMethod = m;
 313             } else if (isDeclaredIn(m, FileObject.class, JavaFileObject.class)) {
 314                 fileObjectMethod = m;
 315             } else
 316                 assert false;
 317         }
 318 
 319         @Override
 320         public ClassLoader getClassLoader(Location location) {
 321             throwUserExceptionIfNeeded(fileManagerMethod, "getClassLoader");
 322             return super.getClassLoader(location);
 323         }
 324 
 325         @Override
 326         public <S> ServiceLoader getServiceLoader(Location location, Class<S> service) throws IOException {
 327             throwUserExceptionIfNeeded(fileManagerMethod, "getServiceLoader");
 328             return super.getServiceLoader(location, service);
 329         }
 330 
 331         @Override
 332         public Iterable<JavaFileObject> list(Location location, String packageName, Set<Kind> kinds, boolean recurse) throws IOException {
 333             throwUserExceptionIfNeeded(fileManagerMethod, "list");
 334             return wrap(super.list(location, packageName, kinds, recurse));
 335         }
 336 
 337         @Override
 338         public String inferBinaryName(Location location, JavaFileObject file) {
 339             throwUserExceptionIfNeeded(fileManagerMethod, "inferBinaryName");
 340             return super.inferBinaryName(location, unwrap(file));
 341         }
 342 
 343         @Override
 344         public boolean isSameFile(FileObject a, FileObject b) {
 345             throwUserExceptionIfNeeded(fileManagerMethod, "isSameFile");
 346             return super.isSameFile(unwrap(a), unwrap(b));
 347         }
 348 
 349         @Override
 350         public boolean handleOption(String current, Iterator<String> remaining) {
 351             throwUserExceptionIfNeeded(fileManagerMethod, "handleOption");
 352             return super.handleOption(current, remaining);
 353         }
 354 
 355         @Override
 356         public boolean hasLocation(Location location) {
 357             throwUserExceptionIfNeeded(fileManagerMethod, "hasLocation");
 358             return super.hasLocation(location);
 359         }
 360 
 361         @Override
 362         public JavaFileObject getJavaFileForInput(Location location, String className, Kind kind) throws IOException {
 363             throwUserExceptionIfNeeded(fileManagerMethod, "getJavaFileForInput");
 364             return wrap(super.getJavaFileForInput(location, className, kind));
 365         }
 366 
 367         @Override
 368         public JavaFileObject getJavaFileForOutput(Location location, String className, Kind kind, FileObject sibling) throws IOException {
 369             throwUserExceptionIfNeeded(fileManagerMethod, "getJavaFileForOutput");
 370             return wrap(super.getJavaFileForOutput(location, className, kind, sibling));
 371         }
 372 
 373         @Override
 374         public FileObject getFileForInput(Location location, String packageName, String relativeName) throws IOException {
 375             throwUserExceptionIfNeeded(fileManagerMethod, "getFileForInput");
 376             return wrap(super.getFileForInput(location, packageName, relativeName));
 377         }
 378 
 379         @Override
 380         public FileObject getFileForOutput(Location location, String packageName, String relativeName, FileObject sibling) throws IOException {
 381             throwUserExceptionIfNeeded(fileManagerMethod, "getFileForOutput");
 382             return wrap(super.getFileForOutput(location, packageName, relativeName, sibling));
 383         }
 384 
 385         @Override
 386         public void flush() throws IOException {
 387             throwUserExceptionIfNeeded(fileManagerMethod, "flush");
 388             super.flush();
 389         }
 390 
 391         @Override
 392         public void close() throws IOException {
 393             throwUserExceptionIfNeeded(fileManagerMethod, "close");
 394             super.close();
 395         }
 396 
 397         @Override
 398         public int isSupportedOption(String option) {
 399             throwUserExceptionIfNeeded(fileManagerMethod, "isSupportedOption");
 400             return super.isSupportedOption(option);
 401         }
 402 
 403         @Override
 404         public Location getModuleLocation(Location location, String moduleName) throws IOException {
 405             throwUserExceptionIfNeeded(fileManagerMethod, "getModuleLocation");
 406             return super.getModuleLocation(location, moduleName);
 407         }
 408 
 409         @Override
 410         public Location getModuleLocation(Location location, JavaFileObject fo, String pkgName) throws IOException {
 411             throwUserExceptionIfNeeded(fileManagerMethod, "getModuleLocation");
 412             return super.getModuleLocation(location, fo, pkgName);
 413         }
 414 
 415         @Override
 416         public String inferModuleName(Location location) throws IOException {
 417             throwUserExceptionIfNeeded(fileManagerMethod, "inferModuleName");
 418             return super.inferModuleName(location);
 419         }
 420 
 421         @Override
 422         public Iterable<Set<Location>> listModuleLocations(Location location) throws IOException {
 423             throwUserExceptionIfNeeded(fileManagerMethod, "listModuleLocations");
 424             return super.listModuleLocations(location);
 425         }
 426 
 427         public FileObject wrap(FileObject fo) {
 428             if (fileObjectMethod == null || fo == null)
 429                 return fo;
 430             return new UserFileObject(fileObjectMethod, (JavaFileObject)fo);
 431         }
 432 
 433         FileObject unwrap(FileObject fo) {
 434             if (fo instanceof UserFileObject)
 435                 return ((UserFileObject) fo).unwrap();
 436             else
 437                 return fo;
 438         }
 439 
 440         public JavaFileObject wrap(JavaFileObject fo) {
 441             if (fileObjectMethod == null || fo == null)
 442                 return fo;
 443             return new UserFileObject(fileObjectMethod, fo);
 444         }
 445 
 446         public Iterable<JavaFileObject> wrap(Iterable<? extends JavaFileObject> list) {
 447             List<JavaFileObject> wrapped = new ArrayList<JavaFileObject>();
 448             for (JavaFileObject fo : list)
 449                 wrapped.add(wrap(fo));
 450             return Collections.unmodifiableList(wrapped);
 451         }
 452 
 453         JavaFileObject unwrap(JavaFileObject fo) {
 454             if (fo instanceof UserFileObject)
 455                 return ((UserFileObject) fo).unwrap();
 456             else
 457                 return fo;
 458         }
 459     }
 460 
 461     static class UserFileObject extends ForwardingJavaFileObject<JavaFileObject> {
 462         Method method;
 463 
 464         UserFileObject(Method m, JavaFileObject delegate) {
 465             super(delegate);
 466             assert isDeclaredIn(m, FileObject.class, JavaFileObject.class);
 467             this.method = m;
 468         }
 469 
 470         JavaFileObject unwrap() {
 471             return fileObject;
 472         }
 473 
 474         @Override
 475         public Kind getKind() {
 476             throwUserExceptionIfNeeded(method, "getKind");
 477             return super.getKind();
 478         }
 479 
 480         @Override
 481         public boolean isNameCompatible(String simpleName, Kind kind) {
 482             throwUserExceptionIfNeeded(method, "isNameCompatible");
 483             return super.isNameCompatible(simpleName, kind);
 484         }
 485 
 486         @Override
 487         public NestingKind getNestingKind() {
 488             throwUserExceptionIfNeeded(method, "getNestingKind");
 489             return super.getNestingKind();
 490         }
 491 
 492         @Override
 493         public Modifier getAccessLevel() {
 494             throwUserExceptionIfNeeded(method, "getAccessLevel");
 495             return super.getAccessLevel();
 496         }
 497 
 498         @Override
 499         public URI toUri() {
 500             throwUserExceptionIfNeeded(method, "toUri");
 501             return super.toUri();
 502         }
 503 
 504         @Override
 505         public String getName() {
 506             throwUserExceptionIfNeeded(method, "getName");
 507             return super.getName();
 508         }
 509 
 510         @Override
 511         public InputStream openInputStream() throws IOException {
 512             throwUserExceptionIfNeeded(method, "openInputStream");
 513             return super.openInputStream();
 514         }
 515 
 516         @Override
 517         public OutputStream openOutputStream() throws IOException {
 518             throwUserExceptionIfNeeded(method, "openOutputStream");
 519             return super.openOutputStream();
 520         }
 521 
 522         @Override
 523         public Reader openReader(boolean ignoreEncodingErrors) throws IOException {
 524             throwUserExceptionIfNeeded(method, "openReader");
 525             return super.openReader(ignoreEncodingErrors);
 526         }
 527 
 528         @Override
 529         public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
 530             throwUserExceptionIfNeeded(method, "getCharContent");
 531             return super.getCharContent(ignoreEncodingErrors);
 532         }
 533 
 534         @Override
 535         public Writer openWriter() throws IOException {
 536             throwUserExceptionIfNeeded(method, "openWriter");
 537             return super.openWriter();
 538         }
 539 
 540         @Override
 541         public long getLastModified() {
 542             throwUserExceptionIfNeeded(method, "getLastModified");
 543             return super.getLastModified();
 544         }
 545 
 546         @Override
 547         public boolean delete() {
 548             throwUserExceptionIfNeeded(method, "delete");
 549             return super.delete();
 550         }
 551 
 552     }
 553 
 554     static class UserProcessor extends JavacTestingAbstractProcessor {
 555         Method method;
 556 
 557         UserProcessor(Method m) {
 558             assert isDeclaredIn(m, Processor.class);
 559             method = m;
 560         }
 561 
 562         @Override
 563         public Set<String> getSupportedOptions() {
 564             throwUserExceptionIfNeeded(method, "getSupportedOptions");
 565             return super.getSupportedOptions();
 566         }
 567 
 568         @Override
 569         public Set<String> getSupportedAnnotationTypes() {
 570             throwUserExceptionIfNeeded(method, "getSupportedAnnotationTypes");
 571             return super.getSupportedAnnotationTypes();
 572         }
 573 
 574         @Override
 575         public SourceVersion getSupportedSourceVersion() {
 576             throwUserExceptionIfNeeded(method, "getSupportedSourceVersion");
 577             return super.getSupportedSourceVersion();
 578         }
 579 
 580         @Override
 581         public void init(ProcessingEnvironment processingEnv) {
 582             throwUserExceptionIfNeeded(method, "init");
 583             super.init(processingEnv);
 584         }
 585 
 586         @Override
 587         public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
 588             throwUserExceptionIfNeeded(method, "process");
 589             return true;
 590         }
 591 
 592         @Override
 593         public Iterable<? extends Completion> getCompletions(Element element, AnnotationMirror annotation, ExecutableElement member, String userText) {
 594             throwUserExceptionIfNeeded(method, "getCompletions");
 595             return super.getCompletions(element, annotation, member, userText);
 596         }
 597     }
 598 
 599     static class UserDiagnosticListener implements DiagnosticListener<JavaFileObject> {
 600         Method method;
 601         PrintWriter out;
 602 
 603         UserDiagnosticListener(Method m, PrintWriter out) {
 604             assert isDeclaredIn(m, DiagnosticListener.class);
 605             this.method = m;
 606             this.out = out;
 607         }
 608 
 609         @Override
 610         public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
 611             throwUserExceptionIfNeeded(method, "report");
 612             out.println("report: " + diagnostic);
 613         }
 614     }
 615 
 616     static class UserTaskListener implements TaskListener {
 617         Method method;
 618         PrintWriter out;
 619 
 620         UserTaskListener(Method m, PrintWriter out) {
 621             assert isDeclaredIn(m, TaskListener.class);
 622             this.method = m;
 623             this.out = out;
 624         }
 625 
 626         @Override
 627         public void started(TaskEvent e) {
 628             throwUserExceptionIfNeeded(method, "started");
 629             out.println("started: " + e);
 630         }
 631 
 632         @Override
 633         public void finished(TaskEvent e) {
 634             throwUserExceptionIfNeeded(method, "finished");
 635             out.println("finished: " + e);
 636         }
 637     }
 638 
 639     // </editor-fold>
 640 }