Allow structural redefinition on non-final classes.

This adds support for structurally redefining non-final,
non-finalizable classes. The only restriction is that one cannot
redefine a class at the same time as any of its supertypes, if a
structural redefinition is occurring. The structural redefinition may
not remove any fields or methods, change the superclass or change the
implemented interfaces. Adding new methods or fields, both static or
non-static, public, private, protected, or package-private, is
supported.

Test: ./test.py --host
Bug: 134162467
Bug: 144168550
Change-Id: I32e9e854b3e56270170b10e8f5aba9de8f6bfdfa
diff --git a/compiler/utils/assembler_thumb_test_expected.cc.inc b/compiler/utils/assembler_thumb_test_expected.cc.inc
index 842716f..49ac2f5 100644
--- a/compiler/utils/assembler_thumb_test_expected.cc.inc
+++ b/compiler/utils/assembler_thumb_test_expected.cc.inc
@@ -76,7 +76,7 @@
   "  f0:	f1bc 0f00 	cmp.w	ip, #0\n",
   "  f4:	bf18      	it	ne\n",
   "  f6:	f20d 4c01 	addwne	ip, sp, #1025	; 0x401\n",
-  "  fa:	f8d9 c09c 	ldr.w	ip, [r9, #156]	; 0x9c\n",
+  "  fa:	f8d9 c0a4 	ldr.w	ip, [r9, #164]	; 0xa4\n",
   "  fe:	f1bc 0f00 	cmp.w	ip, #0\n",
   " 102:	d171      	bne.n	1e8 <VixlJniHelpers+0x1e8>\n",
   " 104:	f8cd c7ff 	str.w	ip, [sp, #2047]	; 0x7ff\n",
@@ -153,7 +153,7 @@
   " 21c:	f8d9 8034 	ldr.w	r8, [r9, #52]	; 0x34\n",
   " 220:	4770      	bx	lr\n",
   " 222:	4660      	mov	r0, ip\n",
-  " 224:	f8d9 c2e4 	ldr.w	ip, [r9, #740]	; 0x2e4\n",
+  " 224:	f8d9 c2ec 	ldr.w	ip, [r9, #748]	; 0x2ec\n",
   " 228:	47e0      	blx	ip\n",
   nullptr
 };
diff --git a/libartbase/base/iteration_range.h b/libartbase/base/iteration_range.h
index cd87d85..eaed8b0 100644
--- a/libartbase/base/iteration_range.h
+++ b/libartbase/base/iteration_range.h
@@ -49,6 +49,11 @@
   return IterationRange<Iter>(begin_it, end_it);
 }
 
+template<typename List>
+inline IterationRange<typename List::iterator> MakeIterationRange(List& list) {
+  return IterationRange<typename List::iterator>(list.begin(), list.end());
+}
+
 template <typename Iter>
 inline IterationRange<Iter> MakeEmptyIterationRange(const Iter& it) {
   return IterationRange<Iter>(it, it);
diff --git a/openjdkjvmti/ti_extension.cc b/openjdkjvmti/ti_extension.cc
index 058a188..1301697 100644
--- a/openjdkjvmti/ti_extension.cc
+++ b/openjdkjvmti/ti_extension.cc
@@ -423,29 +423,31 @@
         reinterpret_cast<jvmtiExtensionFunction>(Redefiner::StructurallyRedefineClasses),
         "com.android.art.class.structurally_redefine_classes",
         "Entrypoint for structural class redefinition. Has the same signature as RedefineClasses."
-        " Currently this only supports adding new static fields to a class without any instance"
-        " fields or methods. After calling this com.android.art.structural_dex_file_load_hook"
-        " events will be triggered, followed by re-transformable ClassFileLoadHook events. After"
-        " this method completes subsequent RetransformClasses calls will use the input to this"
-        " function as the initial class definition.",
+        " Currently does not support redefining a class and any of its supertypes at the same time."
+        " Only supports additive changes, methods and fields may not be removed. Supertypes and"
+        " implemented interfaces may not be changed. After calling this"
+        " com.android.art.structural_dex_file_load_hook events will be triggered, followed by"
+        " re-transformable ClassFileLoadHook events. After this method completes subsequent"
+        " RetransformClasses calls will use the input to this function as the initial class"
+        " definition.",
         {
-          { "num_classes", JVMTI_KIND_IN, JVMTI_TYPE_JINT, false },
-          { "class_definitions", JVMTI_KIND_IN_BUF, JVMTI_TYPE_CVOID, false },
+            { "num_classes", JVMTI_KIND_IN, JVMTI_TYPE_JINT, false },
+            { "class_definitions", JVMTI_KIND_IN_BUF, JVMTI_TYPE_CVOID, false },
         },
         {
-          ERR(CLASS_LOADER_UNSUPPORTED),
-          ERR(FAILS_VERIFICATION),
-          ERR(ILLEGAL_ARGUMENT),
-          ERR(INVALID_CLASS),
-          ERR(MUST_POSSESS_CAPABILITY),
-          ERR(MUST_POSSESS_CAPABILITY),
-          ERR(NULL_POINTER),
-          ERR(OUT_OF_MEMORY),
-          ERR(UNMODIFIABLE_CLASS),
-          ERR(UNSUPPORTED_REDEFINITION_HIERARCHY_CHANGED),
-          ERR(UNSUPPORTED_REDEFINITION_METHOD_ADDED),
-          ERR(UNSUPPORTED_REDEFINITION_METHOD_DELETED),
-          ERR(UNSUPPORTED_REDEFINITION_SCHEMA_CHANGED),
+            ERR(CLASS_LOADER_UNSUPPORTED),
+            ERR(FAILS_VERIFICATION),
+            ERR(ILLEGAL_ARGUMENT),
+            ERR(INVALID_CLASS),
+            ERR(MUST_POSSESS_CAPABILITY),
+            ERR(MUST_POSSESS_CAPABILITY),
+            ERR(NULL_POINTER),
+            ERR(OUT_OF_MEMORY),
+            ERR(UNMODIFIABLE_CLASS),
+            ERR(UNSUPPORTED_REDEFINITION_HIERARCHY_CHANGED),
+            ERR(UNSUPPORTED_REDEFINITION_METHOD_ADDED),
+            ERR(UNSUPPORTED_REDEFINITION_METHOD_DELETED),
+            ERR(UNSUPPORTED_REDEFINITION_SCHEMA_CHANGED),
         });
     if (error != ERR(NONE)) {
       return error;
diff --git a/openjdkjvmti/ti_heap.cc b/openjdkjvmti/ti_heap.cc
index b25b4d1..e1f349a 100644
--- a/openjdkjvmti/ti_heap.cc
+++ b/openjdkjvmti/ti_heap.cc
@@ -1653,6 +1653,10 @@
                 // We don't want to update the declaring class of any objects. They will be replaced
                 // in the heap and we need the declaring class to know its size.
                 return;
+              } else if (UNLIKELY(!is_static && off == art::mirror::Class::SuperClassOffset() &&
+                                  obj->IsClass())) {
+                // We don't want to be messing with the class hierarcy either.
+                return;
               }
               VLOG(plugin) << "Updating field at offset " << off.Uint32Value() << " of type "
                            << obj->GetClass()->PrettyClass();
diff --git a/openjdkjvmti/ti_redefine.cc b/openjdkjvmti/ti_redefine.cc
index ebbe6ac..d794fd2 100644
--- a/openjdkjvmti/ti_redefine.cc
+++ b/openjdkjvmti/ti_redefine.cc
@@ -54,7 +54,10 @@
 #include "base/casts.h"
 #include "base/enums.h"
 #include "base/globals.h"
+#include "base/iteration_range.h"
 #include "base/length_prefixed_array.h"
+#include "base/locks.h"
+#include "base/stl_util.h"
 #include "base/utils.h"
 #include "class_linker-inl.h"
 #include "class_linker.h"
@@ -461,36 +464,6 @@
           "safe to structurally redefine it.";
       return ERR(UNMODIFIABLE_CLASS);
     }
-    // Check for already existing non-static fields/methods.
-    // TODO Remove this once we support generic method/field addition.
-    if (!klass->IsFinal()) {
-      bool non_static_method = false;
-      klass->VisitMethods([&](art::ArtMethod* m) REQUIRES_SHARED(art::Locks::mutator_lock_) {
-        // Since direct-methods (ie privates + <init> are not in any vtable/iftable we can update
-        // them).
-        if (!m->IsDirect()) {
-          non_static_method = true;
-          *error_msg = StringPrintf("%s has a non-direct function %s",
-                                    klass->PrettyClass().c_str(),
-                                    m->PrettyMethod().c_str());
-        }
-      }, art::kRuntimePointerSize);
-      if (non_static_method) {
-        return ERR(UNMODIFIABLE_CLASS);
-      }
-      bool non_static_field = false;
-      klass->VisitFields([&](art::ArtField* f) REQUIRES_SHARED(art::Locks::mutator_lock_) {
-        if (!f->IsStatic()) {
-          non_static_field = true;
-          *error_msg = StringPrintf("%s has a non-static field %s",
-                                    klass->PrettyClass().c_str(),
-                                    f->PrettyField().c_str());
-        }
-      });
-      if (non_static_field) {
-        return ERR(UNMODIFIABLE_CLASS);
-      }
-    }
     // Check for fields/methods which were returned before moving to index jni id type.
     // TODO We might want to rework how this is done. Once full redefinition is implemented we will
     // need to check any subtypes too.
@@ -995,8 +968,7 @@
       RecordHasVirtualMembers();
     }
     if (old_iter == old_methods.cend()) {
-      // TODO Support adding non-static methods.
-      if (is_structural && (new_method.IsStaticOrDirect() || h_klass->IsFinal())) {
+      if (is_structural) {
         RecordNewMethodAdded();
       } else {
         RecordFailure(
@@ -1059,8 +1031,7 @@
       RecordHasVirtualMembers();
     }
     if (old_iter == old_fields.cend()) {
-      // TODO Support adding non-static fields.
-      if (driver_->IsStructuralRedefinition() && (new_field.IsStatic() || h_klass->IsFinal())) {
+      if (driver_->IsStructuralRedefinition()) {
         RecordNewFieldAdded();
       } else {
         RecordFailure(ERR(UNSUPPORTED_REDEFINITION_SCHEMA_CHANGED),
@@ -1219,9 +1190,11 @@
     kSlotNewClassObject = 8,
     kSlotOldInstanceObjects = 9,
     kSlotNewInstanceObjects = 10,
+    kSlotOldClasses = 11,
+    kSlotNewClasses = 12,
 
     // Must be last one.
-    kNumSlots = 11,
+    kNumSlots = 13,
   };
 
   // This needs to have a HandleScope passed in that is capable of creating a new Handle without
@@ -1298,6 +1271,16 @@
     return art::ObjPtr<art::mirror::ObjectArray<art::mirror::Object>>::DownCast(
         GetSlot(klass_index, kSlotNewInstanceObjects));
   }
+  art::ObjPtr<art::mirror::ObjectArray<art::mirror::Class>> GetOldClasses(jint klass_index) const
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    return art::ObjPtr<art::mirror::ObjectArray<art::mirror::Class>>::DownCast(
+        GetSlot(klass_index, kSlotOldClasses));
+  }
+  art::ObjPtr<art::mirror::ObjectArray<art::mirror::Class>> GetNewClasses(jint klass_index) const
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    return art::ObjPtr<art::mirror::ObjectArray<art::mirror::Class>>::DownCast(
+        GetSlot(klass_index, kSlotNewClasses));
+  }
 
   void SetSourceClassLoader(jint klass_index, art::ObjPtr<art::mirror::ClassLoader> loader)
       REQUIRES_SHARED(art::Locks::mutator_lock_) {
@@ -1348,6 +1331,16 @@
       REQUIRES_SHARED(art::Locks::mutator_lock_) {
     SetSlot(klass_index, kSlotNewInstanceObjects, objs);
   }
+  void SetOldClasses(jint klass_index,
+                     art::ObjPtr<art::mirror::ObjectArray<art::mirror::Class>> klasses)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    SetSlot(klass_index, kSlotOldClasses, klasses);
+  }
+  void SetNewClasses(jint klass_index,
+                     art::ObjPtr<art::mirror::ObjectArray<art::mirror::Class>> klasses)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    SetSlot(klass_index, kSlotNewClasses, klasses);
+  }
   int32_t Length() const REQUIRES_SHARED(art::Locks::mutator_lock_) {
     return arr_->GetLength() / kNumSlots;
   }
@@ -1491,6 +1484,14 @@
       REQUIRES_SHARED(art::Locks::mutator_lock_) {
     return holder_.GetNewInstanceObjects(idx_);
   }
+  art::ObjPtr<art::mirror::ObjectArray<art::mirror::Class>> GetOldClasses() const
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    return holder_.GetOldClasses(idx_);
+  }
+  art::ObjPtr<art::mirror::ObjectArray<art::mirror::Class>> GetNewClasses() const
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    return holder_.GetNewClasses(idx_);
+  }
   int32_t GetIndex() const {
     return idx_;
   }
@@ -1539,6 +1540,14 @@
       REQUIRES_SHARED(art::Locks::mutator_lock_) {
     holder_.SetNewInstanceObjects(idx_, objs);
   }
+  void SetOldClasses(art::ObjPtr<art::mirror::ObjectArray<art::mirror::Class>> klasses)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    holder_.SetOldClasses(idx_, klasses);
+  }
+  void SetNewClasses(art::ObjPtr<art::mirror::ObjectArray<art::mirror::Class>> klasses)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    holder_.SetNewClasses(idx_, klasses);
+  }
 
  private:
   int32_t idx_;
@@ -1649,23 +1658,104 @@
   art::VariableSizedHandleScope hs(driver_->self_);
   art::Handle<art::mirror::Class> old_klass(hs.NewHandle(cur_data->GetMirrorClass()));
   std::vector<art::Handle<art::mirror::Object>> old_instances;
+  std::vector<art::Handle<art::mirror::Class>> old_types;
   art::gc::Heap* heap = driver_->runtime_->GetHeap();
+  auto is_subtype = [&](art::mirror::Object* obj) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    // We've already waited for class defines to be finished and paused them. All classes should be
+    // either resolved or error. We don't need to do anything with error classes, since they cannot
+    // be accessed in any observable way.
+    return obj->IsClass() && obj->AsClass()->IsResolved() &&
+           old_klass->IsAssignableFrom(obj->AsClass());
+  };
   auto is_instance = [&](art::mirror::Object* obj) REQUIRES_SHARED(art::Locks::mutator_lock_) {
-    if (HasVirtualMembers()) {
-      return old_klass->IsAssignableFrom(obj->GetClass());
-    } else {
-      // We don't need to deal with objects of subtypes when we don't modify virtuals since the
-      // vtable + field layout will remain the same.
-      return old_klass.Get() == obj->GetClass();
-    }
+    return obj->InstanceOf(old_klass.Get());
   };
   heap->VisitObjects([&](art::mirror::Object* obj) REQUIRES_SHARED(art::Locks::mutator_lock_) {
     if (is_instance(obj)) {
-      CHECK(old_klass.Get() == obj->GetClass()) << "No support for subtypes yet!";
       old_instances.push_back(hs.NewHandle(obj));
+    } else if (is_subtype(obj)) {
+      old_types.push_back(hs.NewHandle(obj->AsClass()));
     }
   });
   VLOG(plugin) << "Collected " << old_instances.size() << " instances to recreate!";
