Add resize arrays JVMTI extension

As a prototype for more general object replacement functionality add a
new JVMTI extension that allows one to change the size of arrays. This
extension is 'com.android.art.heap.change_array_size'. As far as any
JVMTI agent, JNI or Java Language code can observer this extension
atomically replaces every reference (strong and weak, global and
local, etc.) with a newly allocated array with the same contents but a
different length. Internally a whole new array will be created then
the old array will have its contents (including lock-word) copied and
all references to the old array will be replaced with the new array.

Test: ./test.py --host
Bug: 134162467

Change-Id: I92a0beabb02e0c92c8c8f9639836014ff1266878
diff --git a/openjdkjvmti/ti_extension.cc b/openjdkjvmti/ti_extension.cc
index a21a97f..9d31a93 100644
--- a/openjdkjvmti/ti_extension.cc
+++ b/openjdkjvmti/ti_extension.cc
@@ -22,7 +22,6 @@
  *
  * You should have received a copy of the GNU General Public License version
  * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  *
  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  * or visit www.oracle.com if you need additional information or have any
@@ -370,6 +369,28 @@
     return error;
   }
 
+  // ChangeArraySize
+  error = add_extension(
+      reinterpret_cast<jvmtiExtensionFunction>(HeapExtensions::ChangeArraySize),
+      "com.android.art.heap.change_array_size",
+      "Changes the size of a java array. As far as all JNI and java code is concerned this is"
+      " atomic. Must have can_tag_objects capability. If the new length of the array is smaller"
+      " than the original length, then the array will be truncated to the new length. Otherwise,"
+      " all new slots will be filled with null, 0, or False as appropriate for the array type.",
+      {
+        { "array", JVMTI_KIND_IN, JVMTI_TYPE_JOBJECT, false },
+        { "new_size", JVMTI_KIND_IN, JVMTI_TYPE_JINT, false },
+      },
+      {
+         ERR(NULL_POINTER),
+         ERR(MUST_POSSESS_CAPABILITY),
+         ERR(ILLEGAL_ARGUMENT),
+         ERR(OUT_OF_MEMORY),
+      });
+  if (error != ERR(NONE)) {
+    return error;
+  }
+
   // Copy into output buffer.
 
   *extension_count_ptr = ext_vector.size();
diff --git a/openjdkjvmti/ti_heap.cc b/openjdkjvmti/ti_heap.cc
index 898363a..3c2b82e 100644
--- a/openjdkjvmti/ti_heap.cc
+++ b/openjdkjvmti/ti_heap.cc
@@ -16,33 +16,53 @@
 
 #include "ti_heap.h"
 
+#include <ios>
+
+#include "android-base/logging.h"
+#include "android-base/thread_annotations.h"
+#include "arch/context.h"
 #include "art_field-inl.h"
 #include "art_jvmti.h"
 #include "base/macros.h"
 #include "base/mutex.h"
+#include "base/utils.h"
 #include "class_linker.h"
 #include "class_root.h"
+#include "deopt_manager.h"
 #include "dex/primitive.h"
+#include "gc/collector_type.h"
+#include "gc/gc_cause.h"
 #include "gc/heap-visit-objects-inl.h"
-#include "gc/heap.h"
+#include "gc/heap-inl.h"
+#include "gc/scoped_gc_critical_section.h"
 #include "gc_root-inl.h"
+#include "handle.h"
 #include "handle_scope.h"
 #include "java_frame_root_info.h"
 #include "jni/jni_env_ext.h"
 #include "jni/jni_id_manager.h"
 #include "jni/jni_internal.h"
 #include "jvmti_weak_table-inl.h"
+#include "mirror/array-inl.h"
+#include "mirror/array.h"
 #include "mirror/class.h"
 #include "mirror/object-inl.h"
+#include "mirror/object-refvisitor-inl.h"
 #include "mirror/object_array-inl.h"
+#include "mirror/object_array-alloc-inl.h"
+#include "mirror/object_reference.h"
 #include "obj_ptr-inl.h"
+#include "object_callbacks.h"
 #include "object_tagging.h"
+#include "offsets.h"
 #include "runtime.h"
 #include "scoped_thread_state_change-inl.h"
-#include "scoped_thread_state_change.h"
 #include "stack.h"
 #include "thread-inl.h"
 #include "thread_list.h"
+#include "ti_logging.h"
+#include "ti_stack.h"
+#include "ti_thread.h"
 #include "well_known_classes.h"
 
 namespace openjdkjvmti {
@@ -1591,4 +1611,260 @@
                               user_data);
 }
 
