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