Add more standard structural redefinition entrypoints

Add structural redefinition extension function and event that mirror
the 'RedefineClasses' function and 'ClassFileLoadHook' event. The new
extension function is called
'com.android.art.class.structurally_redefine_classes' and the new
extension event is called
'com.android.art.class.structural_dex_file_load_hook'.

These extensions are the preferred way to use structural redefinition.
Like the standard 'RedefineClasses' multiple classes may be redefined
at a time.

The structural_dex_file_load_hook is triggered prior to the
can_retransform_classes ClassFileLoadHook. It is triggered on all
classes, even ones that cannot be structurally changed by
class-loading, class redefinition or by calling the RetransformClasses
function.

Calling 'structurally_redefine_classes' with new definitions that do
not require structural changes will fall back to non-structural
redefinition.

Test: ./test.py --host
Bug: 134162467
Change-Id: If4810930470c5c6509cf6db779910006e114b39f
diff --git a/openjdkjvmti/events-inl.h b/openjdkjvmti/events-inl.h
index 22822f8..23f7151 100644
--- a/openjdkjvmti/events-inl.h
+++ b/openjdkjvmti/events-inl.h
@@ -90,41 +90,42 @@
 
 // Infrastructure to achieve type safety for event dispatch.
 
-#define FORALL_EVENT_TYPES(fn)                                                       \
-  fn(VMInit,                  ArtJvmtiEvent::kVmInit)                                \
-  fn(VMDeath,                 ArtJvmtiEvent::kVmDeath)                               \
-  fn(ThreadStart,             ArtJvmtiEvent::kThreadStart)                           \
-  fn(ThreadEnd,               ArtJvmtiEvent::kThreadEnd)                             \
-  fn(ClassFileLoadHook,       ArtJvmtiEvent::kClassFileLoadHookRetransformable)      \
-  fn(ClassFileLoadHook,       ArtJvmtiEvent::kClassFileLoadHookNonRetransformable)   \
-  fn(ClassLoad,               ArtJvmtiEvent::kClassLoad)                             \
-  fn(ClassPrepare,            ArtJvmtiEvent::kClassPrepare)                          \
-  fn(VMStart,                 ArtJvmtiEvent::kVmStart)                               \
-  fn(Exception,               ArtJvmtiEvent::kException)                             \
-  fn(ExceptionCatch,          ArtJvmtiEvent::kExceptionCatch)                        \
-  fn(SingleStep,              ArtJvmtiEvent::kSingleStep)                            \
-  fn(FramePop,                ArtJvmtiEvent::kFramePop)                              \
-  fn(Breakpoint,              ArtJvmtiEvent::kBreakpoint)                            \
-  fn(FieldAccess,             ArtJvmtiEvent::kFieldAccess)                           \
-  fn(FieldModification,       ArtJvmtiEvent::kFieldModification)                     \
-  fn(MethodEntry,             ArtJvmtiEvent::kMethodEntry)                           \
-  fn(MethodExit,              ArtJvmtiEvent::kMethodExit)                            \
-  fn(NativeMethodBind,        ArtJvmtiEvent::kNativeMethodBind)                      \
-  fn(CompiledMethodLoad,      ArtJvmtiEvent::kCompiledMethodLoad)                    \
-  fn(CompiledMethodUnload,    ArtJvmtiEvent::kCompiledMethodUnload)                  \
-  fn(DynamicCodeGenerated,    ArtJvmtiEvent::kDynamicCodeGenerated)                  \
-  fn(DataDumpRequest,         ArtJvmtiEvent::kDataDumpRequest)                       \
-  fn(MonitorWait,             ArtJvmtiEvent::kMonitorWait)                           \
-  fn(MonitorWaited,           ArtJvmtiEvent::kMonitorWaited)                         \
-  fn(MonitorContendedEnter,   ArtJvmtiEvent::kMonitorContendedEnter)                 \
-  fn(MonitorContendedEntered, ArtJvmtiEvent::kMonitorContendedEntered)               \
-  fn(ResourceExhausted,       ArtJvmtiEvent::kResourceExhausted)                     \
-  fn(GarbageCollectionStart,  ArtJvmtiEvent::kGarbageCollectionStart)                \
-  fn(GarbageCollectionFinish, ArtJvmtiEvent::kGarbageCollectionFinish)               \
-  fn(ObjectFree,              ArtJvmtiEvent::kObjectFree)                            \
-  fn(VMObjectAlloc,           ArtJvmtiEvent::kVmObjectAlloc)                         \
-  fn(DdmPublishChunk,         ArtJvmtiEvent::kDdmPublishChunk)                       \
-  fn(ObsoleteObjectCreated,   ArtJvmtiEvent::kObsoleteObjectCreated)
+#define FORALL_EVENT_TYPES(fn)                                                         \
+  fn(VMInit,                    ArtJvmtiEvent::kVmInit)                                \
+  fn(VMDeath,                   ArtJvmtiEvent::kVmDeath)                               \
+  fn(ThreadStart,               ArtJvmtiEvent::kThreadStart)                           \
+  fn(ThreadEnd,                 ArtJvmtiEvent::kThreadEnd)                             \
+  fn(ClassFileLoadHook,         ArtJvmtiEvent::kClassFileLoadHookRetransformable)      \
+  fn(ClassFileLoadHook,         ArtJvmtiEvent::kClassFileLoadHookNonRetransformable)   \
+  fn(ClassLoad,                 ArtJvmtiEvent::kClassLoad)                             \
+  fn(ClassPrepare,              ArtJvmtiEvent::kClassPrepare)                          \
+  fn(VMStart,                   ArtJvmtiEvent::kVmStart)                               \
+  fn(Exception,                 ArtJvmtiEvent::kException)                             \
+  fn(ExceptionCatch,            ArtJvmtiEvent::kExceptionCatch)                        \
+  fn(SingleStep,                ArtJvmtiEvent::kSingleStep)                            \
+  fn(FramePop,                  ArtJvmtiEvent::kFramePop)                              \
+  fn(Breakpoint,                ArtJvmtiEvent::kBreakpoint)                            \
+  fn(FieldAccess,               ArtJvmtiEvent::kFieldAccess)                           \
+  fn(FieldModification,         ArtJvmtiEvent::kFieldModification)                     \
+  fn(MethodEntry,               ArtJvmtiEvent::kMethodEntry)                           \
+  fn(MethodExit,                ArtJvmtiEvent::kMethodExit)                            \
+  fn(NativeMethodBind,          ArtJvmtiEvent::kNativeMethodBind)                      \
+  fn(CompiledMethodLoad,        ArtJvmtiEvent::kCompiledMethodLoad)                    \
+  fn(CompiledMethodUnload,      ArtJvmtiEvent::kCompiledMethodUnload)                  \
+  fn(DynamicCodeGenerated,      ArtJvmtiEvent::kDynamicCodeGenerated)                  \
+  fn(DataDumpRequest,           ArtJvmtiEvent::kDataDumpRequest)                       \
+  fn(MonitorWait,               ArtJvmtiEvent::kMonitorWait)                           \
+  fn(MonitorWaited,             ArtJvmtiEvent::kMonitorWaited)                         \
+  fn(MonitorContendedEnter,     ArtJvmtiEvent::kMonitorContendedEnter)                 \
+  fn(MonitorContendedEntered,   ArtJvmtiEvent::kMonitorContendedEntered)               \
+  fn(ResourceExhausted,         ArtJvmtiEvent::kResourceExhausted)                     \
+  fn(GarbageCollectionStart,    ArtJvmtiEvent::kGarbageCollectionStart)                \
+  fn(GarbageCollectionFinish,   ArtJvmtiEvent::kGarbageCollectionFinish)               \
+  fn(ObjectFree,                ArtJvmtiEvent::kObjectFree)                            \
+  fn(VMObjectAlloc,             ArtJvmtiEvent::kVmObjectAlloc)                         \
+  fn(DdmPublishChunk,           ArtJvmtiEvent::kDdmPublishChunk)                       \
+  fn(ObsoleteObjectCreated,     ArtJvmtiEvent::kObsoleteObjectCreated)                 \
+  fn(StructuralDexFileLoadHook, ArtJvmtiEvent::kStructuralDexFileLoadHook)
 
 template <ArtJvmtiEvent kEvent>
 struct EventFnType {
@@ -217,7 +218,8 @@
                                                          unsigned char** new_class_data) const {
   art::ScopedThreadStateChange stsc(thread, art::ThreadState::kNative);
   static_assert(kEvent == ArtJvmtiEvent::kClassFileLoadHookRetransformable ||
-                kEvent == ArtJvmtiEvent::kClassFileLoadHookNonRetransformable, "Unsupported event");
+                kEvent == ArtJvmtiEvent::kClassFileLoadHookNonRetransformable ||
+                kEvent == ArtJvmtiEvent::kStructuralDexFileLoadHook, "Unsupported event");
   DCHECK(*new_class_data == nullptr);
   jint current_len = class_data_len;
   unsigned char* current_class_data = const_cast<unsigned char*>(class_data);
@@ -588,6 +590,31 @@
       new_class_data);
 }
 