+namespace {
+
+using ArrayPtr = art::ObjPtr<art::mirror::Array>;
+
+static void ReplaceObjectReferences(ArrayPtr old_arr_ptr, ArrayPtr new_arr_ptr)
+    REQUIRES(art::Locks::mutator_lock_,
+             art::Locks::user_code_suspension_lock_,
+             art::Roles::uninterruptible_) {
+  art::Runtime::Current()->GetHeap()->VisitObjectsPaused(
+      [&](art::mirror::Object* ref) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+        // Rewrite all references in the object if needed.
+        class ResizeReferenceVisitor {
+         public:
+          using CompressedObj = art::mirror::CompressedReference<art::mirror::Object>;
+          ResizeReferenceVisitor(ArrayPtr old_arr, ArrayPtr new_arr)
+              : old_arr_(old_arr), new_arr_(new_arr) {}
+
+          // Ignore class roots. These do not need to be handled for arrays.
+          void VisitRootIfNonNull(CompressedObj* root ATTRIBUTE_UNUSED) const {}
+          void VisitRoot(CompressedObj* root ATTRIBUTE_UNUSED) const {}
+
+          void operator()(art::ObjPtr<art::mirror::Object> obj,
+                          art::MemberOffset off,
+                          bool is_static ATTRIBUTE_UNUSED) const
+              REQUIRES_SHARED(art::Locks::mutator_lock_) {
+            if (obj->GetFieldObject<art::mirror::Object>(off) == old_arr_) {
+              LOG(DEBUG) << "Updating field at offset " << off.Uint32Value() << " of type "
+                         << obj->GetClass()->PrettyClass();
+              obj->SetFieldObject</*transaction*/ false>(off, new_arr_);
+            }
+          }
+
+          // java.lang.ref.Reference visitor.
+          void operator()(art::ObjPtr<art::mirror::Class> klass ATTRIBUTE_UNUSED,
+                          art::ObjPtr<art::mirror::Reference> ref) const
+              REQUIRES_SHARED(art::Locks::mutator_lock_) {
+            operator()(ref, art::mirror::Reference::ReferentOffset(), /* is_static */ false);
+          }
+
+         private:
+          ArrayPtr old_arr_;
+          ArrayPtr new_arr_;
+        };
+
+        ResizeReferenceVisitor rrv(old_arr_ptr, new_arr_ptr);
+        ref->VisitReferences(rrv, rrv);
+      });
+}
+
+static void ReplaceStrongRoots(art::Thread* self, ArrayPtr old_arr_ptr, ArrayPtr new_arr_ptr)
+    REQUIRES(art::Locks::mutator_lock_,
+             art::Locks::user_code_suspension_lock_,
+             art::Roles::uninterruptible_) {
+  // replace root references expcept java frames.
+  struct ResizeRootVisitor : public art::RootVisitor {
+   public:
+    ResizeRootVisitor(ArrayPtr new_val, ArrayPtr old_val)
+        : new_val_(new_val), old_val_(old_val) {}
+
+    // TODO It's somewhat annoying to have to have this function implemented twice. It might be
+    // good/useful to implement operator= for CompressedReference to allow us to use a template to
+    // implement both of these.
+    void VisitRoots(art::mirror::Object*** roots, size_t count, const art::RootInfo& info) override
+        REQUIRES_SHARED(art::Locks::mutator_lock_) {
+      art::mirror::Object*** end = roots + count;
+      for (art::mirror::Object** obj = *roots; roots != end; obj = *(++roots)) {
+        if (*obj == old_val_) {
+          // Java frames might have the JIT doing optimizations (for example loop-unrolling or
+          // eliding bounds checks) so we need deopt them once we're done here.
+          if (info.GetType() == art::RootType::kRootJavaFrame) {
+            threads_with_roots_.insert(info.GetThreadId());
+          }
+          *obj = new_val_.Ptr();
+        }
+      }
+    }
+
+    void VisitRoots(art::mirror::CompressedReference<art::mirror::Object>** roots,
+                    size_t count,
+                    const art::RootInfo& info) override REQUIRES_SHARED(art::Locks::mutator_lock_) {
+      art::mirror::CompressedReference<art::mirror::Object>** end = roots + count;
+      for (art::mirror::CompressedReference<art::mirror::Object>* obj = *roots; roots != end;
+           obj = *(++roots)) {
+        if (obj->AsMirrorPtr() == old_val_) {
+          // Java frames might have the JIT doing optimizations (for example loop-unrolling or
+          // eliding bounds checks) so we need deopt them once we're done here.
+          if (info.GetType() == art::RootType::kRootJavaFrame) {
+            threads_with_roots_.insert(info.GetThreadId());
+          }
+          obj->Assign(new_val_);
+        }
+      }
+    }
+
+    const std::unordered_set<uint32_t>& GetThreadsWithJavaFrameRoots() const {
+      return threads_with_roots_;
+    }
+
+   private:
+    ArrayPtr new_val_;
+    ArrayPtr old_val_;
+    std::unordered_set<uint32_t> threads_with_roots_;
+  };
+  ResizeRootVisitor rrv(new_arr_ptr, old_arr_ptr);
+  art::Runtime::Current()->VisitRoots(&rrv, art::VisitRootFlags::kVisitRootFlagAllRoots);
+  // Handle java Frames. Annoyingly the JIT can embed information about the length of the array into
+  // the compiled code. By changing the length of the array we potentially invalidate these
+  // assumptions and so could cause (eg) OOB array access or other issues.
+  if (!rrv.GetThreadsWithJavaFrameRoots().empty()) {
+    art::MutexLock mu(self, *art::Locks::thread_list_lock_);
+    art::ThreadList* thread_list = art::Runtime::Current()->GetThreadList();
+    art::instrumentation::Instrumentation* instr = art::Runtime::Current()->GetInstrumentation();
+    for (uint32_t id : rrv.GetThreadsWithJavaFrameRoots()) {
+      art::Thread* t = thread_list->FindThreadByThreadId(id);
+      CHECK(t != nullptr) << "id " << id << " does not refer to a valid thread."
+                          << " Where did the roots come from?";
+      // TODO Use deopt manager. We need a version that doesn't acquire all the locks we
+      // already have.
+      // TODO We technically only need to do this if the frames are not already being interpreted.
+      // The cost for doing an extra stack walk is unlikely to be worth it though.
+      instr->InstrumentThreadStack(t);
+    }
+  }
+}
+
+static void ReplaceWeakRoots(ArrayPtr old_arr_ptr, ArrayPtr new_arr_ptr)
+    REQUIRES(art::Locks::mutator_lock_,
+             art::Locks::user_code_suspension_lock_,
+             art::Roles::uninterruptible_) {
+  struct ReplaceWeaksVisitor : public art::IsMarkedVisitor {
+   public:
+    ReplaceWeaksVisitor(ArrayPtr old_arr, ArrayPtr new_arr)
+        : old_arr_(old_arr), new_arr_(new_arr) {}
+
+    art::mirror::Object* IsMarked(art::mirror::Object* obj)
+        REQUIRES_SHARED(art::Locks::mutator_lock_) {
+      if (obj == old_arr_) {
+        return new_arr_.Ptr();
+      } else {
+        return obj;
+      }
+    }
+
+   private:
+    ArrayPtr old_arr_;
+    ArrayPtr new_arr_;
+  };
+  ReplaceWeaksVisitor rwv(old_arr_ptr, new_arr_ptr);
+  art::Runtime::Current()->SweepSystemWeaks(&rwv);
+}
+
+static void PerformArrayReferenceReplacement(art::Thread* self,
+                                             ArrayPtr old_arr_ptr,
+                                             ArrayPtr new_arr_ptr)
+    REQUIRES(art::Locks::mutator_lock_,
+             art::Locks::user_code_suspension_lock_,
+             art::Roles::uninterruptible_) {
+  ReplaceObjectReferences(old_arr_ptr, new_arr_ptr);
+  ReplaceStrongRoots(self, old_arr_ptr, new_arr_ptr);
+  ReplaceWeakRoots(old_arr_ptr, new_arr_ptr);
+}
+
+}  // namespace
+
+jvmtiError HeapExtensions::ChangeArraySize(jvmtiEnv* env, jobject arr, jsize new_size) {
+  if (ArtJvmTiEnv::AsArtJvmTiEnv(env)->capabilities.can_tag_objects != 1) {
+    return ERR(MUST_POSSESS_CAPABILITY);
+  }
+  art::Thread* self = art::Thread::Current();
+  ScopedNoUserCodeSuspension snucs(self);
+  art::ScopedObjectAccess soa(self);
+  if (arr == nullptr) {
+    JVMTI_LOG(INFO, env) << "Cannot resize a null object";
+    return ERR(NULL_POINTER);
+  }
+  art::ObjPtr<art::mirror::Class> klass(soa.Decode<art::mirror::Object>(arr)->GetClass());
+  if (!klass->IsArrayClass()) {
+    JVMTI_LOG(INFO, env) << klass->PrettyClass() << " is not an array class!";
+    return ERR(ILLEGAL_ARGUMENT);
+  }
+  if (new_size < 0) {
+    JVMTI_LOG(INFO, env) << "Cannot resize an array to a negative size";
+    return ERR(ILLEGAL_ARGUMENT);
+  }
+  // Allocate the new copy.
+  art::StackHandleScope<2> hs(self);
+  art::Handle<art::mirror::Array> old_arr(hs.NewHandle(soa.Decode<art::mirror::Array>(arr)));
+  art::MutableHandle<art::mirror::Array> new_arr(hs.NewHandle<art::mirror::Array>(nullptr));
+  if (klass->IsObjectArrayClass()) {
+    new_arr.Assign(
+        art::mirror::ObjectArray<art::mirror::Object>::Alloc(self, old_arr->GetClass(), new_size));
+  } else {
+    // NB This also copies the old array but since we aren't suspended we need to do this again to
+    // catch any concurrent modifications.
+    new_arr.Assign(art::mirror::Array::CopyOf(old_arr, self, new_size));
+  }
+  if (new_arr.IsNull()) {
+    self->AssertPendingOOMException();
+    JVMTI_LOG(INFO, env) << "Unable to allocate " << old_arr->GetClass()->PrettyClass()
+                         << " (length: " << new_size << ") due to OOME. Error was: "
+                         << self->GetException()->Dump();
+    self->ClearException();
+    return ERR(OUT_OF_MEMORY);
+  } else {
+    self->AssertNoPendingException();
+  }
+  // Suspend everything.
+  art::ScopedThreadSuspension sts(self, art::ThreadState::kSuspended);
+  art::gc::ScopedGCCriticalSection sgccs(
+      self, art::gc::GcCause::kGcCauseDebugger, art::gc::CollectorType::kCollectorTypeDebugger);
+  art::ScopedSuspendAll ssa("Resize array!");
+  // Replace internals.
+  new_arr->SetLockWord(old_arr->GetLockWord(false), false);
+  old_arr->SetLockWord(art::LockWord::Default(), false);
+  // Copy the contents now when everything is suspended.
+  int32_t size = std::min(old_arr->GetLength(), new_size);
+  switch (old_arr->GetClass()->GetComponentType()->GetPrimitiveType()) {
+    case art::Primitive::kPrimBoolean:
+      new_arr->AsBooleanArray()->Memcpy(0, old_arr->AsBooleanArray(), 0, size);
+      break;
+    case art::Primitive::kPrimByte:
+      new_arr->AsByteArray()->Memcpy(0, old_arr->AsByteArray(), 0, size);
+      break;
+    case art::Primitive::kPrimChar:
+      new_arr->AsCharArray()->Memcpy(0, old_arr->AsCharArray(), 0, size);
+      break;
+    case art::Primitive::kPrimShort:
+      new_arr->AsShortArray()->Memcpy(0, old_arr->AsShortArray(), 0, size);
+      break;
+    case art::Primitive::kPrimInt:
+      new_arr->AsIntArray()->Memcpy(0, old_arr->AsIntArray(), 0, size);
+      break;
+    case art::Primitive::kPrimLong:
+      new_arr->AsLongArray()->Memcpy(0, old_arr->AsLongArray(), 0, size);
+      break;
+    case art::Primitive::kPrimFloat:
+      new_arr->AsFloatArray()->Memcpy(0, old_arr->AsFloatArray(), 0, size);
+      break;
+    case art::Primitive::kPrimDouble:
+      new_arr->AsDoubleArray()->Memcpy(0, old_arr->AsDoubleArray(), 0, size);
+      break;
+    case art::Primitive::kPrimNot:
+      for (int32_t i = 0; i < size; i++) {
+        new_arr->AsObjectArray<art::mirror::Object>()->Set(
+            i, old_arr->AsObjectArray<art::mirror::Object>()->Get(i));
+      }
+      break;
+    case art::Primitive::kPrimVoid:
+      LOG(FATAL) << "void-array is not a legal type!";
+      UNREACHABLE();
+  }
+  // Actually replace all the pointers.
+  PerformArrayReferenceReplacement(self, old_arr.Get(), new_arr.Get());
+  return OK;
+}
+
 }  // namespace openjdkjvmti
