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