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