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
diff --git a/runtime/openjdkjvmti/ti_heap.cc b/runtime/openjdkjvmti/ti_heap.cc
index c7294a9..5f59489 100644
--- a/runtime/openjdkjvmti/ti_heap.cc
+++ b/runtime/openjdkjvmti/ti_heap.cc
@@ -162,6 +162,439 @@
   return 0;
 }
 
+template <typename UserData>
+bool VisitorFalse(art::ObjPtr<art::mirror::Object> obj ATTRIBUTE_UNUSED,
+                  art::ObjPtr<art::mirror::Class> klass ATTRIBUTE_UNUSED,
+                  art::ArtField& field ATTRIBUTE_UNUSED,
+                  size_t field_index ATTRIBUTE_UNUSED,
+                  UserData* user_data ATTRIBUTE_UNUSED) {
+  return false;
+}
+
+template <typename StaticPrimitiveVisitor,
+          typename StaticReferenceVisitor,
+          typename InstancePrimitiveVisitor,
+          typename InstanceReferenceVisitor,
+          typename UserData,
+          bool kCallVisitorOnRecursion>
+class FieldVisitor {
+ public:
+  // Report the contents of a primitive fields of the given object, if a callback is set.
+  static bool ReportFields(art::ObjPtr<art::mirror::Object> 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<art::mirror::Class> 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<art::mirror::Object> obj,
+                        art::ObjPtr<art::mirror::Class> 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<art::mirror::Object> obj,
+                             art::ObjPtr<art::mirror::Class> 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<UserData>,
+                              VisitorFalse<UserData>,
+                              VisitorFalse<UserData>,
+                              VisitorFalse<UserData>,
+                              &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 <typename T>
+  struct RecursiveInterfaceVisit {
+    static void VisitStatic(art::Thread* self, art::ObjPtr<art::mirror::Class> klass, T& visitor)
+        REQUIRES_SHARED(art::Locks::mutator_lock_) {
+      RecursiveInterfaceVisit rv;
+      rv.Visit(self, klass, visitor);
+    }
+
+    void Visit(art::Thread* self, art::ObjPtr<art::mirror::Class> 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<art::mirror::Class> 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<art::mirror::Class> 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<art::mirror::Class> 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<art::mirror::Class*> 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<art::mirror::Class> klass)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    size_t count = 0;
+    auto visitor = [&count](art::ObjPtr<art::mirror::Class> inf_klass)
+        REQUIRES_SHARED(art::Locks::mutator_lock_) {
+      DCHECK(inf_klass->IsInterface());
+      DCHECK_EQ(0u, inf_klass->NumInstanceFields());
+      count += inf_klass->NumStaticFields();
+    };
+    RecursiveInterfaceVisit<decltype(visitor)>::VisitStatic(art::Thread::Current(), klass, visitor);
+    return count;
+
+    // TODO: Implement caching.
+  }
+
+  UserData* user_data_;
+};
+
+// Debug helper. Prints the structure of an object.
+template <bool kStatic, bool kRef>
+struct DumpVisitor {
+  static bool Callback(art::ObjPtr<art::mirror::Object> obj ATTRIBUTE_UNUSED,
+                       art::ObjPtr<art::mirror::Class> 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<art::mirror::Object> obj)
+    REQUIRES_SHARED(art::Locks::mutator_lock_) {
+  if (obj->IsClass()) {
+    FieldVisitor<decltype(DumpVisitor<true, false>::Callback),
+                 decltype(DumpVisitor<true, true>::Callback),
+                 decltype(DumpVisitor<false, false>::Callback),
+                 decltype(DumpVisitor<false, true>::Callback),
+                 void,
+                 false>::
+        ReportFields(obj,
+                     nullptr,
+                     DumpVisitor<true, false>::Callback,
+                     DumpVisitor<true, true>::Callback,
+                     DumpVisitor<false, false>::Callback,
+                     DumpVisitor<false, true>::Callback);
+  } else {
+    FieldVisitor<decltype(DumpVisitor<true, false>::Callback),
+                 decltype(DumpVisitor<true, true>::Callback),
+                 decltype(DumpVisitor<false, false>::Callback),
+                 decltype(DumpVisitor<false, true>::Callback),
+                 void,
+                 true>::
+        ReportFields(obj,
+                     nullptr,
+                     DumpVisitor<true, false>::Callback,
+                     DumpVisitor<true, true>::Callback,
+                     DumpVisitor<false, false>::Callback,
+                     DumpVisitor<false, true>::Callback);
+  }
+}
+
+class ReportPrimitiveField {
+ public:
+  static bool Report(art::ObjPtr<art::mirror::Object> 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(ReportPrimitiveFieldCallback<true>),
+                            decltype(VisitorFalse<ReportPrimitiveField>),
+                            decltype(VisitorFalse<ReportPrimitiveField>),
+                            decltype(VisitorFalse<ReportPrimitiveField>),
+                            ReportPrimitiveField,
+                            false>::
+            ReportFields(obj,
+                         &rpf,
+                         ReportPrimitiveFieldCallback<true>,
+                         VisitorFalse<ReportPrimitiveField>,
+                         VisitorFalse<ReportPrimitiveField>,
+                         VisitorFalse<ReportPrimitiveField>);
+      } else {
+        return FieldVisitor<decltype(VisitorFalse<ReportPrimitiveField>),
+                            decltype(VisitorFalse<ReportPrimitiveField>),
+                            decltype(ReportPrimitiveFieldCallback<false>),
+                            decltype(VisitorFalse<ReportPrimitiveField>),
+                            ReportPrimitiveField,
+                            true>::
+            ReportFields(obj,
+                         &rpf,
+                         VisitorFalse<ReportPrimitiveField>,
+                         VisitorFalse<ReportPrimitiveField>,
+                         ReportPrimitiveFieldCallback<false>,
+                         VisitorFalse<ReportPrimitiveField>);
+      }
+    }
+    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 <bool kReportStatic>
+  static bool ReportPrimitiveFieldCallback(art::ObjPtr<art::mirror::Object> obj,
+                                           art::ObjPtr<art::mirror::Class> 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<jvmtiPrimitiveType>(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<art::mirror::Object> 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<void*>(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 @@
     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 @@
       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 @@
         }
       }
     }
+
+    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 3e857ab..b6af843 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 890220e..13c3562 100644
--- a/test/906-iterate-heap/iterate_heap.cc
+++ b/test/906-iterate-heap/iterate_heap.cc
@@ -322,5 +322,92 @@
   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<size_t>(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<FindFieldCallbacks*>(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 d499886..365ce0f 100644
--- a/test/906-iterate-heap/src/Main.java
+++ b/test/906-iterate-heap/src/Main.java
@@ -119,6 +119,60 @@
     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 @@
     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 @@
       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 c96edef..437b7b8 100644
--- a/test/913-heaps/expected.txt
+++ b/test/913-heaps/expected.txt
@@ -91,6 +91,30 @@
 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 99bc48e..39fa000 100644
--- a/test/913-heaps/heaps.cc
+++ b/test/913-heaps/heaps.cc
@@ -654,5 +654,95 @@
   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<size_t>(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<FindFieldCallbacks*>(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 14ee268..66f6883 100644
--- a/test/913-heaps/src/Main.java
+++ b/test/913-heaps/src/Main.java
@@ -34,6 +34,7 @@
     Runtime.getRuntime().gc();
 
     doPrimitiveArrayTest();
+    doPrimitiveFieldTest();
 
     Runtime.getRuntime().gc();
     Runtime.getRuntime().gc();
@@ -124,6 +125,62 @@
     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 @@
     }
   }
 
+  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 @@
       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);
 }