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