+template <>
+inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kStructuralDexFileLoadHook>(
+    art::Thread* thread,
+    JNIEnv* jnienv,
+    jclass class_being_redefined,
+    jobject loader,
+    const char* name,
+    jobject protection_domain,
+    jint class_data_len,
+    const unsigned char* class_data,
+    jint* new_class_data_len,
+    unsigned char** new_class_data) const {
+  return DispatchClassFileLoadHookEvent<ArtJvmtiEvent::kStructuralDexFileLoadHook>(
+      thread,
+      jnienv,
+      class_being_redefined,
+      loader,
+      name,
+      protection_domain,
+      class_data_len,
+      class_data,
+      new_class_data_len,
+      new_class_data);
+}
+
 template <ArtJvmtiEvent kEvent>
 inline bool EventHandler::ShouldDispatchOnThread(ArtJvmTiEnv* env, art::Thread* thread) const {
   bool dispatch = env->event_masks.global_event_mask.Test(kEvent);
diff --git a/openjdkjvmti/events.cc b/openjdkjvmti/events.cc
index 3f205eb..56406fc 100644
--- a/openjdkjvmti/events.cc
+++ b/openjdkjvmti/events.cc
@@ -100,6 +100,9 @@
     case static_cast<jint>(ArtJvmtiEvent::kDdmPublishChunk):
       DdmPublishChunk = reinterpret_cast<ArtJvmtiEventDdmPublishChunk>(cb);
       return OK;
+    case static_cast<jint>(ArtJvmtiEvent::kStructuralDexFileLoadHook):
+      StructuralDexFileLoadHook = reinterpret_cast<ArtJvmtiEventStructuralDexFileLoadHook>(cb);
+      return OK;
     default:
       return ERR(ILLEGAL_ARGUMENT);
   }
@@ -116,6 +119,7 @@
   switch (e) {
     case ArtJvmtiEvent::kDdmPublishChunk:
     case ArtJvmtiEvent::kObsoleteObjectCreated:
+    case ArtJvmtiEvent::kStructuralDexFileLoadHook:
       return true;
     default:
       return false;
@@ -1175,6 +1179,7 @@
     case ArtJvmtiEvent::kClassFileLoadHookRetransformable:
     case ArtJvmtiEvent::kDdmPublishChunk:
     case ArtJvmtiEvent::kObsoleteObjectCreated:
+    case ArtJvmtiEvent::kStructuralDexFileLoadHook:
       return DeoptRequirement::kNone;
   }
 }
diff --git a/openjdkjvmti/events.h b/openjdkjvmti/events.h
index d5ab4fb..c9d587a 100644
--- a/openjdkjvmti/events.h
+++ b/openjdkjvmti/events.h
@@ -81,7 +81,8 @@
     kClassFileLoadHookRetransformable = JVMTI_MAX_EVENT_TYPE_VAL + 1,
     kDdmPublishChunk = JVMTI_MAX_EVENT_TYPE_VAL + 2,
     kObsoleteObjectCreated = JVMTI_MAX_EVENT_TYPE_VAL + 3,
-    kMaxNormalEventTypeVal = kObsoleteObjectCreated,
+    kStructuralDexFileLoadHook = JVMTI_MAX_EVENT_TYPE_VAL + 4,
+    kMaxNormalEventTypeVal = kStructuralDexFileLoadHook,
 
     // All that follow are events used to implement internal JVMTI functions. They are not settable
     // directly by agents.
@@ -107,6 +108,17 @@
                                                     jlong* obsolete_tag,
                                                     jlong* new_tag);
 
+using ArtJvmtiEventStructuralDexFileLoadHook = void (*)(jvmtiEnv *jvmti_env,
+                                                        JNIEnv* jni_env,
+                                                        jclass class_being_redefined,
+                                                        jobject loader,
+                                                        const char* name,
+                                                        jobject protection_domain,
+                                                        jint dex_data_len,
+                                                        const unsigned char* dex_data,
+                                                        jint* new_dex_data_len,
+                                                        unsigned char** new_dex_data);
+
 // It is not enough to store a Thread pointer, as these may be reused. Use the pointer and the
 // thread id.
 // Note: We could just use the tid like tracing does.
@@ -119,7 +131,10 @@
 };
 
 struct ArtJvmtiEventCallbacks : jvmtiEventCallbacks {
-  ArtJvmtiEventCallbacks() : DdmPublishChunk(nullptr), ObsoleteObjectCreated(nullptr) {
+  ArtJvmtiEventCallbacks()
+      : DdmPublishChunk(nullptr),
+        ObsoleteObjectCreated(nullptr),
+        StructuralDexFileLoadHook(nullptr) {
     memset(this, 0, sizeof(jvmtiEventCallbacks));
   }
 
@@ -131,6 +146,7 @@
 
   ArtJvmtiEventDdmPublishChunk DdmPublishChunk;
   ArtJvmtiEventObsoleteObjectCreated ObsoleteObjectCreated;
+  ArtJvmtiEventStructuralDexFileLoadHook StructuralDexFileLoadHook;
 };
 
 bool IsExtensionEvent(jint e);
diff --git a/openjdkjvmti/ti_class.cc b/openjdkjvmti/ti_class.cc
index 988274b..82ce916 100644
--- a/openjdkjvmti/ti_class.cc
+++ b/openjdkjvmti/ti_class.cc
@@ -204,6 +204,9 @@
       memcpy(post_non_retransform.data(), def.GetDexData().data(), post_non_retransform.size());
     }
 
+    // Call all structural transformation agents.
+    Transformer::TransformSingleClassDirect<ArtJvmtiEvent::kStructuralDexFileLoadHook>(
+        event_handler, self, &def);
     // Call all retransformable agents.
     Transformer::TransformSingleClassDirect<ArtJvmtiEvent::kClassFileLoadHookRetransformable>(
         event_handler, self, &def);
diff --git a/openjdkjvmti/ti_class_definition.h b/openjdkjvmti/ti_class_definition.h
index 224e664..cb0853b 100644
--- a/openjdkjvmti/ti_class_definition.h
+++ b/openjdkjvmti/ti_class_definition.h
@@ -40,6 +40,7 @@
 
 #include "base/array_ref.h"
 #include "base/mem_map.h"
+#include "events.h"
 
 namespace openjdkjvmti {
 
@@ -65,7 +66,8 @@
         current_dex_file_(),
         redefined_(false),
         from_class_ext_(false),
-        initialized_(false) {}
+        initialized_(false),
+        structural_transform_update_(false) {}
 
   void InitFirstLoad(const char* descriptor,
                      art::Handle<art::mirror::ClassLoader> klass_loader,
@@ -76,7 +78,7 @@
   ArtClassDefinition(ArtClassDefinition&& o) = default;
   ArtClassDefinition& operator=(ArtClassDefinition&& o) = default;
 
-  void SetNewDexData(jint new_dex_len, unsigned char* new_dex_data) {
+  void SetNewDexData(jint new_dex_len, unsigned char* new_dex_data, ArtJvmtiEvent event) {
     DCHECK(IsInitialized());
     if (new_dex_data == nullptr) {
       return;
@@ -86,10 +88,17 @@
         dex_data_memory_.resize(new_dex_len);
         memcpy(dex_data_memory_.data(), new_dex_data, new_dex_len);
         dex_data_ = art::ArrayRef<const unsigned char>(dex_data_memory_);
+        if (event == ArtJvmtiEvent::kStructuralDexFileLoadHook) {
+          structural_transform_update_ = true;
+        }
       }
     }
   }
 
+  bool HasStructuralChanges() const {
+    return structural_transform_update_;
+  }
+
   art::ArrayRef<const unsigned char> GetNewOriginalDexFile() const {
     DCHECK(IsInitialized());
     if (redefined_) {
@@ -187,6 +196,9 @@
 
   bool initialized_;
 
+  // Set if we had a new dex from the given transform type.
+  bool structural_transform_update_;
+
   DISALLOW_COPY_AND_ASSIGN(ArtClassDefinition);
 };
 
diff --git a/openjdkjvmti/ti_extension.cc b/openjdkjvmti/ti_extension.cc
index 5dc7445..058a188 100644
--- a/openjdkjvmti/ti_extension.cc
+++ b/openjdkjvmti/ti_extension.cc
@@ -30,6 +30,7 @@
 
 #include <vector>
 
+#include "jvmti.h"
 #include "ti_extension.h"
 
 #include "art_jvmti.h"
@@ -45,6 +46,7 @@
 #include "ti_monitor.h"
 #include "ti_redefine.h"
 #include "ti_search.h"
+#include "transform.h"
 
 #include "thread-inl.h"
 
@@ -416,7 +418,40 @@
       return error;
     }
 
-    // StructurallyRedefineClass
+    // StructurallyRedefineClasses
+    error = add_extension(
+        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.",
+        {
+          { "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),
+        });
+    if (error != ERR(NONE)) {
+      return error;
+    }
+
+    // StructurallyRedefineClassDirect
     error = add_extension(
         reinterpret_cast<jvmtiExtensionFunction>(Redefiner::StructurallyRedefineClassDirect),
         "com.android.art.UNSAFE.class.structurally_redefine_class_direct",
@@ -494,7 +529,7 @@
                            const char* id,
                            const char* short_description,
                            const std::vector<CParamInfo>& params) {
-    DCHECK(IsExtensionEvent(extension_event_index));
+    DCHECK(IsExtensionEvent(extension_event_index)) << static_cast<jint>(extension_event_index);
     jvmtiExtensionEventInfo event_info;
     jvmtiError error;
 
@@ -592,7 +627,35 @@
   if (error != OK) {
     return error;
   }
-
+  art::Runtime* runtime = art::Runtime::Current();
+  if (runtime->GetJniIdType() == art::JniIdType::kIndices &&
+      (runtime->GetInstrumentation()->IsForcedInterpretOnly() || runtime->IsJavaDebuggable())) {
+    error = add_extension(
+        ArtJvmtiEvent::kStructuralDexFileLoadHook,
+        "com.android.art.class.structural_dex_file_load_hook",
+        "Called during class load, after a 'RetransformClasses' call, or after a 'RedefineClasses'"
+        " call in order to allow the agent to modify the class. This event is called after any"
+        " non-can_retransform_classes ClassFileLoadHookEvents and before any"
+        " can_retransform_classes ClassFileLoadHookEvents. The transformations applied are"
+        " restricted in the same way that transformations applied via the "
+        " 'com.android.art.class.structurally_redefine_classes' extension function. The arguments"
+        " to the event are identical to the ones in the ClassFileLoadHook and have the same"
+        " semantics.",
+        {
+          { "jni_env", JVMTI_KIND_IN, JVMTI_TYPE_JNIENV, false },
+          { "class_being_redefined", JVMTI_KIND_IN, JVMTI_TYPE_JCLASS, true },
+          { "loader", JVMTI_KIND_IN, JVMTI_TYPE_JOBJECT, false },
+          { "name", JVMTI_KIND_IN_PTR, JVMTI_TYPE_CCHAR, false },
+          { "protection_domain", JVMTI_KIND_IN, JVMTI_TYPE_JOBJECT, true },
+          { "dex_data_len", JVMTI_KIND_IN, JVMTI_TYPE_JINT, false },
+          { "dex_data", JVMTI_KIND_IN_BUF, JVMTI_TYPE_CCHAR, false },
+          { "new_dex_data_len", JVMTI_KIND_OUT, JVMTI_TYPE_JINT, false },
+          { "new_dex_data", JVMTI_KIND_ALLOC_BUF, JVMTI_TYPE_CCHAR, true },
+        });
+  } else {
+    LOG(INFO) << "debuggable & jni-type indices are required to implement structural "
+              << "class redefinition extensions.";
+  }
   // Copy into output buffer.
 
   *extension_count_ptr = ext_vector.size();
diff --git a/openjdkjvmti/ti_redefine.cc b/openjdkjvmti/ti_redefine.cc
index 05d7de7..22a3bc5 100644
--- a/openjdkjvmti/ti_redefine.cc
+++ b/openjdkjvmti/ti_redefine.cc
@@ -376,7 +376,7 @@
     return ERR(INVALID_CLASS);
   }
   art::Handle<art::mirror::Class> h_klass(hs.NewHandle(obj->AsClass()));
-  return Redefiner::GetClassRedefinitionError(h_klass, error_msg);
+  return Redefiner::GetClassRedefinitionError<kType>(h_klass, error_msg);
 }
 
 template <RedefinitionType kType>
@@ -574,9 +574,10 @@
   }
 }
 
