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