+  VLOG(plugin) << "Found " << old_types.size() << " types that are/are subtypes of "
+               << old_klass->PrettyClass();
+
+  art::Handle<art::mirror::Class> cls_array_class(
+      hs.NewHandle(art::GetClassRoot<art::mirror::ObjectArray<art::mirror::Class>>(
+          driver_->runtime_->GetClassLinker())));
+  art::Handle<art::mirror::ObjectArray<art::mirror::Class>> old_classes_arr(
+      hs.NewHandle(art::mirror::ObjectArray<art::mirror::Class>::Alloc(
+          driver_->self_, cls_array_class.Get(), old_types.size())));
+  if (old_classes_arr.IsNull()) {
+    driver_->self_->AssertPendingOOMException();
+    driver_->self_->ClearException();
+    RecordFailure(ERR(OUT_OF_MEMORY), "Could not allocate old_classes arrays!");
+    return false;
+  }
+  // Sort the old_types topologically.
+  {
+    art::ScopedAssertNoThreadSuspension sants("Sort classes");
+    auto count_distance =
+      [&](art::ObjPtr<art::mirror::Class> c) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+        uint32_t res = 0;
+        while (c != old_klass.Get()) {
+          DCHECK_NE(c, art::GetClassRoot<art::mirror::Object>());
+          res++;
+          c = c->GetSuperClass();
+        }
+        return res;
+      };
+    auto compare_handles = [&](auto l, auto r) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+      return count_distance(l.Get()) < count_distance(r.Get());
+    };
+    // Sort them by the distance to the base-class. This ensures that any class occurs before any of
+    // its subtypes.
+    std::sort(old_types.begin(), old_types.end(), compare_handles);
+  }
+  for (uint32_t i = 0; i < old_types.size(); ++i) {
+    old_classes_arr->Set(i, old_types[i].Get());
+  }
+  cur_data->SetOldClasses(old_classes_arr.Get());
+  art::Handle<art::mirror::ObjectArray<art::mirror::Class>> new_classes_arr(
+      hs.NewHandle(art::mirror::ObjectArray<art::mirror::Class>::Alloc(
+          driver_->self_, cls_array_class.Get(), old_types.size())));
+  if (new_classes_arr.IsNull()) {
+    driver_->self_->AssertPendingOOMException();
+    driver_->self_->ClearException();
+    RecordFailure(ERR(OUT_OF_MEMORY), "Could not allocate new_classes arrays!");
+    return false;
+  }
+  art::MutableHandle<art::mirror::DexCache> dch(hs.NewHandle<art::mirror::DexCache>(nullptr));
+  art::MutableHandle<art::mirror::Class> superclass(hs.NewHandle<art::mirror::Class>(nullptr));
+  for (size_t i = 0; i < old_types.size(); i++) {
+    art::Handle<art::mirror::Class>& old_class = old_types[i];
+    if (old_class.Get() == cur_data->GetMirrorClass()) {
+      CHECK_EQ(i, 0u) << "original class not at index 0. Bad sort!";
+      new_classes_arr->Set(i, cur_data->GetNewClassObject());
+      continue;
+    } else {
+      auto old_super = std::find_if(old_types.begin(),
+                                    old_types.begin() + i,
+                                    [&](art::Handle<art::mirror::Class>& v)
+                                        REQUIRES_SHARED(art::Locks::mutator_lock_) {
+                                          return v.Get() == old_class->GetSuperClass();
+                                        });
+      // Only the GetMirrorClass should not be in this list.
+      CHECK(old_super != old_types.begin() + i)
+          << "from first " << i << " could not find super of " << old_class->PrettyClass()
+          << " expected to find " << old_class->GetSuperClass()->PrettyClass();
+      superclass.Assign(new_classes_arr->Get(std::distance(old_types.begin(), old_super)));
+      dch.Assign(old_class->GetDexCache());
+      art::ObjPtr<art::mirror::Class> new_class(
+          AllocateNewClassObject(old_class, superclass, dch, old_class->GetDexClassDefIndex()));
+      if (new_class == nullptr) {
+        return false;
+      }
+      new_classes_arr->Set(i, new_class);
+    }
+  }
+  cur_data->SetNewClasses(new_classes_arr.Get());
 
   art::Handle<art::mirror::Class> obj_array_class(
       hs.NewHandle(art::GetClassRoot<art::mirror::ObjectArray<art::mirror::Object>>(
@@ -1693,9 +1783,18 @@
     RecordFailure(ERR(OUT_OF_MEMORY), "Could not allocate new_instance arrays!");
     return false;
   }
-  art::Handle<art::mirror::Class> new_klass(hs.NewHandle(cur_data->GetNewClassObject()));
-  for (uint32_t i = 0; i < old_instances.size(); ++i) {
-    art::ObjPtr<art::mirror::Object> new_instance(new_klass->AllocObject(driver_->self_));
+  for (auto pair : art::ZipCount(art::IterationRange(old_instances.begin(), old_instances.end()))) {
+    art::Handle<art::mirror::Object> hinstance(pair.first);
+    int32_t i = pair.second;
+    auto iterator = art::ZipLeft(old_classes_arr.Iterate<art::mirror::Class>(),
+                                 new_classes_arr.Iterate<art::mirror::Class>());
+    auto [_, new_type] =
+        *(std::find_if(iterator.begin(),
+                       iterator.end(),
+                       [&](auto class_pair) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+                         return class_pair.first == hinstance->GetClass();
+                       }));
+    art::ObjPtr<art::mirror::Object> new_instance(new_type->AllocObject(driver_->self_));
     if (new_instance.IsNull()) {
       driver_->self_->AssertPendingOOMException();
       driver_->self_->ClearException();
@@ -1755,8 +1854,6 @@
     art::Handle<art::mirror::Class> nc(hs.NewHandle(
         AllocateNewClassObject(hs.NewHandle(cur_data->GetNewDexCache()))));
     if (nc.IsNull()) {
-      driver_->self_->ClearException();
-      RecordFailure(ERR(OUT_OF_MEMORY), "Unable to allocate new class object");
       return false;
     }
 
@@ -1770,18 +1867,15 @@
   return true;
 }
 
-uint32_t Redefiner::ClassRedefinition::GetNewClassSize(bool with_embedded_tables,
-                                                       art::Handle<art::mirror::Class> old_klass) {
-  // TODO Once we can add methods this won't work any more.
-  uint32_t num_vtable_entries = old_klass->GetVTableLength();
+uint32_t Redefiner::ClassRedefinition::GetNewClassSize(art::ClassAccessor& accessor) {
   uint32_t num_8bit_static_fields = 0;
   uint32_t num_16bit_static_fields = 0;
   uint32_t num_32bit_static_fields = 0;
   uint32_t num_64bit_static_fields = 0;
   uint32_t num_ref_static_fields = 0;
-  art::ClassAccessor accessor(*dex_file_, dex_file_->GetClassDef(0));
   for (const art::ClassAccessor::Field& f : accessor.GetStaticFields()) {
-    std::string_view desc(dex_file_->GetFieldTypeDescriptor(dex_file_->GetFieldId(f.GetIndex())));
+    std::string_view desc(accessor.GetDexFile().GetFieldTypeDescriptor(
+        accessor.GetDexFile().GetFieldId(f.GetIndex())));
     if (desc[0] == 'L' || desc[0] == '[') {
       num_ref_static_fields++;
     } else if (desc == "Z" || desc == "B") {
@@ -1797,8 +1891,8 @@
     }
   }
 
-  return art::mirror::Class::ComputeClassSize(with_embedded_tables,
-                                              with_embedded_tables ? num_vtable_entries : 0,
+  return art::mirror::Class::ComputeClassSize(/*has_embedded_vtable=*/ false,
+                                              /*num_vtable_entries=*/ 0,
                                               num_8bit_static_fields,
                                               num_16bit_static_fields,
                                               num_32bit_static_fields,
@@ -1809,23 +1903,41 @@
 
 art::ObjPtr<art::mirror::Class>
 Redefiner::ClassRedefinition::AllocateNewClassObject(art::Handle<art::mirror::DexCache> cache) {
+  art::StackHandleScope<2> hs(driver_->self_);
+  art::Handle<art::mirror::Class> old_class(hs.NewHandle(GetMirrorClass()));
+  art::Handle<art::mirror::Class> super_class(hs.NewHandle(old_class->GetSuperClass()));
+  return AllocateNewClassObject(old_class, super_class, cache, /*dex_class_def_index*/0);
+}
+
+art::ObjPtr<art::mirror::Class> Redefiner::ClassRedefinition::AllocateNewClassObject(
+    art::Handle<art::mirror::Class> old_class,
+    art::Handle<art::mirror::Class> super_class,
+    art::Handle<art::mirror::DexCache> cache,
+    uint16_t dex_class_def_index) {
   // This is a stripped down DefineClass. We don't want to use DefineClass directly because it needs
   // to perform a lot of extra steps to tell the ClassTable and the jit and everything about a new
   // class. For now we will need to rely on our tests catching any issues caused by changes in how
   // class_linker sets up classes.
   // TODO Unify/move this into ClassLinker maybe.
-  art::StackHandleScope<5> hs(driver_->self_);
+  art::StackHandleScope<3> hs(driver_->self_);
   art::ClassLinker* linker = driver_->runtime_->GetClassLinker();
-  art::Handle<art::mirror::Class> old_class(hs.NewHandle(GetMirrorClass()));
+  const art::DexFile* dex_file = cache->GetDexFile();
+  art::ClassAccessor accessor(*dex_file, dex_class_def_index);
   art::Handle<art::mirror::Class> new_class(hs.NewHandle(linker->AllocClass(
-      driver_->self_, GetNewClassSize(/*with_embedded_tables=*/false, old_class))));
+      driver_->self_, GetNewClassSize(accessor))));
   if (new_class.IsNull()) {
     driver_->self_->AssertPendingOOMException();
-    JVMTI_LOG(ERROR, driver_->env_) << "Unable to allocate new class object!";
+    RecordFailure(
+        ERR(OUT_OF_MEMORY),
+        "Unable to allocate class object for redefinition of " + old_class->PrettyClass());
+    driver_->self_->ClearException();
     return nullptr;
   }
   new_class->SetDexCache(cache.Get());
-  linker->SetupClass(*dex_file_, dex_file_->GetClassDef(0), new_class, old_class->GetClassLoader());
+  linker->SetupClass(*dex_file,
+                     dex_file->GetClassDef(dex_class_def_index),
+                     new_class,
+                     old_class->GetClassLoader());
 
   // Make sure we are ready for linking. The lock isn't really needed since this isn't visible to
   // other threads but the linker expects it.
@@ -1833,31 +1945,46 @@
   new_class->SetClinitThreadId(driver_->self_->GetTid());
   // Make sure we have a valid empty iftable even if there are errors.
   new_class->SetIfTable(art::GetClassRoot<art::mirror::Object>(linker)->GetIfTable());
-  linker->LoadClass(driver_->self_, *dex_file_, dex_file_->GetClassDef(0), new_class);
+  linker->LoadClass(
+      driver_->self_, *dex_file, dex_file->GetClassDef(dex_class_def_index), new_class);
   // NB. We know the interfaces and supers didn't change! :)
   art::MutableHandle<art::mirror::Class> linked_class(hs.NewHandle<art::mirror::Class>(nullptr));
   art::Handle<art::mirror::ObjectArray<art::mirror::Class>> proxy_ifaces(
       hs.NewHandle<art::mirror::ObjectArray<art::mirror::Class>>(nullptr));
   // No changing hierarchy so everything is loaded.
-  new_class->SetSuperClass(old_class->GetSuperClass());
+  new_class->SetSuperClass(super_class.Get());
   art::mirror::Class::SetStatus(new_class, art::ClassStatus::kLoaded, nullptr);
   if (!linker->LinkClass(driver_->self_, nullptr, new_class, proxy_ifaces, &linked_class)) {
-    JVMTI_LOG(ERROR, driver_->env_)
-        << "failed to link class due to "
+    std::ostringstream oss;
+    oss << "failed to link class due to "
         << (driver_->self_->IsExceptionPending() ? driver_->self_->GetException()->Dump()
                                                  : " unknown");
+    RecordFailure(ERR(INTERNAL), oss.str());
     driver_->self_->ClearException();
     return nullptr;
   }
-  // We will initialize it manually.
+  // Everything is already resolved.
   art::ObjectLock<art::mirror::Class> objlock(driver_->self_, linked_class);
-  // We already verified the class earlier. No need to do it again.
-  linked_class->SetVerificationAttempted();
   // Mark the class as initialized.
-  CHECK(old_class->IsInitialized())
-      << "Attempting to redefine an uninitalized class " << old_class->PrettyClass()
+  CHECK(old_class->IsResolved())
+      << "Attempting to redefine an unresolved class " << old_class->PrettyClass()
       << " status=" << old_class->GetStatus();
-  linker->ForceClassInitialized(driver_->self_, linked_class);
+  CHECK(linked_class->IsResolved());
+  if (old_class->WasVerificationAttempted()) {
+    // Match verification-attempted flag
+    linked_class->SetVerificationAttempted();
+  }
+  if (old_class->ShouldSkipHiddenApiChecks()) {
+    // Match skip hiddenapi flag
+    linked_class->SetSkipHiddenApiChecks();
+  }
+  if (old_class->IsInitialized()) {
+    // We already verified the class earlier. No need to do it again.
+    linker->ForceClassInitialized(driver_->self_, linked_class);
+  } else if (old_class->GetStatus() > linked_class->GetStatus()) {
+    // We want to match the old status.
+    art::mirror::Class::SetStatus(linked_class, old_class->GetStatus(), driver_->self_);
+  }
   // Make sure we have ext-data space for method & field ids. We won't know if we need them until
   // it's too late to create them.
   // TODO We might want to remove these arrays if they're not needed.
@@ -1866,7 +1993,9 @@
       art::mirror::Class::GetOrCreateMethodIds(linked_class).IsNull()) {
     driver_->self_->AssertPendingOOMException();
     driver_->self_->ClearException();
-    JVMTI_LOG(ERROR, driver_->env_) << "Unable to allocate jni-id arrays!";
+    RecordFailure(
+        ERR(OUT_OF_MEMORY),
+        "Unable to allocate jni-id arrays for redefinition of " + old_class->PrettyClass());
     return nullptr;
   }
   // Finish setting up methods.
@@ -1883,6 +2012,9 @@
       DCHECK_EQ(f->GetDeclaringClass(), linked_class.Get());
     });
   }
+  // Reset ClinitThreadId back to the thread that loaded the old class. This is needed if we are in
+  // the middle of initializing a class.
+  linked_class->SetClinitThreadId(old_class->GetClinitThreadId());
   return linked_class.Get();
 }
 
@@ -1913,9 +2045,39 @@
       return false;
     }
   }
+  if (std::any_of(
+          redefinitions_.begin(),
+          redefinitions_.end(),
+          std::function<bool(ClassRedefinition&)>(&ClassRedefinition::IsStructuralRedefinition))) {
+    return CheckClassHierarchy();
+  }
   return true;
 }
 
