diff --git a/src/hotspot/share/services/heapDumper.cpp b/src/hotspot/share/services/heapDumper.cpp index 40a1906c1d8..9064ee1ecd7 100644 --- a/src/hotspot/share/services/heapDumper.cpp +++ b/src/hotspot/share/services/heapDumper.cpp @@ -40,8 +40,11 @@ #include "oops/klass.inline.hpp" #include "oops/objArrayKlass.hpp" #include "oops/objArrayOop.inline.hpp" +#include "oops/flatArrayKlass.hpp" +#include "oops/flatArrayOop.inline.hpp" #include "oops/oop.inline.hpp" #include "oops/typeArrayOop.inline.hpp" +#include "runtime/fieldDescriptor.inline.hpp" #include "runtime/frame.inline.hpp" #include "runtime/handles.inline.hpp" #include "runtime/javaCalls.hpp" @@ -380,6 +383,22 @@ enum { INITIAL_CLASS_COUNT = 200 }; + +// Inlined fields of primitive classes are dumped as identity objects and require unique object ids. +// We cannot use address of the object in container oop (as we do for identity objects) +// because the address can be the same for inlined object which contains inlined field with offset 0. +class InlinedObjectSupport : public StackObj { + friend class DumpWriter; + InlinedObjectSupport(int initial_value = 1) : _counter(initial_value) {} + + int _counter; + int getId() { return _counter++; } +public: + InlinedObjectSupport save() const { + return InlinedObjectSupport(_counter); + } +}; + // Supports I/O operations for a dump class DumpWriter : public StackObj { @@ -414,6 +433,8 @@ class DumpWriter : public StackObj { // Returns true if we have enough room in the buffer for 'len' bytes. bool can_write_fast(size_t len); + InlinedObjectSupport _inlined_object_support; + public: // Takes ownership of the writer and compressor. DumpWriter(AbstractWriter* writer, AbstractCompressor* compressor); @@ -425,6 +446,8 @@ class DumpWriter : public StackObj { char const* error() const { return _backend.error(); } + InlinedObjectSupport& inlined_object_support() { return _inlined_object_support; } + // writer functions void write_raw(void* s, size_t len); void write_u1(u1 x); @@ -432,6 +455,7 @@ class DumpWriter : public StackObj { void write_u4(u4 x); void write_u8(u8 x); void write_objectID(oop o); + void write_inlinedObjectID(InlinedObjectSupport &inlinedObjectSupport); void write_symbolID(Symbol* o); void write_classID(Klass* k); void write_id(u4 x); @@ -538,6 +562,14 @@ void DumpWriter::write_objectID(oop o) { #endif } +void DumpWriter::write_inlinedObjectID(InlinedObjectSupport& inlinedObjectSupport) { +#ifdef _LP64 + write_u8(inlinedObjectSupport.getId()); +#else + write_u4(inlinedObjectSupport.getId()); +#endif +} + void DumpWriter::write_symbolID(Symbol* s) { address a = (address)((uintptr_t)s); #ifdef _LP64 @@ -637,18 +669,28 @@ class DumperSupport : AllStatic { static void dump_float(DumpWriter* writer, jfloat f); // dump a jdouble static void dump_double(DumpWriter* writer, jdouble d); - // dumps the raw value of the given field - static void dump_field_value(DumpWriter* writer, char type, oop obj, int offset); + // dumps the raw value of the given field; obj and offset specify the object (offset is 0 for identity objects) + // for inlined fields writed object id generated by writer->inlined_object_support() + static void dump_field_value(DumpWriter* writer, const FieldStream& fld, oop obj, int offset); // returns the size of the static fields; also counts the static fields static u4 get_static_fields_size(InstanceKlass* ik, u2& field_count); // dumps static fields of the given class static void dump_static_fields(DumpWriter* writer, Klass* k); - // dump the raw values of the instance fields of the given object - static void dump_instance_fields(DumpWriter* writer, oop o); + // dump the raw values of the instance fields of the given identity or inlined object; + // for identity objects offset is 0 and 'klass' is o->klass(), + // for inlined objects offset is the offset in the holder object, 'klass' is inlined object class + static void dump_instance_fields(DumpWriter* writer, oop o, int offset, Klass* klass); + // dump inlined instance fields of the given object (identity or inlined); + // o is the holder object, offset and klass specify flattened field (or field of flattened field, etc.); + // for identity object offset is 0 and klass is o->klass() + static void dump_inlined_instance_fields(DumpWriter* writer, oop o, int offset, Klass* klass, InlinedObjectSupport &ios); + // get the count of the instance fields for a given class static u2 get_instance_fields_count(InstanceKlass* ik); // dumps the definition of the instance fields for a given class static void dump_instance_field_descriptors(DumpWriter* writer, Klass* k); + // creates HPROF_GC_INSTANCE_DUMP record for the given inlined object + static void dump_inlined_object(DumpWriter* writer, oop holder, int offset, InlineKlass* klass, InlinedObjectSupport& ios); // creates HPROF_GC_INSTANCE_DUMP record for the given object static void dump_instance(DumpWriter* writer, oop o); // creates HPROF_GC_CLASS_DUMP record for the given class and each of its @@ -659,7 +701,8 @@ class DumperSupport : AllStatic { static void dump_basic_type_array_class(DumpWriter* writer, Klass* k); // creates HPROF_GC_OBJ_ARRAY_DUMP record for the given object array - static void dump_object_array(DumpWriter* writer, objArrayOop array); + //static void dump_object_array(DumpWriter* writer, objArrayOop array); + static void dump_object_array(DumpWriter* writer, arrayOop array); // creates HPROF_GC_PRIM_ARRAY_DUMP record for the given type array static void dump_prim_array(DumpWriter* writer, typeArrayOop array); // create HPROF_FRAME record for the given method and bci @@ -693,6 +736,7 @@ void DumperSupport:: write_header(DumpWriter* writer, hprofTag tag, u4 len) { hprofTag DumperSupport::sig2tag(Symbol* sig) { switch (sig->char_at(0)) { case JVM_SIGNATURE_CLASS : return HPROF_NORMAL_OBJECT; + case JVM_SIGNATURE_INLINE_TYPE: return HPROF_NORMAL_OBJECT; case JVM_SIGNATURE_ARRAY : return HPROF_NORMAL_OBJECT; case JVM_SIGNATURE_BYTE : return HPROF_BYTE; case JVM_SIGNATURE_CHAR : return HPROF_CHAR; @@ -723,6 +767,7 @@ hprofTag DumperSupport::type2tag(BasicType type) { u4 DumperSupport::sig2size(Symbol* sig) { switch (sig->char_at(0)) { case JVM_SIGNATURE_CLASS: + case JVM_SIGNATURE_INLINE_TYPE: case JVM_SIGNATURE_ARRAY: return sizeof(address); case JVM_SIGNATURE_BOOLEAN: case JVM_SIGNATURE_BYTE: return 1; @@ -765,9 +810,19 @@ void DumperSupport::dump_double(DumpWriter* writer, jdouble d) { writer->write_u8((u8)u.l); } -// dumps the raw value of the given field -void DumperSupport::dump_field_value(DumpWriter* writer, char type, oop obj, int offset) { +// dumps the raw value of the given field; obj and offset specify the object (offset is 0 for identity objects) +// for inlined fields writed object id generated by writer->inlined_object_support() +void DumperSupport::dump_field_value(DumpWriter* writer, const FieldStream& fld, oop obj, int offset) { + char type = fld.signature()->char_at(0); + offset += fld.offset(); switch (type) { + case JVM_SIGNATURE_INLINE_TYPE: { + if (fld.field_descriptor().is_inlined()) { + writer->write_inlinedObjectID(writer->inlined_object_support()); + break; + } + } + // pass through case JVM_SIGNATURE_CLASS : case JVM_SIGNATURE_ARRAY : { oop o = obj->obj_field_access(offset); @@ -895,8 +950,11 @@ void DumperSupport::dump_static_fields(DumpWriter* writer, Klass* k) { writer->write_symbolID(fld.name()); // name writer->write_u1(sig2tag(sig)); // type + // if this changes, need to handle this properly (dump inlined objects after dump_static_fields) + assert(!fld.field_descriptor().is_inlined(), "static fields cannot be inlined"); + // value - dump_field_value(writer, sig->char_at(0), ik->java_mirror(), fld.offset()); + dump_field_value(writer, fld, ik->java_mirror(), 0); } } @@ -926,19 +984,35 @@ void DumperSupport::dump_static_fields(DumpWriter* writer, Klass* k) { } } + // dump the raw values of the instance fields of the given object -void DumperSupport::dump_instance_fields(DumpWriter* writer, oop o) { - InstanceKlass* ik = InstanceKlass::cast(o->klass()); +void DumperSupport::dump_instance_fields(DumpWriter* writer, oop o, int offset, Klass *klass) { + InstanceKlass* ik = InstanceKlass::cast(klass); for (FieldStream fld(ik, false, false); !fld.eos(); fld.next()) { if (!fld.access_flags().is_static()) { - Symbol* sig = fld.signature(); - dump_field_value(writer, sig->char_at(0), o, fld.offset()); + dump_field_value(writer, fld, o, offset); } } } -// dumps the definition of the instance fields for a given class +void DumperSupport::dump_inlined_instance_fields(DumpWriter *writer, oop o, int offset, Klass *klass, InlinedObjectSupport &ios) { + InstanceKlass* ik = InstanceKlass::cast(klass); + + assert(&ios != &writer->inlined_object_support(), "must be saved copy"); + + for (FieldStream fld(ik, false, false); !fld.eos(); fld.next()) { + if (!fld.access_flags().is_static()) { + if (fld.field_descriptor().is_inlined()) { + InstanceKlass* holder_klass = fld.field_descriptor().field_holder(); + InlineKlass* field_klass = InlineKlass::cast(holder_klass->get_inline_type_field_klass(fld.index())); + dump_inlined_object(writer, o, offset + fld.offset(), field_klass, ios); + } + } + } +} + +// gets the count of the instance fields for a given class u2 DumperSupport::get_instance_fields_count(InstanceKlass* ik) { u2 field_count = 0; @@ -964,6 +1038,37 @@ void DumperSupport::dump_instance_field_descriptors(DumpWriter* writer, Klass* k } } +// creates HPROF_GC_INSTANCE_DUMP record for the given inlined object +void DumperSupport::dump_inlined_object(DumpWriter* writer, oop holder, int offset, InlineKlass* klass, InlinedObjectSupport& ios) { + u4 is = instance_size(klass); + u4 size = 1 + sizeof(address) + 4 + sizeof(address) + 4 + is; + + writer->start_sub_record(HPROF_GC_INSTANCE_DUMP, size); + writer->write_inlinedObjectID(ios); + + writer->write_u4(STACK_TRACE_ID); + + // class ID + writer->write_classID(klass); + + // number of bytes that follow + writer->write_u4(is); + + // the object if flattened, so all fields are stored without headers + // update offset here instead of handling it in both dump_instance_fields and dump_inlined_instance_fields + offset -= klass->first_field_offset(); + + InlinedObjectSupport saved_ios = writer->inlined_object_support().save(); + + // field values + dump_instance_fields(writer, holder, offset, klass); + + writer->end_sub_record(); + + // dump flattened fields + dump_inlined_instance_fields(writer, holder, offset, klass, saved_ios); +} + // creates HPROF_GC_INSTANCE_DUMP record for the given object void DumperSupport::dump_instance(DumpWriter* writer, oop o) { InstanceKlass* ik = InstanceKlass::cast(o->klass()); @@ -980,10 +1085,15 @@ void DumperSupport::dump_instance(DumpWriter* writer, oop o) { // number of bytes that follow writer->write_u4(is); + InlinedObjectSupport saved_ios = writer->inlined_object_support().save(); + // field values - dump_instance_fields(writer, o); + dump_instance_fields(writer, o, 0, o->klass()); writer->end_sub_record(); + + // dump inlined fields + dump_inlined_instance_fields(writer, o, 0, o->klass(), saved_ios); } // creates HPROF_GC_CLASS_DUMP record for the given class and each of @@ -1078,8 +1188,8 @@ void DumperSupport::dump_class_and_array_classes(DumpWriter* writer, Klass* k) { // creates HPROF_GC_CLASS_DUMP record for a given primitive array // class (and each multi-dimensional array class too) void DumperSupport::dump_basic_type_array_class(DumpWriter* writer, Klass* k) { - // array classes - while (k != NULL) { + // array classes + while (k != NULL) { Klass* klass = k; u4 size = 1 + sizeof(address) + 4 + 6 * sizeof(address) + 4 + 2 + 2 + 2; @@ -1114,12 +1224,12 @@ void DumperSupport::dump_basic_type_array_class(DumpWriter* writer, Klass* k) { // which means we need to truncate arrays that are too long. int DumperSupport::calculate_array_max_length(DumpWriter* writer, arrayOop array, short header_size) { BasicType type = ArrayKlass::cast(array->klass())->element_type(); - assert(type >= T_BOOLEAN && type <= T_OBJECT, "invalid array element type"); + assert((type >= T_BOOLEAN && type <= T_OBJECT) || type == T_INLINE_TYPE, "invalid array element type"); int length = array->length(); int type_size; - if (type == T_OBJECT) { + if (type == T_OBJECT || type == T_INLINE_TYPE) { type_size = sizeof(address); } else { type_size = type2aelembytes(type); @@ -1139,7 +1249,7 @@ int DumperSupport::calculate_array_max_length(DumpWriter* writer, arrayOop array } // creates HPROF_GC_OBJ_ARRAY_DUMP record for the given object array -void DumperSupport::dump_object_array(DumpWriter* writer, objArrayOop array) { +void DumperSupport::dump_object_array(DumpWriter* writer, arrayOop array) { // sizeof(u1) + 2 * sizeof(u4) + sizeof(objectID) + sizeof(classID) short header_size = 1 + 2 * 4 + 2 * sizeof(address); int length = calculate_array_max_length(writer, array, header_size); @@ -1153,20 +1263,43 @@ void DumperSupport::dump_object_array(DumpWriter* writer, objArrayOop array) { // array class ID writer->write_classID(array->klass()); - // [id]* elements - for (int index = 0; index < length; index++) { - oop o = array->obj_at(index); - if (o != NULL && log_is_enabled(Debug, cds, heap) && mask_dormant_archived_object(o) == NULL) { - ResourceMark rm; - log_debug(cds, heap)("skipped dormant archived object " INTPTR_FORMAT " (%s) referenced by " INTPTR_FORMAT " (%s)", - p2i(o), o->klass()->external_name(), - p2i(array), array->klass()->external_name()); + InlinedObjectSupport ios = writer->inlined_object_support().save(); + if (array->is_objArray()) { + // [id]* elements + objArrayOop objArray = objArrayOop(array); + for (int index = 0; index < length; index++) { + oop o = objArray->obj_at(index); + if (o != NULL && log_is_enabled(Debug, cds, heap) && mask_dormant_archived_object(o) == NULL) { + ResourceMark rm; + log_debug(cds, heap)("skipped dormant archived object " INTPTR_FORMAT " (%s) referenced by " INTPTR_FORMAT " (%s)", + p2i(o), o->klass()->external_name(), + p2i(array), array->klass()->external_name()); + } + o = mask_dormant_archived_object(o); + writer->write_objectID(o); + } + } else { // flatArray + // [id]* elements + flatArrayOop flatArray = flatArrayOop(array); + for (int index = 0; index < length; index++) { + writer->write_inlinedObjectID(writer->inlined_object_support()); } - o = mask_dormant_archived_object(o); - writer->write_objectID(o); } writer->end_sub_record(); + + if (array->is_flatArray()) { + flatArrayOop flatArray = flatArrayOop(array); + FlatArrayKlass* vaklass = FlatArrayKlass::cast(flatArray->klass()); + InlineKlass* vklass = vaklass->element_klass(); + for (int index = 0; index < length; index++) { + // need offset in the holder to read inlined object. calculate it from flatArrayOop::value_at_addr() + int offset = (int)((address)flatArray->value_at_addr(index, vaklass->layout_helper()) + - cast_from_oop
(flatArray)); + + dump_inlined_object(writer, flatArray, offset, vklass, ios); + } + } } #define WRITE_ARRAY(Array, Type, Size, Length) \ @@ -1437,9 +1570,9 @@ void HeapObjectDumper::do_object(oop o) { if (o->is_instance()) { // create a HPROF_GC_INSTANCE record for each object DumperSupport::dump_instance(writer(), o); - } else if (o->is_objArray()) { + } else if (o->is_objArray() || o->is_flatArray()) { // create a HPROF_GC_OBJ_ARRAY_DUMP record for each object array - DumperSupport::dump_object_array(writer(), objArrayOop(o)); + DumperSupport::dump_object_array(writer(), arrayOop(o)); } else if (o->is_typeArray()) { // create a HPROF_GC_PRIM_ARRAY_DUMP record for each type array DumperSupport::dump_prim_array(writer(), typeArrayOop(o)); diff --git a/test/hotspot/jtreg/serviceability/jvmti/Valhalla/HeapDump/HeapDump.java b/test/hotspot/jtreg/serviceability/jvmti/Valhalla/HeapDump/HeapDump.java new file mode 100644 index 00000000000..c4fd1dc43b9 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/Valhalla/HeapDump/HeapDump.java @@ -0,0 +1,495 @@ +/* + * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @library /test/lib + * @requires vm.jvmti + * @run main HeapDump + */ + +import java.io.File; +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.concurrent.TimeUnit; +import jdk.test.lib.apps.LingeredApp; +import jdk.test.lib.JDKToolLauncher; +import jdk.test.lib.hprof.model.HackJavaValue; +import jdk.test.lib.hprof.model.JavaByte; +import jdk.test.lib.hprof.model.JavaClass; +import jdk.test.lib.hprof.model.JavaField; +import jdk.test.lib.hprof.model.JavaHeapObject; +import jdk.test.lib.hprof.model.JavaObject; +import jdk.test.lib.hprof.model.JavaObjectArray; +import jdk.test.lib.hprof.model.JavaStatic; +import jdk.test.lib.hprof.model.JavaThing; +import jdk.test.lib.hprof.model.JavaValueArray; +import jdk.test.lib.hprof.model.Snapshot; +import jdk.test.lib.process.ProcessTools; + +import java.util.Enumeration; + +import jdk.test.lib.hprof.parser.Reader; + + +class TestClass { + + public static primitive class MyPrimitive { + public byte fld1; + public int fld2; + + public MyPrimitive(int v1, int v2) { fld1 = (byte)v1; fld2 = v2; } + public static MyPrimitive create(int v1, int v2) { return new MyPrimitive(v1, v2); } + } + + public static primitive class PrimitiveHolder { + // offset of the inlined flatObj is the same as offset of inlined PrimitiveHolder + public MyPrimitive flatObj = MyPrimitive.create(12, 142); + + public PrimitiveHolder(int n) { } + public static PrimitiveHolder create(int n) { return new PrimitiveHolder(n); } + } + + // primitive class with reference + public static primitive class MyPrimitiveRef { + public int fld1; + public int fld2; + public String strObj; + + public MyPrimitiveRef(int v1, int v2) { fld1 = v1; fld2 = v2; strObj = "#" + String.valueOf(v1); } + public static MyPrimitiveRef create(int v1, int v2) { return new MyPrimitiveRef(v1, v2); } + } + + public static primitive class PrimitiveHolderRef { + public MyPrimitiveRef[] flatArr = new MyPrimitiveRef[4]; + public MyPrimitiveRef flatObj = MyPrimitiveRef.create(13, 143); + + public PrimitiveHolderRef(int n) { + for (int i = 0; i < flatArr.length; i++) { + flatArr[i] = MyPrimitiveRef.create(i + n + 1, i + n + 11); + } + } + public static PrimitiveHolderRef create(int n) { return new PrimitiveHolderRef(n); } + } + + public MyPrimitive[] flatArr = new MyPrimitive[4]; + public MyPrimitive flatObj = MyPrimitive.create(11, 141); + public MyPrimitiveRef flatObjRef = MyPrimitiveRef.create(11, 144); + public MyPrimitiveRef[] flatArrRef = new MyPrimitiveRef[4]; + public String strObj = "targ.strObj"; + + public Object nullObj; + + public final PrimitiveHolder primHolder = PrimitiveHolder.create(16); + // array of compound primitive objects + public final PrimitiveHolderRef[] primHolderArr = new PrimitiveHolderRef[4]; + + + // static inlined fields + public static MyPrimitive flatObjStatic = MyPrimitive.create(11, 241); + public static MyPrimitiveRef[] flatArrRefStatic = new MyPrimitiveRef[4]; + static { + for (int i = 0; i < flatArrRefStatic.length; i++) { + flatArrRefStatic[i] = MyPrimitiveRef.create(i + 200, i + 225); + } + } + + public TestClass() { + for (int i = 0; i < flatArr.length; i++) { + flatArr[i] = MyPrimitive.create(i + 10, i + 110); + } + for (int i = 0; i < flatArrRef.length; i++) { + flatArrRef[i] = MyPrimitiveRef.create(i + 100, i + 120); + } + for (int i = 0; i < primHolderArr.length; i++) { + primHolderArr[i] = PrimitiveHolderRef.create(20+i); + } + } +} + +class HeapDumpTarg extends LingeredApp { + + public static void main(String[] args) { + TestClass testObj = new TestClass(); + LingeredApp.main(args); + System.out.println(testObj); + } + +} + +public class HeapDump { + + public static void copyDirectory(String sourceDirectoryLocation, String destinationDirectoryLocation) + throws Exception { + Files.walk(Paths.get(sourceDirectoryLocation)) + .forEach(source -> { + Path destination = Paths.get(destinationDirectoryLocation, source.toString() + .substring(sourceDirectoryLocation.length())); + try { + Files.copy(source, destination); + } catch (Exception e) { + e.printStackTrace(); + } + }); + } + + public static void main(String[] args) throws Throwable { + LingeredApp theApp = null; + String hprogFile = new File(System.getProperty("test.classes") + "/Myheapdump.hprof").getAbsolutePath(); + try { + theApp = new HeapDumpTarg(); + + LingeredApp.startApp(theApp/*, "-XX:+PrintInlineLayout"*/); + + //jcmd GC.heap_dump + JDKToolLauncher launcher = JDKToolLauncher + .createUsingTestJDK("jcmd") + .addToolArg(Long.toString(theApp.getPid())) + .addToolArg("GC.heap_dump") + .addToolArg(hprogFile); + Process jcmd = ProcessTools.startProcess("jcmd", new ProcessBuilder(launcher.getCommand())); + // If something goes wrong with heap dumping most likely we'll get crash of the target VM + while (!jcmd.waitFor(5, TimeUnit.SECONDS)) { + if (!theApp.getProcess().isAlive()) { + log("ERROR: target VM died, killing jcmd..."); + jcmd.destroyForcibly(); + throw new Exception("Target VM died"); + } + } + + if (jcmd.exitValue() != 0) { + throw new Exception("Jcmd exited with code " + jcmd.exitValue()); + } + } finally { + try { + copyDirectory(".", System.getProperty("test.classes") + "/scratch"); + } finally { + LingeredApp.stopApp(theApp); + } + + } + + // test object to compare + TestClass testObj = new TestClass(); + + log("Reading " + hprogFile + "..."); + try (Snapshot snapshot = Reader.readFile(hprogFile,true, 0)) { + log("Snapshot read, resolving..."); + snapshot.resolve(true); + log("Snapshot resolved."); + + JavaObject dumpObj = findObject(snapshot, testObj.getClass().getName()); + + log(""); + print(dumpObj); + + log(""); + log("Verifying object " + testObj.getClass().getName() + " (dumped object " + dumpObj + ")"); + compareObjectFields(" ", testObj, dumpObj); + } + } + + private static JavaObject findObject(Snapshot snapshot, String className) throws Exception { + log("looking for " + className + "..."); + JavaClass jClass = snapshot.findClass(className); + if (jClass == null) { + throw new Exception("'" + className + "' not found"); + } + Enumeration objects = jClass.getInstances(false); + if (!objects.hasMoreElements()) { + throw new Exception("No '" + className + "' instances found"); + } + JavaHeapObject heapObj = objects.nextElement(); + if (objects.hasMoreElements()) { + throw new Exception("More than 1 instances of '" + className + "' found"); + } + if (!(heapObj instanceof JavaObject)) { + throw new Exception("'" + className + "' instance is not JavaObject (" + heapObj.getClass() + ")"); + } + return (JavaObject)heapObj; + } + + // description of the current object for logging and error reporting + private static String objDescr; + private static boolean errorReported = false; + + private static void compareObjects(String logPrefix, Object testObj, JavaThing dumpObj) throws Exception { + if (testObj == null) { + if (!isNullValue(dumpObj)) { + throw new Exception("null expected, but dumped object is " + dumpObj); + } + log(logPrefix + objDescr + ": null"); + } else if (dumpObj instanceof JavaObject obj) { + // special handling for Strings + // we know testValue != null + if (testObj instanceof String testStr) { + objDescr += " (String, " + obj.getIdString() + ")"; + if (!obj.getClazz().isString()) { + throw new Exception("value (" + obj + ")" + + " is not String (" + obj.getClazz() + ")"); + } + String dumpStr = getStringValue(obj); + if (!testStr.equals(dumpStr)) { + throw new Exception("different values:" + + " expected \"" + testStr + "\", actual \"" + dumpStr + "\""); + } + log(logPrefix + objDescr + ": \"" + testStr + "\" ( == \"" + dumpStr + "\")"); + } else { + // other Object + log(logPrefix + objDescr + ": Object " + obj); + if (isTestClass(obj.getClazz().getName())) { + compareObjectFields(logPrefix + " ", testObj, obj); + } + } + } else { + throw new Exception("Object expected, but the value (" + dumpObj + ")" + + " is not JavaObject (" + dumpObj.getClass() + ")"); + } + } + + private static void compareObjectFields(String logPrefix, Object testObj, JavaObject dumpObj) throws Exception { + Field[] fields = testObj.getClass().getDeclaredFields(); + for (Field testField : fields) { + boolean isStatic = Modifier.isStatic(testField.getModifiers()); + testField.setAccessible(true); + objDescr = "- " + (isStatic ? "(static) " : "") + + testField.getName() + " ('" + testField.getType().descriptorString() + "')"; + try { + Object testValue = testField.get(testObj); + + JavaField dumpField = getField(dumpObj, testField.getName(), isStatic); + JavaThing dumpValue = isStatic + ? dumpObj.getClazz().getStaticField(dumpField.getName()) + : dumpObj.getField(dumpField.getName()); + + objDescr += ", dump signature '" + dumpField.getSignature() + "'"; + + compareType(testField, dumpField); + + if (testValue == null) { + if (!isNullValue(dumpValue)) { + throw new Exception("null expected, but dumped object is " + dumpValue); + } + log(logPrefix + objDescr + ": null"); + } else { + switch (testField.getType().descriptorString().charAt(0)) { + case 'L': + case 'Q': + compareObjects(logPrefix, testValue, dumpValue); + break; + case '[': + int testLength = Array.getLength(testValue); + objDescr += " (Array of '" + testField.getType().getComponentType() + "'" + + ", length = " + testLength + ", " + dumpValue + ")"; + if (dumpValue instanceof JavaValueArray arr) { + // array of primitive type + char testElementType = testField.getType().getComponentType().descriptorString().charAt(0); + if ((char) arr.getElementType() != testElementType) { + throw new Exception("wrong element type: '" + (char) arr.getElementType() + "'"); + } + int dumpLength = arr.getLength(); + if (dumpLength != testLength) { + throw new Exception("wrong array size: " + dumpLength); + } + JavaThing[] dumpElements = arr.getElements(); + log(logPrefix + objDescr); + for (int j = 0; j < testLength; j++) { + Object elementValue = Array.get(testValue, j); + objDescr = "[" + j + "]"; + comparePrimitiveValues(elementValue, dumpElements[j]); + log(logPrefix + " [" + j + "]: " + elementValue + " ( == " + dumpElements[j] + ")"); + } + } else if (dumpValue instanceof JavaObjectArray arr) { + int dumpLength = arr.getLength(); + if (dumpLength != testLength) { + throw new Exception("wrong array size: " + dumpLength); + } + JavaThing[] dumpElements = arr.getElements(); + log(logPrefix + objDescr); + for (int j = 0; j < testLength; j++) { + Object elementValue = Array.get(testValue, j); + objDescr = "[" + j + "]"; + compareObjects(logPrefix + " ", elementValue, dumpElements[j]); + } + } else { + throw new Exception("Array expected, but the value (" + dumpValue + ")" + + " is neither JavaValueArray nor JavaObjectArray" + + " (" + dumpValue.getClass() + ")"); + } + break; + default: + comparePrimitiveValues(testValue, dumpValue); + log(logPrefix + objDescr + ": " + testValue + " ( == " + dumpValue + ")"); + break; + } + } + } catch (Exception ex) { + if (!errorReported) { + log(logPrefix + objDescr + ": ERROR - " + ex.getMessage()); + errorReported = true; + } + throw ex; + } + } + } + + private static JavaField getField(JavaObject obj, String fieldName, boolean isStatic) throws Exception { + if (isStatic) { + JavaStatic[] statics = obj.getClazz().getStatics(); + for (JavaStatic st: statics) { + if (st.getField().getName().equals(fieldName)) { + return st.getField(); + } + } + } else { + JavaField[] fields = obj.getClazz().getFields(); + for (JavaField field : fields) { + if (fieldName.equals(field.getName())) { + return field; + } + } + } + throw new Exception("field '" + fieldName + "' not found"); + } + + private static void compareType(Field field, JavaField dumpField) throws Exception { + String sig = field.getType().descriptorString(); + char type = sig.charAt(0); + if (type == '[' || type == 'Q') { + type = 'L'; + } + if (dumpField.getSignature().charAt(0) != type) { + throw new Exception("type mismatch:" + + " expected '" + type + "' (" + sig + ")" + + ", found '" + dumpField.getSignature().charAt(0) + "' (" + dumpField.getSignature() + ")"); + } + } + + private static void comparePrimitiveValues(Object testValue, JavaThing dumpValue) throws Exception { + // JavaByte.toString() returns hex + String testStr = testValue instanceof Byte byteValue + ? (new JavaByte(byteValue)).toString() + : String.valueOf(testValue); + String dumpStr = dumpValue.toString(); + if (!testStr.equals(dumpStr)) { + throw new Exception("Wrong value: expected " + testStr + ", actual " + dumpStr); + } + } + + private static boolean isNullValue(JavaThing value) { + return value == null + // dumped value is HackJavaValue with string representation "" + || (value instanceof HackJavaValue && "".equals(value.toString())); + } + + private static String getStringValue(JavaObject value) { + JavaThing valueObj = value.getField("value"); + if (valueObj instanceof JavaValueArray valueArr) { + try { + if (valueArr.getElementType() == 'B') { + Field valueField = JavaByte.class.getDeclaredField("value"); + valueField.setAccessible(true); + JavaThing[] things = valueArr.getElements(); + byte[] bytes = new byte[things.length]; + for (int i = 0; i < things.length; i++) { + bytes[i] = valueField.getByte(things[i]); + } + return new String(bytes); + } + } catch (Exception ignored) { + } + return valueArr.valueString(); + } else { + return null; + } + } + + private static void print(JavaObject dumpObject) { + log("Dumped object " + dumpObject + ":"); + print("", dumpObject); + } + + private static void print(String prefix, JavaObject dumpObject) { + JavaClass clazz = dumpObject.getClazz(); + // print only test classes + if (!isTestClass(clazz.getName())) { + return; + } + + JavaField[] fields = clazz.getFields(); + for (JavaField field : fields) { + printFieldValue(prefix, field, false, dumpObject.getField(field.getName())); + } + + JavaStatic[] statics = clazz.getStatics(); + for (JavaStatic st: statics) { + printFieldValue(prefix, st.getField(), true, st.getValue()); + } + } + + private static void printFieldValue(String prefix, JavaField field, boolean isStatic, JavaThing value) { + String logPrefix = prefix + "- " + (isStatic ? "(static) " : "") + + field.getName() + " ('" + field.getSignature() + "'): "; + if (isNullValue(value)) { + log(logPrefix + "null"); + } else { + if (value instanceof JavaObject obj) { + logPrefix += "(class '" + obj.getClazz().getName() + "'): "; + if (obj.getClazz().isString()) { + String dumpStr = getStringValue(obj); + log(logPrefix + "\"" + dumpStr + "\""); + } else { + log(logPrefix + "object " + obj); + print(prefix + " ", obj); + } + } else if (value instanceof JavaObjectArray arr) { + log(logPrefix + " array " + arr + " length: " + arr.getLength()); + JavaThing[] values = arr.getValues(); + for (int v = 0; v < values.length; v++) { + log(prefix + " [" + v + "]: " + values[v]); + if (values[v] instanceof JavaObject obj) { + print(prefix + " ", obj); + } + } + } else if (value instanceof JavaValueArray arr) { // array of primitive type + log(logPrefix + "(array of '" + (char) arr.getElementType() + "')" + ": " + arr.valueString()); + } else { + log(logPrefix + value.toString()); + } + } + } + + private static boolean isTestClass(String className) { + return className.startsWith("TestClass"); + } + + private static void log(String msg) { + System.out.println(msg); + System.out.flush(); + } + +} diff --git a/test/lib/jdk/test/lib/hprof/model/JavaValueArray.java b/test/lib/jdk/test/lib/hprof/model/JavaValueArray.java index 12ca34dd39b..1060fdf7e6d 100644 --- a/test/lib/jdk/test/lib/hprof/model/JavaValueArray.java +++ b/test/lib/jdk/test/lib/hprof/model/JavaValueArray.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2021, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -69,17 +69,17 @@ private static String arrayTypeName(byte sig) { private static int elementSize(byte type) { switch (type) { - case T_BYTE: - case T_BOOLEAN: + case 'B': + case 'Z': return 1; - case T_CHAR: - case T_SHORT: + case 'C': + case 'S': return 2; - case T_INT: - case T_FLOAT: + case 'I': + case 'F': return 4; - case T_LONG: - case T_DOUBLE: + case 'J': + case 'D': return 8; default: throw new RuntimeException("invalid array element type: " + type);