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 }