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 }