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