Merge "ART: Dump allocation stacks in reference table dumps"
diff --git a/runtime/indirect_reference_table.h b/runtime/indirect_reference_table.h
index bf287b1..7daf01c 100644
--- a/runtime/indirect_reference_table.h
+++ b/runtime/indirect_reference_table.h
@@ -278,7 +278,9 @@
 
   void AssertEmpty() REQUIRES_SHARED(Locks::mutator_lock_);
 
-  void Dump(std::ostream& os) const REQUIRES_SHARED(Locks::mutator_lock_);
+  void Dump(std::ostream& os) const
+      REQUIRES_SHARED(Locks::mutator_lock_)
+      REQUIRES(!Locks::alloc_tracker_lock_);
 
   // Return the #of entries in the entire table.  This includes holes, and
   // so may be larger than the actual number of "live" entries.
diff --git a/runtime/java_vm_ext.h b/runtime/java_vm_ext.h
index 50aabdc..b767b19 100644
--- a/runtime/java_vm_ext.h
+++ b/runtime/java_vm_ext.h
@@ -123,7 +123,9 @@
 
   void DumpReferenceTables(std::ostream& os)
       REQUIRES_SHARED(Locks::mutator_lock_)
-      REQUIRES(!Locks::jni_globals_lock_, !Locks::jni_weak_globals_lock_);
+      REQUIRES(!Locks::jni_globals_lock_,
+               !Locks::jni_weak_globals_lock_,
+               !Locks::alloc_tracker_lock_);
 
   bool SetCheckJniEnabled(bool enabled);
 
diff --git a/runtime/jni_env_ext.h b/runtime/jni_env_ext.h
index af933ae..2f6c5dc 100644
--- a/runtime/jni_env_ext.h
+++ b/runtime/jni_env_ext.h
@@ -45,7 +45,8 @@
   ~JNIEnvExt();
 
   void DumpReferenceTables(std::ostream& os)
-      REQUIRES_SHARED(Locks::mutator_lock_);
+      REQUIRES_SHARED(Locks::mutator_lock_)
+      REQUIRES(!Locks::alloc_tracker_lock_);
 
   void SetCheckJniEnabled(bool enabled) REQUIRES(!Locks::jni_function_table_lock_);
 
@@ -53,7 +54,9 @@
   void PopFrame() REQUIRES_SHARED(Locks::mutator_lock_);
 
   template<typename T>
-  T AddLocalReference(ObjPtr<mirror::Object> obj) REQUIRES_SHARED(Locks::mutator_lock_);
+  T AddLocalReference(ObjPtr<mirror::Object> obj)
+      REQUIRES_SHARED(Locks::mutator_lock_)
+      REQUIRES(!Locks::alloc_tracker_lock_);
 
   static Offset SegmentStateOffset(size_t pointer_size);
   static Offset LocalRefCookieOffset(size_t pointer_size);
diff --git a/runtime/reference_table.cc b/runtime/reference_table.cc
index e6e588e..a6df27b 100644
--- a/runtime/reference_table.cc
+++ b/runtime/reference_table.cc
@@ -19,6 +19,8 @@
 #include "android-base/stringprintf.h"
 
 #include "base/mutex.h"
+#include "gc/allocation_record.h"
+#include "gc/heap.h"
 #include "indirect_reference_table.h"
 #include "mirror/array-inl.h"
 #include "mirror/array.h"
@@ -206,6 +208,54 @@
       }
     }
     os << StringPrintf("    %5d: ", idx) << ref << " " << className << extras << "\n";
