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 }