From e731693fd63bfa7d528d8aeaccc1118b920af1db Mon Sep 17 00:00:00 2001 From: Andreas Gampe Date: Sat, 25 Feb 2017 09:15:05 -0800 Subject: [PATCH] ART: Add primitive field reporting Add support for primitive_field_callback. Bug: 31385354 Test: m test-art-host-run-test-906-iterate-heap Test: m test-art-host-run-test-913-heaps Change-Id: I4a700813ae11cc3ab49fd5738e0a2cce8a0002ba --- runtime/openjdkjvmti/ti_heap.cc | 448 +++++++++++++++++++++++++++++++++- test/906-iterate-heap/expected.txt | 24 ++ test/906-iterate-heap/iterate_heap.cc | 87 +++++++ test/906-iterate-heap/src/Main.java | 80 ++++++ test/913-heaps/expected.txt | 24 ++ test/913-heaps/heaps.cc | 90 +++++++ test/913-heaps/src/Main.java | 83 +++++++ 7 files changed, 835 insertions(+), 1 deletion(-) diff --git a/runtime/openjdkjvmti/ti_heap.cc b/runtime/openjdkjvmti/ti_heap.cc index c7294a9b8..5f594891a 100644 --- a/runtime/openjdkjvmti/ti_heap.cc +++ b/runtime/openjdkjvmti/ti_heap.cc @@ -162,6 +162,439 @@ jint ReportPrimitiveArray(art::ObjPtr obj, return 0; } +template +bool VisitorFalse(art::ObjPtr obj ATTRIBUTE_UNUSED, + art::ObjPtr klass ATTRIBUTE_UNUSED, + art::ArtField& field ATTRIBUTE_UNUSED, + size_t field_index ATTRIBUTE_UNUSED, + UserData* user_data ATTRIBUTE_UNUSED) { + return false; +} + +template +class FieldVisitor { + public: + // Report the contents of a primitive fields of the given object, if a callback is set. + static bool ReportFields(art::ObjPtr obj, + UserData* user_data, + StaticPrimitiveVisitor& static_prim_visitor, + StaticReferenceVisitor& static_ref_visitor, + InstancePrimitiveVisitor& instance_prim_visitor, + InstanceReferenceVisitor& instance_ref_visitor) + REQUIRES_SHARED(art::Locks::mutator_lock_) { + FieldVisitor fv(user_data); + + if (obj->IsClass()) { + // When visiting a class, we only visit the static fields of the given class. No field of + // superclasses is visited. + art::ObjPtr klass = obj->AsClass(); + // Only report fields on resolved classes. We need valid field data. + if (!klass->IsResolved()) { + return false; + } + return fv.ReportFieldsImpl(nullptr, + obj->AsClass(), + obj->AsClass()->IsInterface(), + static_prim_visitor, + static_ref_visitor, + instance_prim_visitor, + instance_ref_visitor); + } else { + // See comment above. Just double-checking here, but an instance *should* mean the class was + // resolved. + DCHECK(obj->GetClass()->IsResolved() || obj->GetClass()->IsErroneousResolved()); + return fv.ReportFieldsImpl(obj, + obj->GetClass(), + false, + static_prim_visitor, + static_ref_visitor, + instance_prim_visitor, + instance_ref_visitor); + } + } + + private: + explicit FieldVisitor(UserData* user_data) : user_data_(user_data) {} + + // Report the contents of fields of the given object. If obj is null, report the static fields, + // otherwise the instance fields. + bool ReportFieldsImpl(art::ObjPtr obj, + art::ObjPtr klass, + bool skip_java_lang_object, + StaticPrimitiveVisitor& static_prim_visitor, + StaticReferenceVisitor& static_ref_visitor, + InstancePrimitiveVisitor& instance_prim_visitor, + InstanceReferenceVisitor& instance_ref_visitor) + REQUIRES_SHARED(art::Locks::mutator_lock_) { + // Compute the offset of field indices. + size_t interface_field_count = CountInterfaceFields(klass); + + size_t tmp; + bool aborted = ReportFieldsRecursive(obj, + klass, + interface_field_count, + skip_java_lang_object, + static_prim_visitor, + static_ref_visitor, + instance_prim_visitor, + instance_ref_visitor, + &tmp); + return aborted; + } + + // Visit primitive fields in an object (instance). Return true if the visit was aborted. + bool ReportFieldsRecursive(art::ObjPtr obj, + art::ObjPtr klass, + size_t interface_fields, + bool skip_java_lang_object, + StaticPrimitiveVisitor& static_prim_visitor, + StaticReferenceVisitor& static_ref_visitor, + InstancePrimitiveVisitor& instance_prim_visitor, + InstanceReferenceVisitor& instance_ref_visitor, + size_t* field_index_out) + REQUIRES_SHARED(art::Locks::mutator_lock_) { + DCHECK(klass != nullptr); + size_t field_index; + if (klass->GetSuperClass() == nullptr) { + // j.l.Object. Start with the fields from interfaces. + field_index = interface_fields; + if (skip_java_lang_object) { + *field_index_out = field_index; + return false; + } + } else { + // Report superclass fields. + if (kCallVisitorOnRecursion) { + if (ReportFieldsRecursive(obj, + klass->GetSuperClass(), + interface_fields, + skip_java_lang_object, + static_prim_visitor, + static_ref_visitor, + instance_prim_visitor, + instance_ref_visitor, + &field_index)) { + return true; + } + } else { + // Still call, but with empty visitor. This is required for correct counting. + ReportFieldsRecursive(obj, + klass->GetSuperClass(), + interface_fields, + skip_java_lang_object, + VisitorFalse, + VisitorFalse, + VisitorFalse, + VisitorFalse, + &field_index); + } + } + + // Now visit fields for the current klass. + + for (auto& static_field : klass->GetSFields()) { + if (static_field.IsPrimitiveType()) { + if (static_prim_visitor(obj, + klass, + static_field, + field_index, + user_data_)) { + return true; + } + } else { + if (static_ref_visitor(obj, + klass, + static_field, + field_index, + user_data_)) { + return true; + } + } + field_index++; + } + + for (auto& instance_field : klass->GetIFields()) { + if (instance_field.IsPrimitiveType()) { + if (instance_prim_visitor(obj, + klass, + instance_field, + field_index, + user_data_)) { + return true; + } + } else { + if (instance_ref_visitor(obj, + klass, + instance_field, + field_index, + user_data_)) { + return true; + } + } + field_index++; + } + + *field_index_out = field_index; + return false; + } + + // Implements a visit of the implemented interfaces of a given class. + template + struct RecursiveInterfaceVisit { + static void VisitStatic(art::Thread* self, art::ObjPtr klass, T& visitor) + REQUIRES_SHARED(art::Locks::mutator_lock_) { + RecursiveInterfaceVisit rv; + rv.Visit(self, klass, visitor); + } + + void Visit(art::Thread* self, art::ObjPtr klass, T& visitor) + REQUIRES_SHARED(art::Locks::mutator_lock_) { + // First visit the parent, to get the order right. + // (We do this in preparation for actual visiting of interface fields.) + if (klass->GetSuperClass() != nullptr) { + Visit(self, klass->GetSuperClass(), visitor); + } + for (uint32_t i = 0; i != klass->NumDirectInterfaces(); ++i) { + art::ObjPtr inf_klass = + art::mirror::Class::GetDirectInterface(self, klass, i); + DCHECK(inf_klass != nullptr); + VisitInterface(self, inf_klass, visitor); + } + } + + void VisitInterface(art::Thread* self, art::ObjPtr inf_klass, T& visitor) + REQUIRES_SHARED(art::Locks::mutator_lock_) { + auto it = visited_interfaces.find(inf_klass.Ptr()); + if (it != visited_interfaces.end()) { + return; + } + visited_interfaces.insert(inf_klass.Ptr()); + + // Let the visitor know about this one. Note that this order is acceptable, as the ordering + // of these fields never matters for known visitors. + visitor(inf_klass); + + // Now visit the superinterfaces. + for (uint32_t i = 0; i != inf_klass->NumDirectInterfaces(); ++i) { + art::ObjPtr super_inf_klass = + art::mirror::Class::GetDirectInterface(self, inf_klass, i); + DCHECK(super_inf_klass != nullptr); + VisitInterface(self, super_inf_klass, visitor); + } + } + + std::unordered_set visited_interfaces; + }; + + // Counting interface fields. Note that we cannot use the interface table, as that only contains + // "non-marker" interfaces (= interfaces with methods). + static size_t CountInterfaceFields(art::ObjPtr klass) + REQUIRES_SHARED(art::Locks::mutator_lock_) { + size_t count = 0; + auto visitor = [&count](art::ObjPtr inf_klass) + REQUIRES_SHARED(art::Locks::mutator_lock_) { + DCHECK(inf_klass->IsInterface()); + DCHECK_EQ(0u, inf_klass->NumInstanceFields()); + count += inf_klass->NumStaticFields(); + }; + RecursiveInterfaceVisit::VisitStatic(art::Thread::Current(), klass, visitor); + return count; + + // TODO: Implement caching. + } + + UserData* user_data_; +}; + +// Debug helper. Prints the structure of an object. +template +struct DumpVisitor { + static bool Callback(art::ObjPtr obj ATTRIBUTE_UNUSED, + art::ObjPtr klass ATTRIBUTE_UNUSED, + art::ArtField& field, + size_t field_index, + void* user_data ATTRIBUTE_UNUSED) + REQUIRES_SHARED(art::Locks::mutator_lock_) { + LOG(ERROR) << (kStatic ? "static " : "instance ") + << (kRef ? "ref " : "primitive ") + << field.PrettyField() + << " @ " + << field_index; + return false; + } +}; +ATTRIBUTE_UNUSED +void DumpObjectFields(art::ObjPtr obj) + REQUIRES_SHARED(art::Locks::mutator_lock_) { + if (obj->IsClass()) { + FieldVisitor::Callback), + decltype(DumpVisitor::Callback), + decltype(DumpVisitor::Callback), + decltype(DumpVisitor::Callback), + void, + false>:: + ReportFields(obj, + nullptr, + DumpVisitor::Callback, + DumpVisitor::Callback, + DumpVisitor::Callback, + DumpVisitor::Callback); + } else { + FieldVisitor::Callback), + decltype(DumpVisitor::Callback), + decltype(DumpVisitor::Callback), + decltype(DumpVisitor::Callback), + void, + true>:: + ReportFields(obj, + nullptr, + DumpVisitor::Callback, + DumpVisitor::Callback, + DumpVisitor::Callback, + DumpVisitor::Callback); + } +} + +class ReportPrimitiveField { + public: + static bool Report(art::ObjPtr obj, + ObjectTagTable* tag_table, + const jvmtiHeapCallbacks* cb, + const void* user_data) + REQUIRES_SHARED(art::Locks::mutator_lock_) { + if (UNLIKELY(cb->primitive_field_callback != nullptr)) { + jlong class_tag = tag_table->GetTagOrZero(obj->GetClass()); + ReportPrimitiveField rpf(tag_table, class_tag, cb, user_data); + if (obj->IsClass()) { + return FieldVisitor), + decltype(VisitorFalse), + decltype(VisitorFalse), + decltype(VisitorFalse), + ReportPrimitiveField, + false>:: + ReportFields(obj, + &rpf, + ReportPrimitiveFieldCallback, + VisitorFalse, + VisitorFalse, + VisitorFalse); + } else { + return FieldVisitor), + decltype(VisitorFalse), + decltype(ReportPrimitiveFieldCallback), + decltype(VisitorFalse), + ReportPrimitiveField, + true>:: + ReportFields(obj, + &rpf, + VisitorFalse, + VisitorFalse, + ReportPrimitiveFieldCallback, + VisitorFalse); + } + } + return false; + } + + + private: + ReportPrimitiveField(ObjectTagTable* tag_table, + jlong class_tag, + const jvmtiHeapCallbacks* cb, + const void* user_data) + : tag_table_(tag_table), class_tag_(class_tag), cb_(cb), user_data_(user_data) {} + + template + static bool ReportPrimitiveFieldCallback(art::ObjPtr obj, + art::ObjPtr klass, + art::ArtField& field, + size_t field_index, + ReportPrimitiveField* user_data) + REQUIRES_SHARED(art::Locks::mutator_lock_) { + art::Primitive::Type art_prim_type = field.GetTypeAsPrimitiveType(); + jvmtiPrimitiveType prim_type = + static_cast(art::Primitive::Descriptor(art_prim_type)[0]); + DCHECK(prim_type == JVMTI_PRIMITIVE_TYPE_BOOLEAN || + prim_type == JVMTI_PRIMITIVE_TYPE_BYTE || + prim_type == JVMTI_PRIMITIVE_TYPE_CHAR || + prim_type == JVMTI_PRIMITIVE_TYPE_SHORT || + prim_type == JVMTI_PRIMITIVE_TYPE_INT || + prim_type == JVMTI_PRIMITIVE_TYPE_LONG || + prim_type == JVMTI_PRIMITIVE_TYPE_FLOAT || + prim_type == JVMTI_PRIMITIVE_TYPE_DOUBLE); + jvmtiHeapReferenceInfo info; + info.field.index = field_index; + + jvalue value; + memset(&value, 0, sizeof(jvalue)); + art::ObjPtr src = kReportStatic ? klass : obj; + switch (art_prim_type) { + case art::Primitive::Type::kPrimBoolean: + value.z = field.GetBoolean(src) == 0 ? JNI_FALSE : JNI_TRUE; + break; + case art::Primitive::Type::kPrimByte: + value.b = field.GetByte(src); + break; + case art::Primitive::Type::kPrimChar: + value.c = field.GetChar(src); + break; + case art::Primitive::Type::kPrimShort: + value.s = field.GetShort(src); + break; + case art::Primitive::Type::kPrimInt: + value.i = field.GetInt(src); + break; + case art::Primitive::Type::kPrimLong: + value.j = field.GetLong(src); + break; + case art::Primitive::Type::kPrimFloat: + value.f = field.GetFloat(src); + break; + case art::Primitive::Type::kPrimDouble: + value.d = field.GetDouble(src); + break; + case art::Primitive::Type::kPrimVoid: + case art::Primitive::Type::kPrimNot: { + LOG(FATAL) << "Should not reach here"; + UNREACHABLE(); + } + } + + jlong obj_tag = user_data->tag_table_->GetTagOrZero(src.Ptr()); + const jlong saved_obj_tag = obj_tag; + + jint ret = user_data->cb_->primitive_field_callback(kReportStatic + ? JVMTI_HEAP_REFERENCE_STATIC_FIELD + : JVMTI_HEAP_REFERENCE_FIELD, + &info, + user_data->class_tag_, + &obj_tag, + value, + prim_type, + const_cast(user_data->user_data_)); + + if (saved_obj_tag != obj_tag) { + user_data->tag_table_->Set(src.Ptr(), obj_tag); + } + + if ((ret & JVMTI_VISIT_ABORT) != 0) { + return true; + } + + return false; + } + + ObjectTagTable* tag_table_; + jlong class_tag_; + const jvmtiHeapCallbacks* cb_; + const void* user_data_; +}; + struct HeapFilter { explicit HeapFilter(jint heap_filter) : filter_out_tagged((heap_filter & JVMTI_HEAP_FILTER_TAGGED) != 0), @@ -292,7 +725,12 @@ static void IterateThroughHeapObjectCallback(art::mirror::Object* obj, void* arg ithd->stop_reports = (array_ret & JVMTI_VISIT_ABORT) != 0; } - // TODO Implement primitive field callback. + if (!ithd->stop_reports) { + ithd->stop_reports = ReportPrimitiveField::Report(obj, + ithd->heap_util->GetTags(), + ithd->callbacks, + ithd->user_data); + } } jvmtiError HeapUtil::IterateThroughHeap(jvmtiEnv* env, @@ -626,6 +1064,10 @@ class FollowReferencesHelper FINAL { jint string_ret = ReportString(obj, env, tag_table_, callbacks_, user_data_); stop_reports_ = (string_ret & JVMTI_VISIT_ABORT) != 0; } + + if (!stop_reports_) { + stop_reports_ = ReportPrimitiveField::Report(obj, tag_table_, callbacks_, user_data_); + } } void VisitArray(art::mirror::Object* array) @@ -739,6 +1181,10 @@ class FollowReferencesHelper FINAL { } } } + + if (!stop_reports_) { + stop_reports_ = ReportPrimitiveField::Report(klass, tag_table_, callbacks_, user_data_); + } } void MaybeEnqueue(art::mirror::Object* obj) REQUIRES_SHARED(art::Locks::mutator_lock_) { diff --git a/test/906-iterate-heap/expected.txt b/test/906-iterate-heap/expected.txt index 3e857ab00..b6af8435d 100644 --- a/test/906-iterate-heap/expected.txt +++ b/test/906-iterate-heap/expected.txt @@ -18,3 +18,27 @@ 2 1@0 (32, 2xD '0000000000000000000000000000f03f') 2 +10000@0 (static, int, index=3) 0000000000000000 +10001 +10000@0 (static, int, index=11) 0000000000000000 +10001 +10000@0 (static, int, index=0) 0000000000000000 +10001 +10000@0 (static, int, index=1) 0000000000000000 +10001 +10000@0 (instance, int, index=2) 0000000000000000 +10001@0 (instance, byte, index=4) 0000000000000001 +10002@0 (instance, char, index=5) 0000000000000061 +10003@0 (instance, int, index=6) 0000000000000003 +10004@0 (instance, long, index=7) 0000000000000004 +10005@0 (instance, short, index=9) 0000000000000002 +10006 +10000@0 (instance, int, index=3) 0000000000000000 +10001@0 (instance, byte, index=5) 0000000000000001 +10002@0 (instance, char, index=6) 0000000000000061 +10003@0 (instance, int, index=7) 0000000000000003 +10004@0 (instance, long, index=8) 0000000000000004 +10005@0 (instance, short, index=10) 0000000000000002 +10006@0 (instance, double, index=12) 3ff3ae147ae147ae +10007@0 (instance, float, index=13) 000000003f9d70a4 +10008 diff --git a/test/906-iterate-heap/iterate_heap.cc b/test/906-iterate-heap/iterate_heap.cc index 890220ee8..13c3562b6 100644 --- a/test/906-iterate-heap/iterate_heap.cc +++ b/test/906-iterate-heap/iterate_heap.cc @@ -322,5 +322,92 @@ extern "C" JNIEXPORT jstring JNICALL Java_Main_iterateThroughHeapPrimitiveArray( return env->NewStringUTF(fac.data.c_str()); } +static constexpr const char* GetPrimitiveTypeName(jvmtiPrimitiveType type) { + switch (type) { + case JVMTI_PRIMITIVE_TYPE_BOOLEAN: + return "boolean"; + case JVMTI_PRIMITIVE_TYPE_BYTE: + return "byte"; + case JVMTI_PRIMITIVE_TYPE_CHAR: + return "char"; + case JVMTI_PRIMITIVE_TYPE_SHORT: + return "short"; + case JVMTI_PRIMITIVE_TYPE_INT: + return "int"; + case JVMTI_PRIMITIVE_TYPE_FLOAT: + return "float"; + case JVMTI_PRIMITIVE_TYPE_LONG: + return "long"; + case JVMTI_PRIMITIVE_TYPE_DOUBLE: + return "double"; + } + LOG(FATAL) << "Unknown type " << static_cast(type); + UNREACHABLE(); +} + +extern "C" JNIEXPORT jstring JNICALL Java_Main_iterateThroughHeapPrimitiveFields( + JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jlong tag) { + struct FindFieldCallbacks { + explicit FindFieldCallbacks(jlong t) : tag_to_find(t) {} + + static jint JNICALL HeapIterationCallback(jlong class_tag ATTRIBUTE_UNUSED, + jlong size ATTRIBUTE_UNUSED, + jlong* tag_ptr ATTRIBUTE_UNUSED, + jint length ATTRIBUTE_UNUSED, + void* user_data ATTRIBUTE_UNUSED) { + return 0; + } + + static jint JNICALL PrimitiveFieldValueCallback(jvmtiHeapReferenceKind kind, + const jvmtiHeapReferenceInfo* info, + jlong class_tag, + jlong* tag_ptr, + jvalue value, + jvmtiPrimitiveType value_type, + void* user_data) { + FindFieldCallbacks* p = reinterpret_cast(user_data); + if (*tag_ptr >= p->tag_to_find) { + std::ostringstream oss; + oss << *tag_ptr + << '@' + << class_tag + << " (" + << (kind == JVMTI_HEAP_REFERENCE_FIELD ? "instance, " : "static, ") + << GetPrimitiveTypeName(value_type) + << ", index=" + << info->field.index + << ") "; + // Be lazy, always print eight bytes. + static_assert(sizeof(jvalue) == sizeof(uint64_t), "Unexpected jvalue size"); + uint64_t val; + memcpy(&val, &value, sizeof(uint64_t)); // To avoid undefined behavior. + oss << android::base::StringPrintf("%016" PRIx64, val); + + if (!p->data.empty()) { + p->data += "\n"; + } + p->data += oss.str(); + *tag_ptr = *tag_ptr + 1; + } + return 0; + } + + std::string data; + const jlong tag_to_find; + }; + + jvmtiHeapCallbacks callbacks; + memset(&callbacks, 0, sizeof(jvmtiHeapCallbacks)); + callbacks.heap_iteration_callback = FindFieldCallbacks::HeapIterationCallback; + callbacks.primitive_field_callback = FindFieldCallbacks::PrimitiveFieldValueCallback; + + FindFieldCallbacks ffc(tag); + jvmtiError ret = jvmti_env->IterateThroughHeap(0, nullptr, &callbacks, &ffc); + if (JvmtiErrorToException(env, ret)) { + return nullptr; + } + return env->NewStringUTF(ffc.data.c_str()); +} + } // namespace Test906IterateHeap } // namespace art diff --git a/test/906-iterate-heap/src/Main.java b/test/906-iterate-heap/src/Main.java index d4998865b..365ce0f21 100644 --- a/test/906-iterate-heap/src/Main.java +++ b/test/906-iterate-heap/src/Main.java @@ -119,6 +119,60 @@ public class Main { setTag(dArray, 1); System.out.println(iterateThroughHeapPrimitiveArray(getTag(dArray))); System.out.println(getTag(dArray)); + + // Force GCs to clean up dirt. + Runtime.getRuntime().gc(); + Runtime.getRuntime().gc(); + + doTestPrimitiveFieldsClasses(); + + doTestPrimitiveFieldsIntegral(); + + // Force GCs to clean up dirt. + Runtime.getRuntime().gc(); + Runtime.getRuntime().gc(); + + doTestPrimitiveFieldsFloat(); + + // Force GCs to clean up dirt. + Runtime.getRuntime().gc(); + Runtime.getRuntime().gc(); + } + + private static void doTestPrimitiveFieldsClasses() { + setTag(IntObject.class, 10000); + System.out.println(iterateThroughHeapPrimitiveFields(10000)); + System.out.println(getTag(IntObject.class)); + setTag(IntObject.class, 0); + + setTag(FloatObject.class, 10000); + System.out.println(iterateThroughHeapPrimitiveFields(10000)); + System.out.println(getTag(FloatObject.class)); + setTag(FloatObject.class, 0); + + setTag(Inf1.class, 10000); + System.out.println(iterateThroughHeapPrimitiveFields(10000)); + System.out.println(getTag(Inf1.class)); + setTag(Inf1.class, 0); + + setTag(Inf2.class, 10000); + System.out.println(iterateThroughHeapPrimitiveFields(10000)); + System.out.println(getTag(Inf2.class)); + setTag(Inf2.class, 0); + } + + private static void doTestPrimitiveFieldsIntegral() { + IntObject intObject = new IntObject(); + setTag(intObject, 10000); + System.out.println(iterateThroughHeapPrimitiveFields(10000)); + System.out.println(getTag(intObject)); + } + + private static void doTestPrimitiveFieldsFloat() { + FloatObject floatObject = new FloatObject(); + setTag(floatObject, 10000); + System.out.println(iterateThroughHeapPrimitiveFields(10000)); + System.out.println(getTag(floatObject)); } static class A { @@ -172,6 +226,31 @@ public class Main { return ret; } + private static interface Inf1 { + public final static int A = 1; + } + + private static interface Inf2 extends Inf1 { + public final static int B = 1; + } + + private static class IntObject implements Inf1 { + byte b = (byte)1; + char c= 'a'; + short s = (short)2; + int i = 3; + long l = 4; + Object o = new Object(); + static int sI = 5; + } + + private static class FloatObject extends IntObject implements Inf2 { + float f = 1.23f; + double d = 1.23; + Object p = new Object(); + static int sI = 6; + } + private static native void setTag(Object o, long tag); private static native long getTag(Object o); @@ -188,4 +267,5 @@ public class Main { Class klassFilter); private static native String iterateThroughHeapString(long tag); private static native String iterateThroughHeapPrimitiveArray(long tag); + private static native String iterateThroughHeapPrimitiveFields(long tag); } diff --git a/test/913-heaps/expected.txt b/test/913-heaps/expected.txt index c96edef90..437b7b8c3 100644 --- a/test/913-heaps/expected.txt +++ b/test/913-heaps/expected.txt @@ -91,6 +91,30 @@ root@root --(thread)--> 3000@0 [size=132, length=-1] 4@0 (18, 3xS '010002000300') 1@0 (14, 2xZ '0001') 23456789 +10000@0 (static, int, index=3) 0000000000000000 +10001 +10000@0 (static, int, index=11) 0000000000000000 +10001 +10000@0 (static, int, index=0) 0000000000000000 +10001 +10000@0 (static, int, index=1) 0000000000000000 +10001 +10000@0 (instance, int, index=2) 0000000000000000 +10001@0 (instance, byte, index=4) 0000000000000001 +10002@0 (instance, char, index=5) 0000000000000061 +10003@0 (instance, int, index=6) 0000000000000003 +10004@0 (instance, long, index=7) 0000000000000004 +10005@0 (instance, short, index=9) 0000000000000002 +10006 +10000@0 (instance, int, index=3) 0000000000000000 +10001@0 (instance, byte, index=5) 0000000000000001 +10002@0 (instance, char, index=6) 0000000000000061 +10003@0 (instance, int, index=7) 0000000000000003 +10004@0 (instance, long, index=8) 0000000000000004 +10005@0 (instance, short, index=10) 0000000000000002 +10006@0 (instance, double, index=12) 3ff3ae147ae147ae +10007@0 (instance, float, index=13) 000000003f9d70a4 +10008 --- klass --- root@root --(stack-local[id=1,tag=3000,depth=2,method=doFollowReferencesTestNonRoot,vreg=13,location= 32])--> 1@1000 [size=16, length=-1] 0@0 --(array-element@0)--> 1@1000 [size=16, length=-1] diff --git a/test/913-heaps/heaps.cc b/test/913-heaps/heaps.cc index 99bc48eee..39fa00019 100644 --- a/test/913-heaps/heaps.cc +++ b/test/913-heaps/heaps.cc @@ -654,5 +654,95 @@ extern "C" JNIEXPORT jstring JNICALL Java_Main_followReferencesPrimitiveArray( return env->NewStringUTF(fac.data.c_str()); } +static constexpr const char* GetPrimitiveTypeName(jvmtiPrimitiveType type) { + switch (type) { + case JVMTI_PRIMITIVE_TYPE_BOOLEAN: + return "boolean"; + case JVMTI_PRIMITIVE_TYPE_BYTE: + return "byte"; + case JVMTI_PRIMITIVE_TYPE_CHAR: + return "char"; + case JVMTI_PRIMITIVE_TYPE_SHORT: + return "short"; + case JVMTI_PRIMITIVE_TYPE_INT: + return "int"; + case JVMTI_PRIMITIVE_TYPE_FLOAT: + return "float"; + case JVMTI_PRIMITIVE_TYPE_LONG: + return "long"; + case JVMTI_PRIMITIVE_TYPE_DOUBLE: + return "double"; + } + LOG(FATAL) << "Unknown type " << static_cast(type); + UNREACHABLE(); +} + +extern "C" JNIEXPORT jstring JNICALL Java_Main_followReferencesPrimitiveFields( + JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jobject initial_object) { + struct FindFieldCallbacks { + static jint JNICALL FollowReferencesCallback( + jvmtiHeapReferenceKind reference_kind ATTRIBUTE_UNUSED, + const jvmtiHeapReferenceInfo* reference_info ATTRIBUTE_UNUSED, + jlong class_tag ATTRIBUTE_UNUSED, + jlong referrer_class_tag ATTRIBUTE_UNUSED, + jlong size ATTRIBUTE_UNUSED, + jlong* tag_ptr ATTRIBUTE_UNUSED, + jlong* referrer_tag_ptr ATTRIBUTE_UNUSED, + jint length ATTRIBUTE_UNUSED, + void* user_data ATTRIBUTE_UNUSED) { + return JVMTI_VISIT_OBJECTS; // Continue visiting. + } + + static jint JNICALL PrimitiveFieldValueCallback(jvmtiHeapReferenceKind kind, + const jvmtiHeapReferenceInfo* info, + jlong class_tag, + jlong* tag_ptr, + jvalue value, + jvmtiPrimitiveType value_type, + void* user_data) { + FindFieldCallbacks* p = reinterpret_cast(user_data); + if (*tag_ptr != 0) { + std::ostringstream oss; + oss << *tag_ptr + << '@' + << class_tag + << " (" + << (kind == JVMTI_HEAP_REFERENCE_FIELD ? "instance, " : "static, ") + << GetPrimitiveTypeName(value_type) + << ", index=" + << info->field.index + << ") "; + // Be lazy, always print eight bytes. + static_assert(sizeof(jvalue) == sizeof(uint64_t), "Unexpected jvalue size"); + uint64_t val; + memcpy(&val, &value, sizeof(uint64_t)); // To avoid undefined behavior. + oss << android::base::StringPrintf("%016" PRIx64, val); + + if (!p->data.empty()) { + p->data += "\n"; + } + p->data += oss.str(); + // Update the tag to test whether that works. + *tag_ptr = *tag_ptr + 1; + } + return 0; + } + + std::string data; + }; + + jvmtiHeapCallbacks callbacks; + memset(&callbacks, 0, sizeof(jvmtiHeapCallbacks)); + callbacks.heap_reference_callback = FindFieldCallbacks::FollowReferencesCallback; + callbacks.primitive_field_callback = FindFieldCallbacks::PrimitiveFieldValueCallback; + + FindFieldCallbacks ffc; + jvmtiError ret = jvmti_env->FollowReferences(0, nullptr, initial_object, &callbacks, &ffc); + if (JvmtiErrorToException(env, ret)) { + return nullptr; + } + return env->NewStringUTF(ffc.data.c_str()); +} + } // namespace Test913Heaps } // namespace art diff --git a/test/913-heaps/src/Main.java b/test/913-heaps/src/Main.java index 14ee2687e..66f68834a 100644 --- a/test/913-heaps/src/Main.java +++ b/test/913-heaps/src/Main.java @@ -34,6 +34,7 @@ public class Main { Runtime.getRuntime().gc(); doPrimitiveArrayTest(); + doPrimitiveFieldTest(); Runtime.getRuntime().gc(); Runtime.getRuntime().gc(); @@ -124,6 +125,62 @@ public class Main { System.out.println(getTag(dArray)); } + public static void doPrimitiveFieldTest() throws Exception { + // Force GCs to clean up dirt. + Runtime.getRuntime().gc(); + Runtime.getRuntime().gc(); + + doTestPrimitiveFieldsClasses(); + + doTestPrimitiveFieldsIntegral(); + + // Force GCs to clean up dirt. + Runtime.getRuntime().gc(); + Runtime.getRuntime().gc(); + + doTestPrimitiveFieldsFloat(); + + // Force GCs to clean up dirt. + Runtime.getRuntime().gc(); + Runtime.getRuntime().gc(); + } + + private static void doTestPrimitiveFieldsClasses() { + setTag(IntObject.class, 10000); + System.out.println(followReferencesPrimitiveFields(IntObject.class)); + System.out.println(getTag(IntObject.class)); + setTag(IntObject.class, 0); + + setTag(FloatObject.class, 10000); + System.out.println(followReferencesPrimitiveFields(FloatObject.class)); + System.out.println(getTag(FloatObject.class)); + setTag(FloatObject.class, 0); + + setTag(Inf1.class, 10000); + System.out.println(followReferencesPrimitiveFields(Inf1.class)); + System.out.println(getTag(Inf1.class)); + setTag(Inf1.class, 0); + + setTag(Inf2.class, 10000); + System.out.println(followReferencesPrimitiveFields(Inf2.class)); + System.out.println(getTag(Inf2.class)); + setTag(Inf2.class, 0); + } + + private static void doTestPrimitiveFieldsIntegral() { + IntObject intObject = new IntObject(); + setTag(intObject, 10000); + System.out.println(followReferencesPrimitiveFields(intObject)); + System.out.println(getTag(intObject)); + } + + private static void doTestPrimitiveFieldsFloat() { + FloatObject floatObject = new FloatObject(); + setTag(floatObject, 10000); + System.out.println(followReferencesPrimitiveFields(floatObject)); + System.out.println(getTag(floatObject)); + } + private static void run() { clearStats(); forceGarbageCollection(); @@ -315,6 +372,31 @@ public class Main { } } + private static interface Inf1 { + public final static int A = 1; + } + + private static interface Inf2 extends Inf1 { + public final static int B = 1; + } + + private static class IntObject implements Inf1 { + byte b = (byte)1; + char c= 'a'; + short s = (short)2; + int i = 3; + long l = 4; + Object o = new Object(); + static int sI = 5; + } + + private static class FloatObject extends IntObject implements Inf2 { + float f = 1.23f; + double d = 1.23; + Object p = new Object(); + static int sI = 6; + } + public static class Verifier { // Should roots with vreg=-1 be printed? public final static boolean PRINT_ROOTS_WITH_UNKNOWN_VREG = false; @@ -508,4 +590,5 @@ public class Main { Object initialObject, int stopAfter, int followSet, Object jniRef); public static native String[] followReferencesString(Object initialObject); public static native String followReferencesPrimitiveArray(Object initialObject); + public static native String followReferencesPrimitiveFields(Object initialObject); } -- 2.11.0