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