Remove class-hierarchy restriction from structural redefinition

Previously we restricted structural redefinition such that 2 or more
classes could not be redefined at the same time if one is a superclass
of the other and the superclass was undergoing structural
redefinition. This was done to simplify the creation of new objects.
This restriction is now removed by having the superclass take over the
creation of new instances for any subclasses, even if they are
redefined too.

Test: ./test.py --host
Bug: 134162467
Change-Id: I8ae76e3b1fd17c1cf2a9993d7b82ed21a61503ef
diff --git a/openjdkjvmti/ti_extension.cc b/openjdkjvmti/ti_extension.cc
index 1301697..62c6fb2 100644
--- a/openjdkjvmti/ti_extension.cc
+++ b/openjdkjvmti/ti_extension.cc
@@ -423,7 +423,6 @@
         reinterpret_cast<jvmtiExtensionFunction>(Redefiner::StructurallyRedefineClasses),
         "com.android.art.class.structurally_redefine_classes",
         "Entrypoint for structural class redefinition. Has the same signature as RedefineClasses."
-        " 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"
diff --git a/openjdkjvmti/ti_redefine.cc b/openjdkjvmti/ti_redefine.cc
index 1e77be8..c505933 100644
--- a/openjdkjvmti/ti_redefine.cc
+++ b/openjdkjvmti/ti_redefine.cc
@@ -1188,7 +1188,10 @@
         self,
         art::GetClassRoot<art::mirror::ObjectArray<art::mirror::Object>>(runtime->GetClassLinker()),
         redefinitions->size() * kNumSlots))),
-    redefinitions_(redefinitions) {}
+    redefinitions_(redefinitions),
+    initialized_(redefinitions_->size(), false),
+    actually_structural_(redefinitions_->size(), false),
+    initial_structural_(redefinitions_->size(), false) {}
 
   bool IsNull() const REQUIRES_SHARED(art::Locks::mutator_lock_) {
     return arr_.IsNull();
@@ -1260,6 +1263,16 @@
     return art::ObjPtr<art::mirror::ObjectArray<art::mirror::Class>>::DownCast(
         GetSlot(klass_index, kSlotNewClasses));
   }
+  bool IsInitialized(jint klass_index) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    return initialized_[klass_index];
+  }
+  bool IsActuallyStructural(jint klass_index) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    return actually_structural_[klass_index];
+  }
+
+  bool IsInitialStructural(jint klass_index) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    return initial_structural_[klass_index];
+  }
 
   void SetSourceClassLoader(jint klass_index, art::ObjPtr<art::mirror::ClassLoader> loader)
       REQUIRES_SHARED(art::Locks::mutator_lock_) {
@@ -1320,6 +1333,15 @@
       REQUIRES_SHARED(art::Locks::mutator_lock_) {
     SetSlot(klass_index, kSlotNewClasses, klasses);
   }
+  void SetInitialized(jint klass_index) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    initialized_[klass_index] = true;
+  }
+  void SetActuallyStructural(jint klass_index) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    actually_structural_[klass_index] = true;
+  }
+  void SetInitialStructural(jint klass_index) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    initial_structural_[klass_index] = true;
+  }
   int32_t Length() const REQUIRES_SHARED(art::Locks::mutator_lock_) {
     return arr_->GetLength() / kNumSlots;
   }
@@ -1345,6 +1367,14 @@
  private:
   mutable art::Handle<art::mirror::ObjectArray<art::mirror::Object>> arr_;
   std::vector<Redefiner::ClassRedefinition>* redefinitions_;
+  // Used to mark a particular redefinition as fully initialized.
+  std::vector<bool> initialized_;
+  // Used to mark a redefinition as 'actually' structural. That is either the redefinition is
+  // structural or a superclass is.
+  std::vector<bool> actually_structural_;
+  // Used to mark a redefinition as the initial structural redefinition. This redefinition will take
+  // care of updating all of its subtypes.
+  std::vector<bool> initial_structural_;
 
   art::ObjPtr<art::mirror::Object> GetSlot(jint klass_index, DataSlot slot) const
       REQUIRES_SHARED(art::Locks::mutator_lock_) {
@@ -1471,6 +1501,15 @@
       REQUIRES_SHARED(art::Locks::mutator_lock_) {
     return holder_.GetNewClasses(idx_);
   }
+  bool IsInitialized() const REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    return holder_.IsInitialized(idx_);
+  }
+  bool IsActuallyStructural() const REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    return holder_.IsActuallyStructural(idx_);
+  }
+  bool IsInitialStructural() const REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    return holder_.IsInitialStructural(idx_);
+  }
   int32_t GetIndex() const {
     return idx_;
   }
@@ -1527,6 +1566,15 @@
       REQUIRES_SHARED(art::Locks::mutator_lock_) {
     holder_.SetNewClasses(idx_, klasses);
   }
+  void SetInitialized() REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    holder_.SetInitialized(idx_);
+  }
+  void SetActuallyStructural() REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    holder_.SetActuallyStructural(idx_);
+  }
+  void SetInitialStructural() REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    holder_.SetInitialStructural(idx_);
+  }
 
  private:
   int32_t idx_;
@@ -1629,113 +1677,42 @@
   return true;
 }
 
