1 /*
  2  * Copyright (c) 2024, 2025, 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 // AOT-linked classes are loaded during VM bootstrap by the C++ class AOTLinkedClassBulkLoader.
 26 // Make sure that the Module, Package, CodeSource and ProtectionDomain of these classes are
 27 // set up properly.
 28 
 29 /*
 30  * @test id=static
 31  * @requires vm.cds.supports.aot.class.linking
 32  * @comment work around JDK-8345635
 33  * @requires !vm.jvmci.enabled
 34  * @library /test/jdk/lib/testlibrary /test/lib /test/hotspot/jtreg/runtime/cds/appcds/test-classes
 35  * @build InitiatingLoaderTester BadOldClassA BadOldClassB
 36  * @build BulkLoaderTest SimpleCusty
 37  * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar BulkLoaderTestApp.jar BulkLoaderTestApp MyUtil InitiatingLoaderTester
 38  *                 BadOldClassA BadOldClassB
 39  * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar cust.jar
 40  *                 SimpleCusty
 41  * @run driver BulkLoaderTest STATIC
 42  */
 43 
 44 /*
 45  * @test id=dynamic
 46  * @requires vm.cds.supports.aot.class.linking
 47  * @comment work around JDK-8345635
 48  * @requires !vm.jvmci.enabled
 49  * @library /test/jdk/lib/testlibrary /test/lib /test/hotspot/jtreg/runtime/cds/appcds/test-classes
 50  * @build InitiatingLoaderTester BadOldClassA BadOldClassB
 51  * @build jdk.test.whitebox.WhiteBox BulkLoaderTest SimpleCusty
 52  * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar BulkLoaderTestApp.jar BulkLoaderTestApp MyUtil InitiatingLoaderTester
 53  *                 BadOldClassA BadOldClassB
 54  * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar cust.jar
 55  *                 SimpleCusty
 56  * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
 57  * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. BulkLoaderTest DYNAMIC
 58  */
 59 
 60 /*
 61  * @test id=aot
 62  * @requires vm.cds.supports.aot.class.linking
 63  * @comment work around JDK-8345635
 64  * @requires !vm.jvmci.enabled
 65  * @library /test/jdk/lib/testlibrary /test/lib /test/hotspot/jtreg/runtime/cds/appcds/test-classes
 66  * @build InitiatingLoaderTester BadOldClassA BadOldClassB
 67  * @build BulkLoaderTest SimpleCusty
 68  * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar BulkLoaderTestApp.jar BulkLoaderTestApp MyUtil InitiatingLoaderTester
 69  *                 BadOldClassA BadOldClassB
 70  * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar cust.jar
 71  *                 SimpleCusty
 72  * @run driver BulkLoaderTest AOT
 73  */
 74 
 75 import java.io.File;
 76 import java.lang.StackWalker.StackFrame;
 77 import java.net.URL;
 78 import java.net.URLClassLoader;
 79 import java.util.List;
 80 import java.util.regex.Matcher;
 81 import java.util.regex.Pattern;
 82 import java.util.stream.Collectors;
 83 import java.util.Set;
 84 import jdk.test.lib.cds.CDSAppTester;
 85 import jdk.test.lib.helpers.ClassFileInstaller;
 86 import jdk.test.lib.process.OutputAnalyzer;
 87 
 88 public class BulkLoaderTest {
 89     static final String appJar = ClassFileInstaller.getJarPath("BulkLoaderTestApp.jar");
 90     static final String mainClass = "BulkLoaderTestApp";
 91 
 92     public static void main(String[] args) throws Exception {
 93         Tester t = new Tester();
 94 
 95         // Run with archived FMG loaded
 96         t.run(args);
 97 
 98         // Run with an extra classpath -- archived FMG can still load.
 99         {
100             String extraVmArgs[] = {
101                 "-cp",
102                 appJar + File.pathSeparator + "foobar.jar"
103             };
104             OutputAnalyzer out = t.productionRun(extraVmArgs);
105             out.shouldHaveExitValue(0);
106         }
107 
108         // Run without archived FMG -- fail to load
109         {
110             String extraVmArgs[] = {
111                 "-Xlog:cds",
112                 "-Djdk.module.showModuleResolution=true"
113             };
114             t.setCheckExitValue(false);
115             OutputAnalyzer out = t.productionRun(extraVmArgs);
116             out.shouldHaveExitValue(1);
117             out.shouldContain("CDS archive has aot-linked classes. It cannot be used when archived full module graph is not used.");
118             t.setCheckExitValue(true);
119         }
120     }
121 
122     static class Tester extends CDSAppTester {
123         public Tester() {
124             super(mainClass);
125         }
126 
127         @Override
128         public String classpath(RunMode runMode) {
129             return appJar;
130         }
131 
132         @Override
133         public String[] vmArgs(RunMode runMode) {
134             return new String[] {
135                 "-Xlog:cds,cds+aot+load,cds+class=debug",
136                 "-XX:+AOTClassLinking",
137             };
138         }
139 
140         @Override
141         public String[] appCommandLine(RunMode runMode) {
142             return new String[] {
143                 mainClass,
144             };
145         }
146 
147         @Override
148         public void checkExecution(OutputAnalyzer out, RunMode runMode) throws Exception {
149             if (isAOTWorkflow() && runMode == RunMode.TRAINING) {
150                 out.shouldContain("Skipping BadOldClassA: Unlinked class not supported by AOTConfiguration");
151             }
152 
153             if (isDumping(runMode)) {
154                 // Check that we are archiving classes for custom class loaders.
155                 out.shouldMatch("cds,class.* SimpleCusty");
156             }
157         }
158     }
159 }
160 
161 class BulkLoaderTestApp {
162     static String allPerms = "null.*<no principals>.*java.security.Permissions.*,*java.security.AllPermission.*<all permissions>.*<all actions>";
163 
164     public static void main(String args[]) throws Exception {
165         checkClasses();
166         checkInitiatingLoader();
167         checkOldClasses();
168         checkCustomLoader();
169     }
170 
171     // Check the ClassLoader/Module/Package/ProtectionDomain/CodeSource of classes that are aot-linked
172     static void checkClasses() throws Exception {
173         check(String.class,
174               "null",  // loader
175               "module java.base",
176               "package java.lang",
177               "null",
178               allPerms);
179 
180         check(Class.forName("sun.util.logging.internal.LoggingProviderImpl"),
181               "null",
182               "module java.logging",
183               "package sun.util.logging.internal",
184               "null",
185               allPerms);
186 
187 
188         check(javax.tools.FileObject.class,
189               "^jdk.internal.loader.ClassLoaders[$]PlatformClassLoader@",
190               "module java.compiler",
191               "package javax.tools",
192               "jrt:/java.compiler <no signer certificates>",
193               "jdk.internal.loader.ClassLoaders[$]PlatformClassLoader.*<no principals>.*java.security.Permissions");
194 
195         check(BulkLoaderTestApp.class,
196               "jdk.internal.loader.ClassLoaders[$]AppClassLoader@",
197               "^unnamed module @",
198               "package ",
199               "file:.*BulkLoaderTestApp.jar <no signer certificates>",
200               "jdk.internal.loader.ClassLoaders[$]AppClassLoader.*<no principals>.*java.security.Permissions");
201 
202         check(Class.forName("com.sun.tools.javac.Main"),
203               "jdk.internal.loader.ClassLoaders[$]AppClassLoader@",
204               "module jdk.compiler",
205               "package com.sun.tools.javac",
206               "jrt:/jdk.compiler <no signer certificates>",
207               "jdk.internal.loader.ClassLoaders[$]AppClassLoader.*<no principals>.*java.security.Permissions");
208 
209         doit(() -> {
210             Class<?> lambdaClass = MyUtil.getCallerClass(1);
211             check(lambdaClass,
212               "jdk.internal.loader.ClassLoaders[$]AppClassLoader@",
213               "unnamed module",
214               "package ",
215               "file:.*BulkLoaderTestApp.jar <no signer certificates>",
216               "jdk.internal.loader.ClassLoaders[$]AppClassLoader.*<no principals>.*java.security.Permissions");
217 
218           });
219     }
220 
221     static void check(Class c, String loader, String module, String pkg, String codeSource, String protectionDomain) {
222         System.out.println("====================================================================");
223         System.out.println(c.getName() + ", loader  = " + c.getClassLoader());
224         System.out.println(c.getName() + ", module  = " + c.getModule());
225         System.out.println(c.getName() + ", package = " + c.getPackage());
226         System.out.println(c.getName() + ", CS      = " + c.getProtectionDomain().getCodeSource());
227         System.out.println(c.getName() + ", PD      = " + c.getProtectionDomain());
228 
229         expectMatch("" + c.getClassLoader(), loader);
230         expectMatch("" + c.getModule(), module);
231         expectSame("" + c.getPackage(), pkg);
232         expectMatch("" + c.getProtectionDomain().getCodeSource(), codeSource);
233         expectMatch("" + c.getProtectionDomain(), protectionDomain);
234     }
235 
236     static void expectSame(String a, String b) {
237         if (!a.equals(b)) {
238             throw new RuntimeException("Expected \"" + b + "\" but got \"" + a + "\"");
239         }
240     }
241     static void expectMatch(String string, String pattern) {
242         Matcher matcher = Pattern.compile(pattern, Pattern.DOTALL).matcher(string);
243         if (!matcher.find()) {
244             throw new RuntimeException("Expected pattern \"" + pattern + "\" but got \"" + string + "\"");
245         }
246     }
247 
248     static void doit(Runnable t) {
249         t.run();
250     }
251 
252     static void checkInitiatingLoader() throws Exception {
253         try {
254             InitiatingLoaderTester.tryAccess();
255         } catch (IllegalAccessError t) {
256             if (t.getMessage().contains("cannot access class jdk.internal.misc.Unsafe (in module java.base)")) {
257                 System.out.println("Expected exception:");
258                 t.printStackTrace(System.out);
259                 // Class.forName() should still work. We just can't resolve it in CP entries.
260                 Class<?> c = Class.forName("jdk.internal.misc.Unsafe");
261                 System.out.println("App loader can still resolve by name: " + c);
262                 return;
263             }
264             throw new RuntimeException("Unexpected exception", t);
265         }
266 
267         throw new RuntimeException("Should not have succeeded");
268     }
269 
270     static void checkOldClasses() throws Exception {
271         // Resolve BadOldClassA from the constant pool without linking it.
272         // implNote: BadOldClassA will be excluded, so any resolved refereces
273         // to BadOldClassA should be removed from the archived constant pool.
274         Class c = BadOldClassA.class;
275         Object n = new Object();
276         if (c.isInstance(n)) { // Note that type-testing BadOldClassA here neither links nor initializes it.
277             throw new RuntimeException("Must not succeed");
278         }
279 
280         try {
281             // In dynamic dump, the VM loads BadOldClassB and then attempts to
282             // link it. This will leave BadOldClassB in a "failed verification" state.
283             // All refernces to BadOldClassB from the CP should be purged from the CDS
284             // archive.
285             c = BadOldClassB.class;
286             c.newInstance();
287             throw new RuntimeException("Must not succeed");
288         } catch (VerifyError e) {
289             System.out.println("Caught VerifyError for BadOldClassB: " + e);
290         }
291     }
292 
293 
294     static void checkCustomLoader() throws Exception {
295         Object o = initFromCustomLoader();
296         System.out.println(o);
297     }
298 
299     static Object initFromCustomLoader() throws Exception {
300         String path = "cust.jar";
301         URL url = new File(path).toURI().toURL();
302         URL[] urls = new URL[] {url};
303         URLClassLoader urlClassLoader =
304             new URLClassLoader("MyLoader", urls, null);
305         Class c = Class.forName("SimpleCusty", true, urlClassLoader);
306         return c.newInstance();
307     }
308 }
309 
310 class MyUtil {
311     // depth is 0-based -- i.e., depth==0 returns the class of the immediate caller of getCallerClass
312     static Class<?> getCallerClass(int depth) {
313         // Need to add the frame of the getCallerClass -- so the immediate caller (depth==0) of this method
314         // is at stack.get(1) == stack.get(depth+1);
315         StackWalker walker = StackWalker.getInstance(
316             Set.of(StackWalker.Option.RETAIN_CLASS_REFERENCE,
317                    StackWalker.Option.SHOW_HIDDEN_FRAMES));
318         List<StackFrame> stack = walker.walk(s -> s.limit(depth+2).collect(Collectors.toList()));
319         return stack.get(depth+1).getDeclaringClass();
320     }
321 }