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 }