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);