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 }