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