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