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