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