diff --git a/openjdkjvmti/ti_heap.h b/openjdkjvmti/ti_heap.h
index 382d80f..5a32436 100644
--- a/openjdkjvmti/ti_heap.h
+++ b/openjdkjvmti/ti_heap.h
@@ -72,6 +72,8 @@
                                                   jclass klass,
                                                   const jvmtiHeapCallbacks* callbacks,
                                                   const void* user_data);
+
+  static jvmtiError JNICALL ChangeArraySize(jvmtiEnv* env, jobject arr, jsize new_size);
 };
 
 }  // namespace openjdkjvmti
diff --git a/runtime/thread_list.h b/runtime/thread_list.h
index 93b9bdf..b5b4450 100644
--- a/runtime/thread_list.h
+++ b/runtime/thread_list.h
@@ -148,6 +148,13 @@
   void ForEach(void (*callback)(Thread*, void*), void* context)
       REQUIRES(Locks::thread_list_lock_);
 
+  template<typename CallBack>
+  void ForEach(CallBack cb) REQUIRES(Locks::thread_list_lock_) {
+    ForEach([](Thread* t, void* ctx) REQUIRES(Locks::thread_list_lock_) {
+      (*reinterpret_cast<CallBack*>(ctx))(t);
+    }, &cb);
+  }
+
   // Add/remove current thread from list.
   void Register(Thread* self)
       REQUIRES(Locks::runtime_shutdown_lock_)
