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