1 /*
   2  * Copyright (c) 2011, 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 /*
  25  * @test
  26  * @bug 7092965
  27  * @summary javac should not close processorClassLoader before end of compilation
  28  * @modules jdk.compiler/com.sun.tools.javac.api
  29  *          jdk.compiler/com.sun.tools.javac.file
  30  */
  31 
  32 import com.sun.source.util.JavacTask;
  33 import com.sun.source.util.TaskEvent;
  34 import com.sun.source.util.TaskListener;
  35 import com.sun.tools.javac.api.ClientCodeWrapper.Trusted;
  36 import com.sun.tools.javac.api.BasicJavacTask;
  37 import com.sun.tools.javac.api.JavacTool;
  38 import java.io.ByteArrayOutputStream;
  39 import java.io.File;
  40 import java.io.IOException;
  41 import java.io.PrintStream;
  42 import java.lang.reflect.Field;
  43 import java.net.URI;
  44 import java.util.ArrayList;
  45 import java.util.Arrays;
  46 import java.util.Collections;
  47 import java.util.List;
  48 import javax.annotation.processing.ProcessingEnvironment;
  49 import javax.tools.JavaFileObject;
  50 import javax.tools.SimpleJavaFileObject;
  51 import javax.tools.StandardJavaFileManager;
  52 import javax.tools.StandardLocation;
  53 import javax.tools.ToolProvider;
  54 
  55 /*
  56  * The test compiles an annotation processor and a helper class into a
  57  * custom classes directory.
  58  *
  59  * It then uses them while compiling a dummy file, with the custom classes
  60  * directory on the processor path, thus guaranteeing that references to
  61  * these class are satisfied by the processor class loader.
  62  *
  63  * The annotation processor uses the javac TaskListener to run code
  64  * after annotation processing has completed, to verify that the classloader
  65  * is not closed until the end of the compilation.
  66  */
  67 
  68 @Trusted // avoids use of ClientCodeWrapper
  69 public class TestClose implements TaskListener {
  70     public static final String annoProc =
  71         "import java.util.*;\n" +
  72         "import javax.annotation.processing.*;\n" +
  73         "import javax.lang.model.*;\n" +
  74         "import javax.lang.model.element.*;\n" +
  75         "import com.sun.source.util.*;\n" +
  76         "import com.sun.tools.javac.processing.*;\n" +
  77         "import com.sun.tools.javac.util.*;\n" +
  78         "@SupportedAnnotationTypes(\"*\")\n" +
  79         "public class AnnoProc extends AbstractProcessor {\n" +
  80         "    @Override\n" +
  81         "    public SourceVersion getSupportedSourceVersion() {\n" +
  82         "        return SourceVersion.latest();\n" +
  83         "    }\n" +
  84         "    @Override\n" +
  85         "    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {\n" +
  86         "        System.out.println(\"in AnnoProc.process\");\n" +
  87         "        final ClassLoader cl = getClass().getClassLoader();\n" +
  88         "        if (roundEnv.processingOver()) {\n" +
  89         "            TestClose.add(processingEnv, new Runnable() {\n" +
  90         "                public void run() {\n" +
  91         "                    System.out.println(getClass().getName() + \": run()\");\n" +
  92         "                    try {\n" +
  93         "                        cl.loadClass(\"Callback\")\n" +
  94         "                            .asSubclass(Runnable.class)\n" +
  95         "                            .newInstance()\n" +
  96         "                            .run();\n" +
  97         "                    } catch (ReflectiveOperationException e) {\n" +
  98         "                        throw new Error(e);\n" +
  99         "                    }\n" +
 100         "                }\n" +
 101         "            });\n" +
 102         "        }\n" +
 103         "        return true;\n" +
 104         "    }\n" +
 105         "}\n";
 106 
 107     public static final String callback =
 108         "public class Callback implements Runnable {\n" +
 109         "    public void run() {\n" +
 110         "        System.out.println(getClass().getName() + \": run()\");\n" +
 111         "    }\n" +
 112         "}";
 113 
 114     public static void main(String... args) throws Exception {
 115         new TestClose().run();
 116     }
 117 
 118     void run() throws IOException {
 119         JavacTool tool = (JavacTool) ToolProvider.getSystemJavaCompiler();
 120         try (StandardJavaFileManager fm = tool.getStandardFileManager(null, null, null)) {
 121 
 122             File classes = new File("classes");
 123             classes.mkdirs();
 124             File extraClasses = new File("extraClasses");
 125             extraClasses.mkdirs();
 126 
 127             System.out.println("compiling classes to extraClasses");
 128             {   // setup class in extraClasses
 129                 fm.setLocation(StandardLocation.CLASS_OUTPUT,
 130                         Collections.singleton(extraClasses));
 131                 List<? extends JavaFileObject> files = Arrays.asList(
 132                         new MemFile("AnnoProc.java", annoProc),
 133                         new MemFile("Callback.java", callback));
 134                 List<String> options = Arrays.asList(
 135                         "--add-exports", "jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED",
 136                         "--add-exports", "jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED",
 137                         "-XDaccessInternalAPI");
 138                 JavacTask task = tool.getTask(null, fm, null, options, null, files);
 139                 check(task.call());
 140             }
 141 
 142             System.out.println("compiling dummy to classes with anno processor");
 143             {   // use that class in a TaskListener after processing has completed
 144                 PrintStream prev = System.out;
 145                 String out;
 146                 ByteArrayOutputStream baos = new ByteArrayOutputStream();
 147                 try (PrintStream ps = new PrintStream(baos)) {
 148                     System.setOut(ps);
 149                     File testClasses = new File(System.getProperty("test.classes"));
 150                     fm.setLocation(StandardLocation.CLASS_OUTPUT,
 151                             Collections.singleton(classes));
 152                     fm.setLocation(StandardLocation.ANNOTATION_PROCESSOR_PATH,
 153                             Arrays.asList(extraClasses, testClasses));
 154                     List<? extends JavaFileObject> files = Arrays.asList(
 155                             new MemFile("my://dummy", "class Dummy { }"));
 156                     List<String> options = Arrays.asList("-XDaccessInternalAPI", "-processor", "AnnoProc");
 157                     JavacTask task = tool.getTask(null, fm, null, options, null, files);
 158                     task.setTaskListener(this);
 159                     check(task.call());
 160                 } finally {
 161                     System.setOut(prev);
 162                     out = baos.toString();
 163                     if (!out.isEmpty())
 164                         System.out.println(out);
 165                 }
 166                 check(out.contains("AnnoProc$1: run()"));
 167                 check(out.contains("Callback: run()"));
 168             }
 169         }
 170     }
 171 
 172     @Override
 173     public void started(TaskEvent e) {
 174         System.out.println("Started: " + e);
 175     }
 176 
 177     @Override
 178     public void finished(TaskEvent e) {
 179         System.out.println("Finished: " + e);
 180         if (e.getKind() == TaskEvent.Kind.ANALYZE) {
 181             for (Runnable r: runnables) {
 182                 System.out.println("running " + r);
 183                 r.run();
 184             }
 185         }
 186     }
 187 
 188     void check(boolean b) {
 189         if (!b)
 190             throw new AssertionError();
 191     }
 192 
 193     public static void add(ProcessingEnvironment env, Runnable r) {
 194         // ensure this class in this class loader can access javac internals
 195         try {
 196             JavacTask task = JavacTask.instance(env);
 197             TaskListener l = ((BasicJavacTask) task).getTaskListeners().iterator().next();
 198             // The TaskListener is an instanceof TestClose, but when using the
 199             // default class loaders. the taskListener uses a different
 200             // instance of Class<TestClose> than the anno processor.
 201             // If you try to evaluate
 202             //      TestClose tc = (TestClose) (l).
 203             // you get the following somewhat confusing error:
 204             //   java.lang.ClassCastException: TestClose cannot be cast to TestClose
 205             // The workaround is to access the fields of TestClose with reflection.
 206             Field f = l.getClass().getField("runnables");
 207             @SuppressWarnings("unchecked")
 208             List<Runnable> runnables = (List<Runnable>) f.get(l);
 209             runnables.add(r);
 210         } catch (Throwable t) {
 211             t.printStackTrace();
 212         }
 213     }
 214 
 215     public List<Runnable> runnables = new ArrayList<>();
 216 
 217     class MemFile extends SimpleJavaFileObject {
 218         public final String text;
 219 
 220         MemFile(String name, String text) {
 221             super(URI.create(name), JavaFileObject.Kind.SOURCE);
 222             this.text = text;
 223         }
 224 
 225         @Override
 226         public String getName() {
 227             return uri.toString();
 228         }
 229 
 230         @Override
 231         public String getCharContent(boolean ignoreEncodingErrors) {
 232             return text;
 233         }
 234     }
 235 }