1 /*
  2  * Copyright (c) 2023, 2024, 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 the handling of classes that are excluded from the CDS dump.
 28  * @requires vm.cds.write.archived.java.heap
 29  * @library /test/jdk/lib/testlibrary /test/lib
 30  *          /test/hotspot/jtreg/runtime/cds/appcds/leyden/test-classes
 31  * @build ExcludedClasses Custy
 32  * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar
 33  *                 TestApp
 34  *                 TestApp$Foo
 35  *                 TestApp$Foo$Bar
 36  *                 TestApp$Foo$ShouldBeExcluded
 37  *                 TestApp$MyInvocationHandler
 38  * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar cust.jar
 39  *                 Custy
 40  * @run driver jdk.test.lib.helpers.ClassFileInstaller TestApp$Foo$NotInJar
 41  * @run driver ExcludedClasses LEYDEN
 42  */
 43 
 44 import java.io.File;
 45 import java.lang.invoke.MethodHandles;
 46 import java.lang.invoke.MethodHandles.Lookup;
 47 import java.lang.reflect.InvocationHandler;
 48 import java.lang.reflect.Method;
 49 import java.lang.reflect.Proxy;
 50 import java.net.URL;
 51 import java.net.URLClassLoader;
 52 import java.nio.file.Files;
 53 import java.nio.file.Path;
 54 import java.security.ProtectionDomain;
 55 import java.util.Map;
 56 
 57 import jdk.jfr.Event;
 58 import jdk.test.lib.cds.CDSAppTester;
 59 import jdk.test.lib.helpers.ClassFileInstaller;
 60 import jdk.test.lib.process.OutputAnalyzer;
 61 
 62 public class ExcludedClasses {
 63     static final String appJar = ClassFileInstaller.getJarPath("app.jar");
 64     static final String mainClass = "TestApp";
 65 
 66     public static void main(String[] args) throws Exception {
 67         Tester t = new Tester();
 68         t.run(args);
 69     }
 70 
 71     static class Tester extends CDSAppTester {
 72         public Tester() {
 73             super(mainClass);;
 74         }
 75 
 76         @Override
 77         public String classpath(RunMode runMode) {
 78             return appJar;
 79         }
 80 
 81         @Override
 82         public String[] vmArgs(RunMode runMode) {
 83             return new String[] {
 84                 "-Xlog:cds+resolve=trace",
 85 
 86                 // This is needed to call into ClassLoader::defineClass()
 87                 "--add-opens", "java.base/java.lang=ALL-UNNAMED",
 88 
 89                 //TEMP: uncomment the next line to see the TrainingData::_archived_training_data_dictionary
 90                 //"-XX:+UnlockDiagnosticVMOptions", "-XX:+PrintTrainingInfo",
 91             };
 92         }
 93 
 94         @Override
 95         public String[] appCommandLine(RunMode runMode) {
 96             return new String[] {
 97                 mainClass, runMode.name()
 98             };
 99         }
100 
101         @Override
102         public void checkExecution(OutputAnalyzer out, RunMode runMode) {
103             switch (runMode) {
104             case RunMode.TRAINING:
105             case RunMode.TRAINING0:
106             case RunMode.TRAINING1:
107             case RunMode.DUMP_STATIC:
108                 out.shouldMatch("cds,resolve.*archived field.*TestApp.Foo => TestApp.Foo.Bar.f:I");
109                 out.shouldNotMatch("cds,resolve.*archived field.*TestApp.Foo => TestApp.Foo.ShouldBeExcluded.f:I");
110             }
111         }
112     }
113 }
114 
115 class TestApp {
116     static Object custInstance;
117     static Object notInJarInstance;
118 
119     public static void main(String args[]) throws Exception {
120         // In new workflow, classes from custom loaders are passed from the preimage
121         // to the final image. See ClassPrelinker::record_unregistered_klasses().
122         custInstance = initFromCustomLoader();
123 
124         notInJarInstance = initNotInJar();
125 
126         System.out.println("Counter = " + Foo.hotSpot());
127     }
128 
129     static Object initFromCustomLoader() throws Exception {
130         String path = "cust.jar";
131         URL url = new File(path).toURI().toURL();
132         URL[] urls = new URL[] {url};
133         URLClassLoader urlClassLoader =
134             new URLClassLoader("MyLoader", urls, null);
135         Class c = Class.forName("Custy", true, urlClassLoader);
136         return c.newInstance();
137     }
138 
139     static Object initNotInJar() throws Exception  {
140         byte[] classdata = Files.readAllBytes((new File("TestApp$Foo$NotInJar.class")).toPath());
141         ClassLoader loader = TestApp.class.getClassLoader();
142         Method method = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class,
143                                                             int.class, int.class, ProtectionDomain.class);
144         // The following call needs: --add-opens java.base/java.lang=ALL-UNNAMED
145         method.setAccessible(true);
146         Class<?> c = (Class<?>)method.invoke(loader, "TestApp$Foo$NotInJar",
147                                              classdata, 0, classdata.length, TestApp.class.getProtectionDomain());
148         return c.newInstance();
149     }
150 
151     static class MyInvocationHandler implements InvocationHandler {
152         volatile static int cnt;
153 
154         public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
155             long start = System.currentTimeMillis();
156             while (System.currentTimeMillis() - start < 20) {
157                 cnt += 2;
158                 for (int i = 0; i < 1000; i++) {
159                     int n = cnt - 2;
160                     if (n < 2) {
161                         n = 2;
162                     }
163                     cnt += (i + cnt) % n + cnt % 2;
164                 }
165             }
166             return Integer.valueOf(cnt);
167         }
168     }
169 
170     static class Foo {
171         volatile static int counter;
172         static Class c = ShouldBeExcluded.class;
173 
174         static Map mapProxy = (Map) Proxy.newProxyInstance(
175             Foo.class.getClassLoader(), 
176             new Class[] { Map.class },
177             new MyInvocationHandler());
178 
179         static int hotSpot() {
180             ShouldBeExcluded s = new ShouldBeExcluded();
181             Bar b = new Bar();
182 
183             long start = System.currentTimeMillis();
184             while (System.currentTimeMillis() - start < 1000) {
185                 lambdaHotSpot();
186                 s.hotSpot2();
187                 b.hotSpot3();
188 
189                 // Currently, generated proxy classes are excluded from the CDS archive
190                 Integer i = (Integer)mapProxy.get(null);
191                 counter += i.intValue();
192 
193 
194                 if (custInstance != null) {
195                     // For new workflow only:
196                     // Currently, classes loaded by custom loaders are included in the preimage run
197                     // but excluded from the final image.
198                     counter += custInstance.equals(null) ? 1 : 2;
199                 }
200 
201                 if (notInJarInstance != null) {
202                     notInJarInstance.toString();
203                 }
204             }
205 
206             return counter + s.m() + s.f + b.m() + b.f;
207         }
208 
209         static void f() {
210             if (counter % 2 == 1) {
211                 counter ++;
212             }
213         }
214 
215         // Lambda classes should be excluded from new workflow training run
216         static void lambdaHotSpot() {
217             long start = System.currentTimeMillis();
218             while (System.currentTimeMillis() - start < 20) {
219                 doit(() -> {
220                         counter ++;
221                     });
222             }
223         }
224 
225         static void doit(Runnable r) {
226             r.run();
227         }
228 
229         // All subclasses of jdk.jfr.Event are excluded from the CDS archive.
230         static class ShouldBeExcluded extends jdk.jfr.Event {
231             int f = (int)(System.currentTimeMillis()) + 123;
232             int m() {
233                 return f + 456;
234             }
235 
236             void hotSpot2() {
237                 long start = System.currentTimeMillis();
238                 while (System.currentTimeMillis() - start < 20) {
239                     for (int i = 0; i < 50000; i++) {
240                         counter += i;
241                     }
242                     f();
243                 }
244             }
245         }
246 
247         static class Bar {
248             int f = (int)(System.currentTimeMillis()) + 123;
249             int m() {
250                 return f + 456;
251             }
252 
253             void hotSpot3() {
254                 long start = System.currentTimeMillis();
255                 while (System.currentTimeMillis() - start < 20) {
256                     for (int i = 0; i < 50000; i++) {
257                         counter += i;
258                     }
259                     f();
260                 }
261             }
262         }
263 
264         // This class is not included in the JAR file. Instead, it's defined by the app
265         // using Lookup.defineClass().
266         // This class should be excluded from the CDS archive.
267         static class NotInJar {
268             public String toString() {
269                 hotSpot3();
270                 return "NotInJar";
271             }
272 
273             void hotSpot3() {
274                 long start = System.currentTimeMillis();
275                 while (System.currentTimeMillis() - start < 20) {
276                     for (int i = 0; i < 50000; i++) {
277                         counter += i;
278                     }
279                     f();
280                 }
281             }
282         }
283     }
284 }