ART: Refactor IRT:Add

Do not abort on overflow. Return null and an error message. The
caller is responsible for handling this, e.g., by aborting. In a
future CL, this may be used for driving additional GCs.

Additional side effect is the removal of a frame from an abortion
stack trace.

Test: m
Test: m test-art-host
Change-Id: I80b1e0ee396fc69906d051f1b661d7dba222fc6f
diff --git a/runtime/indirect_reference_table.cc b/runtime/indirect_reference_table.cc
index cff3ea7..2dd4db3 100644
--- a/runtime/indirect_reference_table.cc
+++ b/runtime/indirect_reference_table.cc
@@ -237,7 +237,8 @@
 }
 
 IndirectRef IndirectReferenceTable::Add(IRTSegmentState previous_state,
-                                        ObjPtr<mirror::Object> obj) {
+                                        ObjPtr<mirror::Object> obj,
+                                        std::string* error_msg) {
   if (kDebugIRT) {
     LOG(INFO) << "+++ Add: previous_state=" << previous_state.top_index
               << " top_index=" << segment_state_.top_index
@@ -253,28 +254,34 @@
 
   if (top_index == max_entries_) {
     if (resizable_ == ResizableCapacity::kNo) {
-      LOG(FATAL) << "JNI ERROR (app bug): " << kind_ << " table overflow "
-                 << "(max=" << max_entries_ << ")\n"
-                 << MutatorLockedDumpable<IndirectReferenceTable>(*this);
-      UNREACHABLE();
+      std::ostringstream oss;
+      oss << "JNI ERROR (app bug): " << kind_ << " table overflow "
+          << "(max=" << max_entries_ << ")"
+          << MutatorLockedDumpable<IndirectReferenceTable>(*this);
+      *error_msg = oss.str();
+      return nullptr;
     }
 
     // Try to double space.
     if (std::numeric_limits<size_t>::max() / 2 < max_entries_) {
-      LOG(FATAL) << "JNI ERROR (app bug): " << kind_ << " table overflow "
-                 << "(max=" << max_entries_ << ")" << std::endl
-                 << MutatorLockedDumpable<IndirectReferenceTable>(*this)
-                << " Resizing failed: exceeds size_t";
-      UNREACHABLE();
+      std::ostringstream oss;
+      oss << "JNI ERROR (app bug): " << kind_ << " table overflow "
+          << "(max=" << max_entries_ << ")" << std::endl
+          << MutatorLockedDumpable<IndirectReferenceTable>(*this)
+          << " Resizing failed: exceeds size_t";
+      *error_msg = oss.str();
+      return nullptr;
     }
 
-    std::string error_msg;
-    if (!Resize(max_entries_ * 2, &error_msg)) {
-      LOG(FATAL) << "JNI ERROR (app bug): " << kind_ << " table overflow "
-                 << "(max=" << max_entries_ << ")" << std::endl
-                 << MutatorLockedDumpable<IndirectReferenceTable>(*this)
-                 << " Resizing failed: " << error_msg;
-      UNREACHABLE();
+    std::string inner_error_msg;
+    if (!Resize(max_entries_ * 2, &inner_error_msg)) {
+      std::ostringstream oss;
+      oss << "JNI ERROR (app bug): " << kind_ << " table overflow "
+          << "(max=" << max_entries_ << ")" << std::endl
+          << MutatorLockedDumpable<IndirectReferenceTable>(*this)
+          << " Resizing failed: " << inner_error_msg;
+      *error_msg = oss.str();
+      return nullptr;
     }
   }
 
diff --git a/runtime/indirect_reference_table.h b/runtime/indirect_reference_table.h
index 6d52d95..bf287b1 100644
--- a/runtime/indirect_reference_table.h
+++ b/runtime/indirect_reference_table.h
@@ -244,8 +244,10 @@
   bool IsValid() const;
 
   // Add a new entry. "obj" must be a valid non-null object reference. This function will
-  // abort if the table is full (max entries reached, or expansion failed).
-  IndirectRef Add(IRTSegmentState previous_state, ObjPtr<mirror::Object> obj)
+  // return null if an error happened (with an appropriate error message set).
+  IndirectRef Add(IRTSegmentState previous_state,
+                  ObjPtr<mirror::Object> obj,
+                  std::string* error_msg)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
   // Given an IndirectRef in the table, return the Object it refers to.
diff --git a/runtime/indirect_reference_table_test.cc b/runtime/indirect_reference_table_test.cc
index 6aefe23..9278509 100644
--- a/runtime/indirect_reference_table_test.cc
+++ b/runtime/indirect_reference_table_test.cc
@@ -80,13 +80,13 @@
   EXPECT_FALSE(irt.Remove(cookie, iref0)) << "unexpectedly successful removal";
 
   // Add three, check, remove in the order in which they were added.
-  iref0 = irt.Add(cookie, obj0.Get());
+  iref0 = irt.Add(cookie, obj0.Get(), &error_msg);
   EXPECT_TRUE(iref0 != nullptr);
   CheckDump(&irt, 1, 1);
-  IndirectRef iref1 = irt.Add(cookie, obj1.Get());
+  IndirectRef iref1 = irt.Add(cookie, obj1.Get(), &error_msg);
   EXPECT_TRUE(iref1 != nullptr);
   CheckDump(&irt, 2, 2);
-  IndirectRef iref2 = irt.Add(cookie, obj2.Get());
+  IndirectRef iref2 = irt.Add(cookie, obj2.Get(), &error_msg);
   EXPECT_TRUE(iref2 != nullptr);
   CheckDump(&irt, 3, 3);
 
@@ -108,11 +108,11 @@
   EXPECT_TRUE(irt.Get(iref0) == nullptr);
 
   // Add three, remove in the opposite order.
-  iref0 = irt.Add(cookie, obj0.Get());
+  iref0 = irt.Add(cookie, obj0.Get(), &error_msg);
   EXPECT_TRUE(iref0 != nullptr);
-  iref1 = irt.Add(cookie, obj1.Get());
+  iref1 = irt.Add(cookie, obj1.Get(), &error_msg);
   EXPECT_TRUE(iref1 != nullptr);
-  iref2 = irt.Add(cookie, obj2.Get());
+  iref2 = irt.Add(cookie, obj2.Get(), &error_msg);
   EXPECT_TRUE(iref2 != nullptr);
   CheckDump(&irt, 3, 3);
 
@@ -128,11 +128,11 @@
 
   // Add three, remove middle / middle / bottom / top.  (Second attempt
   // to remove middle should fail.)
-  iref0 = irt.Add(cookie, obj0.Get());
+  iref0 = irt.Add(cookie, obj0.Get(), &error_msg);
   EXPECT_TRUE(iref0 != nullptr);
-  iref1 = irt.Add(cookie, obj1.Get());
+  iref1 = irt.Add(cookie, obj1.Get(), &error_msg);
   EXPECT_TRUE(iref1 != nullptr);
-  iref2 = irt.Add(cookie, obj2.Get());
+  iref2 = irt.Add(cookie, obj2.Get(), &error_msg);
   EXPECT_TRUE(iref2 != nullptr);
   CheckDump(&irt, 3, 3);
 
@@ -157,20 +157,20 @@
   // Add four entries.  Remove #1, add new entry, verify that table size
   // is still 4 (i.e. holes are getting filled).  Remove #1 and #3, verify
   // that we delete one and don't hole-compact the other.
-  iref0 = irt.Add(cookie, obj0.Get());
+  iref0 = irt.Add(cookie, obj0.Get(), &error_msg);
   EXPECT_TRUE(iref0 != nullptr);
-  iref1 = irt.Add(cookie, obj1.Get());
+  iref1 = irt.Add(cookie, obj1.Get(), &error_msg);
   EXPECT_TRUE(iref1 != nullptr);
-  iref2 = irt.Add(cookie, obj2.Get());
+  iref2 = irt.Add(cookie, obj2.Get(), &error_msg);
   EXPECT_TRUE(iref2 != nullptr);
-  IndirectRef iref3 = irt.Add(cookie, obj3.Get());
+  IndirectRef iref3 = irt.Add(cookie, obj3.Get(), &error_msg);
   EXPECT_TRUE(iref3 != nullptr);
   CheckDump(&irt, 4, 4);
 
   ASSERT_TRUE(irt.Remove(cookie, iref1));
   CheckDump(&irt, 3, 3);
 
-  iref1 = irt.Add(cookie, obj1.Get());
+  iref1 = irt.Add(cookie, obj1.Get(), &error_msg);
   EXPECT_TRUE(iref1 != nullptr);
 
   ASSERT_EQ(4U, irt.Capacity()) << "hole not filled";
@@ -193,12 +193,12 @@
   // Add an entry, remove it, add a new entry, and try to use the original
   // iref.  They have the same slot number but are for different objects.
   // With the extended checks in place, this should fail.
-  iref0 = irt.Add(cookie, obj0.Get());
+  iref0 = irt.Add(cookie, obj0.Get(), &error_msg);
   EXPECT_TRUE(iref0 != nullptr);
   CheckDump(&irt, 1, 1);
   ASSERT_TRUE(irt.Remove(cookie, iref0));
   CheckDump(&irt, 0, 0);
-  iref1 = irt.Add(cookie, obj1.Get());
+  iref1 = irt.Add(cookie, obj1.Get(), &error_msg);
   EXPECT_TRUE(iref1 != nullptr);
   CheckDump(&irt, 1, 1);
   ASSERT_FALSE(irt.Remove(cookie, iref0)) << "mismatched del succeeded";
@@ -209,12 +209,12 @@
 
   // Same as above, but with the same object.  A more rigorous checker
   // (e.g. with slot serialization) will catch this.
-  iref0 = irt.Add(cookie, obj0.Get());
+  iref0 = irt.Add(cookie, obj0.Get(), &error_msg);
   EXPECT_TRUE(iref0 != nullptr);
   CheckDump(&irt, 1, 1);
   ASSERT_TRUE(irt.Remove(cookie, iref0));
   CheckDump(&irt, 0, 0);
-  iref1 = irt.Add(cookie, obj0.Get());
+  iref1 = irt.Add(cookie, obj0.Get(), &error_msg);
   EXPECT_TRUE(iref1 != nullptr);
   CheckDump(&irt, 1, 1);
   if (iref0 != iref1) {
@@ -229,7 +229,7 @@
   ASSERT_TRUE(irt.Get(nullptr) == nullptr);
 
   // Stale lookup.
-  iref0 = irt.Add(cookie, obj0.Get());
+  iref0 = irt.Add(cookie, obj0.Get(), &error_msg);
   EXPECT_TRUE(iref0 != nullptr);
   CheckDump(&irt, 1, 1);
   ASSERT_TRUE(irt.Remove(cookie, iref0));
@@ -241,12 +241,12 @@
   static const size_t kTableInitial = kTableMax / 2;
   IndirectRef manyRefs[kTableInitial];
   for (size_t i = 0; i < kTableInitial; i++) {
-    manyRefs[i] = irt.Add(cookie, obj0.Get());
+    manyRefs[i] = irt.Add(cookie, obj0.Get(), &error_msg);
     ASSERT_TRUE(manyRefs[i] != nullptr) << "Failed adding " << i;
     CheckDump(&irt, i + 1, 1);
   }
   // ...this one causes overflow.
-  iref0 = irt.Add(cookie, obj0.Get());
+  iref0 = irt.Add(cookie, obj0.Get(), &error_msg);
   ASSERT_TRUE(iref0 != nullptr);
   ASSERT_EQ(kTableInitial + 1, irt.Capacity());
   CheckDump(&irt, kTableInitial + 1, 1);
@@ -306,16 +306,16 @@
 
     CheckDump(&irt, 0, 0);
 
-    IndirectRef iref0 = irt.Add(cookie0, obj0.Get());
-    IndirectRef iref1 = irt.Add(cookie0, obj1.Get());
-    IndirectRef iref2 = irt.Add(cookie0, obj2.Get());
+    IndirectRef iref0 = irt.Add(cookie0, obj0.Get(), &error_msg);
+    IndirectRef iref1 = irt.Add(cookie0, obj1.Get(), &error_msg);
+    IndirectRef iref2 = irt.Add(cookie0, obj2.Get(), &error_msg);
 
     EXPECT_TRUE(irt.Remove(cookie0, iref1));
 
     // New segment.
     const IRTSegmentState cookie1 = irt.GetSegmentState();
 
-    IndirectRef iref3 = irt.Add(cookie1, obj3.Get());
+    IndirectRef iref3 = irt.Add(cookie1, obj3.Get(), &error_msg);
 
     // Must not have filled the previous hole.
     EXPECT_EQ(irt.Capacity(), 4u);
@@ -337,21 +337,21 @@
 
     CheckDump(&irt, 0, 0);
 
-    IndirectRef iref0 = irt.Add(cookie0, obj0.Get());
+    IndirectRef iref0 = irt.Add(cookie0, obj0.Get(), &error_msg);
 
     // New segment.
     const IRTSegmentState cookie1 = irt.GetSegmentState();
 
-    IndirectRef iref1 = irt.Add(cookie1, obj1.Get());
-    IndirectRef iref2 = irt.Add(cookie1, obj2.Get());
-    IndirectRef iref3 = irt.Add(cookie1, obj3.Get());
+    IndirectRef iref1 = irt.Add(cookie1, obj1.Get(), &error_msg);
+    IndirectRef iref2 = irt.Add(cookie1, obj2.Get(), &error_msg);
+    IndirectRef iref3 = irt.Add(cookie1, obj3.Get(), &error_msg);
 
     EXPECT_TRUE(irt.Remove(cookie1, iref2));
 
     // Pop segment.
     irt.SetSegmentState(cookie1);
 
-    IndirectRef iref4 = irt.Add(cookie1, obj4.Get());
+    IndirectRef iref4 = irt.Add(cookie1, obj4.Get(), &error_msg);
 
     EXPECT_EQ(irt.Capacity(), 2u);
     EXPECT_TRUE(irt.Get(iref2) == nullptr);
@@ -373,25 +373,25 @@
 
     CheckDump(&irt, 0, 0);
 
-    IndirectRef iref0 = irt.Add(cookie0, obj0.Get());
+    IndirectRef iref0 = irt.Add(cookie0, obj0.Get(), &error_msg);
 
     // New segment.
     const IRTSegmentState cookie1 = irt.GetSegmentState();
 
-    IndirectRef iref1 = irt.Add(cookie1, obj1.Get());
-    IndirectRef iref2 = irt.Add(cookie1, obj2.Get());
+    IndirectRef iref1 = irt.Add(cookie1, obj1.Get(), &error_msg);
+    IndirectRef iref2 = irt.Add(cookie1, obj2.Get(), &error_msg);
 
     EXPECT_TRUE(irt.Remove(cookie1, iref1));
 
     // New segment.
     const IRTSegmentState cookie2 = irt.GetSegmentState();
 
-    IndirectRef iref3 = irt.Add(cookie2, obj3.Get());
+    IndirectRef iref3 = irt.Add(cookie2, obj3.Get(), &error_msg);
 
     // Pop segment.
     irt.SetSegmentState(cookie2);
 
-    IndirectRef iref4 = irt.Add(cookie1, obj4.Get());
+    IndirectRef iref4 = irt.Add(cookie1, obj4.Get(), &error_msg);
 
     EXPECT_EQ(irt.Capacity(), 3u);
     EXPECT_TRUE(irt.Get(iref1) == nullptr);
@@ -412,20 +412,20 @@
 
     CheckDump(&irt, 0, 0);
 
-    IndirectRef iref0 = irt.Add(cookie0, obj0.Get());
+    IndirectRef iref0 = irt.Add(cookie0, obj0.Get(), &error_msg);
 
     // New segment.
     const IRTSegmentState cookie1 = irt.GetSegmentState();
 
-    IndirectRef iref1 = irt.Add(cookie1, obj1.Get());
+    IndirectRef iref1 = irt.Add(cookie1, obj1.Get(), &error_msg);
     EXPECT_TRUE(irt.Remove(cookie1, iref1));
 
     // Emptied segment, push new one.
     const IRTSegmentState cookie2 = irt.GetSegmentState();
 
-    IndirectRef iref2 = irt.Add(cookie1, obj1.Get());
-    IndirectRef iref3 = irt.Add(cookie1, obj2.Get());
-    IndirectRef iref4 = irt.Add(cookie1, obj3.Get());
+    IndirectRef iref2 = irt.Add(cookie1, obj1.Get(), &error_msg);
+    IndirectRef iref3 = irt.Add(cookie1, obj2.Get(), &error_msg);
+    IndirectRef iref4 = irt.Add(cookie1, obj3.Get(), &error_msg);
 
     EXPECT_TRUE(irt.Remove(cookie1, iref3));
 
@@ -433,7 +433,7 @@
     UNUSED(cookie2);
     irt.SetSegmentState(cookie1);
 
-    IndirectRef iref5 = irt.Add(cookie1, obj4.Get());
+    IndirectRef iref5 = irt.Add(cookie1, obj4.Get(), &error_msg);
 
     EXPECT_EQ(irt.Capacity(), 2u);
     EXPECT_TRUE(irt.Get(iref3) == nullptr);
@@ -455,14 +455,14 @@
 
     CheckDump(&irt, 0, 0);
 
-    IndirectRef iref0 = irt.Add(cookie0, obj0.Get());
+    IndirectRef iref0 = irt.Add(cookie0, obj0.Get(), &error_msg);
 
     // New segment.
     const IRTSegmentState cookie1 = irt.GetSegmentState();
 
-    IndirectRef iref1 = irt.Add(cookie1, obj1.Get());
-    IndirectRef iref2 = irt.Add(cookie1, obj1.Get());
-    IndirectRef iref3 = irt.Add(cookie1, obj2.Get());
+    IndirectRef iref1 = irt.Add(cookie1, obj1.Get(), &error_msg);
+    IndirectRef iref2 = irt.Add(cookie1, obj1.Get(), &error_msg);
+    IndirectRef iref3 = irt.Add(cookie1, obj2.Get(), &error_msg);
 
     EXPECT_TRUE(irt.Remove(cookie1, iref2));
 
@@ -473,7 +473,7 @@
     const IRTSegmentState cookie1_second = irt.GetSegmentState();
     UNUSED(cookie1_second);
 
-    IndirectRef iref4 = irt.Add(cookie1, obj3.Get());
+    IndirectRef iref4 = irt.Add(cookie1, obj3.Get(), &error_msg);
 
     EXPECT_EQ(irt.Capacity(), 2u);
     EXPECT_TRUE(irt.Get(iref3) == nullptr);
@@ -504,7 +504,7 @@
   const IRTSegmentState cookie = kIRTFirstSegment;
 
   for (size_t i = 0; i != kTableMax + 1; ++i) {
-    irt.Add(cookie, obj0.Get());
+    irt.Add(cookie, obj0.Get(), &error_msg);
   }
 
   EXPECT_EQ(irt.Capacity(), kTableMax + 1);
diff --git a/runtime/java_vm_ext.cc b/runtime/java_vm_ext.cc
index c0d1861..5a16053 100644
--- a/runtime/java_vm_ext.cc
+++ b/runtime/java_vm_ext.cc
@@ -589,7 +589,12 @@
     return nullptr;
   }
   WriterMutexLock mu(self, *Locks::jni_globals_lock_);
-  IndirectRef ref = globals_.Add(kIRTFirstSegment, obj);
+  std::string error_msg;
+  IndirectRef ref = globals_.Add(kIRTFirstSegment, obj, &error_msg);
+  if (UNLIKELY(ref == nullptr)) {
+    LOG(FATAL) << error_msg;
+    UNREACHABLE();
+  }
   return reinterpret_cast<jobject>(ref);
 }
 
@@ -607,7 +612,12 @@
     self->CheckEmptyCheckpointFromWeakRefAccess(Locks::jni_weak_globals_lock_);
     weak_globals_add_condition_.WaitHoldingLocks(self);
   }
-  IndirectRef ref = weak_globals_.Add(kIRTFirstSegment, obj);
+  std::string error_msg;
+  IndirectRef ref = weak_globals_.Add(kIRTFirstSegment, obj, &error_msg);
+  if (UNLIKELY(ref == nullptr)) {
+    LOG(FATAL) << error_msg;
+    UNREACHABLE();
+  }
   return reinterpret_cast<jweak>(ref);
 }
 
