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 how various AOT optimizations handle classes that are excluded from the AOT cache. 28 * @requires vm.cds.write.archived.java.heap 29 * @library /test/jdk/lib/testlibrary /test/lib 30 * /test/hotspot/jtreg/runtime/cds/appcds/aotCache/test-classes 31 * @build ExcludedClasses CustyWithLoop 32 * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar 33 * TestApp 34 * TestApp$Foo 35 * TestApp$Foo$BadFieldSig 36 * TestApp$Foo$BadMethodSig 37 * TestApp$Foo$Bar 38 * TestApp$Foo$GoodSig1 39 * TestApp$Foo$GoodSig2 40 * TestApp$Foo$ShouldBeExcluded 41 * TestApp$Foo$ShouldBeExcludedChild 42 * TestApp$Foo$Taz 43 * TestApp$MyInvocationHandler 44 * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar cust.jar 45 * CustyWithLoop 46 * @run driver ExcludedClasses 47 */ 48 49 import java.io.File; 50 import java.lang.reflect.Array; 51 import java.lang.reflect.Field; 52 import java.lang.reflect.InvocationHandler; 53 import java.lang.reflect.Method; 54 import java.lang.reflect.Proxy; 55 import java.net.URL; 56 import java.net.URLClassLoader; 57 import java.security.ProtectionDomain; 58 import java.util.Map; 59 60 import jdk.jfr.Event; 61 import jdk.test.lib.cds.CDSAppTester; 62 import jdk.test.lib.helpers.ClassFileInstaller; 63 import jdk.test.lib.process.OutputAnalyzer; 64 65 public class ExcludedClasses { 66 static final String appJar = ClassFileInstaller.getJarPath("app.jar"); 67 static final String mainClass = "TestApp"; 68 69 public static void main(String[] args) throws Exception { 70 Tester tester = new Tester(); 71 tester.runAOTWorkflow("AOT", "--two-step-training"); 72 } 73 74 static class Tester extends CDSAppTester { 75 public Tester() { 76 super(mainClass); 77 } 78 79 @Override 80 public String classpath(RunMode runMode) { 81 return appJar; 82 } 83 84 @Override 85 public String[] vmArgs(RunMode runMode) { 86 return new String[] { 87 "-Xlog:aot=debug", 88 "-Xlog:aot+class=debug", 89 "-Xlog:aot+resolve=trace", 90 "-Xlog:aot+verification=trace", 91 "-Xlog:class+load", 92 }; 93 } 94 95 @Override 96 public String[] appCommandLine(RunMode runMode) { 97 return new String[] { 98 mainClass, runMode.name() 99 }; 100 } 101 102 @Override 103 public void checkExecution(OutputAnalyzer out, RunMode runMode) { 104 if (runMode == RunMode.ASSEMBLY) { 105 out.shouldNotMatch("aot,resolve.*archived field.*TestApp.Foo => TestApp.Foo.ShouldBeExcluded.f:I"); 106 out.shouldContain("Archived reflection data in TestApp$Foo$GoodSig1"); 107 out.shouldContain("Archived reflection data in TestApp$Foo$GoodSig2"); 108 out.shouldContain("Cannot archive reflection data in TestApp$Foo$BadMethodSig"); 109 out.shouldContain("Cannot archive reflection data in TestApp$Foo$BadFieldSig"); 110 } else if (runMode == RunMode.PRODUCTION) { 111 out.shouldContain("check_verification_constraint: TestApp$Foo$Taz: TestApp$Foo$ShouldBeExcludedChild must be subclass of TestApp$Foo$ShouldBeExcluded"); 112 out.shouldContain("jdk.jfr.Event source: jrt:/jdk.jfr"); 113 out.shouldMatch("TestApp[$]Foo[$]ShouldBeExcluded source: .*/app.jar"); 114 out.shouldMatch("TestApp[$]Foo[$]ShouldBeExcludedChild source: .*/app.jar"); 115 } 116 } 117 } 118 } 119 120 class TestApp { 121 static volatile Object custInstance; 122 static volatile Object custArrayInstance; 123 124 public static void main(String args[]) throws Exception { 125 // In AOT workflow, classes from custom loaders are passed from the preimage 126 // to the final image. See FinalImageRecipes::record_all_classes(). 127 custInstance = initFromCustomLoader(); 128 custArrayInstance = Array.newInstance(custInstance.getClass(), 0); 129 System.out.println(custArrayInstance); 130 System.out.println("Counter = " + Foo.hotSpot()); 131 } 132 133 static Object initFromCustomLoader() throws Exception { 134 String path = "cust.jar"; 135 URL url = new File(path).toURI().toURL(); 136 URL[] urls = new URL[] {url}; 137 URLClassLoader urlClassLoader = 138 new URLClassLoader("MyLoader", urls, null); 139 Class c = Class.forName("CustyWithLoop", true, urlClassLoader); 140 return c.newInstance(); 141 } 142 143 static class MyInvocationHandler implements InvocationHandler { 144 volatile static int cnt; 145 146 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 147 long start = System.currentTimeMillis(); 148 while (System.currentTimeMillis() - start < 20) { 149 cnt += 2; 150 for (int i = 0; i < 1000; i++) { 151 int n = cnt - 2; 152 if (n < 2) { 153 n = 2; 154 } 155 cnt += (i + cnt) % n + cnt % 2; 156 } 157 } 158 return Integer.valueOf(cnt); 159 } 160 } 161 162 static class Foo { 163 volatile static int counter; 164 static Class c = ShouldBeExcluded.class; 165 166 static Map mapProxy = (Map) Proxy.newProxyInstance( 167 Foo.class.getClassLoader(), 168 new Class[] { Map.class }, 169 new MyInvocationHandler()); 170 171 static int hotSpot() { 172 ShouldBeExcluded s = new ShouldBeExcluded(); 173 Bar b = new Bar(); 174 175 long start = System.currentTimeMillis(); 176 while (System.currentTimeMillis() - start < 1000) { 177 lambdaHotSpot(); 178 s.hotSpot2(); 179 b.hotSpot3(); 180 Taz.hotSpot4(); 181 reflectHotSpots(); 182 183 // In JDK mainline, generated proxy classes are excluded from the AOT cache. 184 // In Leyden/premain, generated proxy classes included. The following code should 185 // work with either repos. 186 Integer i = (Integer)mapProxy.get(null); 187 counter += i.intValue(); 188 189 if (custInstance != null) { 190 // Classes loaded by custom loaders are included in the AOT cache 191 // but their array classes are excluded. 192 counter += custInstance.equals(null) ? 1 : 2; 193 } 194 195 if (custArrayInstance != null) { 196 if ((counter % 3) == 0) { 197 counter += (custArrayInstance instanceof String) ? 0 : 1; 198 } else { 199 counter += (custArrayInstance instanceof Object) ? 0 : 1; 200 } 201 } 202 } 203 204 return counter + s.m() + s.f + b.m() + b.f; 205 } 206 207 static void f() { 208 if (counter % 2 == 1) { 209 counter ++; 210 } 211 } 212 213 // Generated Lambda classes should be excluded from CDS preimage. 214 static void lambdaHotSpot() { 215 long start = System.currentTimeMillis(); 216 while (System.currentTimeMillis() - start < 20) { 217 doit(() -> counter ++ ); 218 } 219 } 220 221 static void doit(Runnable r) { 222 r.run(); 223 } 224 225 // All subclasses of jdk.jfr.Event are excluded from the CDS archive. 226 static class ShouldBeExcluded extends jdk.jfr.Event { 227 int f = (int)(System.currentTimeMillis()) + 123; 228 int m() { 229 return f + 456; 230 } 231 232 void hotSpot2() { 233 long start = System.currentTimeMillis(); 234 while (System.currentTimeMillis() - start < 20) { 235 for (int i = 0; i < 50000; i++) { 236 counter += i; 237 } 238 f(); 239 } 240 } 241 int func() { 242 return 1; 243 } 244 } 245 246 static class ShouldBeExcludedChild extends ShouldBeExcluded { 247 @Override 248 int func() { 249 return 2; 250 } 251 } 252 253 static class Bar { 254 int f = (int)(System.currentTimeMillis()) + 123; 255 int m() { 256 return f + 456; 257 } 258 259 void hotSpot3() { 260 long start = System.currentTimeMillis(); 261 while (System.currentTimeMillis() - start < 20) { 262 for (int i = 0; i < 50000; i++) { 263 counter += i; 264 } 265 f(); 266 } 267 } 268 } 269 270 static class Taz { 271 static ShouldBeExcluded m() { 272 // When verifying this method, we need to check the constraint that 273 // ShouldBeExcluded must be a supertype of ShouldBeExcludedChild. This information 274 // is checked by SystemDictionaryShared::check_verification_constraints() when the Taz 275 // class is linked during the production run. 276 // 277 // Because ShouldBeExcluded is excluded from the AOT archive, it must be loaded 278 // dynamically from app.jar inside SystemDictionaryShared::check_verification_constraints(). 279 // This must happen after the app class loader has been fully restored from the AOT cache. 280 return new ShouldBeExcludedChild(); 281 } 282 static void hotSpot4() { 283 long start = System.currentTimeMillis(); 284 while (System.currentTimeMillis() - start < 20) { 285 for (int i = 0; i < 50000; i++) { 286 counter += i; 287 } 288 f(); 289 } 290 } 291 } 292 293 static volatile Object dummyObj; 294 295 static void reflectHotSpots() { 296 try { 297 // f.clazz points to an excluded class, so we should not archive any fields in 298 // the BadFieldSig 299 Field f = BadFieldSig.class.getDeclaredField("myField"); 300 Method m = BadMethodSig.class.getDeclaredMethod("myMethod", Object.class, ShouldBeExcluded.class); 301 302 // It's OK to archive the reflection data of this class even if its method signatures 303 // refers to a class that's not loaded at all during the assembly phase. 304 // Note: because the app did not reflect on the methods of GoodSig1, we don't 305 // AOT-generate method reflection data for GoodSig. 306 Field f2 = GoodSig1.class.getDeclaredField("myField"); 307 308 // Opposite case as GoodSig1. We should archive its reflection data 309 Method m2 = GoodSig2.class.getDeclaredMethod("myMethod", Object.class, Object.class); 310 311 long start = System.currentTimeMillis(); 312 while (System.currentTimeMillis() - start < 50) { 313 for (int i = 0; i < 50000; i++) { 314 dummyObj = f.get(null); 315 dummyObj = m.invoke(null, null, null); 316 dummyObj = f2.get(null); 317 dummyObj = m2.invoke(null, null, null); 318 } 319 } 320 } catch (Throwable t) { 321 throw new RuntimeException("Unexpected exception", t); 322 } 323 } 324 325 static class BadFieldSig { 326 static ShouldBeExcluded myField = new ShouldBeExcluded(); 327 } 328 329 static class BadMethodSig { 330 static String myMethod(Object o, ShouldBeExcluded s) { 331 return "Foofoo"; 332 } 333 } 334 335 static class GoodSig1 { 336 static Object myField = new Object(); 337 static void method(UnavailableClass1 arg) {} 338 static void method(UnavailableClass2[] arg) {} 339 } 340 341 static class GoodSig2 { 342 static String myMethod(Object o, Object o2) { 343 return "Foofoo"; 344 } 345 346 UnavailableClass2[] unusedField; 347 348 // This field has never been reflected up or resolved during the training run, so the 349 // array type GoodSig2[][][][] is not resolved during either the training run or the 350 // assembly phase. 351 GoodSig2[][][][] unusedField2; 352 } 353 } 354 } 355 356 // These classes are NOT part of app.jar, so they cannot be resolved by the app. 357 class UnavailableClass1 {} 358 class UnavailableClass2 {} 359