-jvmtiError Redefiner::RedefineClasses(jvmtiEnv* jenv,
-                                      jint class_count,
-                                      const jvmtiClassDefinition* definitions) {
+template<RedefinitionType kType>
+jvmtiError Redefiner::RedefineClassesGeneric(jvmtiEnv* jenv,
+                                             jint class_count,
+                                             const jvmtiClassDefinition* definitions) {
   art::Runtime* runtime = art::Runtime::Current();
   art::Thread* self = art::Thread::Current();
   ArtJvmTiEnv* env = ArtJvmTiEnv::AsArtJvmTiEnv(jenv);
@@ -597,7 +598,8 @@
   std::vector<ArtClassDefinition> def_vector;
   def_vector.reserve(class_count);
   for (jint i = 0; i < class_count; i++) {
-    jvmtiError res = Redefiner::GetClassRedefinitionError(definitions[i].klass, &error_msg);
+    jvmtiError res = Redefiner::GetClassRedefinitionError<RedefinitionType::kNormal>(
+        definitions[i].klass, &error_msg);
     if (res != OK) {
       JVMTI_LOG(WARNING, env) << "FAILURE TO REDEFINE " << error_msg;
       return res;
@@ -611,15 +613,35 @@
     def_vector.push_back(std::move(def));
   }
   // Call all the transformation events.
-  Transformer::RetransformClassesDirect(self, &def_vector);
-  jvmtiError res = RedefineClassesDirect(
-      env, runtime, self, def_vector, RedefinitionType::kNormal, &error_msg);
+  Transformer::RetransformClassesDirect<kType>(self, &def_vector);
+  if (kType == RedefinitionType::kStructural) {
+    Transformer::RetransformClassesDirect<RedefinitionType::kNormal>(self, &def_vector);
+  }
+  jvmtiError res = RedefineClassesDirect(env, runtime, self, def_vector, kType, &error_msg);
   if (res != OK) {
     JVMTI_LOG(WARNING, env) << "FAILURE TO REDEFINE " << error_msg;
   }
   return res;
 }
 
+jvmtiError Redefiner::StructurallyRedefineClasses(jvmtiEnv* jenv,
+                                                  jint class_count,
+                                                  const jvmtiClassDefinition* definitions) {
+  ArtJvmTiEnv* art_env = ArtJvmTiEnv::AsArtJvmTiEnv(jenv);
+  if (art_env == nullptr) {
+    return ERR(INVALID_ENVIRONMENT);
+  } else if (art_env->capabilities.can_redefine_classes != 1) {
+    return ERR(MUST_POSSESS_CAPABILITY);
+  }
+  return RedefineClassesGeneric<RedefinitionType::kStructural>(jenv, class_count, definitions);
+}
+
+jvmtiError Redefiner::RedefineClasses(jvmtiEnv* jenv,
+                                      jint class_count,
+                                      const jvmtiClassDefinition* definitions) {
+  return RedefineClassesGeneric<RedefinitionType::kNormal>(jenv, class_count, definitions);
+}
+
 jvmtiError Redefiner::StructurallyRedefineClassDirect(jvmtiEnv* env,
                                                       jclass klass,
                                                       const unsigned char* data,
@@ -1154,13 +1176,10 @@
 
   art::Handle<art::mirror::Class> h_klass(hs.NewHandle(GetMirrorClass()));
   jvmtiError res;
-  switch (driver_->type_) {
-  case RedefinitionType::kNormal:
-    res = Redefiner::GetClassRedefinitionError<RedefinitionType::kNormal>(h_klass, &err);
-    break;
-  case RedefinitionType::kStructural:
+  if (driver_->type_ == RedefinitionType::kStructural && this->IsStructuralRedefinition()) {
     res = Redefiner::GetClassRedefinitionError<RedefinitionType::kStructural>(h_klass, &err);
-    break;
+  } else {
+    res = Redefiner::GetClassRedefinitionError<RedefinitionType::kNormal>(h_klass, &err);
   }
   if (res != OK) {
     RecordFailure(res, err);
@@ -1171,7 +1190,7 @@
 }
 
 bool Redefiner::ClassRedefinition::CheckRedefinitionIsValid() {
-  return CheckRedefinable() && CheckClass() && CheckFields() && CheckMethods();
+  return CheckClass() && CheckFields() && CheckMethods() && CheckRedefinable();
 }
 
 class RedefinitionDataIter;
diff --git a/openjdkjvmti/ti_redefine.h b/openjdkjvmti/ti_redefine.h
index b028dee..58a688c 100644
--- a/openjdkjvmti/ti_redefine.h
+++ b/openjdkjvmti/ti_redefine.h
@@ -87,6 +87,9 @@
   static jvmtiError RedefineClasses(jvmtiEnv* env,
                                     jint class_count,
                                     const jvmtiClassDefinition* definitions);
+  static jvmtiError StructurallyRedefineClasses(jvmtiEnv* env,
+                                                jint class_count,
+                                                const jvmtiClassDefinition* definitions);
 
   static jvmtiError IsModifiableClass(jvmtiEnv* env, jclass klass, jboolean* is_redefinable);
   static jvmtiError IsStructurallyModifiableClass(jvmtiEnv* env,
@@ -287,6 +290,11 @@
       REQUIRES_SHARED(art::Locks::mutator_lock_);
 
   template<RedefinitionType kType = RedefinitionType::kNormal>
+  static jvmtiError RedefineClassesGeneric(jvmtiEnv* env,
+                                           jint class_count,
+                                           const jvmtiClassDefinition* definitions);
+
+  template<RedefinitionType kType = RedefinitionType::kNormal>
   static jvmtiError IsModifiableClassGeneric(jvmtiEnv* env, jclass klass, jboolean* is_redefinable);
 
   template<RedefinitionType kType = RedefinitionType::kNormal>
diff --git a/openjdkjvmti/transform.cc b/openjdkjvmti/transform.cc
index aa37793..6133685 100644
--- a/openjdkjvmti/transform.cc
+++ b/openjdkjvmti/transform.cc
@@ -255,13 +255,17 @@
 template
 void Transformer::TransformSingleClassDirect<ArtJvmtiEvent::kClassFileLoadHookRetransformable>(
     EventHandler* event_handler, art::Thread* self, /*in-out*/ArtClassDefinition* def);
+template
+void Transformer::TransformSingleClassDirect<ArtJvmtiEvent::kStructuralDexFileLoadHook>(
+    EventHandler* event_handler, art::Thread* self, /*in-out*/ArtClassDefinition* def);
 
 template<ArtJvmtiEvent kEvent>
 void Transformer::TransformSingleClassDirect(EventHandler* event_handler,
                                              art::Thread* self,
                                              /*in-out*/ArtClassDefinition* def) {
   static_assert(kEvent == ArtJvmtiEvent::kClassFileLoadHookNonRetransformable ||
-                kEvent == ArtJvmtiEvent::kClassFileLoadHookRetransformable,
+                kEvent == ArtJvmtiEvent::kClassFileLoadHookRetransformable ||
+                kEvent == ArtJvmtiEvent::kStructuralDexFileLoadHook,
                 "bad event type");
   // We don't want to do transitions between calling the event and setting the new data so change to
   // native state early. This also avoids any problems that the FaultHandler might have in
@@ -282,25 +286,30 @@
       dex_data.data(),
       /*out*/&new_len,
       /*out*/&new_data);
-  def->SetNewDexData(new_len, new_data);
+  def->SetNewDexData(new_len, new_data, kEvent);
 }
 
+template <RedefinitionType kType>
 void Transformer::RetransformClassesDirect(
-      art::Thread* self,
-      /*in-out*/std::vector<ArtClassDefinition>* definitions) {
+    art::Thread* self,
+    /*in-out*/ std::vector<ArtClassDefinition>* definitions) {
+  constexpr ArtJvmtiEvent kEvent = kType == RedefinitionType::kNormal
+                                       ? ArtJvmtiEvent::kClassFileLoadHookRetransformable
+                                       : ArtJvmtiEvent::kStructuralDexFileLoadHook;
   for (ArtClassDefinition& def : *definitions) {
-    TransformSingleClassDirect<ArtJvmtiEvent::kClassFileLoadHookRetransformable>(
-        gEventHandler, self, &def);
+    TransformSingleClassDirect<kEvent>(gEventHandler, self, &def);
   }
 }
 
+template void Transformer::RetransformClassesDirect<RedefinitionType::kNormal>(
+      art::Thread* self, /*in-out*/std::vector<ArtClassDefinition>* definitions);
+template void Transformer::RetransformClassesDirect<RedefinitionType::kStructural>(
+      art::Thread* self, /*in-out*/std::vector<ArtClassDefinition>* definitions);
+
 jvmtiError Transformer::RetransformClasses(jvmtiEnv* env,
                                            jint class_count,
                                            const jclass* classes) {
-  if (env == nullptr) {
-    JVMTI_LOG(WARNING, env) << "FAILURE TO RETRANSFORM env was null!";
-    return ERR(INVALID_ENVIRONMENT);
-  } else if (class_count < 0) {
+  if (class_count < 0) {
     JVMTI_LOG(WARNING, env) << "FAILURE TO RETRANSFORM class_count was less then 0";
     return ERR(ILLEGAL_ARGUMENT);
   } else if (class_count == 0) {
@@ -317,7 +326,7 @@
   std::vector<ArtClassDefinition> definitions;
   jvmtiError res = OK;
   for (jint i = 0; i < class_count; i++) {
-    res = Redefiner::GetClassRedefinitionError(classes[i], &error_msg);
+    res = Redefiner::GetClassRedefinitionError<RedefinitionType::kNormal>(classes[i], &error_msg);
     if (res != OK) {
       JVMTI_LOG(WARNING, env) << "FAILURE TO RETRANSFORM " << error_msg;
       return res;
@@ -330,13 +339,16 @@
     }
     definitions.push_back(std::move(def));
   }
-  RetransformClassesDirect(self, &definitions);
-  res = Redefiner::RedefineClassesDirect(ArtJvmTiEnv::AsArtJvmTiEnv(env),
-                                         runtime,
-                                         self,
-                                         definitions,
-                                         RedefinitionType::kNormal,
-                                         &error_msg);
+  RetransformClassesDirect<RedefinitionType::kStructural>(self, &definitions);
+  RetransformClassesDirect<RedefinitionType::kNormal>(self, &definitions);
+  RedefinitionType redef_type =
+      std::any_of(definitions.cbegin(),
+                  definitions.cend(),
+                  [](const auto& it) { return it.HasStructuralChanges(); })
+          ? RedefinitionType::kStructural
+          : RedefinitionType::kNormal;
+  res = Redefiner::RedefineClassesDirect(
+      ArtJvmTiEnv::AsArtJvmTiEnv(env), runtime, self, definitions, redef_type, &error_msg);
   if (res != OK) {
     JVMTI_LOG(WARNING, env) << "FAILURE TO RETRANSFORM " << error_msg;
   }
diff --git a/openjdkjvmti/transform.h b/openjdkjvmti/transform.h
index 40c7267..a58b50e 100644
--- a/openjdkjvmti/transform.h
+++ b/openjdkjvmti/transform.h
@@ -39,6 +39,7 @@
 
 #include "art_jvmti.h"
 #include "ti_class_definition.h"
+#include "ti_redefine.h"
 
 namespace openjdkjvmti {
 
@@ -56,6 +57,7 @@
       art::Thread* self,
       /*in-out*/ArtClassDefinition* def);
 
+  template<RedefinitionType kType>
   static void RetransformClassesDirect(
       art::Thread* self,
       /*in-out*/std::vector<ArtClassDefinition>* definitions);
diff --git a/test/1988-multi-structural-redefine/expected.txt b/test/1988-multi-structural-redefine/expected.txt
new file mode 100644
index 0000000..00aea88
--- /dev/null
+++ b/test/1988-multi-structural-redefine/expected.txt
@@ -0,0 +1,5 @@
+hello - Transform 1
+hello - Transform 2
+Redefining both class art.Test1988$Transform1 and class art.Test1988$Transform2 to use each other.
+Transform1 says hi and Transform2 says bye!
+Transform2 says hi and Transform1 says bye!
diff --git a/test/1988-multi-structural-redefine/info.txt b/test/1988-multi-structural-redefine/info.txt
new file mode 100644
index 0000000..875a5f6
--- /dev/null
+++ b/test/1988-multi-structural-redefine/info.txt
@@ -0,0 +1 @@
+Tests basic functions in the jvmti plugin.
diff --git a/test/1988-multi-structural-redefine/run b/test/1988-multi-structural-redefine/run
new file mode 100755
index 0000000..a36de16
--- /dev/null
+++ b/test/1988-multi-structural-redefine/run
@@ -0,0 +1,18 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+# Ask for stack traces to be dumped to a file rather than to stdout.
+./default-run "$@" --jvmti --android-runtime-option -Xopaque-jni-ids:true
diff --git a/test/1988-multi-structural-redefine/src/Main.java b/test/1988-multi-structural-redefine/src/Main.java
new file mode 100644
index 0000000..7e95671
--- /dev/null
+++ b/test/1988-multi-structural-redefine/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.Test1988.run();
+  }
+}
diff --git a/test/1988-multi-structural-redefine/src/art/Redefinition.java b/test/1988-multi-structural-redefine/src/art/Redefinition.java
new file mode 120000
index 0000000..81eaf31
--- /dev/null
+++ b/test/1988-multi-structural-redefine/src/art/Redefinition.java
@@ -0,0 +1 @@
+../../../jvmti-common/Redefinition.java
\ No newline at end of file
diff --git a/test/1988-multi-structural-redefine/src/art/Test1988.java b/test/1988-multi-structural-redefine/src/art/Test1988.java
new file mode 100644
index 0000000..6dab4da
--- /dev/null
+++ b/test/1988-multi-structural-redefine/src/art/Test1988.java
@@ -0,0 +1,126 @@
+/*
+ * 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 Test1988 {
+  static class Transform1 {
+    public static void sayHi() {
+      System.out.println("hello - Transform 1");
+    }
+  }
+  static class Transform2 {
+    public static void sayHi() {
+      System.out.println("hello - Transform 2");
+    }
+  }
+
+  /** Base64 encoded dex file for
+   *
+   * static class Trasnform1 {
+   *   public static void sayHi() {
+   *     System.out.println("Transform1 says hi and " + Transform2.getBye());
+   *   }
+   *   public static String getBye() {
+   *     return "Transform1 says bye!";
+   *   }
+   * }
+   */
+  public static final byte[] T1_BYTES = Base64.getDecoder().decode(
+    "ZGV4CjAzNQAU4pPI4BKgrMtz7s1Ogc8in1PQhazaRWBcBQAAcAAAAHhWNBIAAAAAAAAAAJgEAAAd" +
+    "AAAAcAAAAAsAAADkAAAABAAAABABAAABAAAAQAEAAAkAAABIAQAAAQAAAJABAACsAwAAsAEAAD4C" +
+    "AABGAgAASQIAAE0CAABoAgAAgwIAAJMCAAC3AgAA1wIAAO4CAAACAwAAFgMAADEDAABFAwAAVAMA" +
+    "AGADAAB2AwAAjwMAAJIDAACWAwAAowMAAKsDAACzAwAAuQMAAL4DAADHAwAAzgMAANgDAADfAwAA" +
+    "AwAAAAQAAAAFAAAABgAAAAcAAAAIAAAACQAAAAoAAAALAAAADAAAABEAAAABAAAABwAAAAAAAAAC" +
+    "AAAACAAAADgCAAARAAAACgAAAAAAAAASAAAACgAAADgCAAAJAAUAFwAAAAAAAgAAAAAAAAAAABUA" +
+    "AAAAAAIAGQAAAAEAAAAVAAAABQADABgAAAAGAAIAAAAAAAgAAgAAAAAACAABABQAAAAIAAAAGgAA" +
+    "AAAAAAAAAAAABgAAAAAAAAANAAAAiAQAAGUEAAAAAAAAAQAAAAAAAAAqAgAAAwAAABoADwARAAAA" +
+    "AQABAAEAAAAmAgAABAAAAHAQBQAAAA4ABAAAAAIAAAAuAgAAGwAAAGIAAABxAAMAAAAMASICCABw" +
+    "EAYAAgAaAxAAbiAHADIAbiAHABIAbhAIAAIADAFuIAQAEAAOAAYADgALAA4ACAAOARoPAAAAAAEA" +
+    "AAAHAAY8aW5pdD4AAUwAAkxMABlMYXJ0L1Rlc3QxOTg4JFRyYW5zZm9ybTE7ABlMYXJ0L1Rlc3Qx" +
+    "OTg4JFRyYW5zZm9ybTI7AA5MYXJ0L1Rlc3QxOTg4OwAiTGRhbHZpay9hbm5vdGF0aW9uL0VuY2xv" +
+    "c2luZ0NsYXNzOwAeTGRhbHZpay9hbm5vdGF0aW9uL0lubmVyQ2xhc3M7ABVMamF2YS9pby9Qcmlu" +
+    "dFN0cmVhbTsAEkxqYXZhL2xhbmcvT2JqZWN0OwASTGphdmEvbGFuZy9TdHJpbmc7ABlMamF2YS9s" +
+    "YW5nL1N0cmluZ0J1aWxkZXI7ABJMamF2YS9sYW5nL1N5c3RlbTsADVRlc3QxOTg4LmphdmEAClRy" +
+    "YW5zZm9ybTEAFFRyYW5zZm9ybTEgc2F5cyBieWUhABdUcmFuc2Zvcm0xIHNheXMgaGkgYW5kIAAB" +
+    "VgACVkwAC2FjY2Vzc0ZsYWdzAAZhcHBlbmQABmdldEJ5ZQAEbmFtZQADb3V0AAdwcmludGxuAAVz" +
+    "YXlIaQAIdG9TdHJpbmcABXZhbHVlAHV+fkQ4eyJjb21waWxhdGlvbi1tb2RlIjoiZGVidWciLCJt" +
+    "aW4tYXBpIjoxLCJzaGEtMSI6IjY4NjQ4NTU3NTM0MDJiYmFjODk2Nzc2YjAzN2RlYmJjOTM4YzQ5" +
+    "NTMiLCJ2ZXJzaW9uIjoiMS43LjYtZGV2In0AAgMBGxgCAgQCEwQIFhcOAAADAACAgATIAwEJsAMB" +
+    "CeADAAAAAAACAAAAVgQAAFwEAAB8BAAAAAAAAAAAAAAAAAAAEAAAAAAAAAABAAAAAAAAAAEAAAAd" +
+    "AAAAcAAAAAIAAAALAAAA5AAAAAMAAAAEAAAAEAEAAAQAAAABAAAAQAEAAAUAAAAJAAAASAEAAAYA" +
+    "AAABAAAAkAEAAAEgAAADAAAAsAEAAAMgAAADAAAAJgIAAAEQAAABAAAAOAIAAAIgAAAdAAAAPgIA" +
+    "AAQgAAACAAAAVgQAAAAgAAABAAAAZQQAAAMQAAACAAAAeAQAAAYgAAABAAAAiAQAAAAQAAABAAAA" +
+    "mAQAAA==");
+
+
+  /** Base64 encoded dex file for
+   *
+   * static class Trasnform2 {
+   *   public static void sayHi() {
+   *     System.out.println("Transform2 says hi and " + Transform1.getBye());
+   *   }
+   *   public static String getBye() {
+   *     return "Transform2 says bye!";
+   *   }
+   * }
+   */
+  public static final byte[] T2_BYTES = Base64.getDecoder().decode(
+    "ZGV4CjAzNQD94cwR+R7Yw7VMom5CwuQd5mZlsV2xrVFcBQAAcAAAAHhWNBIAAAAAAAAAAJgEAAAd" +
+    "AAAAcAAAAAsAAADkAAAABAAAABABAAABAAAAQAEAAAkAAABIAQAAAQAAAJABAACsAwAAsAEAAD4C" +
+    "AABGAgAASQIAAE0CAABoAgAAgwIAAJMCAAC3AgAA1wIAAO4CAAACAwAAFgMAADEDAABFAwAAVAMA" +
+    "AGADAAB2AwAAjwMAAJIDAACWAwAAowMAAKsDAACzAwAAuQMAAL4DAADHAwAAzgMAANgDAADfAwAA" +
+    "AwAAAAQAAAAFAAAABgAAAAcAAAAIAAAACQAAAAoAAAALAAAADAAAABEAAAABAAAABwAAAAAAAAAC" +
+    "AAAACAAAADgCAAARAAAACgAAAAAAAAASAAAACgAAADgCAAAJAAUAFwAAAAAAAAAVAAAAAQACAAAA" +
+    "AAABAAAAFQAAAAEAAgAZAAAABQADABgAAAAGAAIAAAAAAAgAAgAAAAAACAABABQAAAAIAAAAGgAA" +
+    "AAEAAAAAAAAABgAAAAAAAAANAAAAiAQAAGUEAAAAAAAAAQAAAAAAAAAqAgAAAwAAABoADwARAAAA" +
+    "AQABAAEAAAAmAgAABAAAAHAQBQAAAA4ABAAAAAIAAAAuAgAAGwAAAGIAAABxAAAAAAAMASICCABw" +
+    "EAYAAgAaAxAAbiAHADIAbiAHABIAbhAIAAIADAFuIAQAEAAOAA4ADgATAA4AEAAOARoPAAAAAAEA" +
+    "AAAHAAY8aW5pdD4AAUwAAkxMABlMYXJ0L1Rlc3QxOTg4JFRyYW5zZm9ybTE7ABlMYXJ0L1Rlc3Qx" +
+    "OTg4JFRyYW5zZm9ybTI7AA5MYXJ0L1Rlc3QxOTg4OwAiTGRhbHZpay9hbm5vdGF0aW9uL0VuY2xv" +
+    "c2luZ0NsYXNzOwAeTGRhbHZpay9hbm5vdGF0aW9uL0lubmVyQ2xhc3M7ABVMamF2YS9pby9Qcmlu" +
+    "dFN0cmVhbTsAEkxqYXZhL2xhbmcvT2JqZWN0OwASTGphdmEvbGFuZy9TdHJpbmc7ABlMamF2YS9s" +
+    "YW5nL1N0cmluZ0J1aWxkZXI7ABJMamF2YS9sYW5nL1N5c3RlbTsADVRlc3QxOTg4LmphdmEAClRy" +
+    "YW5zZm9ybTIAFFRyYW5zZm9ybTIgc2F5cyBieWUhABdUcmFuc2Zvcm0yIHNheXMgaGkgYW5kIAAB" +
+    "VgACVkwAC2FjY2Vzc0ZsYWdzAAZhcHBlbmQABmdldEJ5ZQAEbmFtZQADb3V0AAdwcmludGxuAAVz" +
+    "YXlIaQAIdG9TdHJpbmcABXZhbHVlAHV+fkQ4eyJjb21waWxhdGlvbi1tb2RlIjoiZGVidWciLCJt" +
+    "aW4tYXBpIjoxLCJzaGEtMSI6IjY4NjQ4NTU3NTM0MDJiYmFjODk2Nzc2YjAzN2RlYmJjOTM4YzQ5" +
+    "NTMiLCJ2ZXJzaW9uIjoiMS43LjYtZGV2In0AAgMBGxgCAgQCEwQIFhcOAAADAAGAgATIAwEJsAMB" +
+    "CeADAAAAAAACAAAAVgQAAFwEAAB8BAAAAAAAAAAAAAAAAAAAEAAAAAAAAAABAAAAAAAAAAEAAAAd" +
+    "AAAAcAAAAAIAAAALAAAA5AAAAAMAAAAEAAAAEAEAAAQAAAABAAAAQAEAAAUAAAAJAAAASAEAAAYA" +
+    "AAABAAAAkAEAAAEgAAADAAAAsAEAAAMgAAADAAAAJgIAAAEQAAABAAAAOAIAAAIgAAAdAAAAPgIA" +
+    "AAQgAAACAAAAVgQAAAAgAAABAAAAZQQAAAMQAAACAAAAeAQAAAYgAAABAAAAiAQAAAAQAAABAAAA" +
+    "mAQAAA==");
+
+
+  public static void run() {
+    doTest();
+  }
+
+  public static void doTest() {
+    Transform1.sayHi();
+    Transform2.sayHi();
+    System.out.println(
+        "Redefining both " + Transform1.class + " and " + Transform2.class + " to use each other.");
+    Redefinition.doMultiStructuralClassRedefinition(
+        new Redefinition.DexOnlyClassDefinition(Transform1.class, T1_BYTES),
+        new Redefinition.DexOnlyClassDefinition(Transform2.class, T2_BYTES));
+    Transform1.sayHi();
+    Transform2.sayHi();
+  }
+}
diff --git a/test/1991-hello-structural-retransform/expected.txt b/test/1991-hello-structural-retransform/expected.txt
new file mode 100644
index 0000000..7478dda
--- /dev/null
+++ b/test/1991-hello-structural-retransform/expected.txt
@@ -0,0 +1,2 @@
+hello
+I say hello and you say goodbye!
diff --git a/test/1991-hello-structural-retransform/info.txt b/test/1991-hello-structural-retransform/info.txt
new file mode 100644
index 0000000..875a5f6
--- /dev/null
+++ b/test/1991-hello-structural-retransform/info.txt
@@ -0,0 +1 @@
+Tests basic functions in the jvmti plugin.
diff --git a/test/1991-hello-structural-retransform/run b/test/1991-hello-structural-retransform/run
new file mode 100755
index 0000000..03e41a5
--- /dev/null
+++ b/test/1991-hello-structural-retransform/run
@@ -0,0 +1,17 @@
+#!/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.
+
+./default-run "$@" --jvmti --runtime-option -Xopaque-jni-ids:true
diff --git a/test/1991-hello-structural-retransform/src/Main.java b/test/1991-hello-structural-retransform/src/Main.java
new file mode 100644
index 0000000..531ca4a
--- /dev/null
+++ b/test/1991-hello-structural-retransform/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.Test1991.run();
+  }
+}
diff --git a/test/1991-hello-structural-retransform/src/art/Redefinition.java b/test/1991-hello-structural-retransform/src/art/Redefinition.java
new file mode 120000
index 0000000..81eaf31
--- /dev/null
+++ b/test/1991-hello-structural-retransform/src/art/Redefinition.java
@@ -0,0 +1 @@
+../../../jvmti-common/Redefinition.java
\ No newline at end of file
diff --git a/test/1991-hello-structural-retransform/src/art/Test1991.java b/test/1991-hello-structural-retransform/src/art/Test1991.java
new file mode 100644
index 0000000..6060c20
--- /dev/null
+++ b/test/1991-hello-structural-retransform/src/art/Test1991.java
@@ -0,0 +1,79 @@
+/*
+ * 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 Test1991 {
+
+  static class Transform {
+    public static void sayHi() {
+      System.out.println("hello");
+    }
+  }
+
+
+  /**
+   * base64 encoded class/dex file for
+   * static class Transform {
+   *   public static void sayHi() {
+   *    System.out.println("I say hello and " + sayGoodbye());
+   *   }
+   *   public static String sayGoodbye() {
+   *     return "you say goodbye!";
+   *   }
+   * }
+   */
+  private static final byte[] DEX_BYTES = Base64.getDecoder().decode(
+    "ZGV4CjAzNQCi0OGZvVpTRbHGfNbo3bfcu60kPpJayMgoBQAAcAAAAHhWNBIAAAAAAAAAAGQEAAAc" +
+    "AAAAcAAAAAoAAADgAAAABAAAAAgBAAABAAAAOAEAAAgAAABAAQAAAQAAAIABAACIAwAAoAEAAC4C" +
+    "AAA2AgAASAIAAEsCAABPAgAAaQIAAHkCAACdAgAAvQIAANQCAADoAgAA/AIAABcDAAArAwAAOgMA" +
+    "AEUDAABIAwAATAMAAFkDAABhAwAAZwMAAGwDAAB1AwAAgQMAAIgDAACSAwAAmQMAAKsDAAAEAAAA" +
+    "BQAAAAYAAAAHAAAACAAAAAkAAAAKAAAACwAAAAwAAAAPAAAAAgAAAAYAAAAAAAAAAwAAAAcAAAAo" +
+    "AgAADwAAAAkAAAAAAAAAEAAAAAkAAAAoAgAACAAEABQAAAAAAAIAAAAAAAAAAAAWAAAAAAACABcA" +
+    "AAAEAAMAFQAAAAUAAgAAAAAABwACAAAAAAAHAAEAEgAAAAcAAAAYAAAAAAAAAAAAAAAFAAAAAAAA" +
+    "AA0AAABUBAAAMgQAAAAAAAABAAAAAAAAABoCAAADAAAAGgAaABEAAAABAAEAAQAAABYCAAAEAAAA" +
+    "cBAEAAAADgAEAAAAAgAAAB4CAAAbAAAAYgAAAHEAAQAAAAwBIgIHAHAQBQACABoDAQBuIAYAMgBu" +
+    "IAYAEgBuEAcAAgAMAW4gAwAQAA4ABgAOAAsADgAIAA4BGg8AAAAAAQAAAAYABjxpbml0PgAQSSBz" +
+    "YXkgaGVsbG8gYW5kIAABTAACTEwAGExhcnQvVGVzdDE5OTEkVHJhbnNmb3JtOwAOTGFydC9UZXN0" +
+    "MTk5MTsAIkxkYWx2aWsvYW5ub3RhdGlvbi9FbmNsb3NpbmdDbGFzczsAHkxkYWx2aWsvYW5ub3Rh" +
+    "dGlvbi9Jbm5lckNsYXNzOwAVTGphdmEvaW8vUHJpbnRTdHJlYW07ABJMamF2YS9sYW5nL09iamVj" +
+    "dDsAEkxqYXZhL2xhbmcvU3RyaW5nOwAZTGphdmEvbGFuZy9TdHJpbmdCdWlsZGVyOwASTGphdmEv" +
+    "bGFuZy9TeXN0ZW07AA1UZXN0MTk5MS5qYXZhAAlUcmFuc2Zvcm0AAVYAAlZMAAthY2Nlc3NGbGFn" +
+    "cwAGYXBwZW5kAARuYW1lAANvdXQAB3ByaW50bG4ACnNheUdvb2RieWUABXNheUhpAAh0b1N0cmlu" +
+    "ZwAFdmFsdWUAEHlvdSBzYXkgZ29vZGJ5ZSEAdn5+RDh7ImNvbXBpbGF0aW9uLW1vZGUiOiJkZWJ1" +
+    "ZyIsIm1pbi1hcGkiOjEsInNoYS0xIjoiNjBkYTRkNjdiMzgxYzQyNDY3NzU3YzQ5ZmI2ZTU1NzU2" +
+    "ZDg4YTJmMyIsInZlcnNpb24iOiIxLjcuMTItZGV2In0AAgIBGRgBAgMCEQQIExcOAAADAACAgAS4" +
+    "AwEJoAMBCdADAAAAAAIAAAAjBAAAKQQAAEgEAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAEAAAAAAAAA" +
+    "AQAAABwAAABwAAAAAgAAAAoAAADgAAAAAwAAAAQAAAAIAQAABAAAAAEAAAA4AQAABQAAAAgAAABA" +
+    "AQAABgAAAAEAAACAAQAAASAAAAMAAACgAQAAAyAAAAMAAAAWAgAAARAAAAEAAAAoAgAAAiAAABwA" +
+    "AAAuAgAABCAAAAIAAAAjBAAAACAAAAEAAAAyBAAAAxAAAAIAAABEBAAABiAAAAEAAABUBAAAABAA" +
+    "AAEAAABkBAAA");
+
+
+  public static void run() {
+    Redefinition.setTestConfiguration(Redefinition.Config.STRUCTURAL_TRANSFORM);
+    doTest();
+  }
+
+  public static void doTest() {
+    Transform.sayHi();
+    Redefinition.addCommonTransformationResult("art/Test1991$Transform", new byte[0], DEX_BYTES);
+    Redefinition.enableCommonRetransformation(true);
+    Redefinition.doCommonClassRetransformation(Transform.class);
+    Transform.sayHi();
+  }
+}
diff --git a/test/1993-fallback-non-structural/expected.txt b/test/1993-fallback-non-structural/expected.txt
new file mode 100644
index 0000000..7e2cdf4
--- /dev/null
+++ b/test/1993-fallback-non-structural/expected.txt
@@ -0,0 +1,3 @@
+Can structurally Redefine: false
+hello
+Goodbye
diff --git a/test/1993-fallback-non-structural/info.txt b/test/1993-fallback-non-structural/info.txt
new file mode 100644
index 0000000..3b558e1
--- /dev/null
+++ b/test/1993-fallback-non-structural/info.txt
@@ -0,0 +1,4 @@
+Tests basic functions in the jvmti plugin.
+
+Tests that using the structural redefinition functions will fall back to non-structural
+redefinition when possible.
diff --git a/test/1993-fallback-non-structural/run b/test/1993-fallback-non-structural/run
new file mode 100755
index 0000000..03e41a5
--- /dev/null
+++ b/test/1993-fallback-non-structural/run
@@ -0,0 +1,17 @@
+#!/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.
+
+./default-run "$@" --jvmti --runtime-option -Xopaque-jni-ids:true
diff --git a/test/1993-fallback-non-structural/src/Main.java b/test/1993-fallback-non-structural/src/Main.java
new file mode 100644
index 0000000..61e060c
--- /dev/null
+++ b/test/1993-fallback-non-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.Test1993.run();
+  }
+}
diff --git a/test/1993-fallback-non-structural/src/art/Redefinition.java b/test/1993-fallback-non-structural/src/art/Redefinition.java
new file mode 120000
index 0000000..81eaf31
--- /dev/null
+++ b/test/1993-fallback-non-structural/src/art/Redefinition.java
@@ -0,0 +1 @@
+../../../jvmti-common/Redefinition.java
\ No newline at end of file
diff --git a/test/1993-fallback-non-structural/src/art/Test1993.java b/test/1993-fallback-non-structural/src/art/Test1993.java
new file mode 100644
index 0000000..f442099
--- /dev/null
+++ b/test/1993-fallback-non-structural/src/art/Test1993.java
@@ -0,0 +1,77 @@
+/*
+ * 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 Test1993 {
+
+  static class Transform {
+    public void sayHi() {
+      // Use lower 'h' to make sure the string will have a different string id
+      // than the transformation (the transformation code is the same except
+      // the actual printed String, which was making the test inacurately passing
+      // in JIT mode when loading the string from the dex cache, as the string ids
+      // of the two different strings were the same).
+      // We know the string ids will be different because lexicographically:
+      // "Goodbye" < "LTransform;" < "hello".
+      System.out.println("hello");
+    }
+  }
+
+  /**
+   * base64 encoded class/dex file for
+   * class Transform {
+   *   public void sayHi() {
+   *    System.out.println("Goodbye");
+   *   }
+   * }
+   */
+  private static final byte[] DEX_BYTES = Base64.getDecoder().decode(
+    "ZGV4CjAzNQDxrdbiBcsn0r58mdtcdyDxVUxwbWfShNQwBAAAcAAAAHhWNBIAAAAAAAAAAGwDAAAV" +
+    "AAAAcAAAAAkAAADEAAAAAgAAAOgAAAABAAAAAAEAAAQAAAAIAQAAAQAAACgBAADoAgAASAEAAJIB" +
+    "AACaAQAAowEAAL0BAADNAQAA8QEAABECAAAoAgAAPAIAAFACAABkAgAAcwIAAH4CAACBAgAAhQIA" +
+    "AJICAACYAgAAnQIAAKYCAACtAgAAtAIAAAIAAAADAAAABAAAAAUAAAAGAAAABwAAAAgAAAAJAAAA" +
+    "DAAAAAwAAAAIAAAAAAAAAA0AAAAIAAAAjAEAAAcABAAQAAAAAAAAAAAAAAAAAAAAEgAAAAQAAQAR" +
+    "AAAABQAAAAAAAAAAAAAAAAAAAAUAAAAAAAAACgAAAFwDAAA7AwAAAAAAAAEAAQABAAAAgAEAAAQA" +
+    "AABwEAMAAAAOAAMAAQACAAAAhAEAAAgAAABiAAAAGgEBAG4gAgAQAA4AAwAOAAUADngAAAAAAQAA" +
+    "AAYABjxpbml0PgAHR29vZGJ5ZQAYTGFydC9UZXN0MTk5MyRUcmFuc2Zvcm07AA5MYXJ0L1Rlc3Qx" +
+    "OTkzOwAiTGRhbHZpay9hbm5vdGF0aW9uL0VuY2xvc2luZ0NsYXNzOwAeTGRhbHZpay9hbm5vdGF0" +
+    "aW9uL0lubmVyQ2xhc3M7ABVMamF2YS9pby9QcmludFN0cmVhbTsAEkxqYXZhL2xhbmcvT2JqZWN0" +
+    "OwASTGphdmEvbGFuZy9TdHJpbmc7ABJMamF2YS9sYW5nL1N5c3RlbTsADVRlc3QxOTkzLmphdmEA" +
+    "CVRyYW5zZm9ybQABVgACVkwAC2FjY2Vzc0ZsYWdzAARuYW1lAANvdXQAB3ByaW50bG4ABXNheUhp" +
+    "AAV2YWx1ZQB2fn5EOHsiY29tcGlsYXRpb24tbW9kZSI6ImRlYnVnIiwibWluLWFwaSI6MSwic2hh" +
+    "LTEiOiJjZDkwMDIzOTMwZDk3M2Y1NzcxMWYxZDRmZGFhZDdhM2U0NzE0NjM3IiwidmVyc2lvbiI6" +
+    "IjEuNy4xNC1kZXYifQACAgETGAECAwIOBAgPFwsAAAEBAICABMgCAQHgAgAAAAAAAAACAAAALAMA" +
+    "ADIDAABQAwAAAAAAAAAAAAAAAAAAEAAAAAAAAAABAAAAAAAAAAEAAAAVAAAAcAAAAAIAAAAJAAAA" +
+    "xAAAAAMAAAACAAAA6AAAAAQAAAABAAAAAAEAAAUAAAAEAAAACAEAAAYAAAABAAAAKAEAAAEgAAAC" +
+    "AAAASAEAAAMgAAACAAAAgAEAAAEQAAABAAAAjAEAAAIgAAAVAAAAkgEAAAQgAAACAAAALAMAAAAg" +
+    "AAABAAAAOwMAAAMQAAACAAAATAMAAAYgAAABAAAAXAMAAAAQAAABAAAAbAMAAA==");
+
+  public static void run() {
+    Redefinition.setTestConfiguration(Redefinition.Config.COMMON_REDEFINE);
+    doTest(new Transform());
+  }
+
+  public static void doTest(Transform t) {
+    // TODO Remove this once the class is structurally modifiable.
+    System.out.println("Can structurally Redefine: " +
+      Redefinition.isStructurallyModifiable(Transform.class));
+    t.sayHi();
+    Redefinition.doCommonStructuralClassRedefinition(Transform.class, DEX_BYTES);
+    t.sayHi();
+  }
+}
diff --git a/test/jvmti-common/Redefinition.java b/test/jvmti-common/Redefinition.java
index 2ebce17..3402fa1 100644
--- a/test/jvmti-common/Redefinition.java
+++ b/test/jvmti-common/Redefinition.java
@@ -19,7 +19,7 @@
 import java.util.ArrayList;
 // Common Redefinition functions. Placed here for use by CTS
 public class Redefinition {
-  public static final class CommonClassDefinition {
+  public static class CommonClassDefinition {
     public final Class<?> target;
     public final byte[] class_file_bytes;
     public final byte[] dex_file_bytes;
@@ -31,12 +31,19 @@
     }
   }
 
+  public static class DexOnlyClassDefinition extends CommonClassDefinition {
+    public DexOnlyClassDefinition(Class<?> target, byte[] dex_file_bytes) {
+      super(target, new byte[0], dex_file_bytes);
+    }
+  }
+
   // A set of possible test configurations. Test should set this if they need to.
   // This must be kept in sync with the defines in ti-agent/common_helper.cc
   public static enum Config {
     COMMON_REDEFINE(0),
     COMMON_RETRANSFORM(1),
-    COMMON_TRANSFORM(2);
+    COMMON_TRANSFORM(2),
+    STRUCTURAL_TRANSFORM(3);
 
     private final int val;
     private Config(int val) {
@@ -90,5 +97,18 @@
                                                           byte[] dex_bytes);
 
   public static native void doCommonStructuralClassRedefinition(Class<?> target, byte[] dex_file);
+  public static void doMultiStructuralClassRedefinition(CommonClassDefinition... defs) {
+    ArrayList<Class<?>> classes = new ArrayList<>();
+    ArrayList<byte[]> dex_files = new ArrayList<>();
+
+    for (CommonClassDefinition d : defs) {
+      classes.add(d.target);
+      dex_files.add(d.dex_file_bytes);
+    }
+    doCommonMultiStructuralClassRedefinition(classes.toArray(new Class<?>[0]),
+                                             dex_files.toArray(new byte[0][]));
+  }
+  public static native void doCommonMultiStructuralClassRedefinition(Class<?>[] targets,
+                                                                     byte[][] dexfiles);
   public static native boolean isStructurallyModifiable(Class<?> target);
 }
diff --git a/test/knownfailures.json b/test/knownfailures.json
index 2e34943..049ff0d 100644
--- a/test/knownfailures.json
+++ b/test/knownfailures.json
@@ -1140,9 +1140,12 @@
                   "1985-structural-redefine-stack-scope",
                   "1986-structural-redefine-multi-thread-stack-scope",
                   "1987-structural-redefine-recursive-stack-scope",
+                  "1988-multi-structural-redefine",
                   "1989-transform-bad-monitor",
                   "1990-structural-bad-verify",
-                  "1992-retransform-no-such-field"
+                  "1991-hello-structural-retransform",
+                  "1992-retransform-no-such-field",
+                  "1993-fallback-non-structural"
                 ],
         "variant": "jvm",
         "description": ["Doesn't run on RI."]