diff --git a/test/1974-resize-array/expected.txt b/test/1974-resize-array/expected.txt
new file mode 100644
index 0000000..4b0d432
--- /dev/null
+++ b/test/1974-resize-array/expected.txt
@@ -0,0 +1,78 @@
+Test instance
+val is: [1, 2, 3] resize +3
+val is: [1, 2, 3, 0, 0, 0, 0, 0]
+Same value? true
+
+Test HashMap
+val is: [1, 2, 3, 4] resize +3
+Map is: ([1, 2, 3, 4]->Other Value), ([1, 2, 3, 4]->THE VALUE), ([1, 4]->Third value), 
+val is: [1, 2, 3, 4, 0, 0, 0]
+Map is: ([1, 2, 3, 4]->Other Value), ([1, 2, 3, 4, 0, 0, 0]->THE VALUE), ([1, 4]->Third value), 
+
+Test j.l.r.WeakReference
+val is: [weak, ref] resize +3
+val is: [weak, ref, null, null, null, null, null]
+Same value? true
+
+Test instance self-ref
+val is: [<SELF REF>, A, B, C] resize +5 item 0 is [<SELF REF>, A, B, C]
+val is: [<SELF REF>, A, B, C, null, null, null, null, null]
+val is: [<SELF REF>, A, B, C, null, null, null, null, null]
+Same value? true
+Same structure? true
+Same inner-structure? true
+
+Test instance self-ref smaller
+val is: [<SELF REF>, A, B, C, null, null, null, null, null] resize -7 item 0 is [<SELF REF>, A, B, C, null, null, null, null, null]
+val is: [<SELF REF>, A]
+val is: [<SELF REF>, A]
+Same value? true
+Same structure? true
+Same inner-structure? true
+
+Test local
+val is: [2, 3, 4] resize +5
+val is: [2, 3, 4, 0, 0, 0, 0, 0]
+Same value? true
+
+Test local smaller
+val is: [1, 2, 3, 4, 5] resize -2
+val is: [1, 2, 3]
+Same value? true
+
+T1: Test local multi-thread
+T1: val is: [1, 2, 3] resize -2
+T1: val is: [1]
+T1: Same value? true
+T2: Test local multi-thread
+T2: val is: [1, 2, 3] resize -2
+T2: val is: [1]
+T2: Same value? true
+
+Test locks
+val is: [A, 2, C] resize -2
+val is: [A]
+Same value? true
+Locks seem to all work.
+
+Test jni-ref
+val is: [1, 11, 111] resize +5
+val is: [1, 11, 111, null, null, null, null, null]
+Same value? true
+
+Test weak jni-ref
+val is: [2, 22, 222] resize +5
+val is: [2, 22, 222, null, null, null, null, null]
+Same value? true
+
+Test jni local ref
+val is: [3, 32, 322]
+Resize +4
+val is: [3, 32, 322, null, null, null, null]
+Same value? true
+
+Test jvmti-tags
+val is: [[3, 33, 333]] resize +5
+val is: [[3, 33, 333, null, null, null, null, null]]
+Same value? true
+
diff --git a/test/1974-resize-array/info.txt b/test/1974-resize-array/info.txt
new file mode 100644
index 0000000..ef4fa40
--- /dev/null
+++ b/test/1974-resize-array/info.txt
@@ -0,0 +1,3 @@
+Test for change_array_size extension function.
+
+Tests that we are able to use the extension function to change the length of arrays.
diff --git a/test/1974-resize-array/resize_array.cc b/test/1974-resize-array/resize_array.cc
new file mode 100644
index 0000000..dadcbea
--- /dev/null
+++ b/test/1974-resize-array/resize_array.cc
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <cstdio>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "android-base/logging.h"
+#include "android-base/macros.h"
+#include "android-base/stringprintf.h"
+
+#include "jni.h"
+#include "jvmti.h"
+#include "scoped_local_ref.h"
+#include "scoped_utf_chars.h"
+
+// Test infrastructure
+#include "jni_helper.h"
+#include "jvmti_helper.h"
+#include "test_env.h"
+#include "ti_macros.h"
+
+namespace art {
+namespace Test1974ResizeArray {
+
+using ChangeArraySize = jvmtiError (*)(jvmtiEnv* env, jobject arr, jint size);
+
+template <typename T> static void Dealloc(T* t) {
+  jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(t));
+}
+
+template <typename T, typename... Rest> static void Dealloc(T* t, Rest... rs) {
+  Dealloc(t);
+  Dealloc(rs...);
+}
+
+static void DeallocParams(jvmtiParamInfo* params, jint n_params) {
+  for (jint i = 0; i < n_params; i++) {
+    Dealloc(params[i].name);
+  }
+}
+
+static jvmtiExtensionFunction FindExtensionMethod(JNIEnv* env, const std::string& name) {
+  jint n_ext;
+  jvmtiExtensionFunctionInfo* infos;
+  if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetExtensionFunctions(&n_ext, &infos))) {
+    return nullptr;
+  }
+  jvmtiExtensionFunction res = nullptr;
+  for (jint i = 0; i < n_ext; i++) {
+    jvmtiExtensionFunctionInfo* cur_info = &infos[i];
+    if (strcmp(name.c_str(), cur_info->id) == 0) {
+      res = cur_info->func;
+    }
+    // Cleanup the cur_info
+    DeallocParams(cur_info->params, cur_info->param_count);
+    Dealloc(cur_info->id, cur_info->short_description, cur_info->params, cur_info->errors);
+  }
+  // Cleanup the array.
+  Dealloc(infos);
+  if (res == nullptr) {
+    ScopedLocalRef<jclass> rt_exception(env, env->FindClass("java/lang/RuntimeException"));
+    env->ThrowNew(rt_exception.get(), (name + " extensions not found").c_str());
+    return nullptr;
+  }
+  return res;
+}
+
+extern "C" JNIEXPORT void JNICALL Java_art_Test1974_ResizeArray(JNIEnv* env,
+                                                                jclass klass ATTRIBUTE_UNUSED,
+                                                                jobject ref_gen,
+                                                                jint new_size) {
+  ChangeArraySize change_array_size = reinterpret_cast<ChangeArraySize>(
+      FindExtensionMethod(env, "com.android.art.heap.change_array_size"));
+  if (change_array_size == nullptr) {
+    return;
+  }
+  jmethodID getArr = env->GetMethodID(
+      env->FindClass("java/util/function/Supplier"), "get", "()Ljava/lang/Object;");
+  jobject arr = env->CallObjectMethod(ref_gen, getArr);
+  JvmtiErrorToException(env, jvmti_env, change_array_size(jvmti_env, arr, new_size));
+}
+
+extern "C" JNIEXPORT jobject JNICALL Java_art_Test1974_ReadJniRef(JNIEnv* env,
+                                                                  jclass klass ATTRIBUTE_UNUSED,
+                                                                  jlong r) {
+  return env->NewLocalRef(reinterpret_cast<jobject>(static_cast<intptr_t>(r)));
+}
+
+extern "C" JNIEXPORT jlong JNICALL
+Java_art_Test1974_GetWeakGlobalJniRef(JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jobject r) {
+  return static_cast<jlong>(reinterpret_cast<intptr_t>(env->NewWeakGlobalRef(r)));
+}
+
+extern "C" JNIEXPORT jlong JNICALL Java_art_Test1974_GetGlobalJniRef(JNIEnv* env,
+                                                                     jclass klass ATTRIBUTE_UNUSED,
+                                                                     jobject r) {
+  return static_cast<jlong>(reinterpret_cast<intptr_t>(env->NewGlobalRef(r)));
+}
+
+extern "C" JNIEXPORT jobjectArray JNICALL
+Java_art_Test1974_GetObjectsWithTag(JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jlong tag) {
+  jsize cnt = 0;
+  jobject* res = nullptr;
+  if (JvmtiErrorToException(
+          env, jvmti_env, jvmti_env->GetObjectsWithTags(1, &tag, &cnt, &res, nullptr))) {
+    return nullptr;
+  }
+  jobjectArray ret = env->NewObjectArray(cnt, env->FindClass("java/lang/Object"), nullptr);
+  if (ret == nullptr) {
+    return nullptr;
+  }
+  for (jsize i = 0; i < cnt; i++) {
+    env->SetObjectArrayElement(ret, i, res[i]);
+  }
+  jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(res));
+  return ret;
+}
+
+extern "C" JNIEXPORT void JNICALL Java_art_Test1974_runNativeTest(JNIEnv* env,
+                                                                  jclass klass ATTRIBUTE_UNUSED,
+                                                                  jobjectArray arr,
+                                                                  jobject resize,
+                                                                  jobject print,
+                                                                  jobject check) {
+  jmethodID run = env->GetMethodID(env->FindClass("java/lang/Runnable"), "run", "()V");
+  jmethodID accept = env->GetMethodID(
+      env->FindClass("java/util/function/Consumer"), "accept", "(Ljava/lang/Object;)V");
+  env->CallVoidMethod(print, accept, arr);
+  env->CallVoidMethod(resize, run);
+  env->CallVoidMethod(print, accept, arr);
+  env->CallVoidMethod(check, accept, arr);
+}
+}  // namespace Test1974ResizeArray
+}  // namespace art
diff --git a/test/1974-resize-array/run b/test/1974-resize-array/run
new file mode 100755
index 0000000..96646c8
--- /dev/null
+++ b/test/1974-resize-array/run
@@ -0,0 +1,18 @@
+#!/bin/bash
+#
+# Copyright 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+./default-run "$@" --jvmti
diff --git a/test/1974-resize-array/src/Main.java b/test/1974-resize-array/src/Main.java
new file mode 100644
index 0000000..3843973
--- /dev/null
+++ b/test/1974-resize-array/src/Main.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public class Main {
+  public static void main(String[] args) throws Exception {
+    art.Test1974.run();
+  }
+}
diff --git a/test/1974-resize-array/src/art/Main.java b/test/1974-resize-array/src/art/Main.java
new file mode 120000
index 0000000..84ae4ac
--- /dev/null
+++ b/test/1974-resize-array/src/art/Main.java
@@ -0,0 +1 @@
+../../../jvmti-common/Main.java
\ No newline at end of file
diff --git a/test/1974-resize-array/src/art/Test1974.java b/test/1974-resize-array/src/art/Test1974.java
new file mode 100644
index 0000000..8460e82
--- /dev/null
+++ b/test/1974-resize-array/src/art/Test1974.java
@@ -0,0 +1,473 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+public class Test1974 {
+
+  public static final boolean DEBUG = false;
+
+  public static int[] static_field = new int[] {1, 2, 3};
+  public static Object[] static_field_ref = new String[] {"a", "b", "c"};
+
+  public static final class InstanceClass {
+    public int[] instance_field = new int[] {1, 2, 3};
+    public Object[] self_ref;
+
+    public InstanceClass() {
+      self_ref = new Object[] {null, "A", "B", "C"};
+      self_ref[0] = self_ref;
+    }
+  }
+
+  static InstanceClass theInstanceClass;
+  static InstanceClass theOtherInstanceClass;
+
+  static {
+    theInstanceClass = new InstanceClass();
+    theOtherInstanceClass = new InstanceClass();
+    theOtherInstanceClass.instance_field = theInstanceClass.instance_field;
+    theOtherInstanceClass.self_ref = theInstanceClass.self_ref;
+  }
+
+  public static void DbgPrintln(String s) {
+    if (DEBUG) {
+      System.out.println(s);
+    }
+  }
+
+  public interface ThrowRunnable extends Runnable {
+    public default void run() {
+      try {
+        throwRun();
+      } catch (Exception e) {
+        throw new Error("Exception in runner!", e);
+      }
+    }
+
+    public void throwRun() throws Exception;
+  }
+
+  public static void runAsThread(ThrowRunnable r) throws Exception {
+    Thread t = new Thread(r);
+    t.start();
+    t.join();
+    System.out.println("");
+  }
+
+  public static void runInstance() {
+    System.out.println("Test instance");
+    DbgPrintln("Pre hash: " + theInstanceClass.instance_field.hashCode());
+    System.out.println(
+        "val is: " + Arrays.toString(theInstanceClass.instance_field) + " resize +3");
+    ResizeArray(() -> theInstanceClass.instance_field, theInstanceClass.instance_field.length + 5);
+    System.out.println("val is: " + Arrays.toString(theInstanceClass.instance_field));
+    DbgPrintln("Post hash: " + theInstanceClass.instance_field.hashCode());
+    System.out.println(
+        "Same value? " + (theInstanceClass.instance_field == theOtherInstanceClass.instance_field));
+  }
+
+  public static void runHashMap() {
+    System.out.println("Test HashMap");
+    HashMap<byte[], Comparable> map = new HashMap();
+    Comparable the_value = "THE VALUE";
+    Supplier<byte[]> get_the_value =
+        () ->
+            map.entrySet().stream()
+                .filter((x) -> x.getValue().equals(the_value))
+                .findFirst()
+                .get()
+                .getKey();
+    map.put(new byte[] {1, 2, 3, 4}, the_value);
+    map.put(new byte[] {1, 2, 3, 4}, "Other Value");
+    map.put(new byte[] {1, 4}, "Third value");
+    System.out.println("val is: " + Arrays.toString(get_the_value.get()) + " resize +3");
+    System.out.print("Map is: ");
+    map.entrySet().stream()
+        .sorted((x, y) -> x.getValue().compareTo(y.getValue()))
+        .forEach(
+            (e) -> {
+              System.out.print("(" + Arrays.toString(e.getKey()) + "->" + e.getValue() + "), ");
+            });
+    System.out.println();
+    ResizeArray(get_the_value, 7);
+    System.out.println("val is: " + Arrays.toString(get_the_value.get()));
+    System.out.print("Map is: ");
+    map.entrySet().stream()
+        .sorted((x, y) -> x.getValue().compareTo(y.getValue()))
+        .forEach(
+            (e) -> {
+              System.out.print("(" + Arrays.toString(e.getKey()) + "->" + e.getValue() + "), ");
+            });
+    System.out.println();
+  }
+
+  public static void runWeakReference() {
+    System.out.println("Test j.l.r.WeakReference");
+    String[] arr = new String[] {"weak", "ref"};
+    WeakReference<String[]> wr = new WeakReference(arr);
+    DbgPrintln("Pre hash: " + wr.get().hashCode());
+    System.out.println("val is: " + Arrays.toString(wr.get()) + " resize +3");
+    ResizeArray(wr::get, wr.get().length + 5);
+    System.out.println("val is: " + Arrays.toString(wr.get()));
+    DbgPrintln("Post hash: " + wr.get().hashCode());
+    System.out.println("Same value? " + (wr.get() == arr));
+  }
+
+  public static void runInstanceSelfRef() {
+    System.out.println("Test instance self-ref");
+    DbgPrintln("Pre hash: " + Integer.toHexString(theInstanceClass.self_ref.hashCode()));
+    String pre_to_string = theInstanceClass.self_ref.toString();
+    System.out.println(
+        "val is: "
+            + Arrays.toString(theInstanceClass.self_ref).replace(pre_to_string, "<SELF REF>")
+            + " resize +5 item 0 is "
+            + Arrays.toString((Object[]) theInstanceClass.self_ref[0])
+                .replace(pre_to_string, "<SELF REF>"));
+    ResizeArray(() -> theInstanceClass.self_ref, theInstanceClass.self_ref.length + 5);
+    System.out.println(
+        "val is: "
+            + Arrays.toString(theInstanceClass.self_ref).replace(pre_to_string, "<SELF REF>"));
+    System.out.println(
+        "val is: "
+            + Arrays.toString((Object[]) theInstanceClass.self_ref[0])
+                .replace(pre_to_string, "<SELF REF>"));
+    DbgPrintln("Post hash: " + Integer.toHexString(theInstanceClass.self_ref.hashCode()));
+    System.out.println(
+        "Same value? " + (theInstanceClass.self_ref == theOtherInstanceClass.self_ref));
+    System.out.println(
+        "Same structure? " + (theInstanceClass.self_ref == theInstanceClass.self_ref[0]));
+    System.out.println(
+        "Same inner-structure? "
+            + (theInstanceClass.self_ref[0] == ((Object[]) theInstanceClass.self_ref[0])[0]));
+  }
+
+  public static void runInstanceSelfRefSmall() {
+    System.out.println("Test instance self-ref smaller");
+    DbgPrintln("Pre hash: " + Integer.toHexString(theInstanceClass.self_ref.hashCode()));
+    String pre_to_string = theInstanceClass.self_ref.toString();
+    System.out.println(
+        "val is: "
+            + Arrays.toString(theInstanceClass.self_ref).replace(pre_to_string, "<SELF REF>")
+            + " resize -7 item 0 is "
+            + Arrays.toString((Object[]) theInstanceClass.self_ref[0])
+                .replace(pre_to_string, "<SELF REF>"));
+    ResizeArray(() -> theInstanceClass.self_ref, theInstanceClass.self_ref.length - 7);
+    System.out.println(
+        "val is: "
+            + Arrays.toString(theInstanceClass.self_ref).replace(pre_to_string, "<SELF REF>"));
+    System.out.println(
+        "val is: "
+            + Arrays.toString((Object[]) theInstanceClass.self_ref[0])
+                .replace(pre_to_string, "<SELF REF>"));
+    DbgPrintln("Post hash: " + Integer.toHexString(theInstanceClass.self_ref.hashCode()));
+    System.out.println(
+        "Same value? " + (theInstanceClass.self_ref == theOtherInstanceClass.self_ref));
+    System.out.println(
+        "Same structure? " + (theInstanceClass.self_ref == theInstanceClass.self_ref[0]));
+    System.out.println(
+        "Same inner-structure? "
+            + (theInstanceClass.self_ref[0] == ((Object[]) theInstanceClass.self_ref[0])[0]));
+  }
+
+  public static void runLocal() throws Exception {
+    final int[] arr_loc = new int[] {2, 3, 4};
+    int[] arr_loc_2 = arr_loc;
+
+    System.out.println("Test local");
+    DbgPrintln("Pre hash: " + arr_loc.hashCode());
+    System.out.println("val is: " + Arrays.toString(arr_loc) + " resize +5");
+    ResizeArray(() -> arr_loc, arr_loc.length + 5);
+    System.out.println("val is: " + Arrays.toString(arr_loc));
+    DbgPrintln("Post hash: " + arr_loc.hashCode());
+    System.out.println("Same value? " + (arr_loc == arr_loc_2));
+  }
+
+  public static void runLocalSmall() throws Exception {
+    final int[] arr_loc = new int[] {1, 2, 3, 4, 5};
+    int[] arr_loc_2 = arr_loc;
+
+    System.out.println("Test local smaller");
+    DbgPrintln("Pre hash: " + arr_loc.hashCode());
+    System.out.println("val is: " + Arrays.toString(arr_loc) + " resize -2");
+    ResizeArray(() -> arr_loc, arr_loc.length - 2);
+    System.out.println("val is: " + Arrays.toString(arr_loc));
+    DbgPrintln("Post hash: " + arr_loc.hashCode());
+    System.out.println("Same value? " + (arr_loc == arr_loc_2));
+  }
+
+  public static void runMultiThreadLocal() throws Exception {
+    final CountDownLatch cdl = new CountDownLatch(1);
+    final CountDownLatch start_cdl = new CountDownLatch(2);
+    final Supplier<Object[]> getArr =
+        new Supplier<Object[]>() {
+          public final Object[] arr = new Object[] {"1", "2", "3"};
+
+          public Object[] get() {
+            return arr;
+          }
+        };
+    final ArrayList<String> msg1 = new ArrayList();
+    final ArrayList<String> msg2 = new ArrayList();
+    final Consumer<String> print1 =
+        (String s) -> {
+          msg1.add(s);
+        };
+    final Consumer<String> print2 =
+        (String s) -> {
+          msg2.add(s);
+        };
+    Function<Consumer<String>, Runnable> r =
+        (final Consumer<String> c) ->
+            () -> {
+              c.accept("Test local multi-thread");
+              Object[] arr_loc = getArr.get();
+              Object[] arr_loc_2 = getArr.get();
+
+              DbgPrintln("Pre hash: " + arr_loc.hashCode());
+              c.accept("val is: " + Arrays.toString(arr_loc) + " resize -2");
+
+              try {
+                start_cdl.countDown();
+                cdl.await();
+              } catch (Exception e) {
+                throw new Error("failed await", e);
+              }
+              c.accept("val is: " + Arrays.toString(arr_loc));
+              DbgPrintln("Post hash: " + arr_loc.hashCode());
+              c.accept("Same value? " + (arr_loc == arr_loc_2));
+            };
+    Thread t1 = new Thread(r.apply(print1));
+    Thread t2 = new Thread(r.apply(print2));
+    t1.start();
+    t2.start();
+    start_cdl.await();
+    ResizeArray(getArr, 1);
+    cdl.countDown();
+    t1.join();
+    t2.join();
+    for (String s : msg1) {
+      System.out.println("T1: " + s);
+    }
+    for (String s : msg2) {
+      System.out.println("T2: " + s);
+    }
+  }
+
+  public static void runWithLocks() throws Exception {
+    final CountDownLatch cdl = new CountDownLatch(1);
+    final CountDownLatch start_cdl = new CountDownLatch(2);
+    final CountDownLatch waiter_start_cdl = new CountDownLatch(1);
+    final Supplier<Object[]> getArr =
+        new Supplier<Object[]>() {
+          public final Object[] arr = new Object[] {"A", "2", "C"};
+
+          public Object[] get() {
+            return arr;
+          }
+        };
+    // basic order of operations noted above each line.
+    // Waiter runs to the 'wait' then t1 runs to the cdl.await, then current thread runs.
+    Runnable r =
+        () -> {
+          System.out.println("Test locks");
+          Object[] arr_loc = getArr.get();
+          Object[] arr_loc_2 = getArr.get();
+
+          DbgPrintln("Pre hash: " + arr_loc.hashCode());
+          System.out.println("val is: " + Arrays.toString(arr_loc) + " resize -2");
+
+          try {
+            // OP 1
+            waiter_start_cdl.await();
+            // OP 6
+            synchronized (arr_loc) {
+              // OP 7
+              synchronized (arr_loc_2) {
+                // OP 8
+                start_cdl.countDown();
+                // OP 9
+                cdl.await();
+                // OP 13
+              }
+            }
+          } catch (Exception e) {
+            throw new Error("failed await", e);
+          }
+          System.out.println("val is: " + Arrays.toString(arr_loc));
+          DbgPrintln("Post hash: " + arr_loc.hashCode());
+          System.out.println("Same value? " + (arr_loc == arr_loc_2));
+        };
+    Thread t1 = new Thread(r);
+    Thread waiter =
+        new Thread(
+            () -> {
+              try {
+                Object a = getArr.get();
+                // OP 2
+                synchronized (a) {
+                  // OP 3
+                  waiter_start_cdl.countDown();
+                  // OP 4
+                  start_cdl.countDown();
+                  // OP 5
+                  a.wait();
+                  // OP 15
+                }
+              } catch (Exception e) {
+                throw new Error("Failed wait!", e);
+              }
+            });
+    waiter.start();
+    t1.start();
+    // OP 10
+    start_cdl.await();
+    // OP 11
+    ResizeArray(getArr, 1);
+    // OP 12
+    cdl.countDown();
+    // OP 14
+    synchronized (getArr.get()) {
+      // Make sure thread wakes up and has the right lock.
+      getArr.get().notifyAll();
+    }
+    waiter.join();
+    t1.join();
+    // Make sure other threads can still lock it.
+    synchronized (getArr.get()) {
+    }
+    System.out.println("Locks seem to all work.");
+  }
+
+  public static void runWithJniGlobal() throws Exception {
+    Object[] arr = new Object[] {"1", "11", "111"};
+    final long globalID = GetGlobalJniRef(arr);
+    System.out.println("Test jni-ref");
+    DbgPrintln("Pre hash: " + ReadJniRef(globalID).hashCode());
+    System.out.println(
+        "val is: " + Arrays.toString((Object[]) ReadJniRef(globalID)) + " resize +5");
+    ResizeArray(() -> ReadJniRef(globalID), ((Object[]) ReadJniRef(globalID)).length + 5);
+    System.out.println("val is: " + Arrays.toString((Object[]) ReadJniRef(globalID)));
+    DbgPrintln("Post hash: " + ReadJniRef(globalID).hashCode());
+    System.out.println("Same value? " + (ReadJniRef(globalID) == arr));
+  }
+
+  public static void runWithJniWeakGlobal() throws Exception {
+    Object[] arr = new Object[] {"2", "22", "222"};
+    final long globalID = GetWeakGlobalJniRef(arr);
+    System.out.println("Test weak jni-ref");
+    DbgPrintln("Pre hash: " + ReadJniRef(globalID).hashCode());
+    System.out.println(
+        "val is: " + Arrays.toString((Object[]) ReadJniRef(globalID)) + " resize +5");
+    ResizeArray(() -> ReadJniRef(globalID), ((Object[]) ReadJniRef(globalID)).length + 5);
+    System.out.println("val is: " + Arrays.toString((Object[]) ReadJniRef(globalID)));
+    DbgPrintln("Post hash: " + ReadJniRef(globalID).hashCode());
+    System.out.println("Same value? " + (ReadJniRef(globalID) == arr));
+    if (ReadJniRef(globalID) != arr) {
+      throw new Error("Didn't update weak global!");
+    }
+  }
+
+  public static void runWithJniLocals() throws Exception {
+    final Object[] arr = new Object[] {"3", "32", "322"};
+    System.out.println("Test jni local ref");
+    Consumer<Object> checker = (o) -> System.out.println("Same value? " + (o == arr));
+    Consumer<Object> printer =
+        (o) -> System.out.println("val is: " + Arrays.toString((Object[]) o));
+    Runnable resize =
+        () -> {
+          System.out.println("Resize +4");
+          ResizeArray(() -> arr, arr.length + 4);
+        };
+    runNativeTest(arr, resize, printer, checker);
+  }
+
+  public static native void runNativeTest(
+      Object[] arr, Runnable resize, Consumer<Object> printer, Consumer<Object> checker);
+
+  public static void runWithJvmtiTags() throws Exception {
+    Object[] arr = new Object[] {"3", "33", "333"};
+    long globalID = 333_333_333l;
+    Main.setTag(arr, globalID);
+    System.out.println("Test jvmti-tags");
+    DbgPrintln("Pre hash: " + arr.hashCode());
+    System.out.println(
+        "val is: " + Arrays.deepToString(GetObjectsWithTag(globalID)) + " resize +5");
+    ResizeArray(() -> arr, arr.length + 5);
+    Object[] after_tagged_obj = GetObjectsWithTag(globalID);
+    System.out.println("val is: " + Arrays.deepToString(GetObjectsWithTag(globalID)));
+    DbgPrintln("Post hash: " + after_tagged_obj[0].hashCode());
+    System.out.println("Same value? " + (after_tagged_obj[0] == arr));
+  }
+
+  public static void run() throws Exception {
+    // Simple
+    runAsThread(Test1974::runInstance);
+
+    // HashMap
+    runAsThread(Test1974::runHashMap);
+
+    // j.l.ref.WeakReference
+    runAsThread(Test1974::runWeakReference);
+
+    // Self-referential arrays.
+    runAsThread(Test1974::runInstanceSelfRef);
+    runAsThread(Test1974::runInstanceSelfRefSmall);
+
+    // Local variables simple
+    runAsThread(Test1974::runLocal);
+    runAsThread(Test1974::runLocalSmall);
+
+    // multiple threads local variables
+    runAsThread(Test1974::runMultiThreadLocal);
+
+    // using as monitors and waiting
+    runAsThread(Test1974::runWithLocks);
+
+    // Basic jni global refs
+    runAsThread(Test1974::runWithJniGlobal);
+
+    // Basic jni weak global refs
+    runAsThread(Test1974::runWithJniWeakGlobal);
+
+    // Basic JNI local refs
+    runAsThread(Test1974::runWithJniLocals);
+
+    // Basic jvmti tags
+    runAsThread(Test1974::runWithJvmtiTags);
+  }
+
+  // Use a supplier so that we don't have to have a local ref to the resized
+  // array if we don't want it
+  public static native <T> void ResizeArray(Supplier<T> arr, int new_size);
+
+  public static native <T> long GetGlobalJniRef(T t);
+
+  public static native <T> long GetWeakGlobalJniRef(T t);
+
+  public static native <T> T ReadJniRef(long t);
+
+  public static native Object[] GetObjectsWithTag(long tag);
+}
diff --git a/test/Android.bp b/test/Android.bp
index baf5e22..2254477 100644
--- a/test/Android.bp
+++ b/test/Android.bp
@@ -300,6 +300,7 @@
         "1968-force-early-return/force_early_return.cc",
         "1969-force-early-return-void/force_early_return_void.cc",
         "1970-force-early-return-long/force_early_return_long.cc",
+        "1974-resize-array/resize_array.cc",
     ],
     // Use NDK-compatible headers for ctstiagent.
     header_libs: [
diff --git a/test/knownfailures.json b/test/knownfailures.json
index 2a64ba6..1d6e36d 100644
--- a/test/knownfailures.json
+++ b/test/knownfailures.json
@@ -1117,7 +1117,8 @@
                   "1951-monitor-enter-no-suspend",
                   "1957-error-ext",
                   "1972-jni-id-swap-indices",
-                  "1973-jni-id-swap-pointer"
+                  "1973-jni-id-swap-pointer",
+                  "1974-resize-array"
                 ],
         "variant": "jvm",
         "description": ["Doesn't run on RI."]