1 /*
  2  * Copyright (c) 2021, 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.
  8  *
  9  * This code is distributed in the hope that it will be useful, but WITHOUT
 10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 12  * version 2 for more details (a copy is included in the LICENSE file that
 13  * accompanied this code).
 14  *
 15  * You should have received a copy of the GNU General Public License version
 16  * 2 along with this work; if not, write to the Free Software Foundation,
 17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 18  *
 19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 20  * or visit www.oracle.com if you need additional information or have any
 21  * questions.
 22  */
 23 
 24 /**
 25  * @ignore Fix JDK-8328468
 26  * @test
 27  * @modules java.base/jdk.internal.value
 28  * @library /test/lib
 29  * @requires vm.jvmti
 30  * @enablePreview
 31  * @compile HeapDump.java
 32  * @run main/othervm HeapDump
 33  */
 34 
 35 import java.io.File;
 36 import java.lang.ref.Reference;
 37 import java.lang.reflect.Array;
 38 import java.lang.reflect.Field;
 39 import java.lang.reflect.Modifier;
 40 import java.util.concurrent.TimeUnit;
 41 import jdk.internal.value.PrimitiveClass;
 42 import jdk.test.lib.apps.LingeredApp;
 43 import jdk.test.lib.JDKToolLauncher;
 44 import jdk.test.lib.hprof.model.HackJavaValue;
 45 import jdk.test.lib.hprof.model.InlinedJavaObject;
 46 import jdk.test.lib.hprof.model.JavaByte;
 47 import jdk.test.lib.hprof.model.JavaClass;
 48 import jdk.test.lib.hprof.model.JavaField;
 49 import jdk.test.lib.hprof.model.JavaHeapObject;
 50 import jdk.test.lib.hprof.model.JavaObject;
 51 import jdk.test.lib.hprof.model.JavaObjectArray;
 52 import jdk.test.lib.hprof.model.JavaStatic;
 53 import jdk.test.lib.hprof.model.JavaThing;
 54 import jdk.test.lib.hprof.model.JavaValueArray;
 55 import jdk.test.lib.hprof.model.Snapshot;
 56 import jdk.test.lib.process.ProcessTools;
 57 
 58 import java.util.Enumeration;
 59 
 60 import jdk.test.lib.hprof.parser.Reader;
 61 
 62 
 63 class TestClass {
 64 
 65     public static primitive class MyPrimitive0 {
 66         public byte prim0_fld1;
 67         public MyPrimitive0(int fld1) { prim0_fld1 = (byte)fld1; }
 68     }
 69     public static primitive class MyPrimitive {
 70         public MyPrimitive0 prim_prim0;
 71         public byte prim_fld1;
 72         public int prim_fld2;
 73 
 74         public MyPrimitive(int p0, int fld1, int fld2) {
 75             prim_fld1 = (byte)fld1; prim_fld2 = fld2; prim_prim0 = new MyPrimitive0(p0);
 76         }
 77     }
 78 
 79     public static primitive class PrimitiveHolder {
 80         // offset of the inlined hld_flatObj is the same as offset of inlined PrimitiveHolder
 81         public MyPrimitive hld_flatObj;
 82 
 83         public PrimitiveHolder(int n) {
 84             hld_flatObj = new MyPrimitive((byte)n, n+1, n+2);
 85         }
 86     }
 87 
 88     // primitive class with reference
 89     public static primitive class MyPrimitiveRef {
 90         public byte ref_fld1;
 91         public int ref_fld2;
 92         public String ref_strObj;
 93 
 94         public MyPrimitiveRef(int v1, int v2) {
 95             ref_fld1 = (byte)v1;
 96             ref_fld2 = v2;
 97             ref_strObj = "#" + String.valueOf(v2); }
 98     }
 99 
100     public static primitive class PrimitiveHolderRef {
101         public MyPrimitiveRef[] flatArr = new MyPrimitiveRef[5];
102         public MyPrimitiveRef flatObj;
103         public String ref_str;
104         public PrimitiveHolderRef(int n) {
105             ref_str = String.valueOf(n);
106             flatObj = new MyPrimitiveRef(n, n + 10);
107             for (int i = 0; i < flatArr.length; i++) {
108                 flatArr[i] = new MyPrimitiveRef(i + n + 1, i + n + 11);
109             }
110         }
111     }
112 
113     public MyPrimitive[] main_flatArr = new MyPrimitive[3];
114     public MyPrimitive main_flatObj = new MyPrimitive(10, 15, 9);
115     public MyPrimitiveRef main_flatObjRef = new MyPrimitiveRef(11, 144);
116     public MyPrimitiveRef[] flatArrRef = new MyPrimitiveRef[4];
117     public String main_strObj = "targ.strObj";
118 
119     public Object main_nullObj;
120 
121     public final PrimitiveHolder main_primHolder = new PrimitiveHolder(16);
122     // array of compound primitive objects
123     public final PrimitiveHolderRef[] primHolderArr = new PrimitiveHolderRef[4];
124 
125 
126     // static inlined fields
127     public static MyPrimitive main_flatObjStatic = new MyPrimitive(13, 241, 24);
128     public static MyPrimitiveRef[] flatArrRefStatic = new MyPrimitiveRef[6];
129     static {
130         for (int i = 0; i < flatArrRefStatic.length; i++) {
131             flatArrRefStatic[i] = new MyPrimitiveRef(i + 200, i + 225);
132         }
133     }
134 
135     public TestClass() {
136         for (int i = 0; i < main_flatArr.length; i++) {
137             main_flatArr[i] = new MyPrimitive(i + 10, i + 110, i + 35);
138         }
139         for (int i = 0; i < flatArrRef.length; i++) {
140             flatArrRef[i] = new MyPrimitiveRef(i + 100, i + 120);
141         }
142         for (int i = 0; i < primHolderArr.length; i++) {
143             primHolderArr[i] = new PrimitiveHolderRef(20 + i);
144         }
145     }
146 }
147 
148 class HeapDumpTarg extends LingeredApp {
149 
150     public static void main(String[] args) {
151         TestClass testObj = new TestClass();
152         LingeredApp.main(args);
153         Reference.reachabilityFence(testObj);
154     }
155 
156 }
157 
158 public class HeapDump {
159     public static void main(String[] args) throws Throwable {
160         LingeredApp theApp = null;
161         String hprogFile = new File(System.getProperty("test.classes") + "/Myheapdump.hprof").getAbsolutePath();
162         try {
163             theApp = new HeapDumpTarg();
164 
165             // -XX:+PrintInlineLayout is debug-only arg
166             LingeredApp.startApp(theApp, "--enable-preview"/*, "-XX:+PrintInlineLayout"*/);
167 
168             // jcmd <pid> GC.heap_dump
169             JDKToolLauncher launcher = JDKToolLauncher
170                                         .createUsingTestJDK("jcmd")
171                                         .addToolArg(Long.toString(theApp.getPid()))
172                                         .addToolArg("GC.heap_dump")
173                                         .addToolArg(hprogFile);
174             Process jcmd = ProcessTools.startProcess("jcmd", new ProcessBuilder(launcher.getCommand()));
175             // If something goes wrong with heap dumping most likely we'll get crash of the target VM
176             while (!jcmd.waitFor(5, TimeUnit.SECONDS)) {
177                 if (!theApp.getProcess().isAlive()) {
178                     log("ERROR: target VM died, killing jcmd...");
179                     jcmd.destroyForcibly();
180                     throw new Exception("Target VM died");
181                 }
182             }
183 
184             if (jcmd.exitValue() != 0) {
185                 throw new Exception("Jcmd exited with code " + jcmd.exitValue());
186             }
187         } finally {
188             LingeredApp.stopApp(theApp);
189         }
190 
191         // test object to compare
192         TestClass testObj = new TestClass();
193 
194         log("Reading " + hprogFile + "...");
195         try (Snapshot snapshot = Reader.readFile(hprogFile,true, 0)) {
196             log("Snapshot read, resolving...");
197             snapshot.resolve(true);
198             log("Snapshot resolved.");
199 
200             JavaObject dumpObj = findObject(snapshot, testObj.getClass().getName());
201 
202             log("");
203             print(dumpObj);
204 
205             log("");
206             log("Verifying object " + testObj.getClass().getName() + " (dumped object " + dumpObj + ")");
207             compareObjectFields("  ", testObj, dumpObj);
208         }
209     }
210 
211     private static JavaObject findObject(Snapshot snapshot, String className) throws Exception {
212         log("looking for " + className + "...");
213         JavaClass jClass = snapshot.findClass(className);
214         if (jClass == null) {
215             throw new Exception("'" + className + "' not found");
216         }
217         Enumeration<JavaHeapObject> objects = jClass.getInstances(false);
218         if (!objects.hasMoreElements()) {
219             throw new Exception("No '" + className + "' instances found");
220         }
221         JavaHeapObject heapObj = objects.nextElement();
222         if (objects.hasMoreElements()) {
223             throw new Exception("More than 1 instances of '" + className + "' found");
224         }
225         if (!(heapObj instanceof JavaObject)) {
226             throw new Exception("'" + className + "' instance is not JavaObject (" + heapObj.getClass() + ")");
227         }
228         return (JavaObject)heapObj;
229     }
230 
231     // description of the current object for logging and error reporting
232     private static String objDescr;
233     private static boolean errorReported = false;
234 
235     private static void compareObjects(String logPrefix, Object testObj, JavaThing dumpObj) throws Exception {
236         if (testObj == null) {
237             if (!isNullValue(dumpObj)) {
238                 throw new Exception("null expected, but dumped object is " + dumpObj);
239             }
240             log(logPrefix + objDescr + ": null");
241         } else if (dumpObj instanceof JavaObject obj) {
242             // special handling for Strings
243             if (testObj instanceof String testStr) {
244                 objDescr += " (String, " + obj.getIdString() + ")";
245                 if (!obj.getClazz().isString()) {
246                     throw new Exception("value (" + obj + ")"
247                             + " is not String (" + obj.getClazz() + ")");
248                 }
249                 String dumpStr = getStringValue(obj);
250                 if (!testStr.equals(dumpStr)) {
251                     throw new Exception("different values:"
252                             + " expected \"" + testStr + "\", actual \"" + dumpStr + "\"");
253                 }
254                 log(logPrefix + objDescr + ": \"" + testStr + "\" ( == \"" + dumpStr + "\")");
255             } else {
256                 // other Object
257                 log(logPrefix + objDescr + ": Object " + obj);
258                 if (isTestClass(obj.getClazz().getName())) {
259                     compareObjectFields(logPrefix + "  ", testObj, obj);
260                 }
261             }
262         } else {
263             throw new Exception("Object expected, but the value (" + dumpObj + ")"
264                     + " is not JavaObject (" + dumpObj.getClass() + ")");
265         }
266     }
267 
268     private static void compareObjectFields(String logPrefix, Object testObj, JavaObject dumpObj) throws Exception {
269         Field[] fields = testObj.getClass().getDeclaredFields();
270         for (Field testField : fields) {
271             boolean isStatic = Modifier.isStatic(testField.getModifiers());
272             testField.setAccessible(true);
273             objDescr = "- " + (isStatic ? "(static) " : "")
274                     + (PrimitiveClass.isPrimitiveClass(testField.getType()) ? "(primitive) " : "")
275                     + testField.getName() + " ('" + testField.getType().descriptorString() + "')";
276             try {
277                 Object testValue = testField.get(testObj);
278 
279                 JavaField dumpField = getField(dumpObj, testField.getName(), isStatic);
280                 JavaThing dumpValue = isStatic
281                         ? dumpObj.getClazz().getStaticField(dumpField.getName())
282                         : dumpObj.getField(dumpField.getName());
283 
284                 objDescr += ", dump signature '" + dumpField.getSignature() + "'";
285 
286                 compareType(testField, dumpField);
287 
288                 if (testValue == null) {
289                     if (!isNullValue(dumpValue)) {
290                         throw new Exception("null expected, but dumped object is " + dumpValue);
291                     }
292                     log(logPrefix + objDescr + ": null");
293                 } else {
294                     switch (testField.getType().descriptorString().charAt(0)) {
295                     case 'L':
296                     case 'Q':
297                         compareObjects(logPrefix, testValue, dumpValue);
298                         break;
299                     case '[':
300                         int testLength = Array.getLength(testValue);
301                         objDescr += " (Array of '" + testField.getType().getComponentType() + "'"
302                                 + ", length = " + testLength + ", " + dumpValue + ")";
303                         if (dumpValue instanceof JavaValueArray arr) {
304                             // array of primitive type
305                             char testElementType = testField.getType().getComponentType().descriptorString().charAt(0);
306                             if ((char)arr.getElementType() != testElementType) {
307                                 throw new Exception("wrong element type: '" + (char)arr.getElementType() + "'");
308                             }
309                             int dumpLength = arr.getLength();
310                             if (dumpLength != testLength) {
311                                 throw new Exception("wrong array size: " + dumpLength);
312                             }
313                             JavaThing[] dumpElements = arr.getElements();
314                             log(logPrefix + objDescr);
315                             for (int j = 0; j < testLength; j++) {
316                                 Object elementValue = Array.get(testValue, j);
317                                 objDescr = "[" + j + "]";
318                                 if (arr.isFlatArray()) {
319                                     compareObjects(logPrefix + "  ", elementValue, dumpElements[j]);
320                                 } else {
321                                     comparePrimitiveValues(elementValue, dumpElements[j]);
322                                     log(logPrefix + "  [" + j + "]: " + elementValue + " ( == " + dumpElements[j] + ")");
323                                 }
324                             }
325                         } else if (dumpValue instanceof JavaObjectArray arr) {
326                             int dumpLength = arr.getLength();
327                             if (dumpLength != testLength) {
328                                 throw new Exception("wrong array size: " + dumpLength);
329                             }
330                             JavaThing[] dumpElements = arr.getElements();
331                             log(logPrefix + objDescr);
332                             for (int j = 0; j < testLength; j++) {
333                                 Object elementValue = Array.get(testValue, j);
334                                 objDescr = "[" + j + "]";
335                                 compareObjects(logPrefix + "  ", elementValue, dumpElements[j]);
336                             }
337                         } else {
338                             throw new Exception("Array expected, but the value (" + dumpValue + ")"
339                                     + " is neither JavaValueArray nor JavaObjectArray"
340                                     + " (" + dumpValue.getClass() + ")");
341                         }
342                         break;
343                     default:
344                         comparePrimitiveValues(testValue, dumpValue);
345                         log(logPrefix + objDescr + ": " + testValue + " ( == " + dumpValue + ")");
346                         break;
347                     }
348                 }
349             } catch (Exception ex) {
350                 if (!errorReported) {
351                     log(logPrefix + objDescr + ": ERROR - " + ex.getMessage());
352                     errorReported = true;
353                 }
354                 throw ex;
355             }
356         }
357     }
358 
359     private static JavaField getField(JavaObject obj, String fieldName, boolean isStatic) throws Exception {
360         if (isStatic) {
361             JavaStatic[] statics = obj.getClazz().getStatics();
362             for (JavaStatic st: statics) {
363                 if (st.getField().getName().equals(fieldName)) {
364                     return st.getField();
365                 }
366             }
367         } else {
368             JavaField[] fields = obj.getClazz().getFields();
369             for (JavaField field : fields) {
370                 if (fieldName.equals(field.getName())) {
371                     return field;
372                 }
373             }
374         }
375         throw new Exception("field '" + fieldName + "' not found");
376     }
377 
378     private static void compareType(Field field, JavaField dumpField) throws Exception {
379         String sig = field.getType().descriptorString();
380         char type = sig.charAt(0);
381         if (type == '[' || type == 'Q') {
382             type = 'L';
383         }
384         char dumpType = dumpField.getSignature().charAt(0);
385         if (dumpType == 'Q') {
386             dumpType = 'L';
387         }
388         if (dumpType != type) {
389             throw new Exception("type mismatch:"
390                     + " expected '" + type + "' (" + sig + ")"
391                     + ", found '" + dumpField.getSignature().charAt(0) + "' (" + dumpField.getSignature() + ")");
392         }
393     }
394 
395     private static void comparePrimitiveValues(Object testValue, JavaThing dumpValue) throws Exception {
396         // JavaByte.toString() returns hex
397         String testStr = testValue instanceof Byte byteValue
398                 ? (new JavaByte(byteValue)).toString()
399                 : String.valueOf(testValue);
400         String dumpStr = dumpValue.toString();
401         if (!testStr.equals(dumpStr)) {
402             throw new Exception("Wrong value: expected " + testStr + ", actual " + dumpStr);
403         }
404     }
405 
406     private static boolean isNullValue(JavaThing value) {
407         return value == null
408                 // dumped value is HackJavaValue with string representation "<null>"
409                 || (value instanceof HackJavaValue &&  "<null>".equals(value.toString()));
410     }
411 
412     private static String getStringValue(JavaObject value) {
413         JavaThing valueObj = value.getField("value");
414         if (valueObj instanceof JavaValueArray valueArr) {
415             try {
416                 if (valueArr.getElementType() == 'B') {
417                     Field valueField = JavaByte.class.getDeclaredField("value");
418                     valueField.setAccessible(true);
419                     JavaThing[] things = valueArr.getElements();
420                     byte[] bytes = new byte[things.length];
421                     for (int i = 0; i < things.length; i++) {
422                         bytes[i] = valueField.getByte(things[i]);
423                     }
424                     return new String(bytes);
425                 }
426             } catch (Exception ignored) {
427             }
428             return valueArr.valueString();
429         } else {
430             return null;
431         }
432     }
433 
434     private static void print(JavaObject dumpObject) {
435         log("Dumped object " + dumpObject + ":");
436         print("", dumpObject);
437     }
438 
439     private static void print(String prefix, JavaObject dumpObject) {
440         JavaClass clazz = dumpObject.getClazz();
441         // print only test classes
442         if (!isTestClass(clazz.getName())) {
443             return;
444         }
445 
446         JavaField[] fields = clazz.getFields();
447         for (JavaField field : fields) {
448             printFieldValue(prefix, field, false, dumpObject.getField(field.getName()));
449         }
450 
451         JavaStatic[] statics = clazz.getStatics();
452         for (JavaStatic st: statics) {
453             printFieldValue(prefix, st.getField(), true, st.getValue());
454         }
455     }
456 
457     private static void printFieldValue(String prefix, JavaField field, boolean isStatic, JavaThing value) {
458         String logPrefix = prefix + "- " + (isStatic ? "(static) " : "")
459                 + field.getName() + " ('" + field.getSignature() + "'): ";
460         if (isNullValue(value)) {
461             log(logPrefix + "null");
462         } else {
463             if (value instanceof JavaObject obj) {
464                 logPrefix += "(class '" + obj.getClazz().getName() + "'): ";
465                 if (obj.getClazz().isString()) {
466                     String dumpStr = getStringValue(obj);
467                     log(logPrefix + "\"" + dumpStr + "\"");
468                 } else {
469                     log(logPrefix + (value instanceof InlinedJavaObject ? "inlined " : "") + "object " + obj);
470                     print(prefix + "  ", obj);
471                 }
472             } else if (value instanceof JavaObjectArray arr) {
473                 log(logPrefix + " array " + arr + " length: " + arr.getLength());
474                 JavaThing[] values = arr.getValues();
475                 for (int v = 0; v < values.length; v++) {
476                     log(prefix + "  [" + v + "]: " + values[v]);
477                     if (values[v] instanceof JavaObject obj) {
478                         print(prefix + "    ", obj);
479                     }
480                 }
481             } else if (value instanceof JavaValueArray arr) { // array of primitive type or flat array
482                 if (arr.isFlatArray()) {
483                     log(logPrefix + " flat array " + arr + " length: " + arr.getLength());
484                     JavaThing[] values = arr.getElements();
485                     for (int v = 0; v < values.length; v++) {
486                         log(prefix + "  [" + v + "]: " + values[v]);
487                         if (values[v] instanceof JavaObject obj) {
488                             print(prefix + "    ", obj);
489                         }
490                     }
491                 } else {
492                     log(logPrefix + "(array of '" + (char)arr.getElementType() + "')" + ": " + arr.valueString());
493                 }
494             } else {
495                 log(logPrefix + "(" + value.getClass() +  ")" + value.toString());
496             }
497         }
498     }
499 
500     private static boolean isTestClass(String className) {
501         return className.startsWith("TestClass");
502     }
503 
504     private static void log(String msg) {
505         System.out.println(msg);
506         System.out.flush();
507     }
508 
509 }