1 /*
  2  * Copyright (c) 2020, 2024, 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.util.Arrays;
 35 import java.util.ArrayList;
 36 import java.util.List;
 37 import java.util.Map;
 38 import java.util.Objects;
 39 import java.util.stream.Stream;
 40 
 41 import jdk.internal.access.SharedSecrets;
 42 import jdk.internal.util.StaticProperty;
 43 
 44 public class CDS {
 45     // Must be in sync with cdsConfig.hpp
 46     private static final int IS_DUMPING_ARCHIVE              = 1 << 0;
 47     private static final int IS_DUMPING_STATIC_ARCHIVE       = 1 << 1;
 48     private static final int IS_LOGGING_LAMBDA_FORM_INVOKERS = 1 << 2;
 49     private static final int IS_USING_ARCHIVE                = 1 << 3;
 50     private static final int IS_DUMPING_HEAP                 = 1 << 4;
 51     private static final int IS_LOGGING_DYNAMIC_PROXIES      = 1 << 5;
 52     private static final int IS_DUMPING_PACKAGES             = 1 << 6;
 53     private static final int IS_DUMPING_PROTECTION_DOMAINS   = 1 << 7;
 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     public static boolean isDumpingHeap() {
 85         return (configStatus & IS_DUMPING_HEAP) != 0;
 86     }
 87 
 88     public static boolean isLoggingDynamicProxies() {
 89         return (configStatus & IS_LOGGING_DYNAMIC_PROXIES) != 0;
 90     }
 91 
 92     public static boolean isDumpingPackages() {
 93         return (configStatus & IS_DUMPING_PACKAGES) != 0;
 94     }
 95 
 96     public static boolean isDumpingProtectionDomains() {
 97         return (configStatus & IS_DUMPING_PROTECTION_DOMAINS) != 0;
 98     }
 99 
100     private static native int getCDSConfigStatus();
101     private static native void logLambdaFormInvoker(String line);
102 
103     /**
104      * Initialize archived static fields in the given Class using archived
105      * values from CDS dump time. Also initialize the classes of objects in
106      * the archived graph referenced by those fields.
107      *
108      * Those static fields remain as uninitialized if there is no mapped CDS
109      * java heap data or there is any error during initialization of the
110      * object class in the archived graph.
111      */
112     public static native void initializeFromArchive(Class<?> c);
113 
114     /**
115      * Ensure that the native representation of all archived java.lang.Module objects
116      * are properly restored.
117      */
118     public static native void defineArchivedModules(ClassLoader platformLoader, ClassLoader systemLoader);
119 
120     /**
121      * Returns a predictable "random" seed derived from the VM's build ID and version,
122      * to be used by java.util.ImmutableCollections to ensure that archived
123      * ImmutableCollections are always sorted the same order for the same VM build.
124      */
125     public static native long getRandomSeedForDumping();
126 
127     /**
128      * log lambda form invoker holder, name and method type
129      */
130     public static void logLambdaFormInvoker(String prefix, String holder, String name, String type) {
131         if (isLoggingLambdaFormInvokers()) {
132             logLambdaFormInvoker(prefix + " " + holder + " " + name + " " + type);
133         }
134     }
135 
136     /**
137       * log species
138       */
139     public static void logSpeciesType(String prefix, String cn) {
140         if (isLoggingLambdaFormInvokers()) {
141             logLambdaFormInvoker(prefix + " " + cn);
142         }
143     }
144 
145     public static void logDynamicProxy(ClassLoader loader, String proxyName,
146                                        Class<?>[] interfaces, int accessFlags) {
147         Objects.requireNonNull(proxyName);
148         Objects.requireNonNull(interfaces);
149         logDynamicProxy0(loader, proxyName, interfaces, accessFlags);
150     }
151     private static native void logDynamicProxy0(ClassLoader loader, String proxyName,
152                                                 Class<?>[] interfaces, int accessFlags);
153 
154     static final String DIRECT_HOLDER_CLASS_NAME  = "java.lang.invoke.DirectMethodHandle$Holder";
155     static final String DELEGATING_HOLDER_CLASS_NAME = "java.lang.invoke.DelegatingMethodHandle$Holder";
156     static final String BASIC_FORMS_HOLDER_CLASS_NAME = "java.lang.invoke.LambdaForm$Holder";
157     static final String INVOKERS_HOLDER_CLASS_NAME = "java.lang.invoke.Invokers$Holder";
158 
159     private static boolean isValidHolderName(String name) {
160         return name.equals(DIRECT_HOLDER_CLASS_NAME)      ||
161                name.equals(DELEGATING_HOLDER_CLASS_NAME)  ||
162                name.equals(BASIC_FORMS_HOLDER_CLASS_NAME) ||
163                name.equals(INVOKERS_HOLDER_CLASS_NAME);
164     }
165 
166     private static boolean isBasicTypeChar(char c) {
167          return "LIJFDV".indexOf(c) >= 0;
168     }
169 
170     private static boolean isValidMethodType(String type) {
171         String[] typeParts = type.split("_");
172         // check return type (second part)
173         if (typeParts.length != 2 || typeParts[1].length() != 1
174                 || !isBasicTypeChar(typeParts[1].charAt(0))) {
175             return false;
176         }
177         // first part
178         if (!isBasicTypeChar(typeParts[0].charAt(0))) {
179             return false;
180         }
181         for (int i = 1; i < typeParts[0].length(); i++) {
182             char c = typeParts[0].charAt(i);
183             if (!isBasicTypeChar(c)) {
184                 if (!(c >= '0' && c <= '9')) {
185                     return false;
186                 }
187             }
188         }
189         return true;
190     }
191 
192     // Throw exception on invalid input
193     private static void validateInputLines(String[] lines) {
194         for (String s: lines) {
195             if (!s.startsWith("[LF_RESOLVE]") && !s.startsWith("[SPECIES_RESOLVE]")) {
196                 throw new IllegalArgumentException("Wrong prefix: " + s);
197             }
198 
199             String[] parts = s.split(" ");
200             boolean isLF = s.startsWith("[LF_RESOLVE]");
201 
202             if (isLF) {
203                 if (parts.length != 4) {
204                     throw new IllegalArgumentException("Incorrect number of items in the line: " + parts.length);
205                 }
206                 if (!isValidHolderName(parts[1])) {
207                     throw new IllegalArgumentException("Invalid holder class name: " + parts[1]);
208                 }
209                 if (!isValidMethodType(parts[3])) {
210                     throw new IllegalArgumentException("Invalid method type: " + parts[3]);
211                 }
212             } else {
213                 if (parts.length != 2) {
214                    throw new IllegalArgumentException("Incorrect number of items in the line: " + parts.length);
215                 }
216            }
217       }
218     }
219 
220     /**
221      * called from vm to generate MethodHandle holder classes
222      * @return {@code Object[]} if holder classes can be generated.
223      * @param lines in format of LF_RESOLVE or SPECIES_RESOLVE output
224      */
225     private static Object[] generateLambdaFormHolderClasses(String[] lines) {
226         Objects.requireNonNull(lines);
227         validateInputLines(lines);
228         Stream<String> lineStream = Arrays.stream(lines);
229         Map<String, byte[]> result = SharedSecrets.getJavaLangInvokeAccess().generateHolderClasses(lineStream);
230         int size = result.size();
231         Object[] retArray = new Object[size * 2];
232         int index = 0;
233         for (Map.Entry<String, byte[]> entry : result.entrySet()) {
234             retArray[index++] = entry.getKey();
235             retArray[index++] = entry.getValue();
236         };
237         return retArray;
238     }
239 
240     private static native void dumpClassList(String listFileName);
241     private static native void dumpDynamicArchive(String archiveFileName);
242 
243     private static String drainOutput(InputStream stream, long pid, String tail, List<String> cmds) {
244         String fileName  = "java_pid" + pid + "_" + tail;
245         new Thread( ()-> {
246             try (InputStreamReader isr = new InputStreamReader(stream);
247                  BufferedReader rdr = new BufferedReader(isr);
248                  PrintStream prt = new PrintStream(fileName)) {
249                 prt.println("Command:");
250                 for (String s : cmds) {
251                     prt.print(s + " ");
252                 }
253                 prt.println("");
254                 String line;
255                 while((line = rdr.readLine()) != null) {
256                     prt.println(line);
257                 }
258             } catch (IOException e) {
259                 throw new RuntimeException("IOException happens during drain stream to file " +
260                                            fileName + ": " + e.getMessage());
261             }}).start();
262         return fileName;
263     }
264 
265     private static String[] excludeFlags = {
266          "-XX:DumpLoadedClassList=",
267          "-XX:+RecordDynamicDumpInfo",
268          "-Xshare:",
269          "-XX:SharedClassListFile=",
270          "-XX:SharedArchiveFile=",
271          "-XX:ArchiveClassesAtExit="};
272     private static boolean containsExcludedFlags(String testStr) {
273        for (String e : excludeFlags) {
274            if (testStr.contains(e)) {
275                return true;
276            }
277        }
278        return false;
279     }
280 
281     /**
282     * called from jcmd VM.cds to dump static or dynamic shared archive
283     * @param isStatic true for dump static archive or false for dynnamic archive.
284     * @param fileName user input archive name, can be null.
285     * @return The archive name if successfully dumped.
286     */
287     private static String dumpSharedArchive(boolean isStatic, String fileName) throws Exception {
288         String cwd = new File("").getAbsolutePath(); // current dir used for printing message.
289         String currentPid = String.valueOf(ProcessHandle.current().pid());
290         String archiveFileName =  fileName != null ? fileName :
291             "java_pid" + currentPid + (isStatic ? "_static.jsa" : "_dynamic.jsa");
292 
293         String tempArchiveFileName = archiveFileName + ".temp";
294         File tempArchiveFile = new File(tempArchiveFileName);
295         // The operation below may cause exception if the file or its dir is protected.
296         if (!tempArchiveFile.exists()) {
297             tempArchiveFile.createNewFile();
298         }
299         tempArchiveFile.delete();
300 
301         if (isStatic) {
302             String listFileName = archiveFileName + ".classlist";
303             File listFile = new File(listFileName);
304             if (listFile.exists()) {
305                 listFile.delete();
306             }
307             dumpClassList(listFileName);
308             String jdkHome = StaticProperty.javaHome();
309             String classPath = System.getProperty("java.class.path");
310             List<String> cmds = new ArrayList<String>();
311             cmds.add(jdkHome + File.separator + "bin" + File.separator + "java"); // java
312             cmds.add("-cp");
313             cmds.add(classPath);
314             cmds.add("-Xlog:cds");
315             cmds.add("-Xshare:dump");
316             cmds.add("-XX:SharedClassListFile=" + listFileName);
317             cmds.add("-XX:SharedArchiveFile=" + tempArchiveFileName);
318 
319             // All runtime args.
320             String[] vmArgs = VM.getRuntimeArguments();
321             if (vmArgs != null) {
322                 for (String arg : vmArgs) {
323                     if (arg != null && !containsExcludedFlags(arg)) {
324                         cmds.add(arg);
325                     }
326                 }
327             }
328 
329             Process proc = Runtime.getRuntime().exec(cmds.toArray(new String[0]));
330 
331             // Drain stdout/stderr to files in new threads.
332             String stdOutFileName = drainOutput(proc.getInputStream(), proc.pid(), "stdout", cmds);
333             String stdErrFileName = drainOutput(proc.getErrorStream(), proc.pid(), "stderr", cmds);
334 
335             proc.waitFor();
336             // done, delete classlist file.
337             listFile.delete();
338 
339             // Check if archive has been successfully dumped. We won't reach here if exception happens.
340             // Throw exception if file is not created.
341             if (!tempArchiveFile.exists()) {
342                 throw new RuntimeException("Archive file " + tempArchiveFileName +
343                                            " is not created, please check stdout file " +
344                                             cwd + File.separator + stdOutFileName + " or stderr file " +
345                                             cwd + File.separator + stdErrFileName + " for more detail");
346             }
347         } else {
348             dumpDynamicArchive(tempArchiveFileName);
349             if (!tempArchiveFile.exists()) {
350                 throw new RuntimeException("Archive file " + tempArchiveFileName +
351                                            " is not created, please check current working directory " +
352                                            cwd  + " for process " +
353                                            currentPid + " output for more detail");
354             }
355         }
356         // Override the existing archive file
357         File archiveFile = new File(archiveFileName);
358         if (archiveFile.exists()) {
359             archiveFile.delete();
360         }
361         if (!tempArchiveFile.renameTo(archiveFile)) {
362             throw new RuntimeException("Cannot rename temp file " + tempArchiveFileName + " to archive file" + archiveFileName);
363         }
364         // Everything goes well, print out the file name.
365         String archiveFilePath = new File(archiveFileName).getAbsolutePath();
366         System.out.println("The process was attached by jcmd and dumped a " + (isStatic ? "static" : "dynamic") + " archive " + archiveFilePath);
367         return archiveFilePath;
368     }
369 }