+bool Redefiner::CheckClassHierarchy() {
+  return std::all_of(
+      redefinitions_.begin(),
+      redefinitions_.end(),
+      [&](ClassRedefinition& r) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+        return std::none_of(
+            redefinitions_.begin(),
+            redefinitions_.end(),
+            [&](ClassRedefinition& r2) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+              bool related = &r2 != &r &&
+                             r2.GetMirrorClass()->IsAssignableFrom(r.GetMirrorClass()) &&
+                             r2.IsStructuralRedefinition();
+              if (related) {
+                std::ostringstream oss;
+                oss << "Structurally redefining " << r2.GetMirrorClass()->PrettyClass()
+                    << " which is a superclass of " << r.GetMirrorClass()->PrettyClass()
+                    << ". This is not currently supported";
+                r2.RecordFailure(ERR(INTERNAL), oss.str());
+              }
+              return related;
+            });
+      });
+}
+
 void Redefiner::RestoreObsoleteMethodMapsIfUnneeded(RedefinitionDataHolder& holder) {
   for (RedefinitionDataIter data = holder.begin(); data != holder.end(); ++data) {
     data.GetRedefinition().RestoreObsoleteMethodMapsIfUnneeded(&data);
@@ -1989,6 +2151,142 @@
   art::Thread* self_;
 };
 
+class ClassDefinitionPauser : public art::ClassLoadCallback {
+ public:
+  explicit ClassDefinitionPauser(art::Thread* self) REQUIRES_SHARED(art::Locks::mutator_lock_)
+      : self_(self),
+        is_running_(false),
+        barrier_(0),
+        release_mu_("SuspendClassDefinition lock", art::kGenericBottomLock),
+        release_barrier_(0),
+        release_cond_("SuspendClassDefinition condvar", release_mu_),
+        count_(0),
+        release_(false) {
+    art::Locks::mutator_lock_->AssertSharedHeld(self_);
+  }
+  ~ClassDefinitionPauser() REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    art::Locks::mutator_lock_->AssertSharedHeld(self_);
+    if (is_running_) {
+      uint32_t count;
+      // Wake up everything.
+      {
+        art::MutexLock mu(self_, release_mu_);
+        release_ = true;
+        count = count_;
+        release_cond_.Broadcast(self_);
+      }
+      // Wait for all threads to leave this structs code.
+      art::ScopedThreadSuspension sts(self_, art::ThreadState::kWaiting);
+      VLOG(plugin) << "Resuming " << count << " threads paused before class-allocation!";
+      release_barrier_.Increment(self_, count);
+    }
+  }
+  void BeginDefineClass() override REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    art::Thread* this_thread = art::Thread::Current();
+    if (this_thread == self_) {
+      // Allow the redefining thread to do whatever.
+      return;
+    }
+    if (this_thread->GetDefineClassCount() != 0) {
+      // We are in the middle of a recursive define-class. Don't suspend now allow it to finish.
+      VLOG(plugin) << "Recursive DefineClass in " << *this_thread
+                   << " allowed to proceed despite class-def pause initiated by " << *self_;
+      return;
+    }
+    art::ScopedThreadSuspension sts(this_thread, art::ThreadState::kSuspended);
+    {
+      art::MutexLock mu(this_thread, release_mu_);
+      if (release_) {
+        // Count already retrieved, no need to pass.
+        return;
+      }
+      VLOG(plugin) << "Suspending " << *this_thread << " due to class definition. class-def pause "
+                   << "initiated by " << *self_;
+      count_++;
+      while (!release_) {
+        release_cond_.Wait(this_thread);
+      }
+    }
+    release_barrier_.Pass(this_thread);
+  }
+  void EndDefineClass() override REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    art::Thread* this_thread = art::Thread::Current();
+    if (this_thread == self_) {
+      // Allow the redefining thread to do whatever.
+      return;
+    }
+    if (this_thread->GetDefineClassCount() == 0) {
+      // We are done with defining classes.
+      barrier_.Pass(this_thread);
+    }
+  }
+
+  void ClassLoad(art::Handle<art::mirror::Class> klass ATTRIBUTE_UNUSED) override {}
+  void ClassPrepare(art::Handle<art::mirror::Class> klass1 ATTRIBUTE_UNUSED,
+                    art::Handle<art::mirror::Class> klass2 ATTRIBUTE_UNUSED) override {}
+
+  void SetRunning() {
+    is_running_ = true;
+  }
+  void WaitFor(uint32_t t) REQUIRES(!art::Locks::mutator_lock_) {
+    barrier_.Increment(self_, t);
+  }
+
+ private:
+  art::Thread* self_;
+  bool is_running_;
+  art::Barrier barrier_;
+  art::Mutex release_mu_;
+  art::Barrier release_barrier_;
+  art::ConditionVariable release_cond_;
+  uint32_t count_ GUARDED_BY(release_mu_);
+  bool release_;
+};
+
+class ScopedSuspendClassLoading {
+ public:
+  ScopedSuspendClassLoading(art::Thread* self, art::Runtime* runtime, RedefinitionDataHolder& h)
+      REQUIRES_SHARED(art::Locks::mutator_lock_)
+      : self_(self), runtime_(runtime), paused_(false), pauser_(self_) {
+    if (std::any_of(h.begin(), h.end(), [](auto r) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+          return r.GetRedefinition().IsStructuralRedefinition();
+        })) {
+      VLOG(plugin) << "Pausing Class loading for structural redefinition.";
+      paused_ = true;
+      {
+        art::ScopedThreadSuspension sts(self_, art::ThreadState::kNative);
+        uint32_t in_progress_defines = 0;
+        {
+          art::ScopedSuspendAll ssa(__FUNCTION__);
+          pauser_.SetRunning();
+          runtime_->GetRuntimeCallbacks()->AddClassLoadCallback(&pauser_);
+          art::MutexLock mu(self_, *art::Locks::thread_list_lock_);
+          runtime_->GetThreadList()->ForEach([&](art::Thread* t) {
+            if (t != self_ && t->GetDefineClassCount() != 0) {
+              in_progress_defines++;
+            }
+          });
+          VLOG(plugin) << "Waiting for " << in_progress_defines << " in progress class-loads to finish";
+        }
+        pauser_.WaitFor(in_progress_defines);
+      }
+    }
+  }
+  ~ScopedSuspendClassLoading() {
+    if (paused_) {
+      art::ScopedThreadSuspension sts(self_, art::ThreadState::kNative);
+      art::ScopedSuspendAll ssa(__FUNCTION__);
+      runtime_->GetRuntimeCallbacks()->RemoveClassLoadCallback(&pauser_);
+    }
+  }
+
+ private:
+  art::Thread* self_;
+  art::Runtime* runtime_;
+  bool paused_;
+  ClassDefinitionPauser pauser_;
+};
+
 class ScopedSuspendAllocations {
  public:
   ScopedSuspendAllocations(art::Runtime* runtime, RedefinitionDataHolder& h)
@@ -2043,6 +2341,7 @@
     return result_;
   }
 
+  ScopedSuspendClassLoading suspend_class_load(self_, runtime_, holder);
   ScopedSuspendAllocations suspend_alloc(runtime_, holder);
   if (!CollectAndCreateNewInstances(holder)) {
     return result_;
@@ -2174,27 +2473,28 @@
     const RedefinitionDataIter& data,
     std::map<art::ArtMethod*, art::ArtMethod*>* method_map,
     std::map<art::ArtField*, art::ArtField*>* field_map) {
-  art::ObjPtr<art::mirror::Class> old_cls(data.GetMirrorClass());
-  art::ObjPtr<art::mirror::Class> new_cls(data.GetNewClassObject());
-  for (art::ArtField& f : old_cls->GetSFields()) {
-    (*field_map)[&f] = new_cls->FindDeclaredStaticField(f.GetName(), f.GetTypeDescriptor());
-  }
-  for (art::ArtField& f : old_cls->GetIFields()) {
-    (*field_map)[&f] = new_cls->FindDeclaredInstanceField(f.GetName(), f.GetTypeDescriptor());
-  }
-  auto new_methods = new_cls->GetMethods(art::kRuntimePointerSize);
-  for (art::ArtMethod& m : old_cls->GetMethods(art::kRuntimePointerSize)) {
-    // No support for finding methods in this way since it's generally not needed. Just do it the
-    // easy way.
-    auto nm_iter = std::find_if(
-        new_methods.begin(),
-        new_methods.end(),
-        [&](art::ArtMethod& cand) REQUIRES_SHARED(art::Locks::mutator_lock_) {
-          return cand.GetNameView() == m.GetNameView() && cand.GetSignature() == m.GetSignature();
-        });
-    CHECK(nm_iter != new_methods.end())
-        << "Could not find redefined version of " << m.PrettyMethod();
-    (*method_map)[&m] = &(*nm_iter);
+  for (auto [new_cls, old_cls] :
+       art::ZipLeft(data.GetNewClasses()->Iterate(), data.GetOldClasses()->Iterate())) {
+    for (art::ArtField& f : old_cls->GetSFields()) {
+      (*field_map)[&f] = new_cls->FindDeclaredStaticField(f.GetName(), f.GetTypeDescriptor());
+    }
+    for (art::ArtField& f : old_cls->GetIFields()) {
+      (*field_map)[&f] = new_cls->FindDeclaredInstanceField(f.GetName(), f.GetTypeDescriptor());
+    }
+    auto new_methods = new_cls->GetMethods(art::kRuntimePointerSize);
+    for (art::ArtMethod& m : old_cls->GetMethods(art::kRuntimePointerSize)) {
+      // No support for finding methods in this way since it's generally not needed. Just do it the
+      // easy way.
+      auto nm_iter = std::find_if(
+          new_methods.begin(),
+          new_methods.end(),
+          [&](art::ArtMethod& cand) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+            return cand.GetNameView() == m.GetNameView() && cand.GetSignature() == m.GetSignature();
+          });
+      CHECK(nm_iter != new_methods.end())
+          << "Could not find redefined version of " << m.PrettyMethod();
+      (*method_map)[&m] = &(*nm_iter);
+    }
   }
 }
 
@@ -2321,6 +2621,8 @@
   art::ScopedAssertNoThreadSuspension sants(__FUNCTION__);
   art::ObjPtr<art::mirror::Class> orig(holder.GetMirrorClass());
   art::ObjPtr<art::mirror::Class> replacement(holder.GetNewClassObject());
+  art::ObjPtr<art::mirror::ObjectArray<art::mirror::Class>> new_classes(holder.GetNewClasses());
+  art::ObjPtr<art::mirror::ObjectArray<art::mirror::Class>> old_classes(holder.GetOldClasses());
   // Collect mappings from old to new fields/methods
   std::map<art::ArtMethod*, art::ArtMethod*> method_map;
   std::map<art::ArtField*, art::ArtField*> field_map;
@@ -2331,13 +2633,20 @@
       holder.GetOldInstanceObjects());
   CHECK(!orig.IsNull());
   CHECK(!replacement.IsNull());
+  // Once we do the ReplaceReferences old_classes will have the new_classes in it. We want to keep
+  // ahold of the old classes so copy them now.
+  std::vector<art::ObjPtr<art::mirror::Class>> old_classes_vec(old_classes->Iterate().begin(),
+                                                               old_classes->Iterate().end());
   // Copy over the static fields of the class and all the instance fields.
-  CopyAndClearFields(/*is_static=*/true, replacement, replacement, orig, orig);
+  for (auto [new_class, old_class] : art::ZipLeft(new_classes->Iterate(), old_classes->Iterate())) {
+    CHECK(!new_class.IsNull());
+    CHECK(!old_class.IsNull());
+    CopyAndClearFields(true, new_class, new_class, old_class, old_class);
+  }
 
   // Copy and clear the fields of the old-instances.
