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