ART: Add string reporting

Add support for string_primitive_value_callback.

Bug: 31385354
Test: m test-art-host-run-test-906-iterate-heap
Test: m test-art-host-run-test-913-heaps
Change-Id: I69f68fd07869ba3a156a84fcb806821fce1d7c03
diff --git a/runtime/openjdkjvmti/ti_heap.cc b/runtime/openjdkjvmti/ti_heap.cc
index fe3e52b..7efeea7 100644
--- a/runtime/openjdkjvmti/ti_heap.cc
+++ b/runtime/openjdkjvmti/ti_heap.cc
@@ -38,14 +38,69 @@
 
 namespace openjdkjvmti {
 
+namespace {
+
+// Report the contents of a string, if a callback is set.
+jint ReportString(art::ObjPtr<art::mirror::Object> obj,
+                  jvmtiEnv* env,
+                  ObjectTagTable* tag_table,
+                  const jvmtiHeapCallbacks* cb,
+                  const void* user_data) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+  if (UNLIKELY(cb->string_primitive_value_callback != nullptr) && obj->IsString()) {
+    art::ObjPtr<art::mirror::String> str = obj->AsString();
+    int32_t string_length = str->GetLength();
+    jvmtiError alloc_error;
+    JvmtiUniquePtr<uint16_t[]> data = AllocJvmtiUniquePtr<uint16_t[]>(env,
+                                                                      string_length,
+                                                                      &alloc_error);
+    if (data == nullptr) {
+      // TODO: Not really sure what to do here. Should we abort the iteration and go all the way
+      //       back? For now just warn.
+      LOG(WARNING) << "Unable to allocate buffer for string reporting! Silently dropping value.";
+      return 0;
+    }
+
+    if (str->IsCompressed()) {
+      uint8_t* compressed_data = str->GetValueCompressed();
+      for (int32_t i = 0; i != string_length; ++i) {
+        data[i] = compressed_data[i];
+      }
+    } else {
+      // Can copy directly.
+      memcpy(data.get(), str->GetValue(), string_length * sizeof(uint16_t));
+    }
+
+    const jlong class_tag = tag_table->GetTagOrZero(obj->GetClass());
+    jlong string_tag = tag_table->GetTagOrZero(obj.Ptr());
+    const jlong saved_string_tag = string_tag;
+
+    jint result = cb->string_primitive_value_callback(class_tag,
+                                                      obj->SizeOf(),
+                                                      &string_tag,
+                                                      data.get(),
+                                                      string_length,
+                                                      const_cast<void*>(user_data));
+    if (string_tag != saved_string_tag) {
+      tag_table->Set(obj.Ptr(), string_tag);
+    }
+
+    return result;
+  }
+  return 0;
+}
+
+}  // namespace
+
 struct IterateThroughHeapData {
   IterateThroughHeapData(HeapUtil* _heap_util,
+                         jvmtiEnv* _env,
                          jint heap_filter,
                          art::ObjPtr<art::mirror::Class> klass,
                          const jvmtiHeapCallbacks* _callbacks,
                          const void* _user_data)
       : heap_util(_heap_util),
         filter_klass(klass),
+        env(_env),
         callbacks(_callbacks),
         user_data(_user_data),
         filter_out_tagged((heap_filter & JVMTI_HEAP_FILTER_TAGGED) != 0),
@@ -78,6 +133,7 @@
 
   HeapUtil* heap_util;
   art::ObjPtr<art::mirror::Class> filter_klass;
+  jvmtiEnv* env;
   const jvmtiHeapCallbacks* callbacks;
   const void* user_data;
   const bool filter_out_tagged;
@@ -111,8 +167,6 @@
     return;
   }
 
-  // TODO: Handle array_primitive_value_callback.
-
   if (ithd->filter_klass != nullptr) {
     if (ithd->filter_klass != klass) {
       return;
@@ -139,11 +193,20 @@
 
   ithd->stop_reports = (ret & JVMTI_VISIT_ABORT) != 0;
 
-  // TODO Implement array primitive and string primitive callback.
+  if (!ithd->stop_reports) {
+    jint string_ret = ReportString(obj,
+                                   ithd->env,
+                                   ithd->heap_util->GetTags(),
+                                   ithd->callbacks,
+                                   ithd->user_data);
+    ithd->stop_reports = (string_ret & JVMTI_VISIT_ABORT) != 0;
+  }
+
+  // TODO Implement array primitive callback.
   // TODO Implement primitive field callback.
 }
 
-jvmtiError HeapUtil::IterateThroughHeap(jvmtiEnv* env ATTRIBUTE_UNUSED,
+jvmtiError HeapUtil::IterateThroughHeap(jvmtiEnv* env,
                                         jint heap_filter,
                                         jclass klass,
                                         const jvmtiHeapCallbacks* callbacks,
@@ -161,6 +224,7 @@
   art::ScopedObjectAccess soa(self);      // Now we know we have the shared lock.
 
   IterateThroughHeapData ithd(this,
+                              env,
                               heap_filter,
                               soa.Decode<art::mirror::Class>(klass),
                               callbacks,
@@ -174,10 +238,12 @@
 class FollowReferencesHelper FINAL {
  public:
   FollowReferencesHelper(HeapUtil* h,
+                         jvmtiEnv* jvmti_env,
                          art::ObjPtr<art::mirror::Object> initial_object,
                          const jvmtiHeapCallbacks* callbacks,
                          const void* user_data)
-      : tag_table_(h->GetTags()),
+      : env(jvmti_env),
+        tag_table_(h->GetTags()),
         initial_object_(initial_object),
         callbacks_(callbacks),
         user_data_(user_data),
@@ -467,6 +533,11 @@
     obj->VisitReferences<false>(visitor, art::VoidFunctor());
 
     stop_reports_ = visitor.stop_reports;
+
+    if (!stop_reports_) {
+      jint string_ret = ReportString(obj, env, tag_table_, callbacks_, user_data_);
+      stop_reports_ = (string_ret & JVMTI_VISIT_ABORT) != 0;
+    }
   }
 
   void VisitArray(art::mirror::Object* array)
@@ -655,6 +726,7 @@
     return result;
   }
 
+  jvmtiEnv* env;
   ObjectTagTable* tag_table_;
   art::ObjPtr<art::mirror::Object> initial_object_;
   const jvmtiHeapCallbacks* callbacks_;
@@ -671,7 +743,7 @@
   friend class CollectAndReportRootsVisitor;
 };
 
-jvmtiError HeapUtil::FollowReferences(jvmtiEnv* env ATTRIBUTE_UNUSED,
+jvmtiError HeapUtil::FollowReferences(jvmtiEnv* env,
                                       jint heap_filter ATTRIBUTE_UNUSED,
                                       jclass klass ATTRIBUTE_UNUSED,
                                       jobject initial_object,
@@ -700,6 +772,7 @@
     art::ScopedSuspendAll ssa("FollowReferences");
 
     FollowReferencesHelper frh(this,
+                               env,
                                self->DecodeJObject(initial_object),
                                callbacks,
                                user_data);