+    if (runtime->GetHeap()->IsAllocTrackingEnabled()) {
+      MutexLock mu(Thread::Current(), *Locks::alloc_tracker_lock_);
+
+      gc::AllocRecordObjectMap* records = runtime->GetHeap()->GetAllocationRecords();
+      DCHECK(records != nullptr);
+      // It's annoying that this is a list. But this code should be very uncommon to be executed.
+
+      auto print_stack = [&](ObjPtr<mirror::Object> to_print, const std::string& msg)
+          REQUIRES_SHARED(Locks::mutator_lock_)
+          REQUIRES(Locks::alloc_tracker_lock_) {
+        for (auto it = records->Begin(), end = records->End(); it != end; ++it) {
+          GcRoot<mirror::Object>& stack_for_object = it->first;
+          gc::AllocRecord& record = it->second;
+          if (stack_for_object.Read() == to_print.Ptr()) {
+            os << "          " << msg << "\n";
+            const gc::AllocRecordStackTrace* trace = record.GetStackTrace();
+            size_t depth = trace->GetDepth();
+            if (depth == 0) {
+              os << "            (No managed frames)\n";
+            } else {
+              for (size_t i = 0; i < depth; ++i) {
+                const gc::AllocRecordStackTraceElement& frame = trace->GetStackElement(i);
+                os << "            ";
+                if (frame.GetMethod() == nullptr) {
+                  os << "(missing method data)\n";
+                  continue;
+                }
+                os << frame.GetMethod()->PrettyMethod(true)
+                   << ":"
+                   << frame.ComputeLineNumber()
+                   << "\n";
+              }
+            }
+            break;
+          }
+        }
+      };
+      // Print the stack trace of the ref.
+      print_stack(ref, "Allocated at:");
+
+      // If it's a reference, see if we have data about the referent.
+      if (ref->IsReferenceInstance()) {
+        ObjPtr<mirror::Object> referent = ref->AsReference()->GetReferent();
+        if (referent != nullptr) {
+          print_stack(referent, "Referent allocated at:");
+        }
+      }
+    }
   }
 
   // Make a copy of the table and sort it, only adding non null and not cleared elements.
diff --git a/runtime/reference_table.h b/runtime/reference_table.h
index 010c6f8..6af5ca5 100644
--- a/runtime/reference_table.h
+++ b/runtime/reference_table.h
@@ -47,7 +47,9 @@
 
   size_t Size() const;
 
-  void Dump(std::ostream& os) REQUIRES_SHARED(Locks::mutator_lock_);
+  void Dump(std::ostream& os)
+      REQUIRES_SHARED(Locks::mutator_lock_)
+      REQUIRES(!Locks::alloc_tracker_lock_);
 
   void VisitRoots(RootVisitor* visitor, const RootInfo& root_info)
       REQUIRES_SHARED(Locks::mutator_lock_);
@@ -56,7 +58,8 @@
   typedef std::vector<GcRoot<mirror::Object>,
                       TrackingAllocator<GcRoot<mirror::Object>, kAllocatorTagReferenceTable>> Table;
   static void Dump(std::ostream& os, Table& entries)
-      REQUIRES_SHARED(Locks::mutator_lock_);
+      REQUIRES_SHARED(Locks::mutator_lock_)
+      REQUIRES(!Locks::alloc_tracker_lock_);
   friend class IndirectReferenceTable;  // For Dump.
 
   std::string name_;
diff --git a/runtime/reference_table_test.cc b/runtime/reference_table_test.cc
index d830387..1e7fc3e 100644
--- a/runtime/reference_table_test.cc
+++ b/runtime/reference_table_test.cc
@@ -16,6 +16,8 @@
 
 #include "reference_table.h"
 
+#include <regex>
+
 #include "android-base/stringprintf.h"
 
 #include "art_method-inl.h"
@@ -30,6 +32,7 @@
 #include "runtime.h"
 #include "scoped_thread_state_change-inl.h"
 #include "thread-current-inl.h"
