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.nio.file.Files;
 35 import java.nio.file.Path;
 36 import java.util.Arrays;
 37 import java.util.ArrayList;
 38 import java.util.HashMap;
 39 import java.util.List;
 40 import java.util.Map;
 41 import java.util.Objects;
 42 import java.util.jar.JarFile;
 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 ClassLoader {
432         static {
433             registerAsParallelCapable();
434         }
435 
436         static interface Source {
437             public byte[] readClassFile(String className) throws IOException;
438         }
439 
440         static class JarSource implements Source {
441             private final JarFile jar;
442 
443             JarSource(File file) throws IOException {
444                 jar = new JarFile(file);
445             }
446 
447             @Override
448             public byte[] readClassFile(String className) throws IOException {
449                 final var entryName = className.replace('.', '/').concat(".class");
450                 final var entry = jar.getEntry(entryName);
451                 if (entry == null) {
452                     throw new IOException("No such entry: " + entryName + " in " + jar.getName());
453                 }
454                 try (final var in = jar.getInputStream(entry)) {
455                     return in.readAllBytes();
456                 }
457             }
458         }
459 
460         static class DirSource implements Source {
461             private final String basePath;
462 
463             DirSource(File dir) {
464                 assert dir.isDirectory();
465                 basePath = dir.toString();
466             }
467 
468             @Override
469             public byte[] readClassFile(String className) throws IOException {
470                 final var subPath = className.replace('.', File.separatorChar).concat(".class");
471                 final var fullPath = Path.of(basePath, subPath);
472                 return Files.readAllBytes(fullPath);
473             }
474         }
475 
476         private final HashMap<String, Source> sources = new HashMap<>();
477 
478         private Source resolveSource(String path) throws IOException {
479             Source source = sources.get(path);
480             if (source != null) {
481                 return source;
482             }
483 
484             final var file = new File(path);
485             if (!file.exists()) {
486                 throw new IOException("No such file: " + path);
487             }
488             if (file.isFile()) {
489                 source = new JarSource(file);
490             } else if (file.isDirectory()) {
491                 source = new DirSource(file);
492             } else {
493                 throw new IOException("Not a normal file: " + path);
494             }
495             sources.put(path, source);
496 
497             return source;
498         }
499 
500         /**
501          * Load the class of the given <code>name</code> from the given <code>source</code>.
502          * <p>
503          * All super classes and interfaces of the named class must have already been loaded:
504          * either defined by this class loader (unregistered ones) or loaded, possibly indirectly,
505          * by the system class loader (registered ones).
506          * <p>
507          * If the named class has a registered super class or interface named N there should be no
508          * unregistered class or interface named N loaded yet.
509          *
510          * @param name the name of the class to be loaded.
511          * @param source path to a directory or a JAR file from which the named class should be
512          *               loaded.
513          */
514         private Class<?> load(String name, String source) throws IOException {
515             final Source resolvedSource = resolveSource(source);
516             final byte[] bytes = resolvedSource.readClassFile(name);
517             // 'defineClass()' may cause loading of supertypes of this unregistered class by VM
518             // calling 'this.loadClass()'.
519             //
520             // For any supertype S named SN specified in the classlist the following is ensured by
521             // the CDS implementation:
522             // - if S is an unregistered class it must have already been defined by this class
523             //   loader and thus will be found by 'this.findLoadedClass(SN)',
524             // - if S is not an unregistered class there should be no unregistered class named SN
525             //   loaded yet so either S has previously been (indirectly) loaded by this class loader
526             //   and thus it will be found when calling 'this.findLoadedClass(SN)' or it will be
527             //   found when delegating to the system class loader, which must have already loaded S,
528             //   by calling 'this.getParent().loadClass(SN, false)'.
529             // See the implementation of 'ClassLoader.loadClass()' for details.
530             //
531             // Therefore, we should resolve all supertypes to the expected ones as specified by the
532             // "super:" and "interfaces:" attributes in the classlist. This invariant is validated
533             // by the C++ function 'ClassListParser::load_class_from_source()'.
534             assert getParent() == getSystemClassLoader();
535             return defineClass(name, bytes, 0, bytes.length);
536         }
537     }
538 
539     /**
540      * This class is used only by native JVM code to spawn a child JVM process to assemble
541      * the AOT cache. <code>args[]</code> are passed in the <code>JAVA_TOOL_OPTIONS</code>
542      * environment variable.
543      */
544     private static class ProcessLauncher {
545         static int execWithJavaToolOptions(String javaLauncher, String args[]) throws IOException, InterruptedException {
546             ProcessBuilder pb = new ProcessBuilder().inheritIO().command(javaLauncher);
547             StringBuilder sb = new StringBuilder();
548 
549             // Encode the args as described in
550             // https://docs.oracle.com/en/java/javase/24/docs/specs/jvmti.html#tooloptions
551             String prefix = "";
552             for (String arg : args) {
553                 sb.append(prefix);
554 
555                 for (int i = 0; i < arg.length(); i++) {
556                     char c = arg.charAt(i);
557                     if (c == '"' || Character.isWhitespace(c)) {
558                         sb.append('\'');
559                         sb.append(c);
560                         sb.append('\'');
561                     } else if (c == '\'') {
562                         sb.append('"');
563                         sb.append(c);
564                         sb.append('"');
565                     } else {
566                         sb.append(c);
567                     }
568                 }
569 
570                 prefix = " ";
571             }
572 
573             Map<String, String> env = pb.environment();
574             env.put("JAVA_TOOL_OPTIONS", sb.toString());
575             env.remove("_JAVA_OPTIONS");
576             env.remove("CLASSPATH");
577             Process process = pb.start();
578             return process.waitFor();
579         }
580     }
581 
582     /**
583      * This class is to ensure that the dynamic CDS archive contains at least one class, so we can avoid
584      * error handling for the degenerative case where the dynamic archive is completely empty (which doesn't
585      * happen for realistic applications).
586      */
587     private static class DummyForDynamicArchive {}
588 }