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