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 }