diff --git a/test/ti-agent/jvmti_helper.cc b/test/ti-agent/jvmti_helper.cc
index ae1a8d3..22bc64a 100644
--- a/test/ti-agent/jvmti_helper.cc
+++ b/test/ti-agent/jvmti_helper.cc
@@ -238,6 +238,26 @@
   }
 }
 
+jint GetExtensionEventId(jvmtiEnv* jvmti, const std::string_view& name) {
+  jint n_ext = 0;
+  jint res = -1;
+  bool found_res = false;
+  jvmtiExtensionEventInfo* infos = nullptr;
+  CHECK_EQ(jvmti->GetExtensionEvents(&n_ext, &infos), JVMTI_ERROR_NONE);
+  for (jint i = 0; i < n_ext; i++) {
+    const jvmtiExtensionEventInfo& info = infos[i];
+    if (name == info.id) {
+      res = info.extension_event_index;
+      found_res = true;
+    }
+    DeallocParams(jvmti, info.params, info.param_count);
+    Dealloc(jvmti, info.short_description, info.id, info.params);
+  }
+  Dealloc(jvmti, infos);
+  CHECK(found_res);
+  return res;
+}
+
 void* GetExtensionFunctionVoid(JNIEnv* env, jvmtiEnv* jvmti, const std::string_view& name) {
   jint n_ext = 0;
   void* res = nullptr;
diff --git a/test/ti-agent/jvmti_helper.h b/test/ti-agent/jvmti_helper.h
index a3b9535..74d594f 100644
--- a/test/ti-agent/jvmti_helper.h
+++ b/test/ti-agent/jvmti_helper.h
@@ -94,6 +94,8 @@
   return reinterpret_cast<T>(GetExtensionFunctionVoid(env, jvmti, name));
 }
 