diff --git a/runtime/jni_env_ext-inl.h b/runtime/jni_env_ext-inl.h
index 25893b7..d66df08 100644
--- a/runtime/jni_env_ext-inl.h
+++ b/runtime/jni_env_ext-inl.h
@@ -25,7 +25,13 @@
 
 template<typename T>
 inline T JNIEnvExt::AddLocalReference(ObjPtr<mirror::Object> obj) {
-  IndirectRef ref = locals.Add(local_ref_cookie, obj);
+  std::string error_msg;
+  IndirectRef ref = locals.Add(local_ref_cookie, obj, &error_msg);
+  if (UNLIKELY(ref == nullptr)) {
+    // This is really unexpected if we allow resizing local IRTs...
+    LOG(FATAL) << error_msg;
+    UNREACHABLE();
+  }
 
   // TODO: fix this to understand PushLocalFrame, so we can turn it on.
   if (false) {
diff --git a/runtime/jni_env_ext.cc b/runtime/jni_env_ext.cc
index 3ff94f9..8352657 100644
--- a/runtime/jni_env_ext.cc
+++ b/runtime/jni_env_ext.cc
@@ -99,7 +99,14 @@
   if (obj == nullptr) {
     return nullptr;
   }
-  return reinterpret_cast<jobject>(locals.Add(local_ref_cookie, obj));
+  std::string error_msg;
+  jobject ref = reinterpret_cast<jobject>(locals.Add(local_ref_cookie, obj, &error_msg));
+  if (UNLIKELY(ref == nullptr)) {
+    // This is really unexpected if we allow resizing local IRTs...
+    LOG(FATAL) << error_msg;
+    UNREACHABLE();
+  }
+  return ref;
 }
 
 void JNIEnvExt::DeleteLocalRef(jobject obj) {