+#include "well_known_classes.h"
 
 namespace art {
 
@@ -156,6 +159,7 @@
     rt.Dump(oss);
     EXPECT_NE(oss.str().find("java.lang.ref.WeakReference (referent is null)"), std::string::npos)
         << oss.str();
+    rt.Remove(empty_reference);
   }
 
   {
@@ -168,6 +172,86 @@
     EXPECT_NE(oss.str().find("java.lang.ref.WeakReference (referent is a java.lang.String)"),
               std::string::npos)
         << oss.str();
+    rt.Remove(non_empty_reference);
+  }
+
+  // Add two objects. Enable allocation tracking for the latter.
+  {
+    StackHandleScope<3> hs(soa.Self());
+    Handle<mirror::String> h_without_trace(hs.NewHandle(
+        mirror::String::AllocFromModifiedUtf8(soa.Self(), "Without")));
+
+    {
+      ScopedThreadSuspension sts(soa.Self(), ThreadState::kSuspended);
+      gc::AllocRecordObjectMap::SetAllocTrackingEnabled(true);
+    }
+
+    // To get a stack, actually make a call. Use substring, that's simple. Calling through JNI
+    // avoids having to create the low-level args array ourselves.
+    Handle<mirror::Object> h_with_trace;
+    {
+      jmethodID substr = soa.Env()->GetMethodID(WellKnownClasses::java_lang_String,
+                                                "substring",
+                                                "(II)Ljava/lang/String;");
+      ASSERT_TRUE(substr != nullptr);
+      jobject jobj = soa.Env()->AddLocalReference<jobject>(h_without_trace.Get());
+      ASSERT_TRUE(jobj != nullptr);
+      jobject result = soa.Env()->CallObjectMethod(jobj,
+                                                   substr,
+                                                   static_cast<jint>(0),
+                                                   static_cast<jint>(4));
+      ASSERT_TRUE(result != nullptr);
+      h_with_trace = hs.NewHandle(soa.Self()->DecodeJObject(result));
+    }
+
+    Handle<mirror::Object> h_ref;
+    {
+      jclass weak_ref_class = soa.Env()->FindClass("java/lang/ref/WeakReference");
+      ASSERT_TRUE(weak_ref_class != nullptr);
+      jmethodID init = soa.Env()->GetMethodID(weak_ref_class,
+                                              "<init>",
+                                              "(Ljava/lang/Object;)V");
+      ASSERT_TRUE(init != nullptr);
+      jobject referent = soa.Env()->AddLocalReference<jobject>(h_with_trace.Get());
+      jobject result = soa.Env()->NewObject(weak_ref_class, init, referent);
+      ASSERT_TRUE(result != nullptr);
+      h_ref = hs.NewHandle(soa.Self()->DecodeJObject(result));
+    }
+
+    rt.Add(h_without_trace.Get());
+    rt.Add(h_with_trace.Get());
+    rt.Add(h_ref.Get());
+
+    std::ostringstream oss;
+    rt.Dump(oss);
+
+    constexpr const char* kStackTracePattern =
+        R"(test reference table dump:\n)"
+        R"(  Last 3 entries \(of 3\):\n)"  // NOLINT
+        R"(        2: 0x[0-9a-f]* java.lang.ref.WeakReference \(referent is a java.lang.String\)\n)"  // NOLINT
+        R"(          Allocated at:\n)"
+        R"(            \(No managed frames\)\n)"  // NOLINT
+        R"(          Referent allocated at:\n)"
+        R"(            java.lang.String java.lang.String.fastSubstring\(int, int\):-2\n)"  // NOLINT
+        R"(            java.lang.String java.lang.String.substring\(int, int\):[0-9]*\n)"  // NOLINT
+        R"(        1: 0x[0-9a-f]* java.lang.String "With"\n)"
+        R"(          Allocated at:\n)"
+        R"(            java.lang.String java.lang.String.fastSubstring\(int, int\):-2\n)"  // NOLINT
+        R"(            java.lang.String java.lang.String.substring\(int, int\):[0-9]*\n)"  // NOLINT
+        R"(        0: 0x[0-9a-f]* java.lang.String "Without"\n)"
+        R"(  Summary:\n)"
+        R"(        2 of java.lang.String \(2 unique instances\)\n)"  // NOLINT
+        R"(        1 of java.lang.ref.WeakReference\n)";
+    std::regex stack_trace_regex(kStackTracePattern);
+    std::smatch stack_trace_match;
+    std::string str = oss.str();
+    bool found = std::regex_search(str, stack_trace_match, stack_trace_regex);
+    EXPECT_TRUE(found) << str;
+
+    {
+      ScopedThreadSuspension sts(soa.Self(), ThreadState::kSuspended);
+      gc::AllocRecordObjectMap::SetAllocTrackingEnabled(false);
+    }
   }
 }