+bool CompareClasses(art::ObjPtr<art::mirror::Class> l, art::ObjPtr<art::mirror::Class> r)
+    REQUIRES_SHARED(art::Locks::mutator_lock_) {
+  auto parents = [](art::ObjPtr<art::mirror::Class> c) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    uint32_t res = 0;
+    while (!c->IsObjectClass()) {
+      res++;
+      c = c->GetSuperClass();
+    }
+    return res;
+  };
+  return parents(l.Ptr()) < parents(r.Ptr());
+}
+
 bool Redefiner::ClassRedefinition::CollectAndCreateNewInstances(
     /*out*/ RedefinitionDataIter* cur_data) {
-  if (!IsStructuralRedefinition()) {
+  if (!cur_data->IsInitialStructural()) {
+    // An earlier structural redefinition already remade all the instances.
     return true;
   }
+  art::gc::Heap* heap = driver_->runtime_->GetHeap();
   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_) {
     return obj->InstanceOf(old_klass.Get());
   };
   heap->VisitObjects([&](art::mirror::Object* obj) REQUIRES_SHARED(art::Locks::mutator_lock_) {
     if (is_instance(obj)) {
       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());
+      hs.NewHandle(cur_data->GetOldClasses()));
   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());
-
+      hs.NewHandle(cur_data->GetNewClasses()));
   art::Handle<art::mirror::Class> obj_array_class(
       hs.NewHandle(art::GetClassRoot<art::mirror::ObjectArray<art::mirror::Object>>(
           driver_->runtime_->GetClassLinker())));
@@ -1788,10 +1765,10 @@
   return true;
 }
 
-bool Redefiner::ClassRedefinition::FinishRemainingAllocations(
+bool Redefiner::ClassRedefinition::FinishRemainingCommonAllocations(
     /*out*/RedefinitionDataIter* cur_data) {
   art::ScopedObjectAccessUnchecked soa(driver_->self_);
-  art::StackHandleScope<4> hs(driver_->self_);
+  art::StackHandleScope<2> hs(driver_->self_);
   cur_data->SetMirrorClass(GetMirrorClass());
   // This shouldn't allocate
   art::Handle<art::mirror::ClassLoader> loader(hs.NewHandle(GetClassLoader()));
@@ -1829,20 +1806,151 @@
     RecordFailure(ERR(OUT_OF_MEMORY), "Unable to allocate array for original dex file");
     return false;
   }
-  if (added_fields_ || added_methods_) {
-    art::Handle<art::mirror::Class> nc(hs.NewHandle(
-        AllocateNewClassObject(hs.NewHandle(cur_data->GetNewDexCache()))));
+  return true;
+}
+
+bool Redefiner::ClassRedefinition::FinishNewClassAllocations(RedefinitionDataHolder &holder,
+                                                             RedefinitionDataIter *cur_data) {
+  if (cur_data->IsInitialized() || !cur_data->IsActuallyStructural()) {
+    cur_data->SetInitialized();
+    return true;
+  }
+
+  art::VariableSizedHandleScope hs(driver_->self_);
+  // If we weren't the lowest structural redef the superclass would have already initialized us.
+  CHECK(IsStructuralRedefinition());
+  CHECK(cur_data->IsInitialStructural()) << "Should have already been initialized by supertype";
+  auto setup_single_redefinition =
+      [this](RedefinitionDataIter* data, art::Handle<art::mirror::Class> super_class)
+          REQUIRES_SHARED(art::Locks::mutator_lock_) -> art::ObjPtr<art::mirror::Class> {
+    art::StackHandleScope<3> chs(driver_->self_);
+    art::Handle<art::mirror::Class> nc(
+        chs.NewHandle(AllocateNewClassObject(chs.NewHandle(data->GetMirrorClass()),
+                                             super_class,
+                                             chs.NewHandle(data->GetNewDexCache()),
+                                             /*dex_class_def_index*/ 0)));
     if (nc.IsNull()) {
-      return false;
+      return nullptr;
     }
 
-    cur_data->SetNewClassObject(nc.Get());
+    data->SetNewClassObject(nc.Get());
     // We really want to be able to resolve to the new class-object using this dex-cache for
     // verification work. Since we haven't put it in the class-table yet we wll just manually add it
     // to the dex-cache.
     // TODO: We should maybe do this in a better spot.
-    cur_data->GetNewDexCache()->SetResolvedType(nc->GetDexTypeIndex(), nc.Get());
+    data->GetNewDexCache()->SetResolvedType(nc->GetDexTypeIndex(), nc.Get());
+    data->SetInitialized();
+    return nc.Get();
+  };
+
+  std::vector<art::Handle<art::mirror::Class>> old_types;
+  {
+    art::gc::Heap* heap = driver_->runtime_->GetHeap();
+    art::Handle<art::mirror::Class>
+        old_klass(hs.NewHandle(cur_data->GetMirrorClass()));
+    if (setup_single_redefinition(cur_data, hs.NewHandle(old_klass->GetSuperClass())).IsNull()) {
+      return false;
+    }
+    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());
+    };
+    heap->VisitObjects([&](art::mirror::Object* obj) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+      if (is_subtype(obj)) {
+        old_types.push_back(hs.NewHandle(obj->AsClass()));
+      }
+    });
+    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");
+    // 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(),
+              [](auto& l, auto& r) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+                return CompareClasses(l.Get(), r.Get());
+              });
+  }
+  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_type = old_types[i];
+    if (old_type.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_type->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_type->PrettyClass()
+          << " expected to find " << old_type->GetSuperClass()->PrettyClass();
+      superclass.Assign(new_classes_arr->Get(std::distance(old_types.begin(), old_super)));
+      auto new_redef = std::find_if(
+          *cur_data + 1, holder.end(), [&](auto it) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+            return it.GetMirrorClass() == old_type.Get();
+          });
+      art::ObjPtr<art::mirror::Class> new_type;
+      if (new_redef == holder.end()) {
+        // We aren't also redefining this subclass. Just allocate a new class and continue.
+        dch.Assign(old_type->GetDexCache());
+        new_type =
+            AllocateNewClassObject(old_type, superclass, dch, old_type->GetDexClassDefIndex());
+      } else {
+        // This subclass is also being redefined. We need to use its new dex-file to load the new
+        // class.
+        CHECK(new_redef.IsActuallyStructural());
+        CHECK(!new_redef.IsInitialStructural());
+        new_type = setup_single_redefinition(&new_redef, superclass);
+      }
+      if (new_type == nullptr) {
+        VLOG(plugin) << "Failed to load new version of class " << old_type->PrettyClass()
+                     << " for structural redefinition!";
+        return false;
+      }
+      new_classes_arr->Set(i, new_type);
+    }
+  }
+  cur_data->SetNewClasses(new_classes_arr.Get());
   return true;
 }
 
