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