+jint GetExtensionEventId(jvmtiEnv* jvmti, const std::string_view& name);
+
 }  // namespace art
 
 #endif  // ART_TEST_TI_AGENT_JVMTI_HELPER_H_
diff --git a/test/ti-agent/redefinition_helper.cc b/test/ti-agent/redefinition_helper.cc
index 9d9f13f..0baa9fe 100644
--- a/test/ti-agent/redefinition_helper.cc
+++ b/test/ti-agent/redefinition_helper.cc
@@ -31,8 +31,13 @@
 
 namespace art {
 
+enum class RedefineType {
+  kNormal,
+  kStructural,
+};
+
 static void SetupCommonRedefine();
-static void SetupCommonRetransform();
+static void SetupCommonRetransform(RedefineType type);
 static void SetupCommonTransform();
 template <bool is_redefine>
 static void throwCommonRedefinitionError(jvmtiEnv* jvmti,
@@ -68,6 +73,7 @@
 #define CONFIGURATION_COMMON_REDEFINE 0
 #define CONFIGURATION_COMMON_RETRANSFORM 1
 #define CONFIGURATION_COMMON_TRANSFORM 2
+#define CONFIGURATION_STRUCTURAL_TRANSFORM 3
 
 extern "C" JNIEXPORT void JNICALL Java_art_Redefinition_nativeSetTestConfiguration(JNIEnv*,
                                                                                    jclass,
@@ -78,21 +84,54 @@
       return;
     }
     case CONFIGURATION_COMMON_RETRANSFORM: {
-      SetupCommonRetransform();
+      SetupCommonRetransform(RedefineType::kNormal);
       return;
     }
     case CONFIGURATION_COMMON_TRANSFORM: {
       SetupCommonTransform();
       return;
     }
+    case CONFIGURATION_STRUCTURAL_TRANSFORM: {
+      SetupCommonRetransform(RedefineType::kStructural);
+      return;
+    }
     default: {
       LOG(FATAL) << "Unknown test configuration: " << type;
     }
   }
 }
 
