1 /*
  2  * Copyright (c) 2020, 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.  Oracle designates this
  8  * particular file as subject to the "Classpath" exception as provided
  9  * by Oracle in the LICENSE file that accompanied this code.
 10  *
 11  * This code is distributed in the hope that it will be useful, but WITHOUT
 12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 14  * version 2 for more details (a copy is included in the LICENSE file that
 15  * accompanied this code).
 16  *
 17  * You should have received a copy of the GNU General Public License version
 18  * 2 along with this work; if not, write to the Free Software Foundation,
 19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 20  *
 21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 22  * or visit www.oracle.com if you need additional information or have any
 23  * questions.
 24  */
 25 
 26 package jdk.internal.misc;
 27 
 28 import java.io.BufferedReader;
 29 import java.io.File;
 30 import java.io.InputStreamReader;
 31 import java.io.InputStream;
 32 import java.io.IOException;
 33 import java.io.PrintStream;
 34 import java.net.URL;
 35 import java.net.URLClassLoader;
 36 import java.nio.file.InvalidPathException;
 37 import java.nio.file.Path;
 38 import java.util.Arrays;
 39 import java.util.ArrayList;
 40 import java.util.List;
 41 import java.util.Map;
 42 import java.util.Objects;
 43 import java.util.stream.Stream;
 44 
 45 import jdk.internal.access.SharedSecrets;
 46 import jdk.internal.util.StaticProperty;
 47 
 48 public class CDS {
 49     // Must be in sync with cdsConfig.hpp
 50     private static final int IS_DUMPING_ARCHIVE              = 1 << 0;
 51     private static final int IS_DUMPING_METHOD_HANDLES       = 1 << 1;
 52     private static final int IS_DUMPING_STATIC_ARCHIVE       = 1 << 2;
 53     private static final int IS_LOGGING_LAMBDA_FORM_INVOKERS = 1 << 3;
 54     private static final int IS_USING_ARCHIVE                = 1 << 4;
 55     private static final int IS_DUMPING_HEAP                 = 1 << 5;
 56     private static final int IS_LOGGING_DYNAMIC_PROXIES      = 1 << 6;
 57     private static final int IS_DUMPING_PACKAGES             = 1 << 7;
 58     private static final int IS_DUMPING_PROTECTION_DOMAINS   = 1 << 8;
 59     private static final int configStatus = getCDSConfigStatus();
 60 
 61     /**
 62      * Should we log the use of lambda form invokers?
 63      */
 64     public static boolean isLoggingLambdaFormInvokers() {
 65         return (configStatus & IS_LOGGING_LAMBDA_FORM_INVOKERS) != 0;
 66     }
 67 
 68     /**
 69       * Is the VM writing to a (static or dynamic) CDS archive.
 70       */
 71     public static boolean isDumpingArchive() {
 72         return (configStatus & IS_DUMPING_ARCHIVE) != 0;
 73     }
 74 
 75     /**
 76       * Is the VM using at least one CDS archive?
 77       */
 78     public static boolean isUsingArchive() {
 79         return (configStatus & IS_USING_ARCHIVE) != 0;
 80     }
 81 
 82     /**
 83       * Is dumping static archive.
 84       */
 85     public static boolean isDumpingStaticArchive() {
 86         return (configStatus & IS_DUMPING_STATIC_ARCHIVE) != 0;
 87     }
 88 
 89     public static boolean isDumpingHeap() {
 90         return (configStatus & IS_DUMPING_HEAP) != 0;
 91     }
 92 
 93     public static boolean isLoggingDynamicProxies() {
 94         return (configStatus & IS_LOGGING_DYNAMIC_PROXIES) != 0;
 95     }
 96 
 97     public static boolean isDumpingPackages() {
 98         return (configStatus & IS_DUMPING_PACKAGES) != 0;
 99     }
100 
101     public static boolean isDumpingProtectionDomains() {
102         return (configStatus & IS_DUMPING_PROTECTION_DOMAINS) != 0;
103     }
104 
105     private static native int getCDSConfigStatus();
106     private static native void logLambdaFormInvoker(String line);
107 
108     /**
109      * Initialize archived static fields in the given Class using archived
110      * values from CDS dump time. Also initialize the classes of objects in
111      * the archived graph referenced by those fields.
112      *
113      * Those static fields remain as uninitialized if there is no mapped CDS
114      * java heap data or there is any error during initialization of the
115      * object class in the archived graph.
116      */
117     public static native void initializeFromArchive(Class<?> c);
118 
119     /**
120      * Ensure that the native representation of all archived java.lang.Module objects
121      * are properly restored.
122      */
123     public static native void defineArchivedModules(ClassLoader platformLoader, ClassLoader systemLoader);
124 
125     /**
126      * Returns a predictable "random" seed derived from the VM's build ID and version,
127      * to be used by java.util.ImmutableCollections to ensure that archived
128      * ImmutableCollections are always sorted the same order for the same VM build.
129      */
130     public static native long getRandomSeedForDumping();
131 
132     /**
133      * log lambda form invoker holder, name and method type
134      */
135     public static void logLambdaFormInvoker(String prefix, String holder, String name, String type) {
136         if (isLoggingLambdaFormInvokers()) {
137             logLambdaFormInvoker(prefix + " " + holder + " " + name + " " + type);
138         }
139     }
140 
141     /**
142       * log species
143       */
144     public static void logSpeciesType(String prefix, String cn) {
145         if (isLoggingLambdaFormInvokers()) {
146             logLambdaFormInvoker(prefix + " " + cn);
147         }
148     }
149 
150     public static void logDynamicProxy(ClassLoader loader, String proxyName,
151                                        Class<?>[] interfaces, int accessFlags) {
152         Objects.requireNonNull(proxyName);
153         Objects.requireNonNull(interfaces);
154         logDynamicProxy0(loader, proxyName, interfaces, accessFlags);
155     }
156     private static native void logDynamicProxy0(ClassLoader loader, String proxyName,
157                                                 Class<?>[] interfaces, int accessFlags);
158 
159     static final String DIRECT_HOLDER_CLASS_NAME  = "java.lang.invoke.DirectMethodHandle$Holder";
160     static final String DELEGATING_HOLDER_CLASS_NAME = "java.lang.invoke.DelegatingMethodHandle$Holder";
161     static final String BASIC_FORMS_HOLDER_CLASS_NAME = "java.lang.invoke.LambdaForm$Holder";
162     static final String INVOKERS_HOLDER_CLASS_NAME = "java.lang.invoke.Invokers$Holder";
163 
164     private static boolean isValidHolderName(String name) {
165         return name.equals(DIRECT_HOLDER_CLASS_NAME)      ||
166                name.equals(DELEGATING_HOLDER_CLASS_NAME)  ||
167                name.equals(BASIC_FORMS_HOLDER_CLASS_NAME) ||
168                name.equals(INVOKERS_HOLDER_CLASS_NAME);
169     }
170 
171     private static boolean isBasicTypeChar(char c) {
172          return "LIJFDV".indexOf(c) >= 0;
173     }
174 
175     private static boolean isValidMethodType(String type) {
176         String[] typeParts = type.split("_");
177         // check return type (second part)
178         if (typeParts.length != 2 || typeParts[1].length() != 1
179                 || !isBasicTypeChar(typeParts[1].charAt(0))) {
180             return false;
181         }
182         // first part
183         if (!isBasicTypeChar(typeParts[0].charAt(0))) {
184             return false;
185         }
186         for (int i = 1; i < typeParts[0].length(); i++) {
187             char c = typeParts[0].charAt(i);
188             if (!isBasicTypeChar(c)) {
189                 if (!(c >= '0' && c <= '9')) {
190                     return false;
191                 }
192             }
193         }
194         return true;
195     }
196 
197     // Throw exception on invalid input
198     private static void validateInputLines(String[] lines) {
199         for (String s: lines) {
200             if (!s.startsWith("[LF_RESOLVE]") && !s.startsWith("[SPECIES_RESOLVE]")) {
201                 throw new IllegalArgumentException("Wrong prefix: " + s);
202             }
203 
204             String[] parts = s.split(" ");
205             boolean isLF = s.startsWith("[LF_RESOLVE]");
206 
207             if (isLF) {
208                 if (parts.length != 4) {
209                     throw new IllegalArgumentException("Incorrect number of items in the line: " + parts.length);
210                 }
211                 if (!isValidHolderName(parts[1])) {
212                     throw new IllegalArgumentException("Invalid holder class name: " + parts[1]);
213                 }
214                 if (!isValidMethodType(parts[3])) {
215                     throw new IllegalArgumentException("Invalid method type: " + parts[3]);
216                 }
217             } else {
218                 if (parts.length != 2) {
219                    throw new IllegalArgumentException("Incorrect number of items in the line: " + parts.length);
220                 }
221            }
222       }
223     }
224 
225     /**
226      * called from vm to generate MethodHandle holder classes
227      * @return {@code Object[]} if holder classes can be generated.
228      * @param lines in format of LF_RESOLVE or SPECIES_RESOLVE output
229      */
230     private static Object[] generateLambdaFormHolderClasses(String[] lines) {
231         Objects.requireNonNull(lines);
232         validateInputLines(lines);
233         Stream<String> lineStream = Arrays.stream(lines);
234         Map<String, byte[]> result = SharedSecrets.getJavaLangInvokeAccess().generateHolderClasses(lineStream);
235         int size = result.size();
236         Object[] retArray = new Object[size * 2];
237         int index = 0;
238         for (Map.Entry<String, byte[]> entry : result.entrySet()) {
239             retArray[index++] = entry.getKey();
240             retArray[index++] = entry.getValue();
241         };
242         return retArray;
243     }
244 
245     private static native void dumpClassList(String listFileName);
246     private static native void dumpDynamicArchive(String archiveFileName);
247 
248     private static String drainOutput(InputStream stream, long pid, String tail, List<String> cmds) {
249         String fileName  = "java_pid" + pid + "_" + tail;
250         new Thread( ()-> {
251             try (InputStreamReader isr = new InputStreamReader(stream);
252                  BufferedReader rdr = new BufferedReader(isr);
253                  PrintStream prt = new PrintStream(fileName)) {
254                 prt.println("Command:");
255                 for (String s : cmds) {
256                     prt.print(s + " ");
257                 }
258                 prt.println("");
259                 String line;
260                 while((line = rdr.readLine()) != null) {
261                     prt.println(line);
262                 }
263             } catch (IOException e) {
264                 throw new RuntimeException("IOException happens during drain stream to file " +
265                                            fileName + ": " + e.getMessage());
266             }}).start();
267         return fileName;
268     }
269 
270     private static String[] excludeFlags = {
271          "-XX:DumpLoadedClassList=",
272          "-XX:+RecordDynamicDumpInfo",
273          "-Xshare:",
274          "-XX:SharedClassListFile=",
275          "-XX:SharedArchiveFile=",
276          "-XX:ArchiveClassesAtExit="};
277     private static boolean containsExcludedFlags(String testStr) {
278        for (String e : excludeFlags) {
279            if (testStr.contains(e)) {
280                return true;
281            }
282        }
283        return false;
284     }
285 
286     /**
287     * called from jcmd VM.cds to dump static or dynamic shared archive
288     * @param isStatic true for dump static archive or false for dynnamic archive.
289     * @param fileName user input archive name, can be null.
290     * @return The archive name if successfully dumped.
291     */
292     private static String dumpSharedArchive(boolean isStatic, String fileName) throws Exception {
293         String cwd = new File("").getAbsolutePath(); // current dir used for printing message.
294         String currentPid = String.valueOf(ProcessHandle.current().pid());
295         String archiveFileName =  fileName != null ? fileName :
296             "java_pid" + currentPid + (isStatic ? "_static.jsa" : "_dynamic.jsa");
297 
298         String tempArchiveFileName = archiveFileName + ".temp";
299         File tempArchiveFile = new File(tempArchiveFileName);
300         // The operation below may cause exception if the file or its dir is protected.
301         if (!tempArchiveFile.exists()) {
302             tempArchiveFile.createNewFile();
303         }
304         tempArchiveFile.delete();
305 
306         if (isStatic) {
307             String listFileName = archiveFileName + ".classlist";
308             File listFile = new File(listFileName);
309             if (listFile.exists()) {
310                 listFile.delete();
311             }
312             dumpClassList(listFileName);
313             String jdkHome = StaticProperty.javaHome();
314             String classPath = System.getProperty("java.class.path");
315             List<String> cmds = new ArrayList<String>();
316             cmds.add(jdkHome + File.separator + "bin" + File.separator + "java"); // java
317             cmds.add("-cp");
318             cmds.add(classPath);
319             cmds.add("-Xlog:cds");
320             cmds.add("-Xshare:dump");
321             cmds.add("-XX:SharedClassListFile=" + listFileName);
322             cmds.add("-XX:SharedArchiveFile=" + tempArchiveFileName);
323 
324             // All runtime args.
325             String[] vmArgs = VM.getRuntimeArguments();
326             if (vmArgs != null) {
327                 for (String arg : vmArgs) {
328                     if (arg != null && !containsExcludedFlags(arg)) {
329                         cmds.add(arg);
330                     }
331                 }
332             }
333 
334             Process proc = Runtime.getRuntime().exec(cmds.toArray(new String[0]));
335 
336             // Drain stdout/stderr to files in new threads.
337             String stdOutFileName = drainOutput(proc.getInputStream(), proc.pid(), "stdout", cmds);
338             String stdErrFileName = drainOutput(proc.getErrorStream(), proc.pid(), "stderr", cmds);
339 
340             proc.waitFor();
341             // done, delete classlist file.
342             listFile.delete();
343 
344             // Check if archive has been successfully dumped. We won't reach here if exception happens.
345             // Throw exception if file is not created.
346             if (!tempArchiveFile.exists()) {
347                 throw new RuntimeException("Archive file " + tempArchiveFileName +
348                                            " is not created, please check stdout file " +
349                                             cwd + File.separator + stdOutFileName + " or stderr file " +
350                                             cwd + File.separator + stdErrFileName + " for more detail");
351             }
352         } else {
353             dumpDynamicArchive(tempArchiveFileName);
354             if (!tempArchiveFile.exists()) {
355                 throw new RuntimeException("Archive file " + tempArchiveFileName +
356                                            " is not created, please check current working directory " +
357                                            cwd  + " for process " +
358                                            currentPid + " output for more detail");
359             }
360         }
361         // Override the existing archive file
362         File archiveFile = new File(archiveFileName);
363         if (archiveFile.exists()) {
364             archiveFile.delete();
365         }
366         if (!tempArchiveFile.renameTo(archiveFile)) {
367             throw new RuntimeException("Cannot rename temp file " + tempArchiveFileName + " to archive file" + archiveFileName);
368         }
369         // Everything goes well, print out the file name.
370         String archiveFilePath = new File(archiveFileName).getAbsolutePath();
371         System.out.println("The process was attached by jcmd and dumped a " + (isStatic ? "static" : "dynamic") + " archive " + archiveFilePath);
372         return archiveFilePath;
373     }
374 
375     /**
376      * Detects if we need to emit explicit class initialization checks in
377      * AOT-cached MethodHandles and VarHandles before accessing static fields
378      * and methods.
379      * @see jdk.internal.misc.Unsafe::shouldBeInitialized
380      *
381      * @return false only if a call to {@code ensureClassInitialized} would have
382      * no effect during the application's production run.
383      */
384     public static boolean needsClassInitBarrier(Class<?> c) {
385         if (c == null) {
386             throw new NullPointerException();
387         }
388 
389         if ((configStatus & IS_DUMPING_METHOD_HANDLES) == 0) {
390             return false;
391         } else {
392             return needsClassInitBarrier0(c);
393         }
394     }
395 
396     private static native boolean needsClassInitBarrier0(Class<?> c);
397 
398     /**
399      * This class is used only by native JVM code at CDS dump time for loading
400      * "unregistered classes", which are archived classes that are intended to
401      * be loaded by custom class loaders during runtime.
402      * See src/hotspot/share/cds/unregisteredClasses.cpp.
403      */
404     private static class UnregisteredClassLoader extends URLClassLoader {
405         private String currentClassName;
406         private Class<?> currentSuperClass;
407         private Class<?>[] currentInterfaces;
408 
409         /**
410          * Used only by native code. Construct an UnregisteredClassLoader for loading
411          * unregistered classes from the specified file. If the file doesn't exist,
412          * the exception will be caughted by native code which will print a warning message and continue.
413          *
414          * @param fileName path of the the JAR file to load unregistered classes from.
415          */
416         private UnregisteredClassLoader(String fileName) throws InvalidPathException, IOException {
417             super(toURLArray(fileName), /*parent*/null);
418             currentClassName = null;
419             currentSuperClass = null;
420             currentInterfaces = null;
421         }
422 
423         private static URL[] toURLArray(String fileName) throws InvalidPathException, IOException {
424             if (!((new File(fileName)).exists())) {
425                 throw new IOException("No such file: " + fileName);
426             }
427             return new URL[] {
428                 // Use an intermediate File object to construct a URI/URL without
429                 // authority component as URLClassPath can't handle URLs with a UNC
430                 // server name in the authority component.
431                 Path.of(fileName).toRealPath().toFile().toURI().toURL()
432             };
433         }
434 
435 
436         /**
437          * Load the class of the given <code>/name<code> from the JAR file that was given to
438          * the constructor of the current UnregisteredClassLoader instance. This class must be
439          * a direct subclass of <code>superClass</code>. This class must be declared to implement
440          * the specified <code>interfaces</code>.
441          * <p>
442          * This method must be called in a single threaded context. It will never be recursed (thus
443          * the asserts)
444          *
445          * @param name the name of the class to be loaded.
446          * @param superClass must not be null. The named class must have a super class.
447          * @param interfaces could be null if the named class does not implement any interfaces.
448          */
449         private Class<?> load(String name, Class<?> superClass, Class<?>[] interfaces)
450             throws ClassNotFoundException
451         {
452             assert currentClassName == null;
453             assert currentSuperClass == null;
454             assert currentInterfaces == null;
455 
456             try {
457                 currentClassName = name;
458                 currentSuperClass = superClass;
459                 currentInterfaces = interfaces;
460 
461                 return findClass(name);
462             } finally {
463                 currentClassName = null;
464                 currentSuperClass = null;
465                 currentInterfaces = null;
466             }
467         }
468 
469         /**
470          * This method must be called from inside the <code>load()</code> method. The <code>/name<code>
471          * can be only:
472          * <ul>
473          * <li> the <code>name</code> parameter for <code>load()</code>
474          * <li> the name of the <code>superClass</code> parameter for <code>load()</code>
475          * <li> the name of one of the interfaces in <code>interfaces</code> parameter for <code>load()</code>
476          * <ul>
477          *
478          * For all other cases, a <code>ClassNotFoundException</code> will be thrown.
479          */
480         protected Class<?> findClass(final String name)
481             throws ClassNotFoundException
482         {
483             Objects.requireNonNull(currentClassName);
484             Objects.requireNonNull(currentSuperClass);
485 
486             if (name.equals(currentClassName)) {
487                 // Note: the following call will call back to <code>this.findClass(name)</code> to
488                 // resolve the super types of the named class.
489                 return super.findClass(name);
490             }
491             if (name.equals(currentSuperClass.getName())) {
492                 return currentSuperClass;
493             }
494             if (currentInterfaces != null) {
495                 for (Class<?> c : currentInterfaces) {
496                     if (name.equals(c.getName())) {
497                         return c;
498                     }
499                 }
500             }
501 
502             throw new ClassNotFoundException(name);
503         }
504     }
505 
506     /**
507      * This class is used only by native JVM code to spawn a child JVM process to assemble
508      * the AOT cache. <code>args[]</code> are passed in the <code>JAVA_TOOL_OPTIONS</code>
509      * environment variable as described in
510      * https://docs.oracle.com/en/java/javase/24/docs/specs/jvmti.html#tooloptions
511      */
512     private static class ProcessLauncher {
513         static int execWithJavaToolOptions(String javaLauncher, String args[]) throws IOException, InterruptedException {
514             ProcessBuilder pb = new ProcessBuilder().inheritIO().command(javaLauncher);
515             StringBuilder sb = new StringBuilder();
516             String prefix = "";
517             for (String arg : args) {
518                 sb.append(prefix);
519 
520                 for (int i = 0; i < arg.length(); i++) {
521                     char c = arg.charAt(i);
522                     if (c == '"' || Character.isWhitespace(c)) {
523                         sb.append('\'');
524                         sb.append(c);
525                         sb.append('\'');
526                     } else if (c == '\'') {
527                         sb.append('"');
528                         sb.append(c);
529                         sb.append('"');
530                     } else {
531                         sb.append(c);
532                     }
533                 }
534 
535                 prefix = " ";
536             }
537 
538             Map<String, String> env = pb.environment();
539             env.put("JAVA_TOOL_OPTIONS", sb.toString());
540             env.remove("_JAVA_OPTIONS");
541             env.remove("CLASSPATH");
542             Process process = pb.start();
543             return process.waitFor();
544         }
545     }
546 
547     /**
548      * This class is to ensure that the dynamic CDS archive contains at least one class, so we can avoid
549      * error handling for the degenerative case where the dynamic archive is completely empty (which doesn't
550      * happen for realistic applications).
551      */
552     private static class DummyForDynamicArchive {}
553 }