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