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