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$Bar
37 * TestApp$Foo$ShouldBeExcluded
38 * TestApp$Foo$ShouldBeExcludedChild
39 * TestApp$Foo$Taz
40 * TestApp$MyInvocationHandler
41 * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar cust.jar
42 * CustyWithLoop
43 * @run driver ExcludedClasses
44 */
45
46 import java.io.File;
47 import java.lang.invoke.MethodHandle;
48 import java.lang.invoke.MethodHandles;
49 import java.lang.reflect.Array;
50 import java.lang.reflect.InvocationHandler;
51 import java.lang.reflect.Method;
52 import java.lang.reflect.Proxy;
53 import java.net.URL;
54 import java.net.URLClassLoader;
55 import java.security.ProtectionDomain;
56 import java.util.Map;
57
58 import jdk.jfr.Event;
59 import jdk.test.lib.cds.CDSAppTester;
60 import jdk.test.lib.helpers.ClassFileInstaller;
61 import jdk.test.lib.process.OutputAnalyzer;
62
63 public class ExcludedClasses {
64 static final String appJar = ClassFileInstaller.getJarPath("app.jar");
65 static final String mainClass = "TestApp";
66
67 public static void main(String[] args) throws Exception {
68 Tester tester = new Tester();
69 tester.runAOTWorkflow("AOT", "--two-step-training");
70 }
71
72 static class Tester extends CDSAppTester {
73 public Tester() {
74 super(mainClass);
75 }
76
77 @Override
78 public String classpath(RunMode runMode) {
79 return appJar;
80 }
81
82 @Override
83 public String[] vmArgs(RunMode runMode) {
84 return new String[] {
85 "-Xlog:aot=debug",
86 "-Xlog:aot+class=debug",
87 "-Xlog:aot+resolve=trace",
88 "-Xlog:aot+verification=trace",
89 "-Xlog:class+load",
90 };
91 }
92
93 @Override
94 public String[] appCommandLine(RunMode runMode) {
95 return new String[] {
96 mainClass, runMode.name()
97 };
98 }
99
100 @Override
101 public void checkExecution(OutputAnalyzer out, RunMode runMode) {
102 if (runMode == RunMode.ASSEMBLY) {
103 out.shouldNotMatch("aot,resolve.*archived field.*TestApp.Foo => TestApp.Foo.ShouldBeExcluded.f:I");
104 } else if (runMode == RunMode.PRODUCTION) {
105 out.shouldContain("jdk.jfr.Event source: jrt:/jdk.jfr");
106 out.shouldMatch("TestApp[$]Foo[$]ShouldBeExcluded source: .*/app.jar");
107 out.shouldMatch("TestApp[$]Foo[$]ShouldBeExcludedChild source: .*/app.jar");
108 }
109 }
110 }
111 }
112
113 class TestApp {
114 static volatile Object custInstance;
115 static volatile Object custArrayInstance;
116
117 public static void main(String args[]) throws Exception {
118 // In AOT workflow, classes from custom loaders are passed from the preimage
119 // to the final image. See FinalImageRecipes::record_all_classes().
120 custInstance = initFromCustomLoader();
121 custArrayInstance = Array.newInstance(custInstance.getClass(), 0);
122 System.out.println(custArrayInstance);
123 System.out.println("Counter = " + Foo.hotSpot());
124 }
125
126 static Object initFromCustomLoader() throws Exception {
127 String path = "cust.jar";
128 URL url = new File(path).toURI().toURL();
129 URL[] urls = new URL[] {url};
130 URLClassLoader urlClassLoader =
131 new URLClassLoader("MyLoader", urls, null);
132 Class c = Class.forName("CustyWithLoop", true, urlClassLoader);
133 return c.newInstance();
134 }
135
136 static class MyInvocationHandler implements InvocationHandler {
137 volatile static int cnt;
138
139 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
140 long start = System.currentTimeMillis();
141 while (System.currentTimeMillis() - start < 20) {
142 cnt += 2;
143 for (int i = 0; i < 1000; i++) {
144 int n = cnt - 2;
145 if (n < 2) {
146 n = 2;
147 }
148 cnt += (i + cnt) % n + cnt % 2;
149 }
150 }
151 return Integer.valueOf(cnt);
152 }
153 }
154
155 static class Foo {
156 volatile static int counter;
157 static Class c = ShouldBeExcluded.class;
158
159 static Map mapProxy = (Map) Proxy.newProxyInstance(
160 Foo.class.getClassLoader(),
161 new Class[] { Map.class },
162 new MyInvocationHandler());
163
164 static int hotSpot() {
165 ShouldBeExcluded s = new ShouldBeExcluded();
166 Bar b = new Bar();
167
168 long start = System.currentTimeMillis();
169 while (System.currentTimeMillis() - start < 1000) {
170 lambdaHotSpot();
171 lambdaHotSpot2();
172 invokeHandleHotSpot();
173 s.hotSpot2();
174 b.hotSpot3();
175 Taz.hotSpot4();
176
177 // In JDK mainline, generated proxy classes are excluded from the AOT cache.
178 // In Leyden/premain, generated proxy classes included. The following code should
179 // work with either repos.
180 Integer i = (Integer)mapProxy.get(null);
181 counter += i.intValue();
182
183 if (custInstance != null) {
184 // Classes loaded by custom loaders are included in the AOT cache
185 // but their array classes are excluded.
186 counter += custInstance.equals(null) ? 1 : 2;
187 }
188
189 if (custArrayInstance != null) {
190 if ((counter % 3) == 0) {
191 counter += (custArrayInstance instanceof String) ? 0 : 1;
192 } else {
193 counter += (custArrayInstance instanceof Object) ? 0 : 1;
194 }
195 }
196 }
197
198 return counter + s.m() + s.f + b.m() + b.f;
199 }
200
201 static void f() {
202 if (counter % 2 == 1) {
203 counter ++;
204 }
205 }
206
207 // Generated Lambda classes should be excluded from CDS preimage.
208 static void lambdaHotSpot() {
209 long start = System.currentTimeMillis();
210 while (System.currentTimeMillis() - start < 20) {
211 doit(() -> counter ++ );
212 }
213 }
214
215 interface A {
216 Object get();
217 }
218
219 // Lambdas that refer to excluded classes should not be AOT-resolved
220 static void lambdaHotSpot2() {
221 long start = System.currentTimeMillis();
222 A a = ShouldBeExcluded::new;
223 while (System.currentTimeMillis() - start < 20) {
224 Object obj = (ShouldBeExcluded)a.get();
225 }
226 }
227
228 static void invokeHandleHotSpot() {
229 try {
230 invokeHandleHotSpotImpl();
231 } catch (Throwable t) {
232 throw new RuntimeException("Unexpected", t);
233 }
234 }
235
236 static void invokeHandleHotSpotImpl() throws Throwable {
237 MethodHandle constructorHandle =
238 MethodHandles.lookup().unreflectConstructor(ShouldBeExcluded.class.getConstructor());
239 long start = System.currentTimeMillis();
240 while (System.currentTimeMillis() - start < 20) {
241 // The JVM rewrites this to:
242 // invokehandle <java/lang/invoke/MethodHandle.invoke()LShouldBeExcluded;>
243 //
244 // The AOT cache must not contain a java.lang.invoke.MethodType that refers to the
245 // ShouldBeExcluded class.
246 ShouldBeExcluded o = (ShouldBeExcluded)constructorHandle.invoke();
247 if (o.getClass() != ShouldBeExcluded.class) {
248 throw new RuntimeException("Unexpected object: " + o);
249 }
250 }
251 }
252
253 static void doit(Runnable r) {
254 r.run();
255 }
256
257 // All subclasses of jdk.jfr.Event are excluded from the CDS archive.
258 static class ShouldBeExcluded extends jdk.jfr.Event {
259 public ShouldBeExcluded() {}
260
261 int f = (int)(System.currentTimeMillis()) + 123;
262 int m() {
263 return f + 456;
264 }
265
266 void hotSpot2() {
267 long start = System.currentTimeMillis();
268 while (System.currentTimeMillis() - start < 20) {
269 for (int i = 0; i < 50000; i++) {
270 counter += i;
271 }
272 f();
273 }
274 }
275 int func() {
276 return 1;
277 }
278 }
279
280 static class ShouldBeExcludedChild extends ShouldBeExcluded {
281 @Override
282 int func() {
283 return 2;
284 }
285 }
286
287 static class Bar {
288 int f = (int)(System.currentTimeMillis()) + 123;
289 int m() {
290 return f + 456;
291 }
292
293 void hotSpot3() {
294 long start = System.currentTimeMillis();
295 while (System.currentTimeMillis() - start < 20) {
296 for (int i = 0; i < 50000; i++) {
297 counter += i;
298 }
299 f();
300 }
301 }
302 }
303
304 static class Taz {
305 static ShouldBeExcluded m() {
306 // Taz should be excluded from the AOT cache because it has a verification constraint that
307 // "ShouldBeExcludedChild must be a subtype of ShouldBeExcluded", but ShouldBeExcluded is
308 // excluded from the AOT cache.
309 return new ShouldBeExcludedChild();
310 }
311 static void hotSpot4() {
312 long start = System.currentTimeMillis();
313 while (System.currentTimeMillis() - start < 20) {
314 for (int i = 0; i < 50000; i++) {
315 counter += i;
316 }
317 f();
318 }
319 }
320 }
321 }
322 }