1 /*
2 * Copyright (c) 2023, 2025, 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 /*
26 * @test
27 * @summary Test for archiving dynamic proxies
28 * @requires vm.cds.write.archived.java.heap
29 * @library /test/jdk/lib/testlibrary /test/lib
30 * @build DynamicProxyTest testpkg.PublicTester
31 * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app1.jar
32 * DynamicProxyApp MyInvocationHandler
33 * DynamicProxyTest$Foo DynamicProxyTest$Boo DynamicProxyTest$Coo
34 * DynamicProxyTest$DefaultMethodWithUnlinkedClass Fruit
35 * testpkg.PublicTester testpkg.NonPublicInterface testpkg.MyInvocationHandler2
36 * jdk.test.lib.Asserts
37 * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app2.jar
38 * Fruit Apple
39 * @run driver DynamicProxyTest
40 */
41
42
43 import java.lang.reflect.InvocationHandler;
44 import java.lang.reflect.Method;
45 import java.lang.reflect.Proxy;
46 import java.security.ProtectionDomain;
47 import java.io.File;
48 import java.util.logging.Filter;
49 import java.util.Map;
50
51 import jdk.test.lib.Asserts;
52 import jdk.test.lib.cds.CDSAppTester;
53 import jdk.test.lib.helpers.ClassFileInstaller;
54 import jdk.test.lib.process.OutputAnalyzer;
55
56 public class DynamicProxyTest {
57 static final String app1Jar = ClassFileInstaller.getJarPath("app1.jar");
58 static final String app2Jar = ClassFileInstaller.getJarPath("app2.jar");
59 static final String mainClass = "DynamicProxyApp";
60
61 public static void main(String[] args) throws Exception {
62 {
63 Tester tester = new Tester();
64 tester.run(new String[] {"AOT"} );
65 }
66 }
67
68 static class Tester extends CDSAppTester {
69 public Tester() {
70 super("DynamicProxyTest");
71 }
72
73 @Override
74 public String classpath(RunMode runMode) {
75 switch (runMode) {
76 case RunMode.TRAINING:
77 case RunMode.DUMP_STATIC:
78 return app1Jar;
79 default:
80 return app1Jar + File.pathSeparator + app2Jar;
81 }
82 }
83
84 @Override
85 public String[] appCommandLine(RunMode runMode) {
86 return new String[] {
87 "DynamicProxyApp", runMode.name()
88 };
89 }
90
91 @Override
92 public String[] vmArgs(RunMode runMode) {
93 return new String[] {
94 "-XX:+ArchiveDynamicProxies",
95 "-Xlog:cds+dynamic+proxy:file=DynamicProxyTest.proxies." + runMode + ".log"
96 };
97 }
98 }
99
100 public static interface Foo {
101 public Object bar();
102 }
103
104 public static interface Boo {
105 public Object far();
106 }
107
108 static interface Coo {
109 public Object car();
110 }
111
112 public static interface DefaultMethodWithUnlinkedClass {
113 default Object doit(boolean flag) {
114 if (flag) {
115 Fruit f = Fruit.get();
116 f.peel();
117 return f;
118 } else {
119 return "Do not link Fruit class";
120 }
121 }
122 }
123 }
124
125 class MyInvocationHandler implements InvocationHandler {
126 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
127 if (method.getName().equals("isLoggable")) {
128 return Boolean.TRUE;
129 } else {
130 return method.getName() + "() in " + proxy.getClass() + " is called";
131 }
132 }
133 }
134
135 class DynamicProxyApp {
136 static String[] classes = {
137 "java.io.Serializable",
138 "java.lang.annotation.Documented",
139 "java.lang.annotation.Inherited",
140 "java.lang.annotation.Retention",
141 "java.lang.annotation.Target",
142 "java.lang.Enum",
143 "java.lang.Integer",
144 "java.lang.management.BufferPoolMXBean",
145 "java.lang.management.ClassLoadingMXBean",
146 "java.lang.management.CompilationMXBean",
147 "java.lang.management.GarbageCollectorMXBean",
148 "java.lang.management.MemoryManagerMXBean",
149 "java.lang.management.MemoryMXBean",
150 "java.lang.management.MemoryPoolMXBean",
151 "java.lang.management.OperatingSystemMXBean",
152 "java.lang.management.PlatformLoggingMXBean",
153 "java.lang.management.PlatformManagedObject",
154 "java.lang.management.RuntimeMXBean",
155 "java.lang.management.ThreadMXBean",
156 "java.lang.Number",
157 "java.lang.Object",
158 "java.lang.Package$1PackageInfoProxy",
159 "java.lang.reflect.Proxy",
160 "java.lang.String",
161 "java.time.LocalDate",
162 "java.util.Collections$UnmodifiableMap",
163 "java.util.Dictionary",
164 "java.util.Hashtable",
165 "java.util.Properties",
166 "javax.management.NotificationBroadcaster",
167 "javax.management.NotificationEmitter",
168 "javax.naming.Referenceable",
169 "javax.sql.CommonDataSource",
170 "javax.sql.DataSource",
171 "jdk.management.jfr.FlightRecorderMXBean",
172 };
173
174 static void checkRead(boolean expected, Module proxyModule, Module targetModule) {
175 Asserts.assertEQ(proxyModule.canRead(targetModule), expected,
176 "(" + proxyModule + ") canRead (" +
177 targetModule + " of loader " + targetModule.getClassLoader() + ") = " +
178 proxyModule.canRead(targetModule) + ", but should be " + expected);
179 }
180
181 static boolean hasArchivedProxies;
182
183 private static void checkArchivedProxy(Class c, boolean shouldBeArchived) {
184 if (hasArchivedProxies) {
185 // We can't use WhiteBox, which would disable full module graph. So we can only check the name.
186 if (shouldBeArchived && !c.getName().contains("$Proxy0")) {
187 throw new RuntimeException("Proxy class " + c + " does not seem to be archived");
188 }
189 if (!shouldBeArchived && c.getName().contains("$Proxy00")) {
190 throw new RuntimeException("Proxy class " + c + " shouldn't be archived");
191 }
192
193 System.out.println(c + (shouldBeArchived ? " is " : " is not ") + "archived, as expected");
194 }
195 }
196
197 public static void main(String args[]) {
198 // We should have archived dynamic proxies after the static archive has been dumped.
199 hasArchivedProxies = args[0].contains("PRODUCTION");
200
201 // Create a Proxy for the java.util.Map interface
202 System.out.println("================test 1: Proxy for java.util.Map");
203 Map instance1 = (Map) Proxy.newProxyInstance(
204 DynamicProxyApp.class.getClassLoader(),
205 new Class[] { Map.class },
206 new MyInvocationHandler());
207 System.out.println(instance1.getClass());
208 System.out.println(instance1.getClass().getClassLoader());
209 System.out.println(instance1.getClass().getPackage());
210 System.out.println(instance1.get("5678"));
211 System.out.println(instance1.getClass().getModule());
212
213 // Proxy$ProxyBuilder defines the proxy classes with a null protection domain, resulting in the
214 // following behavior. This must be preserved by CDS.
215 // TODO: after JDK-8322322 is implemented, add tests for Proxy creation by boot loader code.
216 ProtectionDomain pd = instance1.getClass().getProtectionDomain();
217 Asserts.assertNotNull(pd);
218 Asserts.assertNull(pd.getClassLoader());
219 Asserts.assertNull(pd.getCodeSource());
220
221
222 Module dynModule = instance1.getClass().getModule();
223 // instance1.getClass() should be in a dynamic module like jdk.proxy1, which
224 // contains all proxies that implement only public interfaces for the app loader.
225
226 // This module should be able to read the module that contains Map (java.base)
227 checkRead(true, dynModule, Map.class.getModule());
228
229 // No proxies have been created for these modules yet, so they are't readable by dynModule yet.
230 checkRead(false, dynModule, DynamicProxyApp.class.getModule());
231 checkRead(false, dynModule, Filter.class.getModule());
232
233 checkArchivedProxy(instance1.getClass(), true);
234
235 System.out.println("=================test 2: Proxy for both Map and Runnable");
236 Map instance2 = (Map) Proxy.newProxyInstance(
237 DynamicProxyApp.class.getClassLoader(),
238 new Class[] { Map.class, Runnable.class },
239 new MyInvocationHandler());
240 System.out.println(instance2.getClass());
241 System.out.println(instance2.getClass().getPackage());
242 System.out.println(instance2.get("5678"));
243
244 checkArchivedProxy(instance2.getClass(), true);
245
246 System.out.println("=================test 3: Proxy for an interface in unnamed module");
247 DynamicProxyTest.Foo instance3 = (DynamicProxyTest.Foo) Proxy.newProxyInstance(
248 DynamicProxyApp.class.getClassLoader(),
249 new Class[] { DynamicProxyTest.Foo.class, Runnable.class },
250 new MyInvocationHandler());
251 System.out.println(instance3.getClass());
252 System.out.println(instance3.getClass().getPackage());
253 System.out.println(instance3.bar());
254 System.out.println(instance3.getClass().getModule());
255
256 // Now, dynModule should have access to the UNNAMED module
257 Asserts.assertSame(instance3.getClass().getModule(), dynModule, "proxies of only public interfaces should go in the same module");
258 checkRead(true, dynModule, DynamicProxyApp.class.getModule());
259 checkArchivedProxy(instance3.getClass(), true);
260
261 if (!args[0].equals("CLASSLIST") && !args[0].startsWith("TRAINING")) {
262 // This dynamic proxy is not loaded in the training run, so it shouldn't be
263 // archived.
264 System.out.println("=================test 3: Proxy (unarchived) for an interface in unnamed module");
265 DynamicProxyTest.Boo instance3a = (DynamicProxyTest.Boo) Proxy.newProxyInstance(
266 DynamicProxyApp.class.getClassLoader(),
267 new Class[] { DynamicProxyTest.Boo.class, Runnable.class },
268 new MyInvocationHandler());
269 System.out.println(instance3a.getClass());
270 System.out.println(instance3a.getClass().getPackage());
271 System.out.println(instance3a.far());
272
273 Asserts.assertSame(instance3.getClass().getPackage(), instance3a.getClass().getPackage(), "should be the same package");
274 checkArchivedProxy(instance3a.getClass(), false);
275 }
276
277 System.out.println("=================test 4: Proxy for java.util.logging.Filter in java.logging module");
278 Filter instance4 = (Filter) Proxy.newProxyInstance(
279 DynamicProxyApp.class.getClassLoader(),
280 new Class[] { Filter.class },
281 new MyInvocationHandler());
282 System.out.println(instance4.getClass());
283 System.out.println(instance4.getClass().getPackage());
284 System.out.println(instance4.isLoggable(null));
285
286 // Now dynModule should have access to the java.logging module
287 Asserts.assertSame(instance4.getClass().getModule(), dynModule, "proxies of only public interfaces should go in the same module");
288 checkRead(true, dynModule, Filter.class.getModule());
289
290 System.out.println("=================test 5: Proxy for non-public interface not in any package");
291 DynamicProxyTest.Coo instance5 = (DynamicProxyTest.Coo) Proxy.newProxyInstance(
292 DynamicProxyApp.class.getClassLoader(),
293 new Class[] { DynamicProxyTest.Coo.class},
294 new MyInvocationHandler());
295 System.out.println(instance5.getClass());
296 System.out.println(instance5.getClass().getPackage());
297 System.out.println(instance5.getClass().getModule());
298 System.out.println(instance5.car());
299
300 Asserts.assertSame(instance5.getClass().getPackage(), DynamicProxyApp.class.getPackage(), "should be the same package");
301 Asserts.assertSame(instance5.getClass().getModule(), DynamicProxyApp.class.getModule(), "should be the same module");
302 checkArchivedProxy(instance5.getClass(), false); // CDS doesn't (yet) support such proxies.
303
304 System.out.println("=================test 6: Proxy for non-public interface in a package");
305 {
306 Class<?> c = testpkg.PublicTester.test();
307 checkArchivedProxy(c, false); // CDS doesn't (yet) support such proxies.
308 }
309
310 System.out.println("=================test 7: Proxy with a default method that references unlinked classes");
311 DynamicProxyTest.DefaultMethodWithUnlinkedClass instance7 = (DynamicProxyTest.DefaultMethodWithUnlinkedClass) Proxy.newProxyInstance(
312 DynamicProxyApp.class.getClassLoader(),
313 new Class[] { DynamicProxyTest.DefaultMethodWithUnlinkedClass.class, Runnable.class },
314 new MyInvocationHandler());
315 System.out.println(instance7.getClass());
316 System.out.println(instance7.getClass().getPackage());
317 System.out.println(instance7.doit(false));
318 System.out.println(instance7.getClass().getModule());
319
320 Asserts.assertSame(instance7.getClass().getModule(), dynModule, "proxies of only public interfaces should go in the same module");
321 checkArchivedProxy(instance7.getClass(), true);
322
323 // Get annotations -- this will cause a bunch of proxies to be generated
324 ClassLoader loader = DynamicProxyApp.class.getClassLoader();
325 for (String className: classes) {
326 try {
327 Class.forName(className, false, loader).getDeclaredAnnotations();
328 } catch (Throwable t) {}
329 }
330 }
331 }
332
333 class Fruit {
334 void peel() {}
335 static Fruit get() {
336 return new Apple();
337 }
338 }
339
340 // This class is never included in the JAR files. This means that the Fruit class cannot
341 // be verified. But our test case doesn't link the Fruit class, so it should run without
342 // any error.
343 class Apple extends Fruit {}