-  for (int32_t i = 0; i < old_instances->GetLength(); i++) {
-    art::ObjPtr<art::mirror::Object> old_instance(old_instances->Get(i));
-    art::ObjPtr<art::mirror::Object> new_instance(new_instances->Get(i));
+  for (auto [new_instance, old_instance] :
+       art::ZipLeft(new_instances->Iterate(), old_instances->Iterate())) {
     CopyAndClearFields(/*is_static=*/false,
                        new_instance,
                        new_instance->GetClass(),
@@ -2345,12 +2654,15 @@
                        old_instance->GetClass());
   }
   // Mark old class obsolete.
-  orig->SetObsoleteObject();
-  // Mark methods obsolete. We need to wait until later to actually clear the jit data.
-  for (art::ArtMethod& m : orig->GetMethods(art::kRuntimePointerSize)) {
-    m.SetIsObsolete();
-    m.SetDontCompile();
-    DCHECK_EQ(orig, m.GetDeclaringClass());
+  for (auto old_class : old_classes->Iterate()) {
+    old_class->SetObsoleteObject();
+    // Mark methods obsolete. We need to wait until later to actually clear the jit data.
+    for (art::ArtMethod& m : old_class->GetMethods(art::kRuntimePointerSize)) {
+      m.SetIsObsolete();
+      if (m.IsInvokable()) {
+        m.SetDontCompile();
+      }
+    }
   }
   // Update live pointers in ART code.
   auto could_change_resolution_of = [&](auto* field_or_method,
@@ -2439,9 +2751,16 @@
   std::unordered_map<art::ObjPtr<art::mirror::Object>,
                      art::ObjPtr<art::mirror::Object>,
                      art::HashObjPtr> map;
-  map.emplace(orig, replacement);
-  for (int32_t i = 0; i < old_instances->GetLength(); i++) {
-    map.emplace(old_instances->Get(i), new_instances->Get(i));
+  for (auto [new_class, old_class] : art::ZipLeft(new_classes->Iterate(), old_classes->Iterate())) {
+    map.emplace(old_class, new_class);
+  }
+  for (auto [new_instance, old_instance] :
+       art::ZipLeft(new_instances->Iterate(), old_instances->Iterate())) {
+    map.emplace(old_instance, new_instance);
+    // Bare-bones check that the mapping is correct.
+    CHECK(new_instance->GetClass() == map[old_instance->GetClass()]->AsClass())
+        << new_instance->GetClass()->PrettyClass() << " vs "
+        << map[old_instance->GetClass()]->AsClass()->PrettyClass();
   }
 
   // Actually perform the general replacement. This doesn't affect ArtMethod/ArtFields. It does
@@ -2451,8 +2770,10 @@
 
   // Save the old class so that the JIT gc doesn't get confused by it being collected before the
   // jit code. This is also needed to keep the dex-caches of any obsolete methods live.
-  replacement->GetExtData()->SetObsoleteClass(orig);
-
+  for (auto [new_class, old_class] :
+       art::ZipLeft(new_classes->Iterate(), art::MakeIterationRange(old_classes_vec))) {
+    new_class->GetExtData()->SetObsoleteClass(old_class);
+  }
 
   art::jit::Jit* jit = driver_->runtime_->GetJit();
   if (jit != nullptr) {
@@ -2475,6 +2796,10 @@
     // Just make sure we didn't screw up any of the now obsolete methods or fields. We need their
     // declaring-class to still be the obolete class
     orig->VisitMethods([&](art::ArtMethod* method) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+      if (method->IsCopied()) {
+        // Copied methods have interfaces as their declaring class.
+        return;
+      }
       DCHECK_EQ(method->GetDeclaringClass(), orig) << method->GetDeclaringClass()->PrettyClass()
                                                    << " vs " << orig->PrettyClass();
     }, art::kRuntimePointerSize);
diff --git a/openjdkjvmti/ti_redefine.h b/openjdkjvmti/ti_redefine.h
index cedce92..36bc9d3 100644
--- a/openjdkjvmti/ti_redefine.h
+++ b/openjdkjvmti/ti_redefine.h
@@ -52,6 +52,7 @@
 #include "obj_ptr.h"
 
 namespace art {
+class ClassAccessor;
 namespace dex {
 struct ClassDef;
 }  // namespace dex
@@ -168,10 +169,15 @@
     void FindAndAllocateObsoleteMethods(art::ObjPtr<art::mirror::Class> art_klass)
         REQUIRES(art::Locks::mutator_lock_);
 
+    art::ObjPtr<art::mirror::Class> AllocateNewClassObject(
+        art::Handle<art::mirror::Class> old_class,
+        art::Handle<art::mirror::Class> super_class,
+        art::Handle<art::mirror::DexCache> cache,
+        uint16_t dex_class_def_index) REQUIRES_SHARED(art::Locks::mutator_lock_);
     art::ObjPtr<art::mirror::Class> AllocateNewClassObject(art::Handle<art::mirror::DexCache> cache)
         REQUIRES_SHARED(art::Locks::mutator_lock_);
 
-    uint32_t GetNewClassSize(bool with_embedded_tables, art::Handle<art::mirror::Class> old_class)
+    uint32_t GetNewClassSize(art::ClassAccessor& accessor)
         REQUIRES_SHARED(art::Locks::mutator_lock_);
 
     // Checks that the dex file contains only the single expected class and that the top-level class
@@ -317,6 +323,7 @@
   jvmtiError Run() REQUIRES_SHARED(art::Locks::mutator_lock_);
 
   bool CheckAllRedefinitionAreValid() REQUIRES_SHARED(art::Locks::mutator_lock_);
+  bool CheckClassHierarchy() REQUIRES_SHARED(art::Locks::mutator_lock_);
   bool CheckAllClassesAreVerified(RedefinitionDataHolder& holder)
       REQUIRES_SHARED(art::Locks::mutator_lock_);
   bool EnsureAllClassAllocationsFinished(RedefinitionDataHolder& holder)
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc
index 438d5cb..d4f98af 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -3359,12 +3359,31 @@
       StartsWith(descriptor_sv, "Landroid/media/");
 }
 
+// Helper for maintaining DefineClass counting. We need to notify callbacks when we start/end a
+// define-class and how many recursive DefineClasses we are at in order to allow for doing  things
+// like pausing class definition.
+struct ScopedDefiningClass {
+ public:
+  explicit ScopedDefiningClass(Thread* self) REQUIRES_SHARED(Locks::mutator_lock_) : self_(self) {
+    Runtime::Current()->GetRuntimeCallbacks()->BeginDefineClass();
+    self_->IncrDefineClassCount();
+  }
+  ~ScopedDefiningClass() REQUIRES_SHARED(Locks::mutator_lock_) {
+    self_->DecrDefineClassCount();
+    Runtime::Current()->GetRuntimeCallbacks()->EndDefineClass();
+  }
+
+ private:
+  Thread* self_;
+};
+
 ObjPtr<mirror::Class> ClassLinker::DefineClass(Thread* self,
                                                const char* descriptor,
                                                size_t hash,
                                                Handle<mirror::ClassLoader> class_loader,
                                                const DexFile& dex_file,
                                                const dex::ClassDef& dex_class_def) {
+  ScopedDefiningClass sdc(self);
   StackHandleScope<3> hs(self);
   auto klass = hs.NewHandle<mirror::Class>(nullptr);
 
diff --git a/runtime/class_linker.h b/runtime/class_linker.h
index a1ba461..dd3ef7a 100644
--- a/runtime/class_linker.h
+++ b/runtime/class_linker.h
@@ -1440,6 +1440,10 @@
  public:
   virtual ~ClassLoadCallback() {}
 
+  // Called immediately before beginning class-definition and immediately before returning from it.
+  virtual void BeginDefineClass() REQUIRES_SHARED(Locks::mutator_lock_) {}
+  virtual void EndDefineClass() REQUIRES_SHARED(Locks::mutator_lock_) {}
+
   // If set we will replace initial_class_def & initial_dex_file with the final versions. The
   // callback author is responsible for ensuring these are allocated in such a way they can be
   // cleaned up if another transformation occurs. Note that both must be set or null/unchanged on
diff --git a/runtime/entrypoints/entrypoint_utils-inl.h b/runtime/entrypoints/entrypoint_utils-inl.h
index cc1a7f8..c67b1b0 100644
--- a/runtime/entrypoints/entrypoint_utils-inl.h
+++ b/runtime/entrypoints/entrypoint_utils-inl.h
@@ -358,7 +358,8 @@
       DCHECK(self->IsExceptionPending());  // Throw exception and unwind.
       return nullptr;  // Failure.
     }
-    if (UNLIKELY(is_set && resolved_field->IsFinal() && (fields_class != referring_class))) {
+    if (UNLIKELY(is_set && resolved_field->IsFinal() && (fields_class != referring_class) &&
+                 !referring_class->IsObsoleteVersionOf(fields_class))) {
       ThrowIllegalAccessErrorFinalField(referrer, resolved_field);
       return nullptr;  // Failure.
     } else {
diff --git a/runtime/mirror/class-inl.h b/runtime/mirror/class-inl.h
index db5cbce..79d767d 100644
--- a/runtime/mirror/class-inl.h
+++ b/runtime/mirror/class-inl.h
@@ -550,6 +550,22 @@
       access_to, method, dex_cache, method_idx, throw_invoke_type);
 }
 
+inline bool Class::IsObsoleteVersionOf(ObjPtr<Class> klass) {
+  DCHECK(!klass->IsObsoleteObject()) << klass->PrettyClass() << " is obsolete!";
+  if (LIKELY(!IsObsoleteObject())) {
+    return false;
+  }
+  ObjPtr<Class> current(klass);
+  do {
+    if (UNLIKELY(current == this)) {
+      return true;
+    } else {
+      current = current->GetObsoleteClass();
+    }
+  } while (!current.IsNull());
+  return false;
+}
+
 inline bool Class::IsSubClass(ObjPtr<Class> klass) {
   // Since the SubtypeCheck::IsSubtypeOf needs to lookup the Depth,
   // it is always O(Depth) in terms of speed to do the check.
diff --git a/runtime/mirror/class.cc b/runtime/mirror/class.cc
index 834b857..caa646b 100644
--- a/runtime/mirror/class.cc
+++ b/runtime/mirror/class.cc
@@ -336,6 +336,15 @@
   SetField32Transaction(OFFSET_OF_OBJECT_MEMBER(Class, class_size_), new_class_size);
 }
 
+ObjPtr<Class> Class::GetObsoleteClass() {
+  ObjPtr<ClassExt> ext(GetExtData());
+  if (ext.IsNull()) {
+    return nullptr;
+  } else {
+    return ext->GetObsoleteClass();
+  }
+}
+
 // Return the class' name. The exact format is bizarre, but it's the specified behavior for
 // Class.getName: keywords for primitive types, regular "[I" form for primitive arrays (so "int"
 // but "[I"), and arrays of reference types written between "L" and ";" but with dots rather than
diff --git a/runtime/mirror/class.h b/runtime/mirror/class.h
index b1186d9..f88438b 100644
--- a/runtime/mirror/class.h
+++ b/runtime/mirror/class.h
@@ -617,6 +617,11 @@
   // to themselves. Classes for primitive types may not assign to each other.
   ALWAYS_INLINE bool IsAssignableFrom(ObjPtr<Class> src) REQUIRES_SHARED(Locks::mutator_lock_);
 
+  // Checks if 'klass' is a redefined version of this.
+  bool IsObsoleteVersionOf(ObjPtr<Class> klass) REQUIRES_SHARED(Locks::mutator_lock_);
+
+  ObjPtr<Class> GetObsoleteClass() REQUIRES_SHARED(Locks::mutator_lock_);
+
   template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags,
            ReadBarrierOption kReadBarrierOption = kWithReadBarrier>
   ALWAYS_INLINE ObjPtr<Class> GetSuperClass() REQUIRES_SHARED(Locks::mutator_lock_);
diff --git a/runtime/runtime_callbacks.cc b/runtime/runtime_callbacks.cc
index ac73364..e0f57c0 100644
--- a/runtime/runtime_callbacks.cc
+++ b/runtime/runtime_callbacks.cc
@@ -228,6 +228,19 @@
   }
 }
 
+void RuntimeCallbacks::EndDefineClass() {
+  for (ClassLoadCallback* cb : COPY(class_callbacks_)) {
+    cb->EndDefineClass();
+  }
+}
+
+void RuntimeCallbacks::BeginDefineClass() {
+  for (ClassLoadCallback* cb : COPY(class_callbacks_)) {
+    cb->BeginDefineClass();
+  }
+}
+
+
 void RuntimeCallbacks::ClassPreDefine(const char* descriptor,
                                       Handle<mirror::Class> temp_class,
                                       Handle<mirror::ClassLoader> loader,
diff --git a/runtime/runtime_callbacks.h b/runtime/runtime_callbacks.h
index 7111ba0..3cadd97 100644
--- a/runtime/runtime_callbacks.h
+++ b/runtime/runtime_callbacks.h
@@ -181,6 +181,8 @@
   void AddClassLoadCallback(ClassLoadCallback* cb) REQUIRES(Locks::mutator_lock_);
   void RemoveClassLoadCallback(ClassLoadCallback* cb) REQUIRES(Locks::mutator_lock_);
 
+  void BeginDefineClass() REQUIRES_SHARED(Locks::mutator_lock_);
+  void EndDefineClass() REQUIRES_SHARED(Locks::mutator_lock_);
   void ClassLoad(Handle<mirror::Class> klass) REQUIRES_SHARED(Locks::mutator_lock_);
   void ClassPrepare(Handle<mirror::Class> temp_klass, Handle<mirror::Class> klass)
       REQUIRES_SHARED(Locks::mutator_lock_);
diff --git a/runtime/thread.h b/runtime/thread.h
index 29375e5..09e8810 100644
--- a/runtime/thread.h
+++ b/runtime/thread.h
@@ -263,6 +263,17 @@
         (state_and_flags.as_struct.flags & kSuspendRequest) != 0;
   }
 
+  void DecrDefineClassCount() {
+    tls32_.define_class_counter--;
+  }
+
+  void IncrDefineClassCount() {
+    tls32_.define_class_counter++;
+  }
+  uint32_t GetDefineClassCount() const {
+    return tls32_.define_class_counter;
+  }
+
   // If delta > 0 and (this != self or suspend_barrier is not null), this function may temporarily
   // release thread_suspend_count_lock_ internally.
   ALWAYS_INLINE
@@ -1550,7 +1561,8 @@
           user_code_suspend_count(0),
           force_interpreter_count(0),
           use_mterp(0),
-          make_visibly_initialized_counter(0) {}
+          make_visibly_initialized_counter(0),
+          define_class_counter(0) {}
 
     union StateAndFlags state_and_flags;
     static_assert(sizeof(union StateAndFlags) == sizeof(int32_t),
@@ -1648,6 +1660,10 @@
     // initialized but not visibly initialized for a long time even if no more classes are
     // being initialized anymore.
     uint32_t make_visibly_initialized_counter;
+
+    // Counter for how many nested define-classes are ongoing in this thread. Used to allow waiting
+    // for threads to be done with class-definition work.
+    uint32_t define_class_counter;
   } tls32_;
 
   struct PACKED(8) tls_64bit_sized_values {
diff --git a/test/1983-structural-redefinition-failures/expected.txt b/test/1983-structural-redefinition-failures/expected.txt
index 40a0914..222aac4 100644
--- a/test/1983-structural-redefinition-failures/expected.txt
+++ b/test/1983-structural-redefinition-failures/expected.txt
@@ -24,13 +24,13 @@
 Is Structurally modifiable class java.lang.invoke.VarHandle false
 Is Structurally modifiable class java.lang.invoke.FieldVarHandle false
 Checking non-mirror'd classes
-Is Structurally modifiable class java.util.ArrayList false
+Is Structurally modifiable class java.util.ArrayList true
 Is Structurally modifiable class java.util.Objects true
 Is Structurally modifiable class java.util.Arrays true
 Is Structurally modifiable class [Ljava.lang.Object; false
 Is Structurally modifiable class java.lang.Integer true
-Is Structurally modifiable class java.lang.Number false
+Is Structurally modifiable class java.lang.Number true
 Is Structurally modifiable class art.Test1983$NoVirtuals true
-Is Structurally modifiable class art.Test1983$WithVirtuals false
-Is Structurally modifiable class art.Test1983$SubWithVirtuals false
+Is Structurally modifiable class art.Test1983$WithVirtuals true
+Is Structurally modifiable class art.Test1983$SubWithVirtuals true
 Is Structurally modifiable class java.lang.invoke.MethodHandles true
diff --git a/test/1993-fallback-non-structural/expected.txt b/test/1993-fallback-non-structural/expected.txt
index 7e2cdf4..f523e70 100644
--- a/test/1993-fallback-non-structural/expected.txt
+++ b/test/1993-fallback-non-structural/expected.txt
@@ -1,3 +1,3 @@
-Can structurally Redefine: false
+Can structurally Redefine: true
 hello
 Goodbye
diff --git a/test/1993-fallback-non-structural/src/art/Test1993.java b/test/1993-fallback-non-structural/src/art/Test1993.java
index f442099..e2a8f6e 100644
--- a/test/1993-fallback-non-structural/src/art/Test1993.java
+++ b/test/1993-fallback-non-structural/src/art/Test1993.java
@@ -17,6 +17,7 @@
 package art;
 
 import java.util.Base64;
+import java.lang.reflect.*;
 public class Test1993 {
 
   static class Transform {
@@ -61,17 +62,25 @@
     "AAAASAEAAAMgAAACAAAAgAEAAAEQAAABAAAAjAEAAAIgAAAVAAAAkgEAAAQgAAACAAAALAMAAAAg" +
     "AAABAAAAOwMAAAMQAAACAAAATAMAAAYgAAABAAAAXAMAAAAQAAABAAAAbAMAAA==");
 
-  public static void run() {
+  public static void run() throws Exception {
     Redefinition.setTestConfiguration(Redefinition.Config.COMMON_REDEFINE);
     doTest(new Transform());
   }
 
-  public static void doTest(Transform t) {
-    // TODO Remove this once the class is structurally modifiable.
+  public static void doTest(Transform t) throws Exception {
     System.out.println("Can structurally Redefine: " +
       Redefinition.isStructurallyModifiable(Transform.class));
     t.sayHi();
     Redefinition.doCommonStructuralClassRedefinition(Transform.class, DEX_BYTES);
     t.sayHi();
+    // Check and make sure we didn't structurally redefine by looking for ClassExt.obsoleteClass
+    Field ext_data_field = Class.class.getDeclaredField("extData");
+    ext_data_field.setAccessible(true);
+    Object ext_data = ext_data_field.get(Transform.class);
+    Field obsolete_class_field = ext_data.getClass().getDeclaredField("obsoleteClass");
+    obsolete_class_field.setAccessible(true);
+    if (obsolete_class_field.get(ext_data) != null)  {
+      System.out.println("Expected no ClassExt.obsoleteClass but got " + obsolete_class_field.get(ext_data));
+    }
   }
 }
diff --git a/test/1999-virtual-structural/expected.txt b/test/1999-virtual-structural/expected.txt
new file mode 100644
index 0000000..335b5d7
--- /dev/null
+++ b/test/1999-virtual-structural/expected.txt
@@ -0,0 +1,4 @@
+Hi(SubTransform called 1 times)
+Hi(SubTransform called 2 times)
+Hi(SubTransform called 3 times)
+Hello (Transform called 1 times)(SubTransform called 4 times)
diff --git a/test/1999-virtual-structural/info.txt b/test/1999-virtual-structural/info.txt
new file mode 100644
index 0000000..606c984
--- /dev/null
+++ b/test/1999-virtual-structural/info.txt
@@ -0,0 +1,3 @@
+Tests basic functions in the jvmti plugin.
+
+Tests that using the structural redefinition can add new virtual methods and fields.
diff --git a/test/1999-virtual-structural/run b/test/1999-virtual-structural/run
new file mode 100755
index 0000000..0d41632
--- /dev/null
+++ b/test/1999-virtual-structural/run
@@ -0,0 +1,21 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+# TODO(b/144168550) This test uses access patterns that can be replaced by
+# invoke-virtual-quick during dex2dex compilation. This breaks the test since the
+# -quick opcode encodes the exact byte offset of vtable methods. Since this test
+# changes the offset this causes problems.
+./default-run "$@" --jvmti --runtime-option -Xopaque-jni-ids:true -Xcompiler-option --debuggable
diff --git a/test/1999-virtual-structural/src/Main.java b/test/1999-virtual-structural/src/Main.java
new file mode 100644
index 0000000..86a492b
--- /dev/null
+++ b/test/1999-virtual-structural/src/Main.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2017 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.Test1999.run();
+  }
+}
diff --git a/test/1999-virtual-structural/src/art/Redefinition.java b/test/1999-virtual-structural/src/art/Redefinition.java
new file mode 120000
index 0000000..81eaf31
--- /dev/null
+++ b/test/1999-virtual-structural/src/art/Redefinition.java
@@ -0,0 +1 @@
+../../../jvmti-common/Redefinition.java
\ No newline at end of file
diff --git a/test/1999-virtual-structural/src/art/Test1999.java b/test/1999-virtual-structural/src/art/Test1999.java
new file mode 100644
index 0000000..f6811a9
--- /dev/null
+++ b/test/1999-virtual-structural/src/art/Test1999.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2016 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.util.Base64;
+public class Test1999 {
+
+  public static class Transform {
+    public String getGreeting() {
+      return "Hi";
+    }
+  }
+
+  public static class SubTransform extends Transform {
+    private int count = 0;
+    public void sayHi() {
+      System.out.println(getGreeting() + "(SubTransform called " + (++count) + " times)");
+    }
+  }
+
+  /**
+   * base64 encoded class/dex file for
+   * public static class Transform {
+   *   private int count;
+   *   public String getGreeting() {
+   *     return "Hello (Transform called " + incrCount() + " times)";
+   *   }
+   *   protected int incrCount() {
+   *     return ++count;
+   *   }
+   * }
+   */
+  private static final byte[] DEX_BYTES = Base64.getDecoder().decode(
+"ZGV4CjAzNQAwwbMpPdPdWkU+6UJnvqa7v4VBdcuq2vkMBQAAcAAAAHhWNBIAAAAAAAAAAEgEAAAa" +
+"AAAAcAAAAAkAAADYAAAABQAAAPwAAAABAAAAOAEAAAgAAABAAQAAAQAAAIABAABsAwAAoAEAADoC" +
+"AABDAgAASwIAAGUCAABoAgAAawIAAG8CAABzAgAAjQIAAJ0CAADBAgAA4QIAAPUCAAAJAwAAJAMA" +
+"ADMDAAA+AwAAQQMAAE4DAABWAwAAXQMAAGoDAAB1AwAAewMAAIUDAACMAwAAAwAAAAcAAAAIAAAA" +
+"CQAAAAoAAAALAAAADAAAAA0AAAAQAAAAAwAAAAAAAAAAAAAABAAAAAYAAAAAAAAABQAAAAcAAAAs" +
+"AgAABgAAAAcAAAA0AgAAEAAAAAgAAAAAAAAAAQAAABMAAAABAAQAAQAAAAEAAQAUAAAAAQAAABUA" +
+"AAAFAAQAAQAAAAcABAABAAAABwACABIAAAAHAAMAEgAAAAcAAQAXAAAAAQAAAAEAAAAFAAAAAAAA" +
+"AA4AAAA4BAAAEwQAAAAAAAACAAEAAAAAACgCAAAHAAAAUhAAANgAAAFZEAAADwAAAAQAAQACAAAA" +
+"JAIAABsAAABuEAIAAwAKACIBBwBwEAQAAQAaAgIAbiAGACEAbiAFAAEAGgAAAG4gBgABAG4QBwAB" +
+"AAwAEQAAAAEAAQABAAAAIAIAAAQAAABwEAMAAAAOAAMADgAGAA4ACQAOAAEAAAAAAAAAAQAAAAYA" +
+"ByB0aW1lcykABjxpbml0PgAYSGVsbG8gKFRyYW5zZm9ybSBjYWxsZWQgAAFJAAFMAAJMSQACTEwA" +
+"GExhcnQvVGVzdDE5OTkkVHJhbnNmb3JtOwAOTGFydC9UZXN0MTk5OTsAIkxkYWx2aWsvYW5ub3Rh" +
+"dGlvbi9FbmNsb3NpbmdDbGFzczsAHkxkYWx2aWsvYW5ub3RhdGlvbi9Jbm5lckNsYXNzOwASTGph" +
+"dmEvbGFuZy9PYmplY3Q7ABJMamF2YS9sYW5nL1N0cmluZzsAGUxqYXZhL2xhbmcvU3RyaW5nQnVp" +
+"bGRlcjsADVRlc3QxOTk5LmphdmEACVRyYW5zZm9ybQABVgALYWNjZXNzRmxhZ3MABmFwcGVuZAAF" +
+"Y291bnQAC2dldEdyZWV0aW5nAAlpbmNyQ291bnQABG5hbWUACHRvU3RyaW5nAAV2YWx1ZQB2fn5E" +
+"OHsiY29tcGlsYXRpb24tbW9kZSI6ImRlYnVnIiwibWluLWFwaSI6MSwic2hhLTEiOiI2MGRhNGQ2" +
+"N2IzODFjNDI0Njc3NTdjNDlmYjZlNTU3NTZkODhhMmYzIiwidmVyc2lvbiI6IjEuNy4xMi1kZXYi" +
+"fQACAwEYGAICBAIRBAkWFw8AAQECAAIAgYAEiAQBAcADAQSgAwAAAAAAAgAAAAQEAAAKBAAALAQA" +
+"AAAAAAAAAAAAAAAAABAAAAAAAAAAAQAAAAAAAAABAAAAGgAAAHAAAAACAAAACQAAANgAAAADAAAA" +
+"BQAAAPwAAAAEAAAAAQAAADgBAAAFAAAACAAAAEABAAAGAAAAAQAAAIABAAABIAAAAwAAAKABAAAD" +
+"IAAAAwAAACACAAABEAAAAgAAACwCAAACIAAAGgAAADoCAAAEIAAAAgAAAAQEAAAAIAAAAQAAABME" +
+"AAADEAAAAgAAACgEAAAGIAAAAQAAADgEAAAAEAAAAQAAAEgEAAA=");
+
+
+  public static void run() {
+    Redefinition.setTestConfiguration(Redefinition.Config.COMMON_REDEFINE);
+    doTest(new SubTransform());
+  }
+
+  public static void doTest(SubTransform t) {
+    t.sayHi();
+    t.sayHi();
+    t.sayHi();
+    Redefinition.doCommonStructuralClassRedefinition(Transform.class, DEX_BYTES);
+    t.sayHi();
+  }
+}
diff --git a/test/2000-virtual-list-structural/AbstractCollection.patch b/test/2000-virtual-list-structural/AbstractCollection.patch
new file mode 100644
index 0000000..7507c7d
--- /dev/null
+++ b/test/2000-virtual-list-structural/AbstractCollection.patch
@@ -0,0 +1,16 @@
+--- ../../../libcore/ojluni/src/main/java/java/util/AbstractCollection.java	2019-05-31 10:36:26.634361294 -0700
++++ src-ex/java/util/AbstractCollection.java	2019-11-18 13:04:48.253575013 -0800
+@@ -63,7 +63,13 @@
+      * Sole constructor.  (For invocation by subclass constructors, typically
+      * implicit.)
+      */
++    public static volatile int TOTAL_COUNT;
++    public int cnt;
++
+     protected AbstractCollection() {
++      synchronized (Collection.class) {
++        cnt = ++TOTAL_COUNT;
++      }
+     }
+ 
+     // Query Operations
diff --git a/test/2000-virtual-list-structural/build b/test/2000-virtual-list-structural/build
new file mode 100755
index 0000000..87d6acc
--- /dev/null
+++ b/test/2000-virtual-list-structural/build
@@ -0,0 +1,31 @@
+#!/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.
+
+# Stop on failure.
+set -e
+
+# Deref the symlink.
+mv src-ex/java/util/AbstractCollection.java src-ex/java/util/AbstractCollection.bak
+cp src-ex/java/util/AbstractCollection.bak src-ex/java/util/AbstractCollection.java
+
+# Patch the copied version.
+patch src-ex/java/util/AbstractCollection.java AbstractCollection.patch
+
+DESUGAR=false ./default-build "$@"
+
+# restore the symlink
+rm src-ex/java/util/AbstractCollection.java
+mv src-ex/java/util/AbstractCollection.bak src-ex/java/util/AbstractCollection.java
diff --git a/test/2000-virtual-list-structural/expected.txt b/test/2000-virtual-list-structural/expected.txt
new file mode 100644
index 0000000..9d3e1b6
--- /dev/null
+++ b/test/2000-virtual-list-structural/expected.txt
@@ -0,0 +1,5 @@
+List is: [a, b, c, d]
+List is: [1, 2, 3, 4]
+List is: [1, 2, 3, 4, xyz: 0, xyz: 1, xyz: 2, xyz: 3, xyz: 4, xyz: 5, xyz: 6, xyz: 7, xyz: 8, xyz: 9, xyz: 10, xyz: 11, xyz: 12, xyz: 13, xyz: 14, xyz: 15, xyz: 16, xyz: 17, xyz: 18, xyz: 19, xyz: 20, xyz: 21, xyz: 22, xyz: 23, xyz: 24, xyz: 25, xyz: 26, xyz: 27, xyz: 28, xyz: 29, xyz: 30, xyz: 31, xyz: 32, xyz: 33, xyz: 34, xyz: 35, xyz: 36, xyz: 37, xyz: 38, xyz: 39, xyz: 40, xyz: 41, xyz: 42, xyz: 43, xyz: 44, xyz: 45, xyz: 46, xyz: 47, xyz: 48, xyz: 49, xyz: 50, xyz: 51, xyz: 52, xyz: 53, xyz: 54, xyz: 55, xyz: 56, xyz: 57, xyz: 58, xyz: 59, xyz: 60, xyz: 61, xyz: 62, xyz: 63, xyz: 64, xyz: 65, xyz: 66, xyz: 67, xyz: 68, xyz: 69, xyz: 70, xyz: 71, xyz: 72, xyz: 73, xyz: 74, xyz: 75, xyz: 76, xyz: 77, xyz: 78, xyz: 79, xyz: 80, xyz: 81, xyz: 82, xyz: 83, xyz: 84, xyz: 85, xyz: 86, xyz: 87, xyz: 88, xyz: 89, xyz: 90, xyz: 91, xyz: 92, xyz: 93, xyz: 94, xyz: 95, xyz: 96, xyz: 97, xyz: 98, xyz: 99, xyz: 100, xyz: 101, xyz: 102, xyz: 103, xyz: 104, xyz: 105, xyz: 106, xyz: 107, xyz: 108, xyz: 109, xyz: 110, xyz: 111, xyz: 112, xyz: 113, xyz: 114, xyz: 115, xyz: 116, xyz: 117, xyz: 118, xyz: 119, xyz: 120, xyz: 121, xyz: 122, xyz: 123, xyz: 124, xyz: 125, xyz: 126, xyz: 127, xyz: 128, xyz: 129, xyz: 130, xyz: 131, xyz: 132, xyz: 133, xyz: 134, xyz: 135, xyz: 136, xyz: 137, xyz: 138, xyz: 139, xyz: 140, xyz: 141, xyz: 142, xyz: 143, xyz: 144, xyz: 145, xyz: 146, xyz: 147, xyz: 148, xyz: 149, xyz: 150, xyz: 151, xyz: 152, xyz: 153, xyz: 154, xyz: 155, xyz: 156, xyz: 157, xyz: 158, xyz: 159, xyz: 160, xyz: 161, xyz: 162, xyz: 163, xyz: 164, xyz: 165, xyz: 166, xyz: 167, xyz: 168, xyz: 169, xyz: 170, xyz: 171, xyz: 172, xyz: 173, xyz: 174, xyz: 175, xyz: 176, xyz: 177, xyz: 178, xyz: 179, xyz: 180, xyz: 181, xyz: 182, xyz: 183, xyz: 184, xyz: 185, xyz: 186, xyz: 187, xyz: 188, xyz: 189, xyz: 190, xyz: 191, xyz: 192, xyz: 193, xyz: 194, xyz: 195, xyz: 196, xyz: 197, xyz: 198, xyz: 199, xyz: 200, xyz: 201, xyz: 202, xyz: 203, xyz: 204, xyz: 205, xyz: 206, xyz: 207, xyz: 208, xyz: 209, xyz: 210, xyz: 211, xyz: 212, xyz: 213, xyz: 214, xyz: 215, xyz: 216, xyz: 217, xyz: 218, xyz: 219, xyz: 220, xyz: 221, xyz: 222, xyz: 223, xyz: 224, xyz: 225, xyz: 226, xyz: 227, xyz: 228, xyz: 229, xyz: 230, xyz: 231, xyz: 232, xyz: 233, xyz: 234, xyz: 235, xyz: 236, xyz: 237, xyz: 238, xyz: 239, xyz: 240, xyz: 241, xyz: 242, xyz: 243, xyz: 244, xyz: 245, xyz: 246, xyz: 247, xyz: 248, xyz: 249, xyz: 250, xyz: 251, xyz: 252, xyz: 253, xyz: 254, xyz: 255, xyz: 256, xyz: 257, xyz: 258, xyz: 259, xyz: 260, xyz: 261, xyz: 262, xyz: 263, xyz: 264, xyz: 265, xyz: 266, xyz: 267, xyz: 268, xyz: 269, xyz: 270, xyz: 271, xyz: 272, xyz: 273, xyz: 274, xyz: 275, xyz: 276, xyz: 277, xyz: 278, xyz: 279, xyz: 280, xyz: 281, xyz: 282, xyz: 283, xyz: 284, xyz: 285, xyz: 286, xyz: 287, xyz: 288, xyz: 289, xyz: 290, xyz: 291, xyz: 292, xyz: 293, xyz: 294, xyz: 295, xyz: 296, xyz: 297, xyz: 298, xyz: 299, xyz: 300, xyz: 301, xyz: 302, xyz: 303, xyz: 304, xyz: 305, xyz: 306, xyz: 307, xyz: 308, xyz: 309, xyz: 310, xyz: 311, xyz: 312, xyz: 313, xyz: 314, xyz: 315, xyz: 316, xyz: 317, xyz: 318, xyz: 319, xyz: 320, xyz: 321, xyz: 322, xyz: 323, xyz: 324, xyz: 325, xyz: 326, xyz: 327, xyz: 328, xyz: 329, xyz: 330, xyz: 331, xyz: 332, xyz: 333, xyz: 334, xyz: 335, xyz: 336, xyz: 337, xyz: 338, xyz: 339, xyz: 340, xyz: 341, xyz: 342, xyz: 343, xyz: 344, xyz: 345, xyz: 346, xyz: 347, xyz: 348, xyz: 349, xyz: 350, xyz: 351, xyz: 352, xyz: 353, xyz: 354, xyz: 355, xyz: 356, xyz: 357, xyz: 358, xyz: 359, xyz: 360, xyz: 361, xyz: 362, xyz: 363, xyz: 364, xyz: 365, xyz: 366, xyz: 367, xyz: 368, xyz: 369, xyz: 370, xyz: 371, xyz: 372, xyz: 373, xyz: 374, xyz: 375, xyz: 376, xyz: 377, xyz: 378, xyz: 379, xyz: 380, xyz: 381, xyz: 382, xyz: 383, xyz: 384, xyz: 385, xyz: 386, xyz: 387, xyz: 388, xyz: 389, xyz: 390, xyz: 391, xyz: 392, xyz: 393, xyz: 394, xyz: 395, xyz: 396, xyz: 397, xyz: 398, xyz: 399, xyz: 400, xyz: 401, xyz: 402, xyz: 403, xyz: 404, xyz: 405, xyz: 406, xyz: 407, xyz: 408, xyz: 409, xyz: 410, xyz: 411, xyz: 412, xyz: 413, xyz: 414, xyz: 415, xyz: 416, xyz: 417, xyz: 418, xyz: 419, xyz: 420, xyz: 421, xyz: 422, xyz: 423, xyz: 424, xyz: 425, xyz: 426, xyz: 427, xyz: 428, xyz: 429, xyz: 430, xyz: 431, xyz: 432, xyz: 433, xyz: 434, xyz: 435, xyz: 436, xyz: 437, xyz: 438, xyz: 439, xyz: 440, xyz: 441, xyz: 442, xyz: 443, xyz: 444, xyz: 445, xyz: 446, xyz: 447, xyz: 448, xyz: 449, xyz: 450, xyz: 451, xyz: 452, xyz: 453, xyz: 454, xyz: 455, xyz: 456, xyz: 457, xyz: 458, xyz: 459, xyz: 460, xyz: 461, xyz: 462, xyz: 463, xyz: 464, xyz: 465, xyz: 466, xyz: 467, xyz: 468, xyz: 469, xyz: 470, xyz: 471, xyz: 472, xyz: 473, xyz: 474, xyz: 475, xyz: 476, xyz: 477, xyz: 478, xyz: 479, xyz: 480, xyz: 481, xyz: 482, xyz: 483, xyz: 484, xyz: 485, xyz: 486, xyz: 487, xyz: 488, xyz: 489, xyz: 490, xyz: 491, xyz: 492, xyz: 493, xyz: 494, xyz: 495, xyz: 496, xyz: 497, xyz: 498, xyz: 499, xyz: 500, xyz: 501, xyz: 502, xyz: 503, xyz: 504, xyz: 505, xyz: 506, xyz: 507, xyz: 508, xyz: 509, xyz: 510, xyz: 511, xyz: 512, xyz: 513, xyz: 514, xyz: 515, xyz: 516, xyz: 517, xyz: 518, xyz: 519, xyz: 520, xyz: 521, xyz: 522, xyz: 523, xyz: 524, xyz: 525, xyz: 526, xyz: 527, xyz: 528, xyz: 529, xyz: 530, xyz: 531, xyz: 532, xyz: 533, xyz: 534, xyz: 535, xyz: 536, xyz: 537, xyz: 538, xyz: 539, xyz: 540, xyz: 541, xyz: 542, xyz: 543, xyz: 544, xyz: 545, xyz: 546, xyz: 547, xyz: 548, xyz: 549, xyz: 550, xyz: 551, xyz: 552, xyz: 553, xyz: 554, xyz: 555, xyz: 556, xyz: 557, xyz: 558, xyz: 559, xyz: 560, xyz: 561, xyz: 562, xyz: 563, xyz: 564, xyz: 565, xyz: 566, xyz: 567, xyz: 568, xyz: 569, xyz: 570, xyz: 571, xyz: 572, xyz: 573, xyz: 574, xyz: 575, xyz: 576, xyz: 577, xyz: 578, xyz: 579, xyz: 580, xyz: 581, xyz: 582, xyz: 583, xyz: 584, xyz: 585, xyz: 586, xyz: 587, xyz: 588, xyz: 589, xyz: 590, xyz: 591, xyz: 592, xyz: 593, xyz: 594, xyz: 595, xyz: 596, xyz: 597, xyz: 598, xyz: 599, xyz: 600, xyz: 601, xyz: 602, xyz: 603, xyz: 604, xyz: 605, xyz: 606, xyz: 607, xyz: 608, xyz: 609, xyz: 610, xyz: 611, xyz: 612, xyz: 613, xyz: 614, xyz: 615, xyz: 616, xyz: 617, xyz: 618, xyz: 619, xyz: 620, xyz: 621, xyz: 622, xyz: 623, xyz: 624, xyz: 625, xyz: 626, xyz: 627, xyz: 628, xyz: 629, xyz: 630, xyz: 631, xyz: 632, xyz: 633, xyz: 634, xyz: 635, xyz: 636, xyz: 637, xyz: 638, xyz: 639, xyz: 640, xyz: 641, xyz: 642, xyz: 643, xyz: 644, xyz: 645, xyz: 646, xyz: 647, xyz: 648, xyz: 649, xyz: 650, xyz: 651, xyz: 652, xyz: 653, xyz: 654, xyz: 655, xyz: 656, xyz: 657, xyz: 658, xyz: 659, xyz: 660, xyz: 661, xyz: 662, xyz: 663, xyz: 664, xyz: 665, xyz: 666, xyz: 667, xyz: 668, xyz: 669, xyz: 670, xyz: 671, xyz: 672, xyz: 673, xyz: 674, xyz: 675, xyz: 676, xyz: 677, xyz: 678, xyz: 679, xyz: 680, xyz: 681, xyz: 682, xyz: 683, xyz: 684, xyz: 685, xyz: 686, xyz: 687, xyz: 688, xyz: 689, xyz: 690, xyz: 691, xyz: 692, xyz: 693, xyz: 694, xyz: 695, xyz: 696, xyz: 697, xyz: 698, xyz: 699, xyz: 700, xyz: 701, xyz: 702, xyz: 703, xyz: 704, xyz: 705, xyz: 706, xyz: 707, xyz: 708, xyz: 709, xyz: 710, xyz: 711, xyz: 712, xyz: 713, xyz: 714, xyz: 715, xyz: 716, xyz: 717, xyz: 718, xyz: 719, xyz: 720, xyz: 721, xyz: 722, xyz: 723, xyz: 724, xyz: 725, xyz: 726, xyz: 727, xyz: 728, xyz: 729, xyz: 730, xyz: 731, xyz: 732, xyz: 733, xyz: 734, xyz: 735, xyz: 736, xyz: 737, xyz: 738, xyz: 739, xyz: 740, xyz: 741, xyz: 742, xyz: 743, xyz: 744, xyz: 745, xyz: 746, xyz: 747, xyz: 748, xyz: 749, xyz: 750, xyz: 751, xyz: 752, xyz: 753, xyz: 754, xyz: 755, xyz: 756, xyz: 757, xyz: 758, xyz: 759, xyz: 760, xyz: 761, xyz: 762, xyz: 763, xyz: 764, xyz: 765, xyz: 766, xyz: 767, xyz: 768, xyz: 769, xyz: 770, xyz: 771, xyz: 772, xyz: 773, xyz: 774, xyz: 775, xyz: 776, xyz: 777, xyz: 778, xyz: 779, xyz: 780, xyz: 781, xyz: 782, xyz: 783, xyz: 784, xyz: 785, xyz: 786, xyz: 787, xyz: 788, xyz: 789, xyz: 790, xyz: 791, xyz: 792, xyz: 793, xyz: 794, xyz: 795, xyz: 796, xyz: 797, xyz: 798, xyz: 799, xyz: 800, xyz: 801, xyz: 802, xyz: 803, xyz: 804, xyz: 805, xyz: 806, xyz: 807, xyz: 808, xyz: 809, xyz: 810, xyz: 811, xyz: 812, xyz: 813, xyz: 814, xyz: 815, xyz: 816, xyz: 817, xyz: 818, xyz: 819, xyz: 820, xyz: 821, xyz: 822, xyz: 823, xyz: 824, xyz: 825, xyz: 826, xyz: 827, xyz: 828, xyz: 829, xyz: 830, xyz: 831, xyz: 832, xyz: 833, xyz: 834, xyz: 835, xyz: 836, xyz: 837, xyz: 838, xyz: 839, xyz: 840, xyz: 841, xyz: 842, xyz: 843, xyz: 844, xyz: 845, xyz: 846, xyz: 847, xyz: 848, xyz: 849, xyz: 850, xyz: 851, xyz: 852, xyz: 853, xyz: 854, xyz: 855, xyz: 856, xyz: 857, xyz: 858, xyz: 859, xyz: 860, xyz: 861, xyz: 862, xyz: 863, xyz: 864, xyz: 865, xyz: 866, xyz: 867, xyz: 868, xyz: 869, xyz: 870, xyz: 871, xyz: 872, xyz: 873, xyz: 874, xyz: 875, xyz: 876, xyz: 877, xyz: 878, xyz: 879, xyz: 880, xyz: 881, xyz: 882, xyz: 883, xyz: 884, xyz: 885, xyz: 886, xyz: 887, xyz: 888, xyz: 889, xyz: 890, xyz: 891, xyz: 892, xyz: 893, xyz: 894, xyz: 895, xyz: 896, xyz: 897, xyz: 898, xyz: 899, xyz: 900, xyz: 901, xyz: 902, xyz: 903, xyz: 904, xyz: 905, xyz: 906, xyz: 907, xyz: 908, xyz: 909, xyz: 910, xyz: 911, xyz: 912, xyz: 913, xyz: 914, xyz: 915, xyz: 916, xyz: 917, xyz: 918, xyz: 919, xyz: 920, xyz: 921, xyz: 922, xyz: 923, xyz: 924, xyz: 925, xyz: 926, xyz: 927, xyz: 928, xyz: 929, xyz: 930, xyz: 931, xyz: 932, xyz: 933, xyz: 934, xyz: 935, xyz: 936, xyz: 937, xyz: 938, xyz: 939, xyz: 940, xyz: 941, xyz: 942, xyz: 943, xyz: 944, xyz: 945, xyz: 946, xyz: 947, xyz: 948, xyz: 949, xyz: 950, xyz: 951, xyz: 952, xyz: 953, xyz: 954, xyz: 955, xyz: 956, xyz: 957, xyz: 958, xyz: 959, xyz: 960, xyz: 961, xyz: 962, xyz: 963, xyz: 964, xyz: 965, xyz: 966, xyz: 967, xyz: 968, xyz: 969, xyz: 970, xyz: 971, xyz: 972, xyz: 973, xyz: 974, xyz: 975, xyz: 976, xyz: 977, xyz: 978, xyz: 979, xyz: 980, xyz: 981, xyz: 982, xyz: 983, xyz: 984, xyz: 985, xyz: 986, xyz: 987, xyz: 988, xyz: 989, xyz: 990, xyz: 991, xyz: 992, xyz: 993, xyz: 994, xyz: 995, xyz: 996, xyz: 997, xyz: 998, xyz: 999]
+List is: [1, 2, 3, 4]
+List is: [a, b, c, d]
diff --git a/test/2000-virtual-list-structural/info.txt b/test/2000-virtual-list-structural/info.txt
new file mode 100644
index 0000000..606c984
--- /dev/null
+++ b/test/2000-virtual-list-structural/info.txt
@@ -0,0 +1,3 @@
+Tests basic functions in the jvmti plugin.
+
+Tests that using the structural redefinition can add new virtual methods and fields.
diff --git a/test/2000-virtual-list-structural/run b/test/2000-virtual-list-structural/run
new file mode 100755
index 0000000..0d41632
--- /dev/null
+++ b/test/2000-virtual-list-structural/run
@@ -0,0 +1,21 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+# TODO(b/144168550) This test uses access patterns that can be replaced by
+# invoke-virtual-quick during dex2dex compilation. This breaks the test since the
+# -quick opcode encodes the exact byte offset of vtable methods. Since this test
+# changes the offset this causes problems.
+./default-run "$@" --jvmti --runtime-option -Xopaque-jni-ids:true -Xcompiler-option --debuggable
diff --git a/test/2000-virtual-list-structural/src-ex/java/util/AbstractCollection.java b/test/2000-virtual-list-structural/src-ex/java/util/AbstractCollection.java
new file mode 120000
index 0000000..a30fbdc
--- /dev/null
+++ b/test/2000-virtual-list-structural/src-ex/java/util/AbstractCollection.java
@@ -0,0 +1 @@
+../../../../../../libcore/ojluni/src/main/java/java/util/AbstractCollection.java
\ No newline at end of file
diff --git a/test/2000-virtual-list-structural/src/Main.java b/test/2000-virtual-list-structural/src/Main.java
new file mode 100644
index 0000000..6a10c23
--- /dev/null
+++ b/test/2000-virtual-list-structural/src/Main.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+import art.*;
+import java.util.*;
+import java.lang.invoke.*;
+import java.io.*;
+
+public class Main {
+  public static final boolean PRINT_COUNT = false;
+  public static MethodHandles.Lookup lookup = MethodHandles.publicLookup();
+  public static MethodHandle getcnt;
+  public static MethodHandle get_total_cnt;
+  public static void GetHandles() throws Throwable {
+    getcnt = lookup.findGetter(AbstractCollection.class, "cnt", Integer.TYPE);
+    get_total_cnt = lookup.findStaticGetter(AbstractCollection.class, "TOTAL_COUNT", Integer.TYPE);
+  }
+
+  public static byte[] GetDexBytes() throws Throwable {
+    try (RandomAccessFile dex = new RandomAccessFile(new File(System.getenv("DEX_LOCATION") + "/" + "classes-ex.dex"), "r")) {
+      byte[] res = new byte[(int)dex.length()];
+      dex.read(res);
+      return res;
+    }
+  }
+  public static void PrintListAndData(AbstractCollection<String> c) throws Throwable {
+    if (PRINT_COUNT) {
+      System.out.println("List is: " + c + " count = " + getcnt.invoke(c) + " TOTAL_COUNT = " + get_total_cnt.invoke());
+    } else {
+      System.out.println("List is: " + c);
+    }
+  }
+  public static void main(String[] args) throws Throwable {
+    AbstractCollection<String> l1 = (AbstractCollection<String>)Arrays.asList("a", "b", "c", "d");
+    AbstractCollection<String> l2 = new ArrayList<>();
+    l2.add("1");
+    l2.add("2");
+    l2.add("3");
+    l2.add("4");
+    Redefinition.doCommonStructuralClassRedefinition(AbstractCollection.class, GetDexBytes());
+    GetHandles();
+    AbstractCollection<String> l3 = new HashSet<>(l2);
+    AbstractCollection<String> l4 = new LinkedList<>(l1);
+    PrintListAndData(l1);
+    PrintListAndData(l2);
+    for (int i = 0; i < 1000; i++) {
+      l2.add("xyz: " + i);
+    }
+    PrintListAndData(l2);
+    PrintListAndData(l3);
+    PrintListAndData(l4);
+    CheckLE(getcnt.invoke(l1), get_total_cnt.invoke());
+    CheckLE(getcnt.invoke(l2), get_total_cnt.invoke());
+    CheckLE(getcnt.invoke(l3), get_total_cnt.invoke());
+    CheckLE(getcnt.invoke(l4), get_total_cnt.invoke());
+    CheckEQ(getcnt.invoke(l1), 0);
+    CheckLE(getcnt.invoke(l2), 0);
+    CheckLE(getcnt.invoke(l1), getcnt.invoke(l2));
+    CheckLE(getcnt.invoke(l1), getcnt.invoke(l3));
+    CheckLE(getcnt.invoke(l1), getcnt.invoke(l4));
+    CheckLE(getcnt.invoke(l2), getcnt.invoke(l3));
+    CheckLE(getcnt.invoke(l2), getcnt.invoke(l4));
+    CheckLE(getcnt.invoke(l3), getcnt.invoke(l4));
+  }
+  public static void CheckEQ(Object a, int b) {
+    CheckEQ(((Integer)a).intValue(), b);
+  }
+  public static void CheckLE(Object a, Object b) {
+    CheckLE(((Integer)a).intValue(), ((Integer)b).intValue());
+  }
+  public static void CheckEQ(int a, int b) {
+    if (a != b) {
+      throw new Error(a + " is not equal to " + b);
+    }
+  }
+  public static void CheckLE(int a, int b) {
+    if (!(a <= b)) {
+      throw new Error(a + " is not less than or equal to " + b);
+    }
+  }
+}
diff --git a/test/2000-virtual-list-structural/src/art/Redefinition.java b/test/2000-virtual-list-structural/src/art/Redefinition.java
new file mode 120000
index 0000000..81eaf31
--- /dev/null
+++ b/test/2000-virtual-list-structural/src/art/Redefinition.java
@@ -0,0 +1 @@
+../../../jvmti-common/Redefinition.java
\ No newline at end of file
diff --git a/test/2001-virtual-structural-multithread/expected.txt b/test/2001-virtual-structural-multithread/expected.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/2001-virtual-structural-multithread/expected.txt
diff --git a/test/2001-virtual-structural-multithread/info.txt b/test/2001-virtual-structural-multithread/info.txt
new file mode 100644
index 0000000..3e5291d
--- /dev/null
+++ b/test/2001-virtual-structural-multithread/info.txt
@@ -0,0 +1,4 @@
+Tests structural redefinition with multiple threads.
+
+Tests that using the structural redefinition while concurrently loading and using a subtype of
+the class being redefined doesn't cause any unexpected problems.
diff --git a/test/2001-virtual-structural-multithread/run b/test/2001-virtual-structural-multithread/run
new file mode 100755
index 0000000..421f7b0
--- /dev/null
+++ b/test/2001-virtual-structural-multithread/run
@@ -0,0 +1,21 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+# TODO(b/144168550) This test uses access patterns that can be replaced by
+# iget-object-quick during dex2dex compilation. This breaks the test since the
+# -quick opcode encodes the exact byte offset of fields. Since this test changes
+# the offset this causes problems.
+./default-run "$@" --jvmti --runtime-option -Xopaque-jni-ids:true -Xcompiler-option --debuggable
diff --git a/test/2001-virtual-structural-multithread/src-art/Main.java b/test/2001-virtual-structural-multithread/src-art/Main.java
new file mode 100644
index 0000000..618cdcd
--- /dev/null
+++ b/test/2001-virtual-structural-multithread/src-art/Main.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2017 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.Test2001.run();
+  }
+}
diff --git a/test/2001-virtual-structural-multithread/src-art/art/Redefinition.java b/test/2001-virtual-structural-multithread/src-art/art/Redefinition.java
new file mode 120000
index 0000000..81eaf31
--- /dev/null
+++ b/test/2001-virtual-structural-multithread/src-art/art/Redefinition.java
@@ -0,0 +1 @@
+../../../jvmti-common/Redefinition.java
\ No newline at end of file
diff --git a/test/2001-virtual-structural-multithread/src-art/art/Test2001.java b/test/2001-virtual-structural-multithread/src-art/art/Test2001.java
new file mode 100644
index 0000000..e6ee0ce
--- /dev/null
+++ b/test/2001-virtual-structural-multithread/src-art/art/Test2001.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2016 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 dalvik.system.InMemoryDexClassLoader;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.concurrent.CountDownLatch;
+import java.util.function.Supplier;
+
+public class Test2001 {
+  private static final int NUM_THREADS = 20;
+
+  public static class Transform {
+    public String greetingEnglish;
+
+    public Transform() {
+      this.greetingEnglish = "Hello";
+    }
+
+    public String sayHi() {
+      return greetingEnglish + " from " + Thread.currentThread().getName();
+    }
+  }
+
+  /**
+   * base64 encoded class/dex file for
+   * public static class Transform {
+   *   public String greetingEnglish;
+   *   public String greetingFrench;
+   *   public String greetingDanish;
+   *   public String greetingJapanese;
+   *
+   *   public Transform() {
+   *     this.greetingEnglish = "Hello World";
+   *     this.greetingFrench = "Bonjour le Monde";
+   *     this.greetingDanish = "Hej Verden";
+   *     this.greetingJapanese = "こんにちは世界";
+   *   }
+   *   public String sayHi() {
+   *     return sayHiEnglish() + ", " + sayHiFrench() + ", " + sayHiDanish() + ", " + sayHiJapanese() + " from " + Thread.currentThread().getName();
+   *   }
+   *   public String sayHiEnglish() {
+   *     return greetingEnglish;
+   *   }
+   *   public String sayHiDanish() {
+   *     return greetingDanish;
+   *   }
+   *   public String sayHiJapanese() {
+   *     return greetingJapanese;
+   *   }
+   *   public String sayHiFrench() {
+   *     return greetingFrench;
+   *   }
+   * }
+   */
+  private static final byte[] DEX_BYTES =
+      Base64.getDecoder()
+          .decode(
+              "ZGV4CjAzNQCnKPY06VRa4aM/zFW0MYLmRxT/NtXxD/H4BgAAcAAAAHhWNBIAAAAAAAAAADQGAAAl"
+                  + "AAAAcAAAAAkAAAAEAQAABAAAACgBAAAEAAAAWAEAAAwAAAB4AQAAAQAAANgBAAAABQAA+AEAAEoD"
+                  + "AABSAwAAVgMAAF4DAABwAwAAfAMAAIkDAACMAwAAkAMAAKoDAAC6AwAA3gMAAP4DAAASBAAAJgQA"
+                  + "AEEEAABVBAAAZAQAAG8EAAByBAAAfwQAAIcEAACWBAAAnwQAAK8EAADABAAA0AQAAOIEAADoBAAA"
+                  + "7wQAAPwEAAAKBQAAFwUAACYFAAAwBQAANwUAAMUFAAAIAAAACQAAAAoAAAALAAAADAAAAA0AAAAO"
+                  + "AAAADwAAABIAAAAGAAAABQAAAAAAAAAHAAAABgAAAEQDAAAGAAAABwAAAAAAAAASAAAACAAAAAAA"
+                  + "AAAAAAUAFwAAAAAABQAYAAAAAAAFABkAAAAAAAUAGgAAAAAAAwACAAAAAAAAABwAAAAAAAAAHQAA"
+                  + "AAAAAAAeAAAAAAAAAB8AAAAAAAAAIAAAAAQAAwACAAAABgADAAIAAAAGAAEAFAAAAAYAAAAhAAAA"
+                  + "BwACABUAAAAHAAAAFgAAAAAAAAABAAAABAAAAAAAAAAQAAAAJAYAAOsFAAAAAAAABwABAAIAAAAt"
+                  + "AwAAQQAAAG4QAwAGAAwAbhAEAAYADAFuEAIABgAMAm4QBQAGAAwDcQAKAAAADARuEAsABAAMBCIF"
+                  + "BgBwEAcABQBuIAgABQAaAAEAbiAIAAUAbiAIABUAbiAIAAUAbiAIACUAbiAIAAUAbiAIADUAGgAA"
+                  + "AG4gCAAFAG4gCABFAG4QCQAFAAwAEQAAAAIAAQAAAAAAMQMAAAMAAABUEAAAEQAAAAIAAQAAAAAA"
+                  + "NQMAAAMAAABUEAEAEQAAAAIAAQAAAAAAOQMAAAMAAABUEAIAEQAAAAIAAQAAAAAAPQMAAAMAAABU"
+                  + "EAMAEQAAAAIAAQABAAAAJAMAABQAAABwEAYAAQAaAAUAWxABABoAAwBbEAIAGgAEAFsQAAAaACQA"
+                  + "WxADAA4ACwAOPEtLS0sAEgAOABgADgAVAA4AHgAOABsADgAAAAABAAAABQAGIGZyb20gAAIsIAAG"
+                  + "PGluaXQ+ABBCb25qb3VyIGxlIE1vbmRlAApIZWogVmVyZGVuAAtIZWxsbyBXb3JsZAABTAACTEwA"
+                  + "GExhcnQvVGVzdDIwMDEkVHJhbnNmb3JtOwAOTGFydC9UZXN0MjAwMTsAIkxkYWx2aWsvYW5ub3Rh"
+                  + "dGlvbi9FbmNsb3NpbmdDbGFzczsAHkxkYWx2aWsvYW5ub3RhdGlvbi9Jbm5lckNsYXNzOwASTGph"
+                  + "dmEvbGFuZy9PYmplY3Q7ABJMamF2YS9sYW5nL1N0cmluZzsAGUxqYXZhL2xhbmcvU3RyaW5nQnVp"
+                  + "bGRlcjsAEkxqYXZhL2xhbmcvVGhyZWFkOwANVGVzdDIwMDEuamF2YQAJVHJhbnNmb3JtAAFWAAth"
+                  + "Y2Nlc3NGbGFncwAGYXBwZW5kAA1jdXJyZW50VGhyZWFkAAdnZXROYW1lAA5ncmVldGluZ0Rhbmlz"
+                  + "aAAPZ3JlZXRpbmdFbmdsaXNoAA5ncmVldGluZ0ZyZW5jaAAQZ3JlZXRpbmdKYXBhbmVzZQAEbmFt"
+                  + "ZQAFc2F5SGkAC3NheUhpRGFuaXNoAAxzYXlIaUVuZ2xpc2gAC3NheUhpRnJlbmNoAA1zYXlIaUph"
+                  + "cGFuZXNlAAh0b1N0cmluZwAFdmFsdWUAiwF+fkQ4eyJjb21waWxhdGlvbi1tb2RlIjoiZGVidWci"
+                  + "LCJoYXMtY2hlY2tzdW1zIjpmYWxzZSwibWluLWFwaSI6MSwic2hhLTEiOiJmNjJiOGNlNmEwNTkw"
+                  + "MDU0ZWYzNGExYWVkZTcwYjQ2NjY4ZThiNDlmIiwidmVyc2lvbiI6IjIuMC4xLWRldiJ9AAfjgZPj"
+                  + "gpPjgavjgaHjga/kuJbnlYwAAgIBIhgBAgMCEwQJGxcRAAQBBQABAQEBAQEBAIGABOwFAQH4AwEB"
+                  + "jAUBAaQFAQG8BQEB1AUAAAAAAAAAAgAAANwFAADiBQAAGAYAAAAAAAAAAAAAAAAAABAAAAAAAAAA"
+                  + "AQAAAAAAAAABAAAAJQAAAHAAAAACAAAACQAAAAQBAAADAAAABAAAACgBAAAEAAAABAAAAFgBAAAF"
+                  + "AAAADAAAAHgBAAAGAAAAAQAAANgBAAABIAAABgAAAPgBAAADIAAABgAAACQDAAABEAAAAQAAAEQD"
+                  + "AAACIAAAJQAAAEoDAAAEIAAAAgAAANwFAAAAIAAAAQAAAOsFAAADEAAAAgAAABQGAAAGIAAAAQAA"
+                  + "ACQGAAAAEAAAAQAAADQGAAA=");
+
+  /*
+   * base64 encoded class/dex file for
+    package art;
+    import java.util.function.Supplier;
+    public class SubTransform extends art.Test2001.Transform implements Supplier<String> {
+      public SubTransform() {
+        super();
+      }
+      public String get() {
+        return "from SUBCLASS: " + super.sayHi();
+      }
+    }
+   */
+  private static final byte[] SUB_DEX_BYTES =
+      Base64.getDecoder()
+          .decode(
+              "ZGV4CjAzNQBawzkIDf9khFw00md41U4vIqRuhqBTjM+0BAAAcAAAAHhWNBIAAAAAAAAAAPwDAAAV"
+                  + "AAAAcAAAAAgAAADEAAAABAAAAOQAAAAAAAAAAAAAAAgAAAAUAQAAAQAAAFQBAABAAwAAdAEAAAIC"
+                  + "AAAKAgAADgIAABECAAAVAgAAKQIAAEMCAABiAgAAdgIAAIoCAAClAgAAxAIAAOMCAAD2AgAA+QIA"
+                  + "AAEDAAASAwAAFwMAAB4DAAAoAwAALwMAAAQAAAAFAAAABgAAAAcAAAAIAAAACQAAAAoAAAANAAAA"
+                  + "AgAAAAMAAAAAAAAAAgAAAAQAAAAAAAAAAwAAAAUAAAD8AQAADQAAAAcAAAAAAAAAAAADAAAAAAAA"
+                  + "AAAAEAAAAAAAAQAQAAAAAQADAAAAAAABAAEAEQAAAAUAAwAAAAAABQACAA4AAAAFAAEAEgAAAAAA"
+                  + "AAABAAAAAQAAAPQBAAAMAAAA7AMAAMsDAAAAAAAAAgABAAEAAADpAQAABQAAAG4QAgABAAwAEQAA"
+                  + "AAQAAQACAAAA7QEAABYAAABvEAQAAwAMACIBBQBwEAUAAQAaAg8AbiAGACEAbiAGAAEAbhAHAAEA"
+                  + "DAARAAEAAQABAAAA5AEAAAQAAABwEAMAAAAOAAYADjwABAAOAAkADgAAAAABAAAABgAAAAEAAAAE"
+                  + "AAY8aW5pdD4AAj47AAFMAAJMTAASTGFydC9TdWJUcmFuc2Zvcm07ABhMYXJ0L1Rlc3QyMDAxJFRy"
+                  + "YW5zZm9ybTsAHUxkYWx2aWsvYW5ub3RhdGlvbi9TaWduYXR1cmU7ABJMamF2YS9sYW5nL09iamVj"
+                  + "dDsAEkxqYXZhL2xhbmcvU3RyaW5nOwAZTGphdmEvbGFuZy9TdHJpbmdCdWlsZGVyOwAdTGphdmEv"
+                  + "dXRpbC9mdW5jdGlvbi9TdXBwbGllcjsAHUxqYXZhL3V0aWwvZnVuY3Rpb24vU3VwcGxpZXI8ABFT"
+                  + "dWJUcmFuc2Zvcm0uamF2YQABVgAGYXBwZW5kAA9mcm9tIFNVQkNMQVNTOiAAA2dldAAFc2F5SGkA"
+                  + "CHRvU3RyaW5nAAV2YWx1ZQCLAX5+RDh7ImNvbXBpbGF0aW9uLW1vZGUiOiJkZWJ1ZyIsImhhcy1j"
+                  + "aGVja3N1bXMiOmZhbHNlLCJtaW4tYXBpIjoxLCJzaGEtMSI6ImY2MmI4Y2U2YTA1OTAwNTRlZjM0"
+                  + "YTFhZWRlNzBiNDY2NjhlOGI0OWYiLCJ2ZXJzaW9uIjoiMi4wLjEtZGV2In0AAgIBExwEFwUXCxcI"
+                  + "FwEAAAECAIGABMwDAcEg9AIBAZADAAAAAAAAAQAAAL0DAADkAwAAAAAAAAAAAAAAAAAADwAAAAAA"
+                  + "AAABAAAAAAAAAAEAAAAVAAAAcAAAAAIAAAAIAAAAxAAAAAMAAAAEAAAA5AAAAAUAAAAIAAAAFAEA"
+                  + "AAYAAAABAAAAVAEAAAEgAAADAAAAdAEAAAMgAAADAAAA5AEAAAEQAAACAAAA9AEAAAIgAAAVAAAA"
+                  + "AgIAAAQgAAABAAAAvQMAAAAgAAABAAAAywMAAAMQAAACAAAA4AMAAAYgAAABAAAA7AMAAAAQAAAB"
+                  + "AAAA/AMAAA==");
+
+  public static void run() throws Exception {
+    Redefinition.setTestConfiguration(Redefinition.Config.COMMON_REDEFINE);
+    doTest();
+  }
+
+  public static Supplier<String> mkTransform() {
+    try {
+      return (Supplier<String>)
+          (new InMemoryDexClassLoader(
+                  ByteBuffer.wrap(SUB_DEX_BYTES), Test2001.class.getClassLoader())
+              .loadClass("art.SubTransform")
+              .newInstance());
+    } catch (Exception e) {
+      return () -> {
+        return e.toString();
+      };
+    }
+  }
+
+  public static final class MyThread extends Thread {
+    public MyThread(CountDownLatch delay, int id) {
+      super("Thread: " + id);
+      this.thr_id = id;
+      this.results = new ArrayList<>(1000);
+      this.finish = false;
+      this.delay = delay;
+    }
+
+    public void run() {
+      delay.countDown();
+      while (!finish) {
+        Supplier<String> t = mkTransform();
+        results.add(t.get());
+      }
+    }
+
+    public void finish() throws Exception {
+      finish = true;
+      this.join();
+    }
+
+    public void Check() throws Exception {
+      for (String s : results) {
+        if (!s.equals("from SUBCLASS: Hello from " + getName())
+            && !s.equals("from SUBCLASS: Hello, null, null, null from " + getName())
+            && !s.equals(
+                "from SUBCLASS: Hello World, Bonjour le Monde, Hej Verden, こんにちは世界 from "
+                    + getName())) {
+          System.out.println("FAIL " + thr_id + ": Unexpected result: " + s);
+        }
+      }
+    }
+
+    public ArrayList<String> results;
+    public volatile boolean finish;
+    public int thr_id;
+    public CountDownLatch delay;
+  }
+
+  public static MyThread[] startThreads(int num_threads) throws Exception {
+    CountDownLatch cdl = new CountDownLatch(num_threads);
+    MyThread[] res = new MyThread[num_threads];
+    for (int i = 0; i < num_threads; i++) {
+      res[i] = new MyThread(cdl, i);
+      res[i].start();
+    }
+    cdl.await();
+    return res;
+  }
+
+  public static void finishThreads(MyThread[] thrs) throws Exception {
+    for (MyThread t : thrs) {
+      t.finish();
+    }
+    for (MyThread t : thrs) {
+      t.Check();
+    }
+  }
+
+  public static void doTest() throws Exception {
+    MyThread[] threads = startThreads(NUM_THREADS);
+    Redefinition.doCommonStructuralClassRedefinition(Transform.class, DEX_BYTES);
+    finishThreads(threads);
+  }
+}
diff --git a/test/2001-virtual-structural-multithread/src/Main.java b/test/2001-virtual-structural-multithread/src/Main.java
new file mode 100644
index 0000000..89b8557
--- /dev/null
+++ b/test/2001-virtual-structural-multithread/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 {
+    System.out.println("FAIL: Test is only for art!");
+  }
+}
diff --git a/test/2002-virtual-structural-initializing/expected.txt b/test/2002-virtual-structural-initializing/expected.txt
new file mode 100644
index 0000000..c1c8a70
--- /dev/null
+++ b/test/2002-virtual-structural-initializing/expected.txt
@@ -0,0 +1 @@
+Initialized Static Hello
diff --git a/test/2002-virtual-structural-initializing/info.txt b/test/2002-virtual-structural-initializing/info.txt
new file mode 100644
index 0000000..3e5291d
--- /dev/null
+++ b/test/2002-virtual-structural-initializing/info.txt
@@ -0,0 +1,4 @@
+Tests structural redefinition with multiple threads.
+
+Tests that using the structural redefinition while concurrently loading and using a subtype of
+the class being redefined doesn't cause any unexpected problems.
diff --git a/test/2002-virtual-structural-initializing/run b/test/2002-virtual-structural-initializing/run
new file mode 100755
index 0000000..421f7b0
--- /dev/null
+++ b/test/2002-virtual-structural-initializing/run
@@ -0,0 +1,21 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+# TODO(b/144168550) This test uses access patterns that can be replaced by
+# iget-object-quick during dex2dex compilation. This breaks the test since the
+# -quick opcode encodes the exact byte offset of fields. Since this test changes
+# the offset this causes problems.
+./default-run "$@" --jvmti --runtime-option -Xopaque-jni-ids:true -Xcompiler-option --debuggable
diff --git a/test/2002-virtual-structural-initializing/src-art/Main.java b/test/2002-virtual-structural-initializing/src-art/Main.java
new file mode 100644
index 0000000..a0aab42
--- /dev/null
+++ b/test/2002-virtual-structural-initializing/src-art/Main.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2017 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.Test2002.run();
+  }
+}
diff --git a/test/2002-virtual-structural-initializing/src-art/art/Redefinition.java b/test/2002-virtual-structural-initializing/src-art/art/Redefinition.java
new file mode 120000
index 0000000..81eaf31
--- /dev/null
+++ b/test/2002-virtual-structural-initializing/src-art/art/Redefinition.java
@@ -0,0 +1 @@
+../../../jvmti-common/Redefinition.java
\ No newline at end of file
diff --git a/test/2002-virtual-structural-initializing/src-art/art/Test2002.java b/test/2002-virtual-structural-initializing/src-art/art/Test2002.java
new file mode 100644
index 0000000..f91e3f7
--- /dev/null
+++ b/test/2002-virtual-structural-initializing/src-art/art/Test2002.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2016 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 dalvik.system.InMemoryDexClassLoader;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.concurrent.CountDownLatch;
+import java.util.function.Supplier;
+
+public class Test2002 {
+  public static final CountDownLatch start_latch = new CountDownLatch(1);
+  public static final CountDownLatch finish_latch = new CountDownLatch(1);
+  public static class Transform {
+    public Transform() { }
+
+    public String sayHi() {
+      return "Hi";
+    }
+  }
+
+  /**
+   * base64 encoded class/dex file for
+   * public static class Transform {
+   *   public String greeting;
+   *
+   *   public Transform() {
+   *     greeting = "Hello";
+   *   }
+   *   public String sayHi() {
+   *     return greeting;
+   *   }
+   * }
+   */
+  private static final byte[] DEX_BYTES =
+      Base64.getDecoder()
+          .decode(
+"ZGV4CjAzNQBlpDFxr5PhCBfCyN+GZYuYQvSqtTEESU3oAwAAcAAAAHhWNBIAAAAAAAAAADADAAAS" +
+"AAAAcAAAAAcAAAC4AAAAAgAAANQAAAABAAAA7AAAAAMAAAD0AAAAAQAAAAwBAAC8AgAALAEAAHAB" +
+"AAB4AQAAfwEAAIIBAACcAQAArAEAANABAADwAQAABAIAABgCAAAnAgAAMgIAADUCAABCAgAATAIA" +
+"AFICAABZAgAAYAIAAAMAAAAEAAAABQAAAAYAAAAHAAAACAAAAAsAAAACAAAABQAAAAAAAAALAAAA" +
+"BgAAAAAAAAAAAAUADQAAAAAAAQAAAAAAAAAAAA8AAAAEAAEAAAAAAAAAAAABAAAABAAAAAAAAAAJ" +
+"AAAAIAMAAP0CAAAAAAAAAgABAAAAAABqAQAAAwAAAFQQAAARAAAAAgABAAEAAABkAQAACAAAAHAQ" +
+"AgABABoAAQBbEAAADgAGAA48SwAKAA4AAAAGPGluaXQ+AAVIZWxsbwABTAAYTGFydC9UZXN0MjAw" +
+"MiRUcmFuc2Zvcm07AA5MYXJ0L1Rlc3QyMDAyOwAiTGRhbHZpay9hbm5vdGF0aW9uL0VuY2xvc2lu" +
+"Z0NsYXNzOwAeTGRhbHZpay9hbm5vdGF0aW9uL0lubmVyQ2xhc3M7ABJMamF2YS9sYW5nL09iamVj" +
+"dDsAEkxqYXZhL2xhbmcvU3RyaW5nOwANVGVzdDIwMDIuamF2YQAJVHJhbnNmb3JtAAFWAAthY2Nl" +
+"c3NGbGFncwAIZ3JlZXRpbmcABG5hbWUABXNheUhpAAV2YWx1ZQCLAX5+RDh7ImNvbXBpbGF0aW9u" +
+"LW1vZGUiOiJkZWJ1ZyIsImhhcy1jaGVja3N1bXMiOmZhbHNlLCJtaW4tYXBpIjoxLCJzaGEtMSI6" +
+"ImY2MmI4Y2U2YTA1OTAwNTRlZjM0YTFhZWRlNzBiNDY2NjhlOGI0OWYiLCJ2ZXJzaW9uIjoiMi4w" +
+"LjEtZGV2In0AAgIBEBgBAgMCDAQJDhcKAAEBAQABAIGABMQCAQGsAgAAAAAAAAACAAAA7gIAAPQC" +
+"AAAUAwAAAAAAAAAAAAAAAAAADwAAAAAAAAABAAAAAAAAAAEAAAASAAAAcAAAAAIAAAAHAAAAuAAA" +
+"AAMAAAACAAAA1AAAAAQAAAABAAAA7AAAAAUAAAADAAAA9AAAAAYAAAABAAAADAEAAAEgAAACAAAA" +
+"LAEAAAMgAAACAAAAZAEAAAIgAAASAAAAcAEAAAQgAAACAAAA7gIAAAAgAAABAAAA/QIAAAMQAAAC" +
+"AAAAEAMAAAYgAAABAAAAIAMAAAAQAAABAAAAMAMAAA==");
+
+  /*
+   * base64 encoded class/dex file for
+    package art;
+    import java.util.function.Supplier;
+    import java.util.concurrent.CountDownLatch;
+
+    public class SubTransform extends art.Test2002.Transform implements Supplier<String> {
+      public static final String staticId;
+      static {
+        String res = null;
+        try {
+          Test2002.start_latch.countDown();
+          Test2002.finish_latch.await();
+          res = "Initialized Static";
+        } catch (Exception e) {
+          res = e.toString();
+        }
+        staticId = res;
+      }
+      public SubTransform() {
+        super();
+      }
+      public String get() {
+        return SubTransform.staticId + " " + sayHi();
+      }
+    }
+   */
+  private static final byte[] SUB_DEX_BYTES =
+      Base64.getDecoder()
+          .decode(
+"ZGV4CjAzNQB0BhXQtGTKXAGE/UzeevPgeNK7UrQJRJkoBgAAcAAAAHhWNBIAAAAAAAAAAGQFAAAf" +
+"AAAAcAAAAAsAAADsAAAABAAAABgBAAADAAAASAEAAAwAAABgAQAAAQAAAMABAABIBAAA4AEAAM4C" +
+"AADRAgAA2wIAAOMCAADnAgAA+wIAAP4CAAACAwAAFgMAADADAABAAwAAXwMAAHYDAACKAwAAngMA" +
+"ALkDAADgAwAA/wMAAB4EAAAxBAAANAQAADwEAABDBAAATgQAAFwEAABhBAAAaAQAAHUEAAB/BAAA" +
+"iQQAAJAEAAAHAAAACAAAAAkAAAAKAAAACwAAAAwAAAANAAAADgAAAA8AAAAQAAAAEwAAAAUAAAAF" +
+"AAAAAAAAAAUAAAAGAAAAAAAAAAYAAAAHAAAAyAIAABMAAAAKAAAAAAAAAAAABgAbAAAAAgAIABcA" +
+"AAACAAgAGgAAAAAAAwABAAAAAAADAAIAAAAAAAAAGAAAAAAAAQAYAAAAAAABABkAAAABAAMAAgAA" +
+"AAQAAQAcAAAABwADAAIAAAAHAAIAFAAAAAcAAQAcAAAACAADABUAAAAIAAMAFgAAAAAAAAABAAAA" +
+"AQAAAMACAAASAAAAVAUAACwFAAAAAAAAAgABAAEAAAC1AgAABQAAAG4QAwABAAwAEQAAAAQAAQAC" +
+"AAAAuQIAABsAAABiAAAAbhAEAAMADAEiAgcAcBAHAAIAbiAIAAIAGgAAAG4gCAACAG4gCAASAG4Q" +
+"CQACAAwAEQAAAAEAAAABAAEApAIAABYAAAAAAGIAAgBuEAsAAABiAAEAbhAKAAAAGgAEACgGDQBu" +
+"EAYAAAAMAGkAAAAOAAEAAAAMAAEAAQEEDgEAAQABAAAAsAIAAAQAAABwEAUAAAAOAAgADh9aWi8b" +
+"HkwtABMADjwABQAOABYADgAAAAABAAAACQAAAAEAAAAGAAEgAAg8Y2xpbml0PgAGPGluaXQ+AAI+" +
+"OwASSW5pdGlhbGl6ZWQgU3RhdGljAAFMAAJMTAASTGFydC9TdWJUcmFuc2Zvcm07ABhMYXJ0L1Rl" +
+"c3QyMDAyJFRyYW5zZm9ybTsADkxhcnQvVGVzdDIwMDI7AB1MZGFsdmlrL2Fubm90YXRpb24vU2ln" +
+"bmF0dXJlOwAVTGphdmEvbGFuZy9FeGNlcHRpb247ABJMamF2YS9sYW5nL09iamVjdDsAEkxqYXZh" +
+"L2xhbmcvU3RyaW5nOwAZTGphdmEvbGFuZy9TdHJpbmdCdWlsZGVyOwAlTGphdmEvdXRpbC9jb25j" +
+"dXJyZW50L0NvdW50RG93bkxhdGNoOwAdTGphdmEvdXRpbC9mdW5jdGlvbi9TdXBwbGllcjsAHUxq" +
+"YXZhL3V0aWwvZnVuY3Rpb24vU3VwcGxpZXI8ABFTdWJUcmFuc2Zvcm0uamF2YQABVgAGYXBwZW5k" +
+"AAVhd2FpdAAJY291bnREb3duAAxmaW5pc2hfbGF0Y2gAA2dldAAFc2F5SGkAC3N0YXJ0X2xhdGNo" +
+"AAhzdGF0aWNJZAAIdG9TdHJpbmcABXZhbHVlAIsBfn5EOHsiY29tcGlsYXRpb24tbW9kZSI6ImRl" +
+"YnVnIiwiaGFzLWNoZWNrc3VtcyI6ZmFsc2UsIm1pbi1hcGkiOjEsInNoYS0xIjoiZjYyYjhjZTZh" +
+"MDU5MDA1NGVmMzRhMWFlZGU3MGI0NjY2OGU4YjQ5ZiIsInZlcnNpb24iOiIyLjAuMS1kZXYifQAC" +
+"AwEdHAQXCBcRFw0XAwEAAgIAGQCIgATEBAGBgASMBQLBIOADAQH8AwAAAAAAAQAAAB4FAABMBQAA" +
+"AAAAAAAAAAAAAAAAEAAAAAAAAAABAAAAAAAAAAEAAAAfAAAAcAAAAAIAAAALAAAA7AAAAAMAAAAE" +
+"AAAAGAEAAAQAAAADAAAASAEAAAUAAAAMAAAAYAEAAAYAAAABAAAAwAEAAAEgAAAEAAAA4AEAAAMg" +
+"AAAEAAAApAIAAAEQAAACAAAAwAIAAAIgAAAfAAAAzgIAAAQgAAABAAAAHgUAAAAgAAABAAAALAUA" +
+"AAMQAAACAAAASAUAAAYgAAABAAAAVAUAAAAQAAABAAAAZAUAAA==");
+
+  public static void run() throws Exception {
+    Redefinition.setTestConfiguration(Redefinition.Config.COMMON_REDEFINE);
+    doTest();
+  }
+
+  public static Supplier<String> mkTransform() {
+    try {
+      return (Supplier<String>)
+          (new InMemoryDexClassLoader(
+                  ByteBuffer.wrap(SUB_DEX_BYTES), Test2002.class.getClassLoader())
+              .loadClass("art.SubTransform")
+              .newInstance());
+    } catch (Exception e) {
+      return () -> {
+        return e.toString();
+      };
+    }
+  }
+
+  public static void doTest() throws Exception {
+    Thread t = new Thread(() -> {
+      Supplier<String> s = mkTransform();
+      System.out.println(s.get());
+    });
+    t.start();
+    start_latch.await();
+    Redefinition.doCommonStructuralClassRedefinition(Transform.class, DEX_BYTES);
+    finish_latch.countDown();
+    t.join();
+  }
+}
diff --git a/test/2002-virtual-structural-initializing/src/Main.java b/test/2002-virtual-structural-initializing/src/Main.java
new file mode 100644
index 0000000..89b8557
--- /dev/null
+++ b/test/2002-virtual-structural-initializing/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 {
+    System.out.println("FAIL: Test is only for art!");
+  }
+}
diff --git a/test/knownfailures.json b/test/knownfailures.json
index 069cecb..8cfb3a5 100644
--- a/test/knownfailures.json
+++ b/test/knownfailures.json
@@ -1150,7 +1150,11 @@
                   "1995-final-virtual-structural-multithread",
                   "1996-final-override-virtual-structural",
                   "1997-structural-shadow-method",
-                  "1998-structural-shadow-field"
+                  "1998-structural-shadow-field",
+                  "1999-virtual-structural",
+                  "2000-virtual-list-structural",
+                  "2001-virtual-structural-multithread",
+                  "2002-virtual-structural-initializing"
                 ],
         "variant": "jvm",
         "description": ["Doesn't run on RI."]