1 /*
2 * Copyright (c) 2021, 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.
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 * @test
26 * @requires vm.debug
27 * @modules java.base/jdk.internal.value
28 * @library /test/lib
29 * @modules java.base/jdk.internal.vm.annotation java.base/jdk.internal.value
30 * @enablePreview
31 * @run main/othervm HeapDump
32 */
33
34 import java.io.File;
35 import java.lang.ref.Reference;
36 import java.lang.reflect.Array;
37 import java.lang.reflect.Field;
38 import java.lang.reflect.Modifier;
39 import java.util.concurrent.TimeUnit;
40 import java.util.Enumeration;
41 import jdk.internal.value.ValueClass;
42 import jdk.internal.vm.annotation.NullRestricted;
43 import jdk.internal.vm.annotation.Strict;
44
45 import jdk.test.lib.apps.LingeredApp;
46 import jdk.test.lib.JDKToolLauncher;
47 import jdk.test.lib.hprof.model.HackJavaValue;
48 import jdk.test.lib.hprof.model.JavaByte;
49 import jdk.test.lib.hprof.model.JavaClass;
50 import jdk.test.lib.hprof.model.JavaField;
51 import jdk.test.lib.hprof.model.JavaHeapObject;
52 import jdk.test.lib.hprof.model.JavaObject;
53 import jdk.test.lib.hprof.model.JavaObjectArray;
54 import jdk.test.lib.hprof.model.JavaStatic;
55 import jdk.test.lib.hprof.model.JavaThing;
56 import jdk.test.lib.hprof.model.JavaValueArray;
57 import jdk.test.lib.hprof.model.Snapshot;
58 import jdk.test.lib.hprof.parser.Reader;
59 import jdk.test.lib.process.ProcessTools;
60
61
62 value class TestClass {
63
64 public static value class Value0 {
65 public int value0_int;
66 public Value0(int i) { value0_int = i; }
67 }
68
69 // value class with the only value filed
70 // offset of flattened value_value0 is the same as offset of flattened Value
71 public static value class Value {
72 public Value0 value_value0;
73
74 public Value(int i) {
75 value_value0 = i == 0 ? null : new Value0(i);
76 }
77 }
78
79 public static value class ValueHolder {
80 public Value holder_value;
81
82 public ValueHolder(int i) {
83 holder_value = new Value(i);
84 }
85 }
86
87 // value class with reference (String)
88 public static value class ValueRef {
89 public Value ref_value;
90 public String ref_str;
91
92 public ValueRef(int i) {
93 ref_value = new Value(i);
94 ref_str = "#" + String.valueOf(i);
95 }
96 }
97
98 // nullable value
99 public Value nullableValue = new Value(1);
100 // null restricted value
101 @Strict
102 @NullRestricted
103 public Value nullRestrictedValue = new Value(11);
104 // null value
105 public Value nullValue = null;
106 // value object with reference
107 public ValueRef refValue = new ValueRef(21);
108
109 // nullable array
110 public Value[] nullableArray = createNullableArray(101);
111 // null restricted array
112 public Value[] nullRestrictedArray = createNullRestrictedArray(201);
113
114 // static value field (for sanity, cannot be flat)
115 public static Value staticValue = new Value(31);
116
117 public TestClass() {
118 }
119
120 static Value[] createNullableArray(int seed) {
121 Value[] arr = (Value[])ValueClass.newNullableAtomicArray(Value.class, 5);
122 for (int i = 0; i < arr.length; i++) {
123 arr[i] = (i % 2 != 0) ? null : new Value(seed + i);
124 }
125 return arr;
126 }
127
128 static Value[] createNullRestrictedArray(int seed) {
129 Value defValue = new Value(0);
130 Value[] arr = (Value[])ValueClass.newNullRestrictedNonAtomicArray(Value.class, 5, defValue);
131 for (int i = 0; i < arr.length; i++) {
132 arr[i] = new Value(seed + i);
133 }
134 return arr;
135 }
136 }
137
138 class HeapDumpTarg extends LingeredApp {
139 public static void main(String[] args) {
140 TestClass testObj = new TestClass();
141 LingeredApp.main(args);
142 Reference.reachabilityFence(testObj);
143 }
144 }
145
146 public class HeapDump {
147 public static void main(String[] args) throws Throwable {
148 LingeredApp theApp = null;
149 String hprogFile = new File(System.getProperty("test.classes") + "/Myheapdump.hprof").getAbsolutePath();
150 try {
151 theApp = new HeapDumpTarg();
152
153 // -XX:+PrintInlineLayout is debug-only arg
154 LingeredApp.startApp(theApp, "--enable-preview", "-XX:+UnlockDiagnosticVMOptions",
155 "-XX:+PrintInlineLayout", "-XX:+PrintFlatArrayLayout",
156 "--add-modules=java.base",
157 "--add-exports=java.base/jdk.internal.value=ALL-UNNAMED");
158
159 // jcmd <pid> GC.heap_dump
160 JDKToolLauncher launcher = JDKToolLauncher
161 .createUsingTestJDK("jcmd")
162 .addToolArg(Long.toString(theApp.getPid()))
163 .addToolArg("GC.heap_dump")
164 .addToolArg(hprogFile);
165 Process jcmd = ProcessTools.startProcess("jcmd", new ProcessBuilder(launcher.getCommand()));
166 // If something goes wrong with heap dumping most likely we'll get crash of the target VM
167 while (!jcmd.waitFor(5, TimeUnit.SECONDS)) {
168 if (!theApp.getProcess().isAlive()) {
169 log("ERROR: target VM died, killing jcmd...");
170 jcmd.destroyForcibly();
171 throw new Exception("Target VM died");
172 }
173 }
174
175 if (jcmd.exitValue() != 0) {
176 throw new Exception("Jcmd exited with code " + jcmd.exitValue());
177 }
178 } finally {
179 LingeredApp.stopApp(theApp);
180 }
181
182 // test object to compare
183 TestClass testObj = new TestClass();
184
185 log("Reading " + hprogFile + "...");
186 try (Snapshot snapshot = Reader.readFile(hprogFile,true, 0)) {
187 log("Snapshot read, resolving...");
188 snapshot.resolve(true);
189 log("Snapshot resolved.");
190
191 JavaObject dumpObj = findObject(snapshot, testObj.getClass().getName());
192
193 log("");
194 print(dumpObj);
195
196 log("");
197 log("Verifying object " + testObj.getClass().getName() + " (dumped object " + dumpObj + ")");
198 compareObjectFields(" ", testObj, dumpObj);
199 }
200 }
201
202 private static JavaObject findObject(Snapshot snapshot, String className) throws Exception {
203 log("looking for " + className + "...");
204 JavaClass jClass = snapshot.findClass(className);
205 if (jClass == null) {
206 throw new Exception("'" + className + "' not found");
207 }
208 Enumeration<JavaHeapObject> objects = jClass.getInstances(false);
209 if (!objects.hasMoreElements()) {
210 throw new Exception("No '" + className + "' instances found");
211 }
212 JavaHeapObject heapObj = objects.nextElement();
213 if (objects.hasMoreElements()) {
214 throw new Exception("More than 1 instances of '" + className + "' found");
215 }
216 if (!(heapObj instanceof JavaObject)) {
217 throw new Exception("'" + className + "' instance is not JavaObject (" + heapObj.getClass() + ")");
218 }
219 return (JavaObject)heapObj;
220 }
221
222 // description of the current object for logging and error reporting
223 private static String objDescr;
224 private static boolean errorReported = false;
225
226 private static void compareObjects(String logPrefix, Object testObj, JavaThing dumpObj) throws Exception {
227 if (testObj == null) {
228 if (!isNullValue(dumpObj)) {
229 throw new Exception("null expected, but dumped object is " + dumpObj);
230 }
231 log(logPrefix + objDescr + ": null");
232 } else if (dumpObj instanceof JavaObject obj) {
233 // special handling for Strings
234 if (testObj instanceof String testStr) {
235 objDescr += " (String, " + obj.getIdString() + ")";
236 if (!obj.getClazz().isString()) {
237 throw new Exception("value (" + obj + ")"
238 + " is not String (" + obj.getClazz() + ")");
239 }
240 String dumpStr = getStringValue(obj);
241 if (!testStr.equals(dumpStr)) {
242 throw new Exception("different values:"
243 + " expected \"" + testStr + "\", actual \"" + dumpStr + "\"");
244 }
245 log(logPrefix + objDescr + ": \"" + testStr + "\" ( == \"" + dumpStr + "\")");
246 } else {
247 // other Object
248 log(logPrefix + objDescr + ": Object " + obj);
249 if (isTestClass(obj.getClazz().getName())) {
250 compareObjectFields(logPrefix + " ", testObj, obj);
251 }
252 }
253 } else {
254 throw new Exception("Object expected, but the value (" + dumpObj + ")"
255 + " is not JavaObject (" + dumpObj.getClass() + ")");
256 }
257 }
258
259 private static void compareObjectFields(String logPrefix, Object testObj, JavaObject dumpObj) throws Exception {
260 Field[] fields = testObj.getClass().getDeclaredFields();
261 for (Field testField : fields) {
262 boolean isStatic = Modifier.isStatic(testField.getModifiers());
263 testField.setAccessible(true);
264 objDescr = "- " + (isStatic ? "(static) " : "")
265 + testField.getName() + " ('" + testField.getType().descriptorString() + "')";
266 try {
267 Object testValue = testField.get(testObj);
268
269 JavaField dumpField = getField(dumpObj, testField.getName(), isStatic);
270 JavaThing dumpValue = isStatic
271 ? dumpObj.getClazz().getStaticField(dumpField.getName())
272 : dumpObj.getField(dumpField.getName());
273
274 objDescr += ", dump signature '" + dumpField.getSignature() + "'";
275
276 compareType(testField, dumpField);
277
278 if (testValue == null) {
279 if (!isNullValue(dumpValue)) {
280 throw new Exception("null expected, but dumped object is " + dumpValue);
281 }
282 log(logPrefix + objDescr + ": null");
283 } else {
284 switch (testField.getType().descriptorString().charAt(0)) {
285 case 'L':
286 compareObjects(logPrefix, testValue, dumpValue);
287 break;
288 case '[':
289 int testLength = Array.getLength(testValue);
290 objDescr += " (Array of '" + testField.getType().getComponentType() + "'"
291 + ", length = " + testLength + ", " + dumpValue + ")";
292 if (dumpValue instanceof JavaValueArray arr) {
293 // array of primitive type
294 char testElementType = testField.getType().getComponentType().descriptorString().charAt(0);
295 if ((char)arr.getElementType() != testElementType) {
296 throw new Exception("wrong element type: '" + (char)arr.getElementType() + "'");
297 }
298 int dumpLength = arr.getLength();
299 if (dumpLength != testLength) {
300 throw new Exception("wrong array size: " + dumpLength);
301 }
302 JavaThing[] dumpElements = arr.getElements();
303 log(logPrefix + objDescr);
304 for (int j = 0; j < testLength; j++) {
305 Object elementValue = Array.get(testValue, j);
306 objDescr = "[" + j + "]";
307 comparePrimitiveValues(elementValue, dumpElements[j]);
308 log(logPrefix + " [" + j + "]: " + elementValue + " ( == " + dumpElements[j] + ")");
309 }
310 } else if (dumpValue instanceof JavaObjectArray arr) {
311 int dumpLength = arr.getLength();
312 if (dumpLength != testLength) {
313 throw new Exception("wrong array size: " + dumpLength);
314 }
315 JavaThing[] dumpElements = arr.getElements();
316 log(logPrefix + objDescr);
317 for (int j = 0; j < testLength; j++) {
318 Object elementValue = Array.get(testValue, j);
319 objDescr = "[" + j + "]";
320 compareObjects(logPrefix + " ", elementValue, dumpElements[j]);
321 }
322 } else {
323 throw new Exception("Array expected, but the value (" + dumpValue + ")"
324 + " is neither JavaValueArray nor JavaObjectArray"
325 + " (" + dumpValue.getClass() + ")");
326 }
327 break;
328 default:
329 comparePrimitiveValues(testValue, dumpValue);
330 log(logPrefix + objDescr + ": " + testValue + " ( == " + dumpValue + ")");
331 break;
332 }
333 }
334 } catch (Exception ex) {
335 if (!errorReported) {
336 log(logPrefix + objDescr + ": ERROR - " + ex.getMessage());
337 errorReported = true;
338 }
339 throw ex;
340 }
341 }
342 }
343
344 private static JavaField getField(JavaObject obj, String fieldName, boolean isStatic) throws Exception {
345 if (isStatic) {
346 JavaStatic[] statics = obj.getClazz().getStatics();
347 for (JavaStatic st: statics) {
348 if (st.getField().getName().equals(fieldName)) {
349 return st.getField();
350 }
351 }
352 } else {
353 JavaField[] fields = obj.getClazz().getFields();
354 for (JavaField field : fields) {
355 if (fieldName.equals(field.getName())) {
356 return field;
357 }
358 }
359 }
360 throw new Exception("field '" + fieldName + "' not found");
361 }
362
363 private static void compareType(Field field, JavaField dumpField) throws Exception {
364 String sig = field.getType().descriptorString();
365 char type = sig.charAt(0);
366 if (type == '[') {
367 type = 'L';
368 }
369 char dumpType = dumpField.getSignature().charAt(0);
370 if (dumpType != type) {
371 throw new Exception("type mismatch:"
372 + " expected '" + type + "' (" + sig + ")"
373 + ", found '" + dumpField.getSignature().charAt(0) + "' (" + dumpField.getSignature() + ")");
374 }
375 }
376
377 private static void comparePrimitiveValues(Object testValue, JavaThing dumpValue) throws Exception {
378 // JavaByte.toString() returns hex
379 String testStr = testValue instanceof Byte byteValue
380 ? (new JavaByte(byteValue)).toString()
381 : String.valueOf(testValue);
382 String dumpStr = dumpValue.toString();
383 if (!testStr.equals(dumpStr)) {
384 throw new Exception("Wrong value: expected " + testStr + ", actual " + dumpStr);
385 }
386 }
387
388 private static boolean isNullValue(JavaThing value) {
389 return value == null
390 // dumped value is HackJavaValue with string representation "<null>"
391 || (value instanceof HackJavaValue && "<null>".equals(value.toString()));
392 }
393
394 private static String getStringValue(JavaObject value) {
395 JavaThing valueObj = value.getField("value");
396 if (valueObj instanceof JavaValueArray valueArr) {
397 try {
398 if (valueArr.getElementType() == 'B') {
399 Field valueField = JavaByte.class.getDeclaredField("value");
400 valueField.setAccessible(true);
401 JavaThing[] things = valueArr.getElements();
402 byte[] bytes = new byte[things.length];
403 for (int i = 0; i < things.length; i++) {
404 bytes[i] = valueField.getByte(things[i]);
405 }
406 return new String(bytes);
407 }
408 } catch (Exception ignored) {
409 }
410 return valueArr.valueString();
411 } else {
412 return null;
413 }
414 }
415
416 private static void print(JavaObject dumpObject) {
417 log("Dumped object " + dumpObject + ":");
418 print("", dumpObject);
419 }
420
421 private static void print(String prefix, JavaObject dumpObject) {
422 JavaClass clazz = dumpObject.getClazz();
423 // print only test classes
424 if (!isTestClass(clazz.getName())) {
425 return;
426 }
427
428 JavaField[] fields = clazz.getFields();
429 for (JavaField field : fields) {
430 printFieldValue(prefix, field, false, dumpObject.getField(field.getName()));
431 }
432
433 JavaStatic[] statics = clazz.getStatics();
434 for (JavaStatic st: statics) {
435 printFieldValue(prefix, st.getField(), true, st.getValue());
436 }
437 }
438
439 private static void printFieldValue(String prefix, JavaField field, boolean isStatic, JavaThing value) {
440 String logPrefix = prefix + "- " + (isStatic ? "(static) " : "")
441 + field.getName() + " ('" + field.getSignature() + "'): ";
442 if (isNullValue(value)) {
443 log(logPrefix + "null");
444 } else {
445 if (value instanceof JavaObject obj) {
446 logPrefix += "(class '" + obj.getClazz().getName() + "'): ";
447 if (obj.getClazz().isString()) {
448 String dumpStr = getStringValue(obj);
449 log(logPrefix + "\"" + dumpStr + "\"");
450 } else {
451 log(logPrefix + "object " + obj);
452 print(prefix + " ", obj);
453 }
454 } else if (value instanceof JavaObjectArray arr) {
455 log(logPrefix + " array " + arr + " length: " + arr.getLength());
456 JavaThing[] values = arr.getValues();
457 for (int v = 0; v < values.length; v++) {
458 log(prefix + " [" + v + "]: " + values[v]);
459 if (values[v] instanceof JavaObject obj) {
460 print(prefix + " ", obj);
461 }
462 }
463 } else if (value instanceof JavaValueArray arr) {
464 log(logPrefix + "(array of '" + (char)arr.getElementType() + "')" + ": " + arr.valueString());
465 } else {
466 log(logPrefix + "(" + value.getClass() + ")" + value.toString());
467 }
468 }
469 }
470
471 private static boolean isTestClass(String className) {
472 return className.startsWith("TestClass");
473 }
474
475 private static void log(String msg) {
476 System.out.println(msg);
477 System.out.flush();
478 }
479
480 }