+template<RedefineType kType>
+static bool SupportsAndIsJVM() {
+  if constexpr (kType == RedefineType::kStructural) {
+    return false;
+  } else {
+    return IsJVM();
+  }
+}
+
+
 namespace common_redefine {
 
+template <RedefineType kType>
+static jvmtiError CallRedefineEntrypoint(JNIEnv* env,
+                                         jvmtiEnv* jvmti,
+                                         jint num_defs,
+                                         const jvmtiClassDefinition* defs) {
+  decltype(jvmti->functions->RedefineClasses) entrypoint = nullptr;
+  if constexpr (kType == RedefineType::kNormal) {
+    entrypoint = jvmti->functions->RedefineClasses;
+  } else {
+    entrypoint = GetExtensionFunction<decltype(entrypoint)>(
+        env, jvmti_env, "com.android.art.class.structurally_redefine_classes");
+  }
+  if (entrypoint == nullptr) {
+    LOG(INFO) << "Could not find entrypoint!";
+    return JVMTI_ERROR_NOT_AVAILABLE;
+  }
+  return entrypoint(jvmti, num_defs, defs);
+}
+
 static void throwRedefinitionError(jvmtiEnv* jvmti,
                                    JNIEnv* env,
                                    jint num_targets,
@@ -101,6 +140,7 @@
   return throwCommonRedefinitionError<true>(jvmti, env, num_targets, target, res);
 }
 
+template<RedefineType kType>
 static void DoMultiClassRedefine(jvmtiEnv* jvmti_env,
                                  JNIEnv* env,
                                  jint num_redefines,
@@ -109,24 +149,25 @@
                                  jbyteArray* dex_file_bytes) {
   std::vector<jvmtiClassDefinition> defs;
   for (jint i = 0; i < num_redefines; i++) {
-    jbyteArray desired_array = IsJVM() ? class_file_bytes[i] : dex_file_bytes[i];
+    jbyteArray desired_array = SupportsAndIsJVM<kType>() ? class_file_bytes[i] : dex_file_bytes[i];
     jint len = static_cast<jint>(env->GetArrayLength(desired_array));
     const unsigned char* redef_bytes = reinterpret_cast<const unsigned char*>(
         env->GetByteArrayElements(desired_array, nullptr));
     defs.push_back({targets[i], static_cast<jint>(len), redef_bytes});
   }
-  jvmtiError res = jvmti_env->RedefineClasses(num_redefines, defs.data());
+  jvmtiError res = CallRedefineEntrypoint<kType>(env, jvmti_env, num_redefines, defs.data());
   if (res != JVMTI_ERROR_NONE) {
     throwRedefinitionError(jvmti_env, env, num_redefines, targets, res);
   }
 }
 
+template<RedefineType kType>
 static void DoClassRedefine(jvmtiEnv* jvmti_env,
                             JNIEnv* env,
                             jclass target,
                             jbyteArray class_file_bytes,
                             jbyteArray dex_file_bytes) {
-  return DoMultiClassRedefine(jvmti_env, env, 1, &target, &class_file_bytes, &dex_file_bytes);
+  return DoMultiClassRedefine<kType>(jvmti_env, env, 1, &target, &class_file_bytes, &dex_file_bytes);
 }
 
 extern "C" JNIEXPORT jboolean JNICALL
@@ -145,25 +186,44 @@
 
 extern "C" JNIEXPORT void JNICALL Java_art_Redefinition_doCommonStructuralClassRedefinition(
     JNIEnv* env, jclass, jclass target, jbyteArray dex_file_bytes) {
-  using ArtStructurallyRedefineClassDirect =
-      jvmtiError (*)(jvmtiEnv * env, jclass k, jbyte* data, jint len);
-  ArtStructurallyRedefineClassDirect redef =
-      GetExtensionFunction<ArtStructurallyRedefineClassDirect>(
-          env, jvmti_env, "com.android.art.UNSAFE.class.structurally_redefine_class_direct");
-  if (redef == nullptr || env->ExceptionCheck()) {
-    return;
-  }
-  jint len = env->GetArrayLength(dex_file_bytes);
-  std::vector<jbyte> v(len, 0);
-  env->GetByteArrayRegion(dex_file_bytes, 0, len, v.data());
-  JvmtiErrorToException(env, jvmti_env, redef(jvmti_env, target, v.data(), len));
+  DoClassRedefine<RedefineType::kStructural>(jvmti_env, env, target, nullptr, dex_file_bytes);
 }
 
 // Magic JNI export that classes can use for redefining classes.
 // To use classes should declare this as a native function with signature (Ljava/lang/Class;[B[B)V
 extern "C" JNIEXPORT void JNICALL Java_art_Redefinition_doCommonClassRedefinition(
     JNIEnv* env, jclass, jclass target, jbyteArray class_file_bytes, jbyteArray dex_file_bytes) {
-  DoClassRedefine(jvmti_env, env, target, class_file_bytes, dex_file_bytes);
+  DoClassRedefine<RedefineType::kNormal>(jvmti_env, env, target, class_file_bytes, dex_file_bytes);
+}
+
+// Magic JNI export that classes can use for redefining classes.
+// To use classes should declare this as a native function with signature
+// ([Ljava/lang/Class;[[B[[B)V
+extern "C" JNIEXPORT void JNICALL Java_art_Redefinition_doCommonMultiStructuralClassRedefinition(
+    JNIEnv* env,
+    jclass,
+    jobjectArray targets,
+    jobjectArray dex_file_bytes) {
+  std::vector<jclass> classes;
+  std::vector<jbyteArray> class_files;
+  std::vector<jbyteArray> dex_files;
+  jint len = env->GetArrayLength(targets);
+  if (len != env->GetArrayLength(dex_file_bytes)) {
+    env->ThrowNew(env->FindClass("java/lang/IllegalArgumentException"),
+                  "the three array arguments passed to this function have different lengths!");
+    return;
+  }
+  for (jint i = 0; i < len; i++) {
+    classes.push_back(static_cast<jclass>(env->GetObjectArrayElement(targets, i)));
+    dex_files.push_back(static_cast<jbyteArray>(env->GetObjectArrayElement(dex_file_bytes, i)));
+    class_files.push_back(nullptr);
+  }
+  return DoMultiClassRedefine<RedefineType::kStructural>(jvmti_env,
+                                                         env,
+                                                         len,
+                                                         classes.data(),
+                                                         class_files.data(),
+                                                         dex_files.data());
 }
 
 // Magic JNI export that classes can use for redefining classes.
@@ -189,12 +249,12 @@
     dex_files.push_back(static_cast<jbyteArray>(env->GetObjectArrayElement(dex_file_bytes, i)));
     class_files.push_back(static_cast<jbyteArray>(env->GetObjectArrayElement(class_file_bytes, i)));
   }
