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