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 {}