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