-  return DoMultiClassRedefine(jvmti_env,
-                              env,
-                              len,
-                              classes.data(),
-                              class_files.data(),
-                              dex_files.data());
+  return DoMultiClassRedefine<RedefineType::kNormal>(jvmti_env,
+                                                     env,
+                                                     len,
+                                                     classes.data(),
+                                                     class_files.data(),
+                                                     dex_files.data());
 }
 
 // Get all capabilities except those related to retransformation.
@@ -380,7 +440,7 @@
     printf("Unable to get jvmti env!\n");
     return 1;
   }
-  SetupCommonRetransform();
+  SetupCommonRetransform(RedefineType::kNormal);
   return 0;
 }
 
@@ -409,11 +469,20 @@
   jvmti_env->AddCapabilities(&caps);
 }
 
-static void SetupCommonRetransform() {
+static void SetupCommonRetransform(RedefineType type) {
   SetStandardCapabilities(jvmti_env);
-  current_callbacks.ClassFileLoadHook = common_retransform::CommonClassFileLoadHookRetransformable;
-  jvmtiError res = jvmti_env->SetEventCallbacks(&current_callbacks, sizeof(current_callbacks));
-  CHECK_EQ(res, JVMTI_ERROR_NONE);
+  if (type == RedefineType::kNormal) {
+    current_callbacks.ClassFileLoadHook =
+        common_retransform::CommonClassFileLoadHookRetransformable;
+    jvmtiError res = jvmti_env->SetEventCallbacks(&current_callbacks, sizeof(current_callbacks));
+    CHECK_EQ(res, JVMTI_ERROR_NONE);
+  } else {
+    jvmtiError res = jvmti_env->SetExtensionEventCallback(
+        GetExtensionEventId(jvmti_env, "com.android.art.class.structural_dex_file_load_hook"),
+        reinterpret_cast<jvmtiExtensionEvent>(
+            common_retransform::CommonClassFileLoadHookRetransformable));
+    CHECK_EQ(res, JVMTI_ERROR_NONE);
+  }
   common_retransform::gTransformations.clear();
 }