@@ -2024,45 +2132,34 @@
       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);
   }
 }
 
+void Redefiner::MarkStructuralChanges(RedefinitionDataHolder& holder) {
+  for (RedefinitionDataIter data = holder.begin(); data != holder.end(); ++data) {
+    if (data.IsActuallyStructural()) {
+      // A superclass was structural and it marked all subclasses already. No need to do anything.
+      CHECK(!data.IsInitialStructural());
+    } else if (data.GetRedefinition().IsStructuralRedefinition()) {
+      data.SetActuallyStructural();
+      data.SetInitialStructural();
+      // Go over all potential subtypes and mark any that are actually subclasses as structural.
+      for (RedefinitionDataIter sub_data = data + 1; sub_data != holder.end(); ++sub_data) {
+        if (sub_data.GetRedefinition().GetMirrorClass()->IsSubClass(
+                data.GetRedefinition().GetMirrorClass())) {
+          sub_data.SetActuallyStructural();
+        }
+      }
+    }
+  }
+}
+
 bool Redefiner::EnsureAllClassAllocationsFinished(RedefinitionDataHolder& holder) {
   for (RedefinitionDataIter data = holder.begin(); data != holder.end(); ++data) {
     if (!data.GetRedefinition().EnsureClassAllocationsFinished(&data)) {
@@ -2082,10 +2179,20 @@
   return true;
 }
 
-bool Redefiner::FinishAllRemainingAllocations(RedefinitionDataHolder& holder) {
+bool Redefiner::FinishAllNewClassAllocations(RedefinitionDataHolder& holder) {
   for (RedefinitionDataIter data = holder.begin(); data != holder.end(); ++data) {
     // Allocate the data this redefinition requires.
-    if (!data.GetRedefinition().FinishRemainingAllocations(&data)) {
+    if (!data.GetRedefinition().FinishNewClassAllocations(holder, &data)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool Redefiner::FinishAllRemainingCommonAllocations(RedefinitionDataHolder& holder) {
+  for (RedefinitionDataIter data = holder.begin(); data != holder.end(); ++data) {
+    // Allocate the data this redefinition requires.
+    if (!data.GetRedefinition().FinishRemainingCommonAllocations(&data)) {
       return false;
     }
   }
@@ -2298,8 +2405,15 @@
 
 jvmtiError Redefiner::Run() {
   art::StackHandleScope<1> hs(self_);
-  // Allocate an array to hold onto all java temporary objects associated with this redefinition.
-  // We will let this be collected after the end of this function.
+  // Sort the redefinitions_ array topologically by class. This makes later steps easier since we
+  // know that every class precedes all of its supertypes.
+  std::sort(redefinitions_.begin(),
+            redefinitions_.end(),
+            [&](auto& l, auto& r) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+              return CompareClasses(l.GetMirrorClass(), r.GetMirrorClass());
+            });
+  // Allocate an array to hold onto all java temporary objects associated with this
+  // redefinition. We will let this be collected after the end of this function.
   RedefinitionDataHolder holder(&hs, runtime_, self_, &redefinitions_);
   if (holder.IsNull()) {
     self_->AssertPendingOOMException();
@@ -2309,18 +2423,24 @@
   }
 
   // First we just allocate the ClassExt and its fields that we need. These can be updated
-  // atomically without any issues (since we allocate the map arrays as empty) so we don't bother
-  // doing a try loop. The other allocations we need to ensure that nothing has changed in the time
-  // between allocating them and pausing all threads before we can update them so we need to do a
-  // try loop.
-  if (!CheckAllRedefinitionAreValid() ||
-      !EnsureAllClassAllocationsFinished(holder) ||
-      !FinishAllRemainingAllocations(holder) ||
+  // atomically without any issues (since we allocate the map arrays as empty).
+  if (!CheckAllRedefinitionAreValid()) {
+    return result_;
+  }
+  // Mark structural changes.
+  MarkStructuralChanges(holder);
+  // Now we pause class loading. If we are doing a structural redefinition we will need to get an
+  // accurate picture of the classes loaded and having loads in the middle would make that
+  // impossible. This only pauses class-loading if we actually have at least one structural
+  // redefinition.
+  ScopedSuspendClassLoading suspend_class_load(self_, runtime_, holder);
+  if (!EnsureAllClassAllocationsFinished(holder) ||
+      !FinishAllRemainingCommonAllocations(holder) ||
+      !FinishAllNewClassAllocations(holder) ||
       !CheckAllClassesAreVerified(holder)) {
     return result_;
   }
 
-  ScopedSuspendClassLoading suspend_class_load(self_, runtime_, holder);
   ScopedSuspendAllocations suspend_alloc(runtime_, holder);
   if (!CollectAndCreateNewInstances(holder)) {
     return result_;
@@ -2592,7 +2712,8 @@
 }
 
 void Redefiner::ClassRedefinition::UpdateClassStructurally(const RedefinitionDataIter& holder) {
-  DCHECK(IsStructuralRedefinition());
+  DCHECK(holder.IsActuallyStructural());
+  DCHECK(holder.IsInitialStructural());
   // LETS GO. We've got all new class structures so no need to do all the updating of the stacks.
   // Instead we need to update everything else.
   // Just replace the class and be done with it.
@@ -2847,9 +2968,10 @@
 
 // Performs final updates to class for redefinition.
 void Redefiner::ClassRedefinition::UpdateClass(const RedefinitionDataIter& holder) {
-  if (IsStructuralRedefinition()) {
+  CHECK(holder.IsInitialized());
+  if (holder.IsInitialStructural()) {
     UpdateClassStructurally(holder);
-  } else {
+  } else if (!holder.IsActuallyStructural()) {
     UpdateClassInPlace(holder);
   }
   UpdateClassCommon(holder);
@@ -2877,7 +2999,7 @@
 // obsolete methods).
 void Redefiner::ClassRedefinition::RestoreObsoleteMethodMapsIfUnneeded(
     const RedefinitionDataIter* cur_data) {
-  if (IsStructuralRedefinition()) {
+  if (cur_data->IsActuallyStructural()) {
     // We didn't touch these in this case.
     return;
   }
@@ -2930,7 +3052,8 @@
     RecordFailure(ERR(OUT_OF_MEMORY), "Could not allocate ClassExt");
     return false;
   }
-  if (!IsStructuralRedefinition()) {
+  if (!cur_data->IsActuallyStructural()) {
+    CHECK(!IsStructuralRedefinition());
     // First save the old values of the 2 arrays that make up the obsolete methods maps. Then
     // allocate the 2 arrays that make up the obsolete methods map. Since the contents of the arrays
     // are only modified when all threads (other than the modifying one) are suspended we don't need
diff --git a/openjdkjvmti/ti_redefine.h b/openjdkjvmti/ti_redefine.h
index 36bc9d3..e419e57 100644
--- a/openjdkjvmti/ti_redefine.h
+++ b/openjdkjvmti/ti_redefine.h
@@ -125,6 +125,17 @@
     // NO_THREAD_SAFETY_ANALYSIS so we can unlock the class in the destructor.
     ~ClassRedefinition() NO_THREAD_SAFETY_ANALYSIS;
 
+    // Move assignment so we can sort these in a vector.
+    ClassRedefinition& operator=(ClassRedefinition&& other) {
+      driver_ = other.driver_;
+      klass_ = other.klass_;
+      dex_file_ = std::move(other.dex_file_);
+      class_sig_ = std::move(other.class_sig_);
+      original_dex_file_ = other.original_dex_file_;
+      other.driver_ = nullptr;
+      return *this;
+    }
+
     // Move constructor so we can put these into a vector.
     ClassRedefinition(ClassRedefinition&& other)
         : driver_(other.driver_),
@@ -135,6 +146,10 @@
       other.driver_ = nullptr;
     }
 
+    // No copy!
+    ClassRedefinition(ClassRedefinition&) = delete;
+    ClassRedefinition& operator=(ClassRedefinition&) = delete;
+
     art::ObjPtr<art::mirror::Class> GetMirrorClass() REQUIRES_SHARED(art::Locks::mutator_lock_);
     art::ObjPtr<art::mirror::ClassLoader> GetClassLoader()
         REQUIRES_SHARED(art::Locks::mutator_lock_);
@@ -154,9 +169,12 @@
       driver_->RecordFailure(e, class_sig_, err);
     }
 
-    bool FinishRemainingAllocations(/*out*/RedefinitionDataIter* cur_data)
+    bool FinishRemainingCommonAllocations(/*out*/RedefinitionDataIter* cur_data)
         REQUIRES_SHARED(art::Locks::mutator_lock_);
 
+    bool FinishNewClassAllocations(RedefinitionDataHolder& holder,
+                                   /*out*/RedefinitionDataIter* cur_data)
+        REQUIRES_SHARED(art::Locks::mutator_lock_);
     bool CollectAndCreateNewInstances(/*out*/RedefinitionDataIter* cur_data)
         REQUIRES_SHARED(art::Locks::mutator_lock_);
 
@@ -323,12 +341,15 @@
   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_);
+  void MarkStructuralChanges(RedefinitionDataHolder& holder)
+      REQUIRES_SHARED(art::Locks::mutator_lock_);
   bool EnsureAllClassAllocationsFinished(RedefinitionDataHolder& holder)
       REQUIRES_SHARED(art::Locks::mutator_lock_);
-  bool FinishAllRemainingAllocations(RedefinitionDataHolder& holder)
+  bool FinishAllRemainingCommonAllocations(RedefinitionDataHolder& holder)
+      REQUIRES_SHARED(art::Locks::mutator_lock_);
+  bool FinishAllNewClassAllocations(RedefinitionDataHolder& holder)
       REQUIRES_SHARED(art::Locks::mutator_lock_);
   bool CollectAndCreateNewInstances(RedefinitionDataHolder& holder)
       REQUIRES_SHARED(art::Locks::mutator_lock_);
diff --git a/test/2003-double-virtual-structural/expected.txt b/test/2003-double-virtual-structural/expected.txt
new file mode 100644
index 0000000..3ace4f3
--- /dev/null
+++ b/test/2003-double-virtual-structural/expected.txt
@@ -0,0 +1,6 @@
+Hi(SubTransform called 1 times)
+Hi(SubTransform called 2 times)
+Hi(SubTransform called 3 times)
+Hello(SubTransform called 4 times, Transform called 1 times)
+Hello(SubTransform called 5 times, Transform called 2 times)
+Hello(SubTransform called 6 times, Transform called 3 times)
diff --git a/test/2003-double-virtual-structural/info.txt b/test/2003-double-virtual-structural/info.txt
new file mode 100644
index 0000000..9910b99
--- /dev/null
+++ b/test/2003-double-virtual-structural/info.txt
@@ -0,0 +1,4 @@
+Tests basic functions in the jvmti plugin.
+
+Tests that using the structural redefinition can add new virtual methods and fields that are
+accessed by a subtype undergoing non-structural redefinition simultaneously.
diff --git a/test/2003-double-virtual-structural/run b/test/2003-double-virtual-structural/run
new file mode 100755
index 0000000..b59f97c
--- /dev/null
+++ b/test/2003-double-virtual-structural/run
@@ -0,0 +1,17 @@
+#!/bin/bash
+#
+# Copyright 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+./default-run "$@" --jvmti --runtime-option -Xopaque-jni-ids:true
diff --git a/test/2003-double-virtual-structural/src/Main.java b/test/2003-double-virtual-structural/src/Main.java
new file mode 100644
index 0000000..ab0145a
--- /dev/null
+++ b/test/2003-double-virtual-structural/src/Main.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public class Main {
+  public static void main(String[] args) throws Exception {
+    art.Test2003.run();
+  }
+}
diff --git a/test/2003-double-virtual-structural/src/art/Redefinition.java b/test/2003-double-virtual-structural/src/art/Redefinition.java
new file mode 120000
index 0000000..81eaf31
--- /dev/null
+++ b/test/2003-double-virtual-structural/src/art/Redefinition.java
@@ -0,0 +1 @@
+../../../jvmti-common/Redefinition.java
\ No newline at end of file
diff --git a/test/2003-double-virtual-structural/src/art/Test2003.java b/test/2003-double-virtual-structural/src/art/Test2003.java
new file mode 100644
index 0000000..e8a10e0
--- /dev/null
+++ b/test/2003-double-virtual-structural/src/art/Test2003.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+import java.util.Base64;
+public class Test2003 {
+
+  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 SubTransform extends Transform {
+   *   private int count = 0;
+   *   public void sayHi() {
+   *     System.out.println(getGreeting() + "(SubTransform called " + (++count) + " times, Transform called " + getCount() + " times)");
+   *   }
+   * }
+   */
+  private static final byte[] SUBTRANSFORM_DEX_BYTES = Base64.getDecoder().decode(
+"ZGV4CjAzNQCh/FHVi1J2bdQclTBNUHHeAbrR2Sy2cWXoBQAAcAAAAHhWNBIAAAAAAAAAACQFAAAh" +
+"AAAAcAAAAAsAAAD0AAAABgAAACABAAACAAAAaAEAAAoAAAB4AQAAAQAAAMgBAAAABAAA6AEAAJ4C" +
+"AACnAgAAwgIAANkCAADhAgAA5AIAAOcCAADrAgAA7wIAAAwDAAAmAwAANgMAAFoDAAB6AwAAkQMA" +
+"AKUDAADAAwAA1AMAAOIDAADxAwAA9AMAAPgDAAAFBAAADQQAABQEAAAeBAAAKwQAADEEAAA2BAAA" +
+"PwQAAEYEAABQBAAAVwQAAAQAAAAIAAAACQAAAAoAAAALAAAADAAAAA0AAAAOAAAADwAAABAAAAAT" +
+"AAAABAAAAAAAAAAAAAAABQAAAAcAAAAAAAAABgAAAAgAAACQAgAABwAAAAgAAACYAgAAEwAAAAoA" +
+"AAAAAAAAFAAAAAoAAACYAgAAAQAAABcAAAAJAAYAGwAAAAEABAADAAAAAQAAABgAAAABAAEAGQAA" +
+"AAEABAAdAAAAAgAEAAMAAAAGAAUAHAAAAAgABAADAAAACAACABYAAAAIAAMAFgAAAAgAAQAeAAAA" +
+"AQAAAAEAAAACAAAAAAAAABIAAAAUBQAA9AQAAAAAAAACAAEAAQAAAIICAAAHAAAAcBAEAAEAEgBZ" +
+"EAAADgAAAAYAAQACAAAAhwIAADUAAABiAAEAbhACAAUADAFSUgAA2AICAVlSAABuEAEABQAKAyIE" +
+"CABwEAYABABuIAgAFAAaAQIAbiAIABQAbiAHACQAGgEBAG4gCAAUAG4gBwA0ABoBAABuIAgAFABu" +
+"EAkABAAMAW4gBQAQAA4ABQAOPAAIAA4BNA8AAAABAAAAAAAAAAEAAAAHAAcgdGltZXMpABkgdGlt" +
+"ZXMsIFRyYW5zZm9ybSBjYWxsZWQgABUoU3ViVHJhbnNmb3JtIGNhbGxlZCAABjxpbml0PgABSQAB" +
+"TAACTEkAAkxMABtMYXJ0L1Rlc3QyMDAzJFN1YlRyYW5zZm9ybTsAGExhcnQvVGVzdDIwMDMkVHJh" +
+"bnNmb3JtOwAOTGFydC9UZXN0MjAwMzsAIkxkYWx2aWsvYW5ub3RhdGlvbi9FbmNsb3NpbmdDbGFz" +
+"czsAHkxkYWx2aWsvYW5ub3RhdGlvbi9Jbm5lckNsYXNzOwAVTGphdmEvaW8vUHJpbnRTdHJlYW07" +
+"ABJMamF2YS9sYW5nL1N0cmluZzsAGUxqYXZhL2xhbmcvU3RyaW5nQnVpbGRlcjsAEkxqYXZhL2xh" +
+"bmcvU3lzdGVtOwAMU3ViVHJhbnNmb3JtAA1UZXN0MjAwMy5qYXZhAAFWAAJWTAALYWNjZXNzRmxh" +
+"Z3MABmFwcGVuZAAFY291bnQACGdldENvdW50AAtnZXRHcmVldGluZwAEbmFtZQADb3V0AAdwcmlu" +
+"dGxuAAVzYXlIaQAIdG9TdHJpbmcABXZhbHVlAIsBfn5EOHsiY29tcGlsYXRpb24tbW9kZSI6ImRl" +
+"YnVnIiwiaGFzLWNoZWNrc3VtcyI6ZmFsc2UsIm1pbi1hcGkiOjEsInNoYS0xIjoiODViZjE2Yzc1" +
+"NjUzZDQwNGE0YzNlZDQzNjA3Yzc3Yjg1YmFmMzFlZSIsInZlcnNpb24iOiIyLjAuNS1kZXYifQAC" +
+"BAEfGAMCBQIVBAkaFxEAAQEBAAIAgYAE6AMDAYgEAAAAAAIAAADlBAAA6wQAAAgFAAAAAAAAAAAA" +
+"AAAAAAAQAAAAAAAAAAEAAAAAAAAAAQAAACEAAABwAAAAAgAAAAsAAAD0AAAAAwAAAAYAAAAgAQAA" +
+"BAAAAAIAAABoAQAABQAAAAoAAAB4AQAABgAAAAEAAADIAQAAASAAAAIAAADoAQAAAyAAAAIAAACC" +
+"AgAAARAAAAIAAACQAgAAAiAAACEAAACeAgAABCAAAAIAAADlBAAAACAAAAEAAAD0BAAAAxAAAAIA" +
+"AAAEBQAABiAAAAEAAAAUBQAAABAAAAEAAAAkBQAA");
+
+  /**
+   * base64 encoded class/dex file for
+   * public static class Transform {
+   *   private int count;
+   *   public String getGreeting() {
+   *     incrCount();
+   *     return "Hello";
+   *   }
+   *   protected void incrCount() {
+   *     ++count;
+   *   }
+   *   protected int getCount() {
+   *     return count;
+   *   }
+   * }
+   */
+  private static final byte[] TRANSFORM_DEX_BYTES = Base64.getDecoder().decode(
+"ZGV4CjAzNQCAt16FlKvFzDaE6l56jUkorc7YXyrJmRpsBAAAcAAAAHhWNBIAAAAAAAAAALQDAAAV" +
+"AAAAcAAAAAgAAADEAAAAAwAAAOQAAAABAAAACAEAAAUAAAAQAQAAAQAAADgBAAAUAwAAWAEAANQB" +
+"AADcAQAA4wEAAOYBAADpAQAAAwIAABMCAAA3AgAAVwIAAGsCAAB/AgAAjgIAAJkCAACcAgAAqQIA" +
+"ALACAAC6AgAAxwIAANICAADYAgAA3wIAAAIAAAAEAAAABQAAAAYAAAAHAAAACAAAAAkAAAAMAAAA" +
+"AgAAAAAAAAAAAAAAAwAAAAYAAAAAAAAADAAAAAcAAAAAAAAAAQAAAA4AAAABAAIAAAAAAAEAAAAP" +
+"AAAAAQABABAAAAABAAIAEQAAAAUAAgAAAAAAAQAAAAEAAAAFAAAAAAAAAAoAAACkAwAAfAMAAAAA" +
+"AAACAAEAAAAAAMYBAAADAAAAUhAAAA8AAAACAAEAAQAAAMoBAAAGAAAAbhADAAEAGgABABEAAQAB" +
+"AAEAAADCAQAABAAAAHAQBAAAAA4AAgABAAAAAADPAQAABwAAAFIQAADYAAABWRAAAA4ACwAOABUA" +
+"DgAOAA48ABIADmkABjxpbml0PgAFSGVsbG8AAUkAAUwAGExhcnQvVGVzdDIwMDMkVHJhbnNmb3Jt" +
+"OwAOTGFydC9UZXN0MjAwMzsAIkxkYWx2aWsvYW5ub3RhdGlvbi9FbmNsb3NpbmdDbGFzczsAHkxk" +
+"YWx2aWsvYW5ub3RhdGlvbi9Jbm5lckNsYXNzOwASTGphdmEvbGFuZy9PYmplY3Q7ABJMamF2YS9s" +
+"YW5nL1N0cmluZzsADVRlc3QyMDAzLmphdmEACVRyYW5zZm9ybQABVgALYWNjZXNzRmxhZ3MABWNv" +
+"dW50AAhnZXRDb3VudAALZ2V0R3JlZXRpbmcACWluY3JDb3VudAAEbmFtZQAFdmFsdWUAiwF+fkQ4" +
+"eyJjb21waWxhdGlvbi1tb2RlIjoiZGVidWciLCJoYXMtY2hlY2tzdW1zIjpmYWxzZSwibWluLWFw" +
+"aSI6MSwic2hhLTEiOiI4NWJmMTZjNzU2NTNkNDA0YTRjM2VkNDM2MDdjNzdiODViYWYzMWVlIiwi" +
+"dmVyc2lvbiI6IjIuMC41LWRldiJ9AAIDARMYAgIEAg0ECRIXCwABAQMAAgCBgASMAwEE2AIBAfAC" +
+"AQSkAwAAAAACAAAAbQMAAHMDAACYAwAAAAAAAAAAAAAAAAAADwAAAAAAAAABAAAAAAAAAAEAAAAV" +
+"AAAAcAAAAAIAAAAIAAAAxAAAAAMAAAADAAAA5AAAAAQAAAABAAAACAEAAAUAAAAFAAAAEAEAAAYA" +
+"AAABAAAAOAEAAAEgAAAEAAAAWAEAAAMgAAAEAAAAwgEAAAIgAAAVAAAA1AEAAAQgAAACAAAAbQMA" +
+"AAAgAAABAAAAfAMAAAMQAAACAAAAlAMAAAYgAAABAAAApAMAAAAQAAABAAAAtAMAAA==");
+
+
+  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.doMultiStructuralClassRedefinition(
+        new Redefinition.CommonClassDefinition(SubTransform.class, null, SUBTRANSFORM_DEX_BYTES),
+        new Redefinition.CommonClassDefinition(Transform.class, null, TRANSFORM_DEX_BYTES));
+    t.sayHi();
+    t.sayHi();
+    t.sayHi();
+  }
+}
diff --git a/test/2004-double-virtual-structural-abstract/expected.txt b/test/2004-double-virtual-structural-abstract/expected.txt
new file mode 100644
index 0000000..c705270
--- /dev/null
+++ b/test/2004-double-virtual-structural-abstract/expected.txt
@@ -0,0 +1,2 @@
+Hi
+Hello Alex
diff --git a/test/2004-double-virtual-structural-abstract/info.txt b/test/2004-double-virtual-structural-abstract/info.txt
new file mode 100644
index 0000000..9910b99
--- /dev/null
+++ b/test/2004-double-virtual-structural-abstract/info.txt
@@ -0,0 +1,4 @@
+Tests basic functions in the jvmti plugin.
+
+Tests that using the structural redefinition can add new virtual methods and fields that are
+accessed by a subtype undergoing non-structural redefinition simultaneously.
diff --git a/test/2004-double-virtual-structural-abstract/run b/test/2004-double-virtual-structural-abstract/run
new file mode 100755
index 0000000..b59f97c
--- /dev/null
+++ b/test/2004-double-virtual-structural-abstract/run
@@ -0,0 +1,17 @@
+#!/bin/bash
+#
+# Copyright 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+./default-run "$@" --jvmti --runtime-option -Xopaque-jni-ids:true
diff --git a/test/2004-double-virtual-structural-abstract/src/Main.java b/test/2004-double-virtual-structural-abstract/src/Main.java
new file mode 100644
index 0000000..592a7ba
--- /dev/null
+++ b/test/2004-double-virtual-structural-abstract/src/Main.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public class Main {
+  public static void main(String[] args) throws Exception {
+    art.Test2004.run();
+  }
+}
diff --git a/test/2004-double-virtual-structural-abstract/src/art/Redefinition.java b/test/2004-double-virtual-structural-abstract/src/art/Redefinition.java
new file mode 120000
index 0000000..81eaf31
--- /dev/null
+++ b/test/2004-double-virtual-structural-abstract/src/art/Redefinition.java
@@ -0,0 +1 @@
+../../../jvmti-common/Redefinition.java
\ No newline at end of file
diff --git a/test/2004-double-virtual-structural-abstract/src/art/Test2004.java b/test/2004-double-virtual-structural-abstract/src/art/Test2004.java
new file mode 100644
index 0000000..d4a8c03
--- /dev/null
+++ b/test/2004-double-virtual-structural-abstract/src/art/Test2004.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+import java.util.Base64;
+public class Test2004 {
+
+  public static abstract class Transform {
+    public String getGreeting() {
+      return "Hi";
+    }
+  }
+
+  public static class SubTransform extends Transform {
+    public void sayHi() {
+      System.out.println(getGreeting());
+    }
+  }
+  /**
+   * base64 encoded class/dex file for
+   * public static class SubTransform extends Transform {
+   *   private int count = 0;
+   *   public void sayHi() {
+   *     System.out.println(getGreeting());
+   *   }
+   *   public string getName() {
+   *     return "Alex";
+   *   }
+   * }
+   */
+  private static final byte[] SUBTRANSFORM_DEX_BYTES = Base64.getDecoder().decode(
+"ZGV4CjAzNQA5zD9gOMLav7UxQjoS9LNQj2bnqGOL4VrcBAAAcAAAAHhWNBIAAAAAAAAAABgEAAAa" +
+"AAAAcAAAAAoAAADYAAAAAwAAAAABAAACAAAAJAEAAAYAAAA0AQAAAQAAAGQBAABYAwAAhAEAAPYB" +
+"AAD+AQAABAIAAAcCAAAKAgAAJwIAAEECAABRAgAAdQIAAJUCAACsAgAAwAIAANQCAADiAgAA8QIA" +
+"APQCAAD4AgAABQMAAAwDAAAZAwAAIgMAACgDAAAtAwAANgMAAD0DAABEAwAAAgAAAAQAAAAFAAAA" +
+"BgAAAAcAAAAIAAAACQAAAAoAAAALAAAADgAAAAMAAAAHAAAAAAAAAA4AAAAJAAAAAAAAAA8AAAAJ" +
+"AAAA8AEAAAEAAAARAAAACAAGABUAAAABAAEAAAAAAAEAAAASAAAAAQAAABMAAAABAAEAFwAAAAIA" +
+"AQAAAAAABgACABYAAAABAAAAAQAAAAIAAAAAAAAADQAAAAgEAADhAwAAAAAAAAIAAQAAAAAA5QEA" +
+"AAMAAAAaAAEAEQAAAAIAAQABAAAA4AEAAAcAAABwEAQAAQASAFkQAAAOAAAAAwABAAIAAADpAQAA" +
+"CgAAAGIAAQBuEAEAAgAMAW4gBQAQAA4ACgAOPAAQAA4ADQAOlgAAAAEAAAAHAAY8aW5pdD4ABEFs" +
+"ZXgAAUkAAUwAG0xhcnQvVGVzdDIwMDQkU3ViVHJhbnNmb3JtOwAYTGFydC9UZXN0MjAwNCRUcmFu" +
+"c2Zvcm07AA5MYXJ0L1Rlc3QyMDA0OwAiTGRhbHZpay9hbm5vdGF0aW9uL0VuY2xvc2luZ0NsYXNz" +
+"OwAeTGRhbHZpay9hbm5vdGF0aW9uL0lubmVyQ2xhc3M7ABVMamF2YS9pby9QcmludFN0cmVhbTsA" +
+"EkxqYXZhL2xhbmcvU3RyaW5nOwASTGphdmEvbGFuZy9TeXN0ZW07AAxTdWJUcmFuc2Zvcm0ADVRl" +
+"c3QyMDA0LmphdmEAAVYAAlZMAAthY2Nlc3NGbGFncwAFY291bnQAC2dldEdyZWV0aW5nAAdnZXRO" +
+"YW1lAARuYW1lAANvdXQAB3ByaW50bG4ABXNheUhpAAV2YWx1ZQCLAX5+RDh7ImNvbXBpbGF0aW9u" +
+"LW1vZGUiOiJkZWJ1ZyIsImhhcy1jaGVja3N1bXMiOmZhbHNlLCJtaW4tYXBpIjoxLCJzaGEtMSI6" +
+"Ijg1YmYxNmM3NTY1M2Q0MDRhNGMzZWQ0MzYwN2M3N2I4NWJhZjMxZWUiLCJ2ZXJzaW9uIjoiMi4w" +
+"LjUtZGV2In0AAgQBGBgDAgUCEAQJFBcMAAEBAgACAIGABJwDAgGEAwEBvAMAAAAAAAAAAgAAANID" +
+"AADYAwAA/AMAAAAAAAAAAAAAAAAAABAAAAAAAAAAAQAAAAAAAAABAAAAGgAAAHAAAAACAAAACgAA" +
+"ANgAAAADAAAAAwAAAAABAAAEAAAAAgAAACQBAAAFAAAABgAAADQBAAAGAAAAAQAAAGQBAAABIAAA" +
+"AwAAAIQBAAADIAAAAwAAAOABAAABEAAAAQAAAPABAAACIAAAGgAAAPYBAAAEIAAAAgAAANIDAAAA" +
+"IAAAAQAAAOEDAAADEAAAAgAAAPgDAAAGIAAAAQAAAAgEAAAAEAAAAQAAABgEAAA=");
+
+  /**
+   * base64 encoded class/dex file for
+   * public static abstract class Transform {
+   *   public String getGreeting() {
+   *     return "Hello " + getName();
+   *   }
+   *   public abstract string getName();
+   * }
+   */
+  private static final byte[] TRANSFORM_DEX_BYTES = Base64.getDecoder().decode(
+"ZGV4CjAzNQDtwEbrWZHwf9ALLXnPJ2zRU6kQs/yHTCJ4BAAAcAAAAHhWNBIAAAAAAAAAAMADAAAW" +
+"AAAAcAAAAAgAAADIAAAAAwAAAOgAAAAAAAAAAAAAAAcAAAAMAQAAAQAAAEQBAAAUAwAAZAEAAMYB" +
+"AADOAQAA1gEAANkBAADdAQAA9wEAAAcCAAArAgAASwIAAF8CAABzAgAAjgIAAJ0CAACoAgAAqwIA" +
+"ALgCAADAAgAAzQIAANYCAADcAgAA5gIAAO0CAAAEAAAABQAAAAYAAAAHAAAACAAAAAkAAAAKAAAA" +
+"DQAAAAIAAAAFAAAAAAAAAAMAAAAGAAAAwAEAAA0AAAAHAAAAAAAAAAAAAgAAAAAAAAAAABAAAAAA" +
+"AAAAEQAAAAQAAgAAAAAABgACAAAAAAAGAAEADwAAAAYAAAATAAAAAAAAAAEEAAAEAAAAAAAAAAsA" +
+"AACwAwAAiwMAAAAAAAAEAAEAAgAAALwBAAAWAAAAbhACAAMADAAiAQYAcBAEAAEAGgIBAG4gBQAh" +
+"AG4gBQABAG4QBgABAAwAEQABAAEAAQAAALgBAAAEAAAAcBADAAAADgAEAA4ABgAOAAEAAAAFAAY8" +
+"aW5pdD4ABkhlbGxvIAABTAACTEwAGExhcnQvVGVzdDIwMDQkVHJhbnNmb3JtOwAOTGFydC9UZXN0" +
+"MjAwNDsAIkxkYWx2aWsvYW5ub3RhdGlvbi9FbmNsb3NpbmdDbGFzczsAHkxkYWx2aWsvYW5ub3Rh" +
+"dGlvbi9Jbm5lckNsYXNzOwASTGphdmEvbGFuZy9PYmplY3Q7ABJMamF2YS9sYW5nL1N0cmluZzsA" +
+"GUxqYXZhL2xhbmcvU3RyaW5nQnVpbGRlcjsADVRlc3QyMDA0LmphdmEACVRyYW5zZm9ybQABVgAL" +
+"YWNjZXNzRmxhZ3MABmFwcGVuZAALZ2V0R3JlZXRpbmcAB2dldE5hbWUABG5hbWUACHRvU3RyaW5n" +
+"AAV2YWx1ZQCLAX5+RDh7ImNvbXBpbGF0aW9uLW1vZGUiOiJkZWJ1ZyIsImhhcy1jaGVja3N1bXMi" +
+"OmZhbHNlLCJtaW4tYXBpIjoxLCJzaGEtMSI6Ijg1YmYxNmM3NTY1M2Q0MDRhNGMzZWQ0MzYwN2M3" +
+"N2I4NWJhZjMxZWUiLCJ2ZXJzaW9uIjoiMi4wLjUtZGV2In0AAgIBFBgBAgMCDiQJBBIXDAAAAQIA" +
+"gYAEoAMBAeQCAYEIAAAAAAAAAAACAAAAewMAAIEDAACkAwAAAAAAAAAAAAAAAAAADwAAAAAAAAAB" +
+"AAAAAAAAAAEAAAAWAAAAcAAAAAIAAAAIAAAAyAAAAAMAAAADAAAA6AAAAAUAAAAHAAAADAEAAAYA" +
+"AAABAAAARAEAAAEgAAACAAAAZAEAAAMgAAACAAAAuAEAAAEQAAABAAAAwAEAAAIgAAAWAAAAxgEA" +
+"AAQgAAACAAAAewMAAAAgAAABAAAAiwMAAAMQAAACAAAAoAMAAAYgAAABAAAAsAMAAAAQAAABAAAA" +
+"wAMAAA==");
+
+
+  public static void run() {
+    Redefinition.setTestConfiguration(Redefinition.Config.COMMON_REDEFINE);
+    doTest(new SubTransform());
+  }
+
+  public static void doTest(SubTransform t) {
+    t.sayHi();
+    Redefinition.doMultiStructuralClassRedefinition(
+        new Redefinition.CommonClassDefinition(SubTransform.class, null, SUBTRANSFORM_DEX_BYTES),
+        new Redefinition.CommonClassDefinition(Transform.class, null, TRANSFORM_DEX_BYTES));
+    t.sayHi();
+  }
+}