Revert "Revert "Basic structural redefinition support""

This reverts commit 5a2301d897294ff4ee6de71f459dc2566dc3fa1a.

Bug: 134162467

Reason for revert: Relanding as unclear if issue is due to topic.

Change-Id: Ib1d1cf2e9132e30c9649b760ae9ae2d8ceacf843
diff --git a/cmdline/cmdline_types.h b/cmdline/cmdline_types.h
index a83a0dc..bcf5336 100644
--- a/cmdline/cmdline_types.h
+++ b/cmdline/cmdline_types.h
@@ -597,6 +597,8 @@
         log_verbosity.image = true;
       } else if (verbose_options[j] == "systrace-locks") {
         log_verbosity.systrace_lock_logging = true;
+      } else if (verbose_options[j] == "plugin") {
+        log_verbosity.plugin = true;
       } else if (verbose_options[j] == "agents") {
         log_verbosity.agents = true;
       } else if (verbose_options[j] == "dex") {
diff --git a/libartbase/base/logging.h b/libartbase/base/logging.h
index 9d83e51..47c66bc 100644
--- a/libartbase/base/logging.h
+++ b/libartbase/base/logging.h
@@ -56,6 +56,7 @@
   bool systrace_lock_logging;  // Enabled with "-verbose:sys-locks".
   bool agents;
   bool dex;  // Some dex access output etc.
+  bool plugin;  // Used by some plugins.
 };
 
 // Global log verbosity setting, initialized by InitLogging.
diff --git a/libdexfile/dex/modifiers.h b/libdexfile/dex/modifiers.h
index 5caa402..918feb3 100644
--- a/libdexfile/dex/modifiers.h
+++ b/libdexfile/dex/modifiers.h
@@ -55,6 +55,9 @@
 // Used by a class to denote that the verifier has attempted to check it at least once.
 static constexpr uint32_t kAccVerificationAttempted = 0x00080000;  // class (runtime)
 static constexpr uint32_t kAccSkipHiddenapiChecks =   0x00100000;  // class (runtime)
+// Used by a class to denote that this class and any objects with this as a
+// declaring-class/super-class are to be considered obsolete, meaning they should not be used by.
+static constexpr uint32_t kAccObsoleteObject =        0x00200000;  // class (runtime)
 // This is set by the class linker during LinkInterfaceMethods. It is used by a method to represent
 // that it was copied from its declaring class into another class. All methods marked kAccMiranda
 // and kAccDefaultConflict will have this bit set. Any kAccDefault method contained in the methods_
diff --git a/openjdkjvmti/ti_extension.cc b/openjdkjvmti/ti_extension.cc
index 9666562..5dc7445 100644
--- a/openjdkjvmti/ti_extension.cc
+++ b/openjdkjvmti/ti_extension.cc
@@ -34,6 +34,8 @@
 
 #include "art_jvmti.h"
 #include "events.h"
+#include "jni_id_type.h"
+#include "runtime-inl.h"
 #include "ti_allocator.h"
 #include "ti_class.h"
 #include "ti_ddms.h"
@@ -41,6 +43,7 @@
 #include "ti_heap.h"
 #include "ti_logging.h"
 #include "ti_monitor.h"
+#include "ti_redefine.h"
 #include "ti_search.h"
 
 #include "thread-inl.h"
@@ -391,6 +394,65 @@
     return error;
   }
 
+  // These require index-ids and debuggable to function
+  art::Runtime* runtime = art::Runtime::Current();
+  if (runtime->GetJniIdType() == art::JniIdType::kIndices &&
+      (runtime->GetInstrumentation()->IsForcedInterpretOnly() || runtime->IsJavaDebuggable())) {
+    // IsStructurallyModifiableClass
+    error = add_extension(
+        reinterpret_cast<jvmtiExtensionFunction>(Redefiner::IsStructurallyModifiableClass),
+        "com.android.art.class.is_structurally_modifiable_class",
+        "Returns whether a class can potentially be 'structurally' redefined using the various"
+        " structural redefinition extensions provided.",
+        {
+          { "klass", JVMTI_KIND_IN, JVMTI_TYPE_JCLASS, false },
+          { "result", JVMTI_KIND_OUT, JVMTI_TYPE_JBOOLEAN, false },
+        },
+        {
+          ERR(INVALID_CLASS),
+          ERR(NULL_POINTER),
+        });
+    if (error != ERR(NONE)) {
+      return error;
+    }
+
+    // StructurallyRedefineClass
+    error = add_extension(
+        reinterpret_cast<jvmtiExtensionFunction>(Redefiner::StructurallyRedefineClassDirect),
+        "com.android.art.UNSAFE.class.structurally_redefine_class_direct",
+        "Temporary prototype entrypoint for redefining a single class structurally. Currently this"
+        " only supports adding new static fields to a class without any instances."
+        " ClassFileLoadHook events will NOT be triggered. This does not currently support creating"
+        " obsolete methods. This function only has rudimentary error checking. This should not be"
+        " used except for testing.",
+        {
+          { "klass", JVMTI_KIND_IN, JVMTI_TYPE_JCLASS, false },
+          { "new_def", JVMTI_KIND_IN_BUF, JVMTI_TYPE_CCHAR, false },
+          { "new_def_len", JVMTI_KIND_IN, JVMTI_TYPE_JINT, 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;
+    }
+  } 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_heap.cc b/openjdkjvmti/ti_heap.cc
index 81d1fc7..b0511a9 100644
--- a/openjdkjvmti/ti_heap.cc
+++ b/openjdkjvmti/ti_heap.cc
@@ -1618,11 +1618,10 @@
 
 namespace {
 
-using ArrayPtr = art::ObjPtr<art::mirror::Array>;
+using ObjectPtr = art::ObjPtr<art::mirror::Object>;
 
-static void ReplaceObjectReferences(ArrayPtr old_arr_ptr, ArrayPtr new_arr_ptr)
+static void ReplaceObjectReferences(ObjectPtr old_obj_ptr, ObjectPtr new_obj_ptr)
     REQUIRES(art::Locks::mutator_lock_,
-             art::Locks::user_code_suspension_lock_,
              art::Roles::uninterruptible_) {
   art::Runtime::Current()->GetHeap()->VisitObjectsPaused(
       [&](art::mirror::Object* ref) REQUIRES_SHARED(art::Locks::mutator_lock_) {
@@ -1630,21 +1629,31 @@
         class ResizeReferenceVisitor {
          public:
           using CompressedObj = art::mirror::CompressedReference<art::mirror::Object>;
-          ResizeReferenceVisitor(ArrayPtr old_arr, ArrayPtr new_arr)
-              : old_arr_(old_arr), new_arr_(new_arr) {}
+          ResizeReferenceVisitor(ObjectPtr old_arr, ObjectPtr new_arr)
+              : old_obj_(old_arr), new_obj_(new_arr) {}
 
-          // Ignore class roots. These do not need to be handled for arrays.
-          void VisitRootIfNonNull(CompressedObj* root ATTRIBUTE_UNUSED) const {}
-          void VisitRoot(CompressedObj* root ATTRIBUTE_UNUSED) const {}
+          // Ignore class roots.
+          void VisitRootIfNonNull(CompressedObj* root) const
+              REQUIRES_SHARED(art::Locks::mutator_lock_) {
+            if (root != nullptr) {
+              VisitRoot(root);
+            }
+          }
+          void VisitRoot(CompressedObj* root) const REQUIRES_SHARED(art::Locks::mutator_lock_) {
+            if (root->AsMirrorPtr() == old_obj_) {
+              root->Assign(new_obj_);
+              art::WriteBarrier::ForEveryFieldWrite(new_obj_);
+            }
+          }
 
           void operator()(art::ObjPtr<art::mirror::Object> obj,
                           art::MemberOffset off,
                           bool is_static ATTRIBUTE_UNUSED) const
               REQUIRES_SHARED(art::Locks::mutator_lock_) {
-            if (obj->GetFieldObject<art::mirror::Object>(off) == old_arr_) {
+            if (obj->GetFieldObject<art::mirror::Object>(off) == old_obj_) {
               LOG(DEBUG) << "Updating field at offset " << off.Uint32Value() << " of type "
                          << obj->GetClass()->PrettyClass();
-              obj->SetFieldObject</*transaction*/ false>(off, new_arr_);
+              obj->SetFieldObject</*transaction*/ false>(off, new_obj_);
             }
           }
 
@@ -1656,23 +1665,31 @@
           }
 
          private:
-          ArrayPtr old_arr_;
-          ArrayPtr new_arr_;
+          ObjectPtr old_obj_;
+          ObjectPtr new_obj_;
         };
 
-        ResizeReferenceVisitor rrv(old_arr_ptr, new_arr_ptr);
-        ref->VisitReferences(rrv, rrv);
+        ResizeReferenceVisitor rrv(old_obj_ptr, new_obj_ptr);
+        if (ref->IsClass()) {
+          // Class object native roots are the ArtField and ArtMethod 'declaring_class_' fields
+          // which we don't want to be messing with as it would break ref-visitor assumptions about
+          // what a class looks like. We want to keep the default behavior in other cases (such as
+          // dex-cache) though. Unfortunately there is no way to tell from the visitor where exactly
+          // the root came from.
+          // TODO It might be nice to have the visitors told where the reference came from.
+          ref->VisitReferences</*kVisitNativeRoots*/false>(rrv, rrv);
+        } else {
+          ref->VisitReferences</*kVisitNativeRoots*/true>(rrv, rrv);
+        }
       });
 }
 
-static void ReplaceStrongRoots(art::Thread* self, ArrayPtr old_arr_ptr, ArrayPtr new_arr_ptr)
-    REQUIRES(art::Locks::mutator_lock_,
-             art::Locks::user_code_suspension_lock_,
-             art::Roles::uninterruptible_) {
+static void ReplaceStrongRoots(art::Thread* self, ObjectPtr old_obj_ptr, ObjectPtr new_obj_ptr)
+    REQUIRES(art::Locks::mutator_lock_, art::Roles::uninterruptible_) {
   // replace root references expcept java frames.
   struct ResizeRootVisitor : public art::RootVisitor {
    public:
-    ResizeRootVisitor(ArrayPtr new_val, ArrayPtr old_val)
+    ResizeRootVisitor(ObjectPtr new_val, ObjectPtr old_val)
         : new_val_(new_val), old_val_(old_val) {}
 
     // TODO It's somewhat annoying to have to have this function implemented twice. It might be
@@ -1686,7 +1703,16 @@
           // Java frames might have the JIT doing optimizations (for example loop-unrolling or
           // eliding bounds checks) so we need deopt them once we're done here.
           if (info.GetType() == art::RootType::kRootJavaFrame) {
-            threads_with_roots_.insert(info.GetThreadId());
+            const art::JavaFrameRootInfo& jfri =
+                art::down_cast<const art::JavaFrameRootInfo&>(info);
+            if (jfri.GetVReg() == art::JavaFrameRootInfo::kMethodDeclaringClass) {
+              info.Describe(LOG_STREAM(INFO) << "Not changing declaring-class during stack walk. "
+                                                "Found obsolete java frame id ");
+              continue;
+            } else {
+              info.Describe(LOG_STREAM(INFO) << "Found java frame id ");
+              threads_with_roots_.insert(info.GetThreadId());
+            }
           }
           *obj = new_val_.Ptr();
         }
@@ -1703,7 +1729,16 @@
           // Java frames might have the JIT doing optimizations (for example loop-unrolling or
           // eliding bounds checks) so we need deopt them once we're done here.
           if (info.GetType() == art::RootType::kRootJavaFrame) {
-            threads_with_roots_.insert(info.GetThreadId());
+            const art::JavaFrameRootInfo& jfri =
+                art::down_cast<const art::JavaFrameRootInfo&>(info);
+            if (jfri.GetVReg() == art::JavaFrameRootInfo::kMethodDeclaringClass) {
+              info.Describe(LOG_STREAM(INFO) << "Not changing declaring-class during stack walk. "
+                                                "Found obsolete java frame id ");
+              continue;
+            } else {
+              info.Describe(LOG_STREAM(INFO) << "Found java frame id ");
+              threads_with_roots_.insert(info.GetThreadId());
+            }
           }
           obj->Assign(new_val_);
         }
@@ -1715,11 +1750,11 @@
     }
 
    private:
-    ArrayPtr new_val_;
-    ArrayPtr old_val_;
+    ObjectPtr new_val_;
+    ObjectPtr old_val_;
     std::unordered_set<uint32_t> threads_with_roots_;
   };
-  ResizeRootVisitor rrv(new_arr_ptr, old_arr_ptr);
+  ResizeRootVisitor rrv(new_obj_ptr, old_obj_ptr);
   art::Runtime::Current()->VisitRoots(&rrv, art::VisitRootFlags::kVisitRootFlagAllRoots);
   // Handle java Frames. Annoyingly the JIT can embed information about the length of the array into
   // the compiled code. By changing the length of the array we potentially invalidate these
@@ -1732,6 +1767,7 @@
       art::Thread* t = thread_list->FindThreadByThreadId(id);
       CHECK(t != nullptr) << "id " << id << " does not refer to a valid thread."
                           << " Where did the roots come from?";
+      LOG(DEBUG) << "Instrumenting thread stack of thread " << *t;
       // TODO Use deopt manager. We need a version that doesn't acquire all the locks we
       // already have.
       // TODO We technically only need to do this if the frames are not already being interpreted.
@@ -1743,11 +1779,9 @@
 
 static void ReplaceWeakRoots(art::Thread* self,
                              EventHandler* event_handler,
-                             ArrayPtr old_arr_ptr,
-                             ArrayPtr new_arr_ptr)
-    REQUIRES(art::Locks::mutator_lock_,
-             art::Locks::user_code_suspension_lock_,
-             art::Roles::uninterruptible_) {
+                             ObjectPtr old_obj_ptr,
+                             ObjectPtr new_obj_ptr)
+    REQUIRES(art::Locks::mutator_lock_, art::Roles::uninterruptible_) {
   // Handle tags. We want to do this seprately from other weak-refs (handled below) because we need
   // to send additional events and handle cases where the agent might have tagged the new
   // replacement object during the VMObjectAlloc. We do this by removing all tags associated with
@@ -1767,8 +1801,8 @@
     // Get the tags and clear them (so we don't need to special-case the normal weak-ref visitor)
     jlong new_tag = 0;
     jlong obsolete_tag = 0;
-    bool had_new_tag = env->object_tag_table->RemoveLocked(new_arr_ptr, &new_tag);
-    bool had_obsolete_tag = env->object_tag_table->RemoveLocked(old_arr_ptr, &obsolete_tag);
+    bool had_new_tag = env->object_tag_table->RemoveLocked(new_obj_ptr, &new_tag);
+    bool had_obsolete_tag = env->object_tag_table->RemoveLocked(old_obj_ptr, &obsolete_tag);
     // Dispatch event.
     if (had_obsolete_tag || had_new_tag) {
       event_handler->DispatchEventOnEnv<ArtJvmtiEvent::kObsoleteObjectCreated>(env,
@@ -1779,60 +1813,56 @@
       non_obsolete_tags[env] = new_tag;
     }
     // After weak-ref update we need to go back and re-add obsoletes. We wait to avoid having to
-    // deal with the visit-weaks overwriting the initial new_arr_ptr tag and generally making things
+    // deal with the visit-weaks overwriting the initial new_obj_ptr tag and generally making things
     // difficult.
     env->object_tag_table->Unlock();
   });
   // Handle weak-refs.
   struct ReplaceWeaksVisitor : public art::IsMarkedVisitor {
    public:
-    ReplaceWeaksVisitor(ArrayPtr old_arr, ArrayPtr new_arr)
-        : old_arr_(old_arr), new_arr_(new_arr) {}
+    ReplaceWeaksVisitor(ObjectPtr old_obj, ObjectPtr new_obj)
+        : old_obj_(old_obj), new_obj_(new_obj) {}
 
     art::mirror::Object* IsMarked(art::mirror::Object* obj)
         REQUIRES_SHARED(art::Locks::mutator_lock_) {
-      if (obj == old_arr_) {
-        return new_arr_.Ptr();
+      if (obj == old_obj_) {
+        return new_obj_.Ptr();
       } else {
         return obj;
       }
     }
 
    private:
-    ArrayPtr old_arr_;
-    ArrayPtr new_arr_;
+    ObjectPtr old_obj_;
+    ObjectPtr new_obj_;
   };
-  ReplaceWeaksVisitor rwv(old_arr_ptr, new_arr_ptr);
+  ReplaceWeaksVisitor rwv(old_obj_ptr, new_obj_ptr);
   art::Runtime::Current()->SweepSystemWeaks(&rwv);
-  // Re-add the object tags. At this point all weak-references to the old_arr_ptr are gone.
+  // Re-add the object tags. At this point all weak-references to the old_obj_ptr are gone.
   event_handler->ForEachEnv(self, [&](ArtJvmTiEnv* env) {
     // Cannot have REQUIRES(art::Locks::mutator_lock_) since ForEachEnv doesn't require it.
     art::Locks::mutator_lock_->AssertExclusiveHeld(self);
     env->object_tag_table->Lock();
     if (obsolete_tags.find(env) != obsolete_tags.end()) {
-      env->object_tag_table->SetLocked(old_arr_ptr, obsolete_tags[env]);
+      env->object_tag_table->SetLocked(old_obj_ptr, obsolete_tags[env]);
     }
     if (non_obsolete_tags.find(env) != non_obsolete_tags.end()) {
-      env->object_tag_table->SetLocked(new_arr_ptr, non_obsolete_tags[env]);
+      env->object_tag_table->SetLocked(new_obj_ptr, non_obsolete_tags[env]);
     }
     env->object_tag_table->Unlock();
   });
 }
 
-static void PerformArrayReferenceReplacement(art::Thread* self,
-                                             EventHandler* event_handler,
-                                             ArrayPtr old_arr_ptr,
-                                             ArrayPtr new_arr_ptr)
-    REQUIRES(art::Locks::mutator_lock_,
-             art::Locks::user_code_suspension_lock_,
-             art::Roles::uninterruptible_) {
-  ReplaceObjectReferences(old_arr_ptr, new_arr_ptr);
-  ReplaceStrongRoots(self, old_arr_ptr, new_arr_ptr);
-  ReplaceWeakRoots(self, event_handler, old_arr_ptr, new_arr_ptr);
-}
-
 }  // namespace
 
+void HeapExtensions::ReplaceReference(art::Thread* self,
+                                      art::ObjPtr<art::mirror::Object> old_obj_ptr,
+                                      art::ObjPtr<art::mirror::Object> new_obj_ptr) {
+  ReplaceObjectReferences(old_obj_ptr, new_obj_ptr);
+  ReplaceStrongRoots(self, old_obj_ptr, new_obj_ptr);
+  ReplaceWeakRoots(self, HeapExtensions::gEventHandler, old_obj_ptr, new_obj_ptr);
+}
+
 jvmtiError HeapExtensions::ChangeArraySize(jvmtiEnv* env, jobject arr, jsize new_size) {
   if (ArtJvmTiEnv::AsArtJvmTiEnv(env)->capabilities.can_tag_objects != 1) {
     return ERR(MUST_POSSESS_CAPABILITY);
@@ -1921,7 +1951,7 @@
       UNREACHABLE();
   }
   // Actually replace all the pointers.
-  PerformArrayReferenceReplacement(self, gEventHandler, old_arr.Get(), new_arr.Get());
+  ReplaceReference(self, old_arr.Get(), new_arr.Get());
   return OK;
 }
 
diff --git a/openjdkjvmti/ti_heap.h b/openjdkjvmti/ti_heap.h
index bf6fce6..2e27cc7 100644
--- a/openjdkjvmti/ti_heap.h
+++ b/openjdkjvmti/ti_heap.h
@@ -19,6 +19,16 @@
 
 #include "jvmti.h"
 
+#include "base/locks.h"
+
+namespace art {
+class Thread;
+template<typename T> class ObjPtr;
+namespace mirror {
+class Object;
+}  // namespace mirror
+}  // namespace art
+
 namespace openjdkjvmti {
 
 class EventHandler;
@@ -78,6 +88,12 @@
 
   static jvmtiError JNICALL ChangeArraySize(jvmtiEnv* env, jobject arr, jsize new_size);
 
+  static void ReplaceReference(art::Thread* self,
+                               art::ObjPtr<art::mirror::Object> original,
+                               art::ObjPtr<art::mirror::Object> replacement)
+      REQUIRES(art::Locks::mutator_lock_,
+               art::Roles::uninterruptible_);
+
  private:
   static EventHandler* gEventHandler;
 };
diff --git a/openjdkjvmti/ti_redefine.cc b/openjdkjvmti/ti_redefine.cc
index e720317..af16b4e 100644
--- a/openjdkjvmti/ti_redefine.cc
+++ b/openjdkjvmti/ti_redefine.cc
@@ -31,30 +31,51 @@
 
 #include "ti_redefine.h"
 
+#include <algorithm>
+#include <atomic>
 #include <iterator>
 #include <limits>
+#include <sstream>
 #include <string_view>
 #include <unordered_map>
 
 #include <android-base/logging.h>
 #include <android-base/stringprintf.h>
 
+#include "android-base/thread_annotations.h"
 #include "art_field-inl.h"
+#include "art_field.h"
 #include "art_jvmti.h"
 #include "art_method-inl.h"
+#include "art_method.h"
 #include "base/array_ref.h"
+#include "base/casts.h"
+#include "base/enums.h"
+#include "base/globals.h"
+#include "base/length_prefixed_array.h"
+#include "base/utils.h"
 #include "class_linker-inl.h"
+#include "class_linker.h"
 #include "class_root.h"
+#include "class_status.h"
 #include "debugger.h"
 #include "dex/art_dex_file_loader.h"
 #include "dex/class_accessor-inl.h"
+#include "dex/class_accessor.h"
 #include "dex/dex_file.h"
 #include "dex/dex_file_loader.h"
 #include "dex/dex_file_types.h"
+#include "dex/primitive.h"
 #include "dex/signature-inl.h"
+#include "dex/signature.h"
 #include "events-inl.h"
+#include "events.h"
 #include "gc/allocation_listener.h"
 #include "gc/heap.h"
+#include "gc/heap-inl.h"
+#include "gc/heap-visit-objects-inl.h"
+#include "handle.h"
+#include "handle_scope.h"
 #include "instrumentation.h"
 #include "intern_table.h"
 #include "jdwp/jdwp.h"
@@ -64,29 +85,56 @@
 #include "jit/jit.h"
 #include "jit/jit_code_cache.h"
 #include "jni/jni_env_ext-inl.h"
+#include "jni/jni_id_manager.h"
+#include "jvmti.h"
 #include "jvmti_allocator.h"
 #include "linear_alloc.h"
 #include "mirror/array-alloc-inl.h"
+#include "mirror/array.h"
 #include "mirror/class-alloc-inl.h"
 #include "mirror/class-inl.h"
+#include "mirror/class-refvisitor-inl.h"
+#include "mirror/class.h"
 #include "mirror/class_ext-inl.h"
+#include "mirror/dex_cache-inl.h"
+#include "mirror/dex_cache.h"
+#include "mirror/executable-inl.h"
+#include "mirror/field-inl.h"
+#include "mirror/method.h"
+#include "mirror/method_handle_impl-inl.h"
 #include "mirror/object.h"
 #include "mirror/object_array-alloc-inl.h"
 #include "mirror/object_array-inl.h"
+#include "mirror/object_array.h"
+#include "mirror/string.h"
+#include "mirror/var_handle-inl.h"
 #include "nativehelper/scoped_local_ref.h"
 #include "non_debuggable_classes.h"
+#include "obj_ptr.h"
 #include "object_lock.h"
 #include "runtime.h"
+#include "runtime_globals.h"
 #include "stack.h"
+#include "thread.h"
 #include "thread_list.h"
 #include "ti_breakpoint.h"
+#include "ti_class_definition.h"
 #include "ti_class_loader.h"
+#include "ti_heap.h"
+#include "ti_logging.h"
+#include "ti_thread.h"
 #include "transform.h"
 #include "verifier/class_verifier.h"
 #include "verifier/verifier_enums.h"
+#include "well_known_classes.h"
+#include "write_barrier.h"
 
 namespace openjdkjvmti {
 
+// Debug check to force us to directly check we saw all methods and fields exactly once directly.
+// Normally we don't need to do this since if any are missing the count will be different
+constexpr bool kCheckAllMethodsSeenOnce = art::kIsDebugBuild;
+
 using android::base::StringPrintf;
 
 // A helper that fills in a classes obsolete_methods_ and obsolete_dex_caches_ classExt fields as
@@ -286,9 +334,12 @@
   ObsoleteMap* obsolete_maps_;
 };
 
-jvmtiError Redefiner::IsModifiableClass(jvmtiEnv* env ATTRIBUTE_UNUSED,
-                                        jclass klass,
-                                        jboolean* is_redefinable) {
+template <RedefinitionType kType>
+jvmtiError
+Redefiner::IsModifiableClassGeneric(jvmtiEnv* env, jclass klass, jboolean* is_redefinable) {
+  if (env == nullptr) {
+    return ERR(INVALID_ENVIRONMENT);
+  }
   art::Thread* self = art::Thread::Current();
   art::ScopedObjectAccess soa(self);
   art::StackHandleScope<1> hs(self);
@@ -299,12 +350,24 @@
   art::Handle<art::mirror::Class> h_klass(hs.NewHandle(obj->AsClass()));
   std::string err_unused;
   *is_redefinable =
-      Redefiner::GetClassRedefinitionError(h_klass, &err_unused) != ERR(UNMODIFIABLE_CLASS)
-      ? JNI_TRUE : JNI_FALSE;
+      Redefiner::GetClassRedefinitionError<kType>(h_klass, &err_unused) != ERR(UNMODIFIABLE_CLASS)
+          ? JNI_TRUE
+          : JNI_FALSE;
   return OK;
 }
 
-jvmtiError Redefiner::GetClassRedefinitionError(jclass klass, /*out*/std::string* error_msg) {
+jvmtiError
+Redefiner::IsStructurallyModifiableClass(jvmtiEnv* env, jclass klass, jboolean* is_redefinable) {
+  return Redefiner::IsModifiableClassGeneric<RedefinitionType::kStructural>(
+      env, klass, is_redefinable);
+}
+
+jvmtiError Redefiner::IsModifiableClass(jvmtiEnv* env, jclass klass, jboolean* is_redefinable) {
+  return Redefiner::IsModifiableClassGeneric<RedefinitionType::kNormal>(env, klass, is_redefinable);
+}
+
+template <RedefinitionType kType>
+jvmtiError Redefiner::GetClassRedefinitionError(jclass klass, /*out*/ std::string* error_msg) {
   art::Thread* self = art::Thread::Current();
   art::ScopedObjectAccess soa(self);
   art::StackHandleScope<1> hs(self);
@@ -316,13 +379,15 @@
   return Redefiner::GetClassRedefinitionError(h_klass, error_msg);
 }
 
+template <RedefinitionType kType>
 jvmtiError Redefiner::GetClassRedefinitionError(art::Handle<art::mirror::Class> klass,
-                                                /*out*/std::string* error_msg) {
+                                                /*out*/ std::string* error_msg) {
+  art::Thread* self = art::Thread::Current();
   if (!klass->IsResolved()) {
     // It's only a problem to try to retransform/redefine a unprepared class if it's happening on
     // the same thread as the class-linking process. If it's on another thread we will be able to
     // wait for the preparation to finish and continue from there.
-    if (klass->GetLockOwnerThreadId() == art::Thread::Current()->GetThreadId()) {
+    if (klass->GetLockOwnerThreadId() == self->GetThreadId()) {
       *error_msg = "Modification of class " + klass->PrettyClass() +
           " from within the classes ClassLoad callback is not supported to prevent deadlocks." +
           " Please use ClassFileLoadHook directly instead.";
@@ -350,15 +415,106 @@
   }
 
   for (jclass c : art::NonDebuggableClasses::GetNonDebuggableClasses()) {
-    if (klass.Get() == art::Thread::Current()->DecodeJObject(c)->AsClass()) {
+    if (klass.Get() == self->DecodeJObject(c)->AsClass()) {
       *error_msg = "Class might have stack frames that cannot be made obsolete";
       return ERR(UNMODIFIABLE_CLASS);
     }
   }
 
+  if (kType == RedefinitionType::kStructural) {
+    art::StackHandleScope<2> hs(self);
+    art::Handle<art::mirror::ObjectArray<art::mirror::Class>> roots(
+        hs.NewHandle(art::Runtime::Current()->GetClassLinker()->GetClassRoots()));
+    art::MutableHandle<art::mirror::Class> obj(hs.NewHandle<art::mirror::Class>(nullptr));
+    for (int32_t i = 0; i < roots->GetLength(); i++) {
+      obj.Assign(roots->Get(i));
+      // check if the redefined class is a superclass of any root (i.e. mirror plus a few other
+      // important types).
+      if (klass->IsAssignableFrom(obj.Get())) {
+        std::string pc(klass->PrettyClass());
+        *error_msg = StringPrintf("Class %s is an important runtime class and cannot be "
+                                  "structurally redefined.",
+                                  pc.c_str());
+        return ERR(UNMODIFIABLE_CLASS);
+      }
+    }
+    // Check Thread specifically since it's not a root but too many things reach into it with Unsafe
+    // too allow structural redefinition.
+    if (klass->IsAssignableFrom(
+            self->DecodeJObject(art::WellKnownClasses::java_lang_Thread)->AsClass())) {
+      *error_msg =
+          "java.lang.Thread has fields accessed using sun.misc.unsafe directly. It is not "
+          "safe to structurally redefine it.";
+      return ERR(UNMODIFIABLE_CLASS);
+    }
+    // Check for already existing non-static fields/methods.
+    // TODO Remove this once we support generic method/field addition.
+    bool non_static_method = false;
+    klass->VisitMethods([&](art::ArtMethod* m) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+      // Since direct-methods (ie privates + <init> are not in any vtable/iftable we can update
+      // them).
+      if (!m->IsDirect()) {
+        non_static_method = true;
+        *error_msg = StringPrintf("%s has a non-direct function %s",
+                                  klass->PrettyClass().c_str(),
+                                  m->PrettyMethod().c_str());
+      }
+    }, art::kRuntimePointerSize);
+    if (non_static_method) {
+      return ERR(UNMODIFIABLE_CLASS);
+    }
+    bool non_static_field = false;
+    klass->VisitFields([&](art::ArtField* f) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+      if (!f->IsStatic()) {
+        non_static_field = true;
+        *error_msg = StringPrintf(
+            "%s has a non-static field %s", klass->PrettyClass().c_str(), f->PrettyField().c_str());
+      }
+    });
+    if (non_static_field) {
+      return ERR(UNMODIFIABLE_CLASS);
+    }
+    // Check for fields/methods which were returned before moving to index jni id type.
+    // TODO We might want to rework how this is done. Once full redefinition is implemented we will
+    // need to check any subtypes too.
+    art::ObjPtr<art::mirror::ClassExt> ext(klass->GetExtData());
+    if (!ext.IsNull()) {
+      bool non_index_id = false;
+      ext->VisitJFieldIDs([&](jfieldID id, uint32_t idx, bool is_static)
+          REQUIRES_SHARED(art::Locks::mutator_lock_) {
+        if (!art::jni::JniIdManager::IsIndexId(id)) {
+          non_index_id = true;
+          *error_msg =
+              StringPrintf("%s Field %d (%s) has non-index jni-ids.",
+                           (is_static ? "static" : "non-static"),
+                           idx,
+                           (is_static ? klass->GetStaticField(idx)
+                                      : klass->GetInstanceField(idx))->PrettyField().c_str());
+        }
+      });
+      ext->VisitJMethodIDs([&](jmethodID id, uint32_t idx)
+          REQUIRES_SHARED(art::Locks::mutator_lock_) {
+        if (!art::jni::JniIdManager::IsIndexId(id)) {
+          non_index_id = true;
+          *error_msg = StringPrintf(
+              "method %d (%s) has non-index jni-ids.",
+              idx,
+              klass->GetDeclaredMethodsSlice(art::kRuntimePointerSize)[idx].PrettyMethod().c_str());
+        }
+      });
+      if (non_index_id) {
+        return ERR(UNMODIFIABLE_CLASS);
+      }
+    }
+  }
   return OK;
 }
 
+template jvmtiError Redefiner::GetClassRedefinitionError<RedefinitionType::kNormal>(
+    art::Handle<art::mirror::Class> klass, /*out*/ std::string* error_msg);
+template jvmtiError Redefiner::GetClassRedefinitionError<RedefinitionType::kStructural>(
+    art::Handle<art::mirror::Class> klass, /*out*/ std::string* error_msg);
+
 // Moves dex data to an anonymous, read-only mmap'd region.
 art::MemMap Redefiner::MoveDataToMemMap(const std::string& original_location,
                                         art::ArrayRef<const unsigned char> data,
@@ -440,13 +596,47 @@
     // Something went wrong with transformation!
     return res;
   }
-  return RedefineClassesDirect(env, runtime, self, def_vector, error_msg);
+  return RedefineClassesDirect(
+      env, runtime, self, def_vector, RedefinitionType::kNormal, error_msg);
+}
+
+jvmtiError Redefiner::StructurallyRedefineClassDirect(jvmtiEnv* env,
+                                                      jclass klass,
+                                                      const unsigned char* data,
+                                                      jint data_size) {
+  if (env == nullptr) {
+    return ERR(INVALID_ENVIRONMENT);
+  } else if (ArtJvmTiEnv::AsArtJvmTiEnv(env)->capabilities.can_redefine_classes != 1) {
+    JVMTI_LOG(INFO, env) << "Does not have can_redefine_classes cap!";
+    return ERR(MUST_POSSESS_CAPABILITY);
+  }
+  std::vector<ArtClassDefinition> acds;
+  ArtClassDefinition acd;
+  jvmtiError err = acd.Init(
+      art::Thread::Current(),
+      jvmtiClassDefinition{ .klass = klass, .class_byte_count = data_size, .class_bytes = data });
+  if (err != OK) {
+    return err;
+  }
+  acds.push_back(std::move(acd));
+  std::string err_msg;
+  err = RedefineClassesDirect(ArtJvmTiEnv::AsArtJvmTiEnv(env),
+                              art::Runtime::Current(),
+                              art::Thread::Current(),
+                              acds,
+                              RedefinitionType::kStructural,
+                              &err_msg);
+  if (err != OK) {
+    JVMTI_LOG(WARNING, env) << "Failed structural redefinition: " << err_msg;
+  }
+  return err;
 }
 
 jvmtiError Redefiner::RedefineClassesDirect(ArtJvmTiEnv* env,
                                             art::Runtime* runtime,
                                             art::Thread* self,
                                             const std::vector<ArtClassDefinition>& definitions,
+                                            RedefinitionType type,
                                             std::string* error_msg) {
   DCHECK(env != nullptr);
   if (definitions.size() == 0) {
@@ -455,10 +645,11 @@
   }
   // Stop JIT for the duration of this redefine since the JIT might concurrently compile a method we
   // are going to redefine.
+  // TODO We should prevent user-code suspensions to make sure this isn't held for too long.
   art::jit::ScopedJitSuspend suspend_jit;
   // Get shared mutator lock so we can lock all the classes.
   art::ScopedObjectAccess soa(self);
-  Redefiner r(env, runtime, self, error_msg);
+  Redefiner r(env, runtime, self, type, error_msg);
   for (const ArtClassDefinition& def : definitions) {
     // Only try to transform classes that have been modified.
     if (def.IsModified()) {
@@ -617,6 +808,7 @@
 // TODO Rewrite so we can do this only once regardless of how many redefinitions there are.
 void Redefiner::ClassRedefinition::FindAndAllocateObsoleteMethods(
     art::ObjPtr<art::mirror::Class> art_klass) {
+  DCHECK(!IsStructuralRedefinition());
   art::ScopedAssertNoThreadSuspension ns("No thread suspension during thread stack walking");
   art::ObjPtr<art::mirror::ClassExt> ext = art_klass->GetExtData();
   CHECK(ext->GetObsoleteMethods() != nullptr);
@@ -657,35 +849,82 @@
   }
 }
 
-// Try and get the declared method. First try to get a virtual method then a direct method if that's
-// not found.
-static art::ArtMethod* FindMethod(art::Handle<art::mirror::Class> klass,
-                                  std::string_view name,
-                                  art::Signature sig) REQUIRES_SHARED(art::Locks::mutator_lock_) {
-  DCHECK(!klass->IsProxyClass());
-  for (art::ArtMethod& m : klass->GetDeclaredMethodsSlice(art::kRuntimePointerSize)) {
-    if (m.GetName() == name && m.GetSignature() == sig) {
-      return &m;
-    }
+namespace {
+template <typename T> struct SignatureType {};
+template <> struct SignatureType<art::ArtField> { using type = std::string_view; };
+template <> struct SignatureType<art::ArtMethod> { using type = art::Signature; };
+
+template <typename T> struct NameAndSignature {
+ public:
+  using SigType = typename SignatureType<T>::type;
+
+  NameAndSignature(const art::DexFile* dex_file, uint32_t id);
+
+  NameAndSignature(const std::string_view& name, const SigType& sig) : name_(name), sig_(sig) {}
+
+  bool operator==(const NameAndSignature<T>& o) {
+    return name_ == o.name_ && sig_ == o.sig_;
   }
-  return nullptr;
+
+  std::ostream& dump(std::ostream& os) const {
+    return os << "'" << name_ << "' (sig: " << sig_ << ")";
+  }
+
+  std::string ToString() const {
+    std::ostringstream os;
+    os << *this;
+    return os.str();
+  }
+
+  std::string_view name_;
+  SigType sig_;
+};
+
+template <typename T>
+std::ostream& operator<<(std::ostream& os, const NameAndSignature<T>& nas) {
+  return nas.dump(os);
 }
 
-bool Redefiner::ClassRedefinition::CheckSameMethods() {
+using FieldNameAndSignature = NameAndSignature<art::ArtField>;
+template <>
+FieldNameAndSignature::NameAndSignature(const art::DexFile* dex_file, uint32_t id)
+    : FieldNameAndSignature(dex_file->GetFieldName(dex_file->GetFieldId(id)),
+                            dex_file->GetFieldTypeDescriptor(dex_file->GetFieldId(id))) {}
+
+using MethodNameAndSignature = NameAndSignature<art::ArtMethod>;
+template <>
+MethodNameAndSignature::NameAndSignature(const art::DexFile* dex_file, uint32_t id)
+    : MethodNameAndSignature(dex_file->GetMethodName(dex_file->GetMethodId(id)),
+                             dex_file->GetMethodSignature(dex_file->GetMethodId(id))) {}
+
+}  // namespace
+
+void Redefiner::ClassRedefinition::RecordNewMethodAdded() {
+  DCHECK(driver_->IsStructuralRedefinition());
+  added_methods_ = true;
+}
+void Redefiner::ClassRedefinition::RecordNewFieldAdded() {
+  DCHECK(driver_->IsStructuralRedefinition());
+  added_fields_ = true;
+}
+
+bool Redefiner::ClassRedefinition::CheckMethods() {
   art::StackHandleScope<1> hs(driver_->self_);
   art::Handle<art::mirror::Class> h_klass(hs.NewHandle(GetMirrorClass()));
   DCHECK_EQ(dex_file_->NumClassDefs(), 1u);
 
-  // Make sure we have the same number of methods.
+  // Make sure we have the same number of methods (or the same or greater if we're structural).
   art::ClassAccessor accessor(*dex_file_, dex_file_->GetClassDef(0));
   uint32_t num_new_method = accessor.NumMethods();
   uint32_t num_old_method = h_klass->GetDeclaredMethodsSlice(art::kRuntimePointerSize).size();
-  if (num_new_method != num_old_method) {
+  const bool is_structural = driver_->IsStructuralRedefinition();
+  if (!is_structural && num_new_method != num_old_method) {
     bool bigger = num_new_method > num_old_method;
     RecordFailure(bigger ? ERR(UNSUPPORTED_REDEFINITION_METHOD_ADDED)
                          : ERR(UNSUPPORTED_REDEFINITION_METHOD_DELETED),
                   StringPrintf("Total number of declared methods changed from %d to %d",
-                               num_old_method, num_new_method));
+                               num_old_method,
+                               num_new_method));
     return false;
   }
 
@@ -693,38 +932,63 @@
   // Check each of the methods. NB we don't need to specifically check for removals since the 2 dex
   // files have the same number of methods, which means there must be an equal amount of additions
   // and removals. We should have already checked the fields.
-  for (const art::ClassAccessor::Method& method : accessor.GetMethods()) {
+  const art::DexFile& old_dex_file = h_klass->GetDexFile();
+  art::ClassAccessor old_accessor(old_dex_file, *h_klass->GetClassDef());
+  // We need this to check for methods going missing in structural cases.
+  std::vector<bool> seen_old_methods(
+      (kCheckAllMethodsSeenOnce || is_structural) ? old_accessor.NumMethods() : 0, false);
+  const auto old_methods = old_accessor.GetMethods();
+  for (const art::ClassAccessor::Method& new_method : accessor.GetMethods()) {
     // Get the data on the method we are searching for
-    const art::dex::MethodId& new_method_id = dex_file_->GetMethodId(method.GetIndex());
-    const char* new_method_name = dex_file_->GetMethodName(new_method_id);
-    art::Signature new_method_signature = dex_file_->GetMethodSignature(new_method_id);
-    art::ArtMethod* old_method = FindMethod(h_klass, new_method_name, new_method_signature);
-    // If we got past the check for the same number of methods above that means there must be at
-    // least one added and one removed method. We will return the ADDED failure message since it is
-    // easier to get a useful error report for it.
-    if (old_method == nullptr) {
-      RecordFailure(ERR(UNSUPPORTED_REDEFINITION_METHOD_ADDED),
-                    StringPrintf("Unknown method '%s' (sig: %s) was added!",
-                                  new_method_name,
-                                  new_method_signature.ToString().c_str()));
+    MethodNameAndSignature new_method_id(dex_file_.get(), new_method.GetIndex());
+    const auto old_iter =
+        std::find_if(old_methods.cbegin(), old_methods.cend(), [&](const auto& current_old_method) {
+          MethodNameAndSignature old_method_id(&old_dex_file, current_old_method.GetIndex());
+          return old_method_id == new_method_id;
+        });
+
+    if (old_iter == old_methods.cend()) {
+      // TODO Support adding non-static methods.
+      if (is_structural && new_method.IsStaticOrDirect()) {
+        RecordNewMethodAdded();
+      } else {
+        RecordFailure(
+            ERR(UNSUPPORTED_REDEFINITION_METHOD_ADDED),
+            StringPrintf("Unknown virtual method %s was added!", new_method_id.ToString().c_str()));
+        return false;
+      }
+    } else if (new_method.GetAccessFlags() != old_iter->GetAccessFlags()) {
+      RecordFailure(
+          ERR(UNSUPPORTED_REDEFINITION_METHOD_MODIFIERS_CHANGED),
+          StringPrintf("method %s had different access flags", new_method_id.ToString().c_str()));
       return false;
+    } else if (kCheckAllMethodsSeenOnce || is_structural) {
+      // We only need this if we are structural.
+      size_t off = std::distance(old_methods.cbegin(), old_iter);
+      DCHECK(!seen_old_methods[off])
+          << "field at " << off << "("
+          << MethodNameAndSignature(&old_dex_file, old_iter->GetIndex()) << ") already seen?";
+      seen_old_methods[off] = true;
     }
-    // Since direct methods have different flags than virtual ones (specifically direct methods must
-    // have kAccPrivate or kAccStatic or kAccConstructor flags) we can tell if a method changes from
-    // virtual to direct.
-    uint32_t new_flags = method.GetAccessFlags();
-    if (new_flags != (old_method->GetAccessFlags() & art::kAccValidMethodFlags)) {
-      RecordFailure(ERR(UNSUPPORTED_REDEFINITION_METHOD_MODIFIERS_CHANGED),
-                    StringPrintf("method '%s' (sig: %s) had different access flags",
-                                 new_method_name,
-                                 new_method_signature.ToString().c_str()));
-      return false;
-    }
+  }
+  if ((kCheckAllMethodsSeenOnce || is_structural) &&
+      !std::all_of(seen_old_methods.cbegin(), seen_old_methods.cend(), [](auto x) { return x; })) {
+    DCHECK(is_structural) << "We should have hit an earlier failure before getting here!";
+    auto first_fail =
+        std::find_if(seen_old_methods.cbegin(), seen_old_methods.cend(), [](auto x) { return !x; });
+    auto off = std::distance(seen_old_methods.cbegin(), first_fail);
+    auto fail = old_methods.cbegin();
+    std::advance(fail, off);
+    RecordFailure(
+        ERR(UNSUPPORTED_REDEFINITION_METHOD_DELETED),
+        StringPrintf("Method %s missing!",
+                     FieldNameAndSignature(&old_dex_file, fail->GetIndex()).ToString().c_str()));
+    return false;
   }
   return true;
 }
 
-bool Redefiner::ClassRedefinition::CheckSameFields() {
+bool Redefiner::ClassRedefinition::CheckFields() {
   art::StackHandleScope<1> hs(driver_->self_);
   art::Handle<art::mirror::Class> h_klass(hs.NewHandle(GetMirrorClass()));
   DCHECK_EQ(dex_file_->NumClassDefs(), 1u);
@@ -734,58 +998,48 @@
   art::ClassAccessor old_accessor(old_dex_file, *h_klass->GetClassDef());
   // Instance and static fields can be differentiated by their flags so no need to check them
   // separately.
-  auto old_fields = old_accessor.GetFields();
-  auto old_iter = old_fields.begin();
+  std::vector<bool> seen_old_fields(old_accessor.NumFields(), false);
+  const auto old_fields = old_accessor.GetFields();
   for (const art::ClassAccessor::Field& new_field : new_accessor.GetFields()) {
     // Get the data on the method we are searching for
-    const art::dex::FieldId& new_field_id = dex_file_->GetFieldId(new_field.GetIndex());
-    const char* new_field_name = dex_file_->GetFieldName(new_field_id);
-    const char* new_field_type = dex_file_->GetFieldTypeDescriptor(new_field_id);
-
-    if (old_iter == old_fields.end()) {
-      // We are missing the old version of this method!
-      RecordFailure(ERR(UNSUPPORTED_REDEFINITION_SCHEMA_CHANGED),
-                    StringPrintf("Unknown field '%s' (type: %s) added!",
-                                  new_field_name,
-                                  new_field_type));
+    FieldNameAndSignature new_field_id(dex_file_.get(), new_field.GetIndex());
+    const auto old_iter =
+        std::find_if(old_fields.cbegin(), old_fields.cend(), [&](const auto& old_iter) {
+          FieldNameAndSignature old_field_id(&old_dex_file, old_iter.GetIndex());
+          return old_field_id == new_field_id;
+        });
+    if (old_iter == old_fields.cend()) {
+      // TODO Support adding non-static fields.
+      if (driver_->IsStructuralRedefinition() && new_field.IsStatic()) {
+        RecordNewFieldAdded();
+      } else {
+        RecordFailure(ERR(UNSUPPORTED_REDEFINITION_SCHEMA_CHANGED),
+                      StringPrintf("Unknown field %s added!", new_field_id.ToString().c_str()));
+        return false;
+      }
+    } else if (new_field.GetAccessFlags() != old_iter->GetAccessFlags()) {
+      RecordFailure(
+          ERR(UNSUPPORTED_REDEFINITION_SCHEMA_CHANGED),
+          StringPrintf("Field %s had different access flags", new_field_id.ToString().c_str()));
       return false;
+    } else {
+      size_t off = std::distance(old_fields.cbegin(), old_iter);
+      DCHECK(!seen_old_fields[off])
+          << "field at " << off << "(" << FieldNameAndSignature(&old_dex_file, old_iter->GetIndex())
+          << ") already seen?";
+      seen_old_fields[off] = true;
     }
-
-    const art::dex::FieldId& old_field_id = old_dex_file.GetFieldId(old_iter->GetIndex());
-    const char* old_field_name = old_dex_file.GetFieldName(old_field_id);
-    const char* old_field_type = old_dex_file.GetFieldTypeDescriptor(old_field_id);
-
-    // Check name and type.
-    if (strcmp(old_field_name, new_field_name) != 0 ||
-        strcmp(old_field_type, new_field_type) != 0) {
-      RecordFailure(ERR(UNSUPPORTED_REDEFINITION_SCHEMA_CHANGED),
-                    StringPrintf("Field changed from '%s' (sig: %s) to '%s' (sig: %s)!",
-                                  old_field_name,
-                                  old_field_type,
-                                  new_field_name,
-                                  new_field_type));
-      return false;
-    }
-
-    // Since static fields have different flags than instance ones (specifically static fields must
-    // have the kAccStatic flag) we can tell if a field changes from static to instance.
-    if (new_field.GetAccessFlags() != old_iter->GetAccessFlags()) {
-      RecordFailure(ERR(UNSUPPORTED_REDEFINITION_SCHEMA_CHANGED),
-                    StringPrintf("Field '%s' (sig: %s) had different access flags",
-                                  new_field_name,
-                                  new_field_type));
-      return false;
-    }
-
-    ++old_iter;
   }
-  if (old_iter != old_fields.end()) {
-    RecordFailure(ERR(UNSUPPORTED_REDEFINITION_SCHEMA_CHANGED),
-                  StringPrintf("field '%s' (sig: %s) is missing!",
-                                old_dex_file.GetFieldName(old_dex_file.GetFieldId(
-                                    old_iter->GetIndex())),
-                                old_dex_file.GetFieldTypeDescriptor(old_dex_file.GetFieldId(
-                                    old_iter->GetIndex()))));
+  if (!std::all_of(seen_old_fields.cbegin(), seen_old_fields.cend(), [](auto x) { return x; })) {
+    auto first_fail =
+        std::find_if(seen_old_fields.cbegin(), seen_old_fields.cend(), [](auto x) { return !x; });
+    auto off = std::distance(seen_old_fields.cbegin(), first_fail);
+    auto fail = old_fields.cbegin();
+    std::advance(fail, off);
+    RecordFailure(
+        ERR(UNSUPPORTED_REDEFINITION_SCHEMA_CHANGED),
+        StringPrintf("Field %s is missing!",
+                     FieldNameAndSignature(&old_dex_file, fail->GetIndex()).ToString().c_str()));
     return false;
   }
   return true;
@@ -842,6 +1096,7 @@
   const art::dex::TypeList* interfaces = dex_file_->GetInterfacesList(def);
   if (interfaces == nullptr) {
     if (current_class->NumDirectInterfaces() != 0) {
+      // TODO Support this for kStructural.
       RecordFailure(ERR(UNSUPPORTED_REDEFINITION_HIERARCHY_CHANGED), "Interfaces added");
       return false;
     }
@@ -849,6 +1104,7 @@
     DCHECK(!current_class->IsProxyClass());
     const art::dex::TypeList* current_interfaces = current_class->GetInterfaceTypeList();
     if (current_interfaces == nullptr || current_interfaces->Size() != interfaces->Size()) {
+      // TODO Support this for kStructural.
       RecordFailure(ERR(UNSUPPORTED_REDEFINITION_HIERARCHY_CHANGED), "Interfaces added or removed");
       return false;
     }
@@ -872,7 +1128,15 @@
   art::StackHandleScope<1> hs(driver_->self_);
 
   art::Handle<art::mirror::Class> h_klass(hs.NewHandle(GetMirrorClass()));
-  jvmtiError res = Redefiner::GetClassRedefinitionError(h_klass, &err);
+  jvmtiError res;
+  switch (driver_->type_) {
+  case RedefinitionType::kNormal:
+    res = Redefiner::GetClassRedefinitionError<RedefinitionType::kNormal>(h_klass, &err);
+    break;
+  case RedefinitionType::kStructural:
+    res = Redefiner::GetClassRedefinitionError<RedefinitionType::kStructural>(h_klass, &err);
+    break;
+  }
   if (res != OK) {
     RecordFailure(res, err);
     return false;
@@ -882,10 +1146,7 @@
 }
 
 bool Redefiner::ClassRedefinition::CheckRedefinitionIsValid() {
-  return CheckRedefinable() &&
-      CheckClass() &&
-      CheckSameFields() &&
-      CheckSameMethods();
+  return CheckRedefinable() && CheckClass() && CheckFields() && CheckMethods();
 }
 
 class RedefinitionDataIter;
@@ -904,9 +1165,10 @@
     kSlotOrigDexFile = 5,
     kSlotOldObsoleteMethods = 6,
     kSlotOldDexCaches = 7,
+    kSlotNewClassObject = 8,
 
     // Must be last one.
-    kNumSlots = 8,
+    kNumSlots = 9,
   };
 
   // This needs to have a HandleScope passed in that is capable of creating a new Handle without
@@ -967,6 +1229,11 @@
         GetSlot(klass_index, kSlotOldDexCaches));
   }
 
+  art::ObjPtr<art::mirror::Class> GetNewClassObject(jint klass_index) const
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    return art::ObjPtr<art::mirror::Class>::DownCast(GetSlot(klass_index, kSlotNewClassObject));
+  }
+
   void SetSourceClassLoader(jint klass_index, art::ObjPtr<art::mirror::ClassLoader> loader)
       REQUIRES_SHARED(art::Locks::mutator_lock_) {
     SetSlot(klass_index, kSlotSourceClassLoader, loader);
@@ -1001,6 +1268,11 @@
     SetSlot(klass_index, kSlotOldDexCaches, caches);
   }
 
+  void SetNewClassObject(jint klass_index, art::ObjPtr<art::mirror::Class> klass)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    SetSlot(klass_index, kSlotNewClassObject, klass);
+  }
+
   int32_t Length() const REQUIRES_SHARED(art::Locks::mutator_lock_) {
     return arr_->GetLength() / kNumSlots;
   }
@@ -1126,6 +1398,11 @@
     return holder_.GetOldDexCaches(idx_);
   }
 
+  art::ObjPtr<art::mirror::Class> GetNewClassObject() const
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    return holder_.GetNewClassObject(idx_);
+  }
+
   int32_t GetIndex() const {
     return idx_;
   }
@@ -1162,6 +1439,10 @@
       REQUIRES_SHARED(art::Locks::mutator_lock_) {
     holder_.SetOldDexCaches(idx_, caches);
   }
+  void SetNewClassObject(art::ObjPtr<art::mirror::Class> klass)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    holder_.SetNewClassObject(idx_, klass);
+  }
 
  private:
   int32_t idx_;
@@ -1260,7 +1541,7 @@
 bool Redefiner::ClassRedefinition::FinishRemainingAllocations(
     /*out*/RedefinitionDataIter* cur_data) {
   art::ScopedObjectAccessUnchecked soa(driver_->self_);
-  art::StackHandleScope<2> hs(driver_->self_);
+  art::StackHandleScope<4> hs(driver_->self_);
   cur_data->SetMirrorClass(GetMirrorClass());
   // This shouldn't allocate
   art::Handle<art::mirror::ClassLoader> loader(hs.NewHandle(GetClassLoader()));
@@ -1298,9 +1579,132 @@
     RecordFailure(ERR(OUT_OF_MEMORY), "Unable to allocate array for original dex file");
     return false;
   }
+  if (added_fields_ || added_methods_) {
+    art::Handle<art::mirror::Class> nc(hs.NewHandle(
+        AllocateNewClassObject(hs.NewHandle(cur_data->GetNewDexCache()))));
+    if (nc.IsNull()) {
+      driver_->self_->ClearException();
+      RecordFailure(ERR(OUT_OF_MEMORY), "Unable to allocate new class object");
+      return false;
+    }
+
+    cur_data->SetNewClassObject(nc.Get());
+  }
   return true;
 }
 
+uint32_t Redefiner::ClassRedefinition::GetNewClassSize(bool with_embedded_tables,
+                                                       art::Handle<art::mirror::Class> old_klass) {
+  // TODO Once we can add methods this won't work any more.
+  uint32_t num_vtable_entries = old_klass->GetVTableLength();
+  uint32_t num_8bit_static_fields = 0;
+  uint32_t num_16bit_static_fields = 0;
+  uint32_t num_32bit_static_fields = 0;
+  uint32_t num_64bit_static_fields = 0;
+  uint32_t num_ref_static_fields = 0;
+  art::ClassAccessor accessor(*dex_file_, dex_file_->GetClassDef(0));
+  for (const art::ClassAccessor::Field& f : accessor.GetStaticFields()) {
+    std::string_view desc(dex_file_->GetFieldTypeDescriptor(dex_file_->GetFieldId(f.GetIndex())));
+    if (desc[0] == 'L' || desc[0] == '[') {
+      num_ref_static_fields++;
+    } else if (desc == "Z" || desc == "B") {
+      num_8bit_static_fields++;
+    } else if (desc == "C" || desc == "S") {
+      num_16bit_static_fields++;
+    } else if (desc == "I" || desc == "F") {
+      num_32bit_static_fields++;
+    } else if (desc == "J" || desc == "D") {
+      num_64bit_static_fields++;
+    } else {
+      LOG(FATAL) << "Unknown type descriptor! " << desc;
+    }
+  }
+
+  return art::mirror::Class::ComputeClassSize(with_embedded_tables,
+                                              with_embedded_tables ? num_vtable_entries : 0,
+                                              num_8bit_static_fields,
+                                              num_16bit_static_fields,
+                                              num_32bit_static_fields,
+                                              num_64bit_static_fields,
+                                              num_ref_static_fields,
+                                              art::kRuntimePointerSize);
+}
+
+art::ObjPtr<art::mirror::Class>
+Redefiner::ClassRedefinition::AllocateNewClassObject(art::Handle<art::mirror::DexCache> cache) {
+  // This is a stripped down DefineClass. We don't want to use DefineClass directly because it needs
+  // to perform a lot of extra steps to tell the ClassTable and the jit and everything about a new
+  // class. For now we will need to rely on our tests catching any issues caused by changes in how
+  // class_linker sets up classes.
+  // TODO Unify/move this into ClassLinker maybe.
+  art::StackHandleScope<5> hs(driver_->self_);
+  art::ClassLinker* linker = driver_->runtime_->GetClassLinker();
+  art::Handle<art::mirror::Class> old_class(hs.NewHandle(GetMirrorClass()));
+  art::Handle<art::mirror::Class> new_class(hs.NewHandle(linker->AllocClass(
+      driver_->self_, GetNewClassSize(/*with_embedded_tables=*/false, old_class))));
+  if (new_class.IsNull()) {
+    driver_->self_->AssertPendingOOMException();
+    JVMTI_LOG(ERROR, driver_->env_) << "Unable to allocate new class object!";
+    return nullptr;
+  }
+  new_class->SetDexCache(cache.Get());
+  linker->SetupClass(*dex_file_, dex_file_->GetClassDef(0), new_class, old_class->GetClassLoader());
+
+  // Make sure we are ready for linking. The lock isn't really needed since this isn't visible to
+  // other threads but the linker expects it.
+  art::ObjectLock<art::mirror::Class> lock(driver_->self_, new_class);
+  new_class->SetClinitThreadId(driver_->self_->GetTid());
+  // Make sure we have a valid empty iftable even if there are errors.
+  new_class->SetIfTable(art::GetClassRoot<art::mirror::Object>(linker)->GetIfTable());
+  linker->LoadClass(driver_->self_, *dex_file_, dex_file_->GetClassDef(0), new_class);
+  // NB. We know the interfaces and supers didn't change! :)
+  art::MutableHandle<art::mirror::Class> linked_class(hs.NewHandle<art::mirror::Class>(nullptr));
+  art::Handle<art::mirror::ObjectArray<art::mirror::Class>> proxy_ifaces(
+      hs.NewHandle<art::mirror::ObjectArray<art::mirror::Class>>(nullptr));
+  // No changing hierarchy so everything is loaded.
+  new_class->SetSuperClass(old_class->GetSuperClass());
+  art::mirror::Class::SetStatus(new_class, art::ClassStatus::kLoaded, nullptr);
+  if (!linker->LinkClass(driver_->self_, nullptr, new_class, proxy_ifaces, &linked_class)) {
+    JVMTI_LOG(ERROR, driver_->env_)
+        << "failed to link class due to "
+        << (driver_->self_->IsExceptionPending() ? driver_->self_->GetException()->Dump()
+                                                 : " unknown");
+    driver_->self_->ClearException();
+    return nullptr;
+  }
+  // We will initialize it manually.
+  art::ObjectLock<art::mirror::Class> objlock(driver_->self_, linked_class);
+  // We already verified the class earlier. No need to do it again.
+  linked_class->SetVerificationAttempted();
+  linked_class->SetStatus(linked_class, art::ClassStatus::kVisiblyInitialized, driver_->self_);
+  // Make sure we have ext-data space for method & field ids. We won't know if we need them until
+  // it's too late to create them.
+  // TODO We might want to remove these arrays if they're not needed.
+  if (art::mirror::Class::GetOrCreateInstanceFieldIds(linked_class).IsNull() ||
+      art::mirror::Class::GetOrCreateStaticFieldIds(linked_class).IsNull() ||
+      art::mirror::Class::GetOrCreateMethodIds(linked_class).IsNull()) {
+    driver_->self_->AssertPendingOOMException();
+    driver_->self_->ClearException();
+    JVMTI_LOG(ERROR, driver_->env_) << "Unable to allocate jni-id arrays!";
+    return nullptr;
+  }
+  // Finish setting up methods.
+  linked_class->VisitMethods([&](art::ArtMethod* m) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    linker->SetEntryPointsToInterpreter(m);
+    m->SetNotIntrinsic();
+    DCHECK(m->IsCopied() || m->GetDeclaringClass() == linked_class.Get())
+        << m->PrettyMethod()
+        << " m->GetDeclaringClass(): " << m->GetDeclaringClass()->PrettyClass()
+        << " != linked_class.Get(): " << linked_class->PrettyClass();
+  }, art::kRuntimePointerSize);
+  if (art::kIsDebugBuild) {
+    linked_class->VisitFields([&](art::ArtField* f) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+      DCHECK_EQ(f->GetDeclaringClass(), linked_class.Get());
+    });
+  }
+  return linked_class.Get();
+}
+
 void Redefiner::ClassRedefinition::UnregisterJvmtiBreakpoints() {
   BreakpointUtil::RemoveBreakpointsInClass(driver_->env_, GetMirrorClass().Ptr());
 }
@@ -1443,10 +1847,7 @@
     if (data.GetSourceClassLoader() != nullptr) {
       ClassLoaderHelper::UpdateJavaDexFile(data.GetJavaDexFile(), data.GetNewDexFileCookie());
     }
-    art::ObjPtr<art::mirror::Class> klass = data.GetMirrorClass();
-    // TODO Rewrite so we don't do a stack walk for each and every class.
-    redef.FindAndAllocateObsoleteMethods(klass);
-    redef.UpdateClass(klass, data.GetNewDexCache(), data.GetOriginalDexFile());
+    redef.UpdateClass(data);
   }
   RestoreObsoleteMethodMapsIfUnneeded(holder);
   // TODO We should check for if any of the redefined methods are intrinsic methods here and, if any
@@ -1514,11 +1915,372 @@
   }
 }
 
-// Performs updates to class that will allow us to verify it.
-void Redefiner::ClassRedefinition::UpdateClass(
-    art::ObjPtr<art::mirror::Class> mclass,
-    art::ObjPtr<art::mirror::DexCache> new_dex_cache,
-    art::ObjPtr<art::mirror::Object> original_dex_file) {
+void Redefiner::ClassRedefinition::CollectNewFieldAndMethodMappings(
+    const RedefinitionDataIter& data,
+    std::map<art::ArtMethod*, art::ArtMethod*>* method_map,
+    std::map<art::ArtField*, art::ArtField*>* field_map) {
+  art::ObjPtr<art::mirror::Class> old_cls(data.GetMirrorClass());
+  art::ObjPtr<art::mirror::Class> new_cls(data.GetNewClassObject());
+  for (art::ArtField& f : old_cls->GetSFields()) {
+    (*field_map)[&f] = new_cls->FindDeclaredStaticField(f.GetName(), f.GetTypeDescriptor());
+  }
+  for (art::ArtField& f : old_cls->GetIFields()) {
+    (*field_map)[&f] = new_cls->FindDeclaredInstanceField(f.GetName(), f.GetTypeDescriptor());
+  }
+  auto new_methods = new_cls->GetMethods(art::kRuntimePointerSize);
+  for (art::ArtMethod& m : old_cls->GetMethods(art::kRuntimePointerSize)) {
+    // No support for finding methods in this way since it's generally not needed. Just do it the
+    // easy way.
+    auto nm_iter = std::find_if(
+        new_methods.begin(),
+        new_methods.end(),
+        [&](art::ArtMethod& cand) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+          return cand.GetNameView() == m.GetNameView() && cand.GetSignature() == m.GetSignature();
+        });
+    CHECK(nm_iter != new_methods.end())
+        << "Could not find redefined version of " << m.PrettyMethod();
+    (*method_map)[&m] = &(*nm_iter);
+  }
+}
+
+namespace {
+
+template <typename T>
+struct FuncVisitor : public art::ClassVisitor {
+ public:
+  explicit FuncVisitor(T f) : f_(f) {}
+  bool operator()(art::ObjPtr<art::mirror::Class> k) override REQUIRES(art::Locks::mutator_lock_) {
+    return f_(*this, k);
+  }
+
+ private:
+  T f_;
+};
+
+// TODO We should put this in Runtime once we have full ArtMethod/ArtField updating.
+template <typename FieldVis, typename MethodVis>
+void VisitReflectiveObjects(art::Thread* self,
+                            art::gc::Heap* heap,
+                            FieldVis&& fv,
+                            MethodVis&& mv) REQUIRES(art::Locks::mutator_lock_) {
+  // Horray for captures!
+  auto get_visitor = [&mv, &fv](const char* desc) REQUIRES(art::Locks::mutator_lock_) {
+    return [&mv, &fv, desc](auto* v) REQUIRES(art::Locks::mutator_lock_) {
+      if constexpr (std::is_same_v<decltype(v), art::ArtMethod*>) {
+        return mv(v, desc);
+      } else {
+        static_assert(std::is_same_v<decltype(v), art::ArtField*>,
+                      "Visitor called with unexpected type");
+        return fv(v, desc);
+      }
+    };
+  };
+  heap->VisitObjectsPaused(
+    [&](art::mirror::Object* ref) NO_THREAD_SAFETY_ANALYSIS {
+      art::Locks::mutator_lock_->AssertExclusiveHeld(self);
+      art::ObjPtr<art::mirror::Class> klass(ref->GetClass());
+      // All these classes are in the BootstrapClassLoader.
+      if (!klass->IsBootStrapClassLoaded()) {
+        return;
+      }
+      if (art::GetClassRoot<art::mirror::Method>()->IsAssignableFrom(klass) ||
+          art::GetClassRoot<art::mirror::Constructor>()->IsAssignableFrom(klass)) {
+        art::down_cast<art::mirror::Executable*>(ref)->VisitTarget(
+            get_visitor("java.lang.reflect.Executable"));
+      } else if (art::GetClassRoot<art::mirror::Field>() == klass) {
+        art::down_cast<art::mirror::Field*>(ref)->VisitTarget(
+            get_visitor("java.lang.reflect.Field"));
+      } else if (art::GetClassRoot<art::mirror::MethodHandle>()->IsAssignableFrom(klass)) {
+        art::down_cast<art::mirror::MethodHandle*>(ref)->VisitTarget(
+            get_visitor("java.lang.invoke.MethodHandle"));
+      } else if (art::GetClassRoot<art::mirror::FieldVarHandle>()->IsAssignableFrom(klass)) {
+        art::down_cast<art::mirror::FieldVarHandle*>(ref)->VisitTarget(
+            get_visitor("java.lang.invoke.FieldVarHandle"));
+      }
+    });
+}
+
+}  // namespace
+
+void Redefiner::ClassRedefinition::UpdateClassStructurally(const RedefinitionDataIter& holder) {
+  DCHECK(IsStructuralRedefinition());
+  // LETS GO. We've got all new class structures so no need to do all the updating of the stacks.
+  // Instead we need to update everything else.
+  // Just replace the class and be done with it.
+  art::Locks::mutator_lock_->AssertExclusiveHeld(driver_->self_);
+  art::ScopedAssertNoThreadSuspension sants(__FUNCTION__);
+  art::ObjPtr<art::mirror::Class> orig(holder.GetMirrorClass());
+  art::ObjPtr<art::mirror::Class> replacement(holder.GetNewClassObject());
+  // Collect mappings from old to new fields/methods
+  std::map<art::ArtMethod*, art::ArtMethod*> method_map;
+  std::map<art::ArtField*, art::ArtField*> field_map;
+  CollectNewFieldAndMethodMappings(holder, &method_map, &field_map);
+  // Copy over the fields of the object.
+  CHECK(!orig.IsNull());
+  CHECK(!replacement.IsNull());
+  for (art::ArtField& f : orig->GetSFields()) {
+    art::ArtField* new_field =
+        replacement->FindDeclaredStaticField(f.GetName(), f.GetTypeDescriptor());
+    CHECK(new_field != nullptr) << "could not find new version of " << f.PrettyField();
+    art::Primitive::Type ftype = f.GetTypeAsPrimitiveType();
+    CHECK_EQ(ftype, new_field->GetTypeAsPrimitiveType())
+        << f.PrettyField() << " vs " << new_field->PrettyField();
+    if (ftype == art::Primitive::kPrimNot) {
+      new_field->SetObject<false>(replacement, f.GetObject(orig));
+    } else {
+      switch (ftype) {
+#define UPDATE_FIELD(TYPE)                                       \
+  case art::Primitive::kPrim##TYPE:                              \
+    new_field->Set##TYPE<false>(replacement, f.Get##TYPE(orig)); \
+    break
+
+        UPDATE_FIELD(Int);
+        UPDATE_FIELD(Float);
+        UPDATE_FIELD(Long);
+        UPDATE_FIELD(Double);
+        UPDATE_FIELD(Short);
+        UPDATE_FIELD(Char);
+        UPDATE_FIELD(Byte);
+        UPDATE_FIELD(Boolean);
+        case art::Primitive::kPrimNot:
+        case art::Primitive::kPrimVoid:
+          LOG(FATAL) << "Unexpected field with type " << ftype << " found!";
+          UNREACHABLE();
+#undef UPDATE_FIELD
+      }
+    }
+  }
+  // Mark old class obsolete.
+  orig->SetObsoleteObject();
+  // Mark methods obsolete. We need to wait until later to actually clear the jit data.
+  for (art::ArtMethod& m : orig->GetMethods(art::kRuntimePointerSize)) {
+    m.SetIsObsolete();
+    m.SetDontCompile();
+    DCHECK_EQ(orig, m.GetDeclaringClass());
+  }
+  // TODO Update live pointers in ART code. Currently we just assume there aren't any
+  // ArtMethod/ArtField*s hanging around in the runtime that need to be updated to the new
+  // non-obsolete versions. This isn't a totally safe assumption and we need to fix this oversight.
+  // Update jni-ids
+  driver_->runtime_->GetJniIdManager()->VisitIds(
+      driver_->self_,
+      [&](jmethodID mid, art::ArtMethod** meth) REQUIRES(art::Locks::mutator_lock_) {
+        auto repl = method_map.find(*meth);
+        if (repl != method_map.end()) {
+          // Set the new method to have the same id.
+          // TODO This won't be true when we do updates with actual instances.
+          DCHECK_EQ(repl->second->GetDeclaringClass(), replacement)
+              << "different classes! " << repl->second->GetDeclaringClass()->PrettyClass()
+              << " vs " << replacement->PrettyClass();
+          VLOG(plugin) << "Updating jmethodID " << reinterpret_cast<uintptr_t>(mid) << " from "
+                       << (*meth)->PrettyMethod() << " to " << repl->second->PrettyMethod();
+          *meth = repl->second;
+          replacement->GetExtData()->GetJMethodIDs()->SetElementPtrSize(
+              replacement->GetMethodsSlice(art::kRuntimePointerSize).OffsetOf(repl->second),
+              mid,
+              art::kRuntimePointerSize);
+        }
+      },
+      [&](jfieldID fid, art::ArtField** field) REQUIRES(art::Locks::mutator_lock_) {
+        auto repl = field_map.find(*field);
+        if (repl != field_map.end()) {
+          // Set the new field to have the same id.
+          // TODO This won't be true when we do updates with actual instances.
+          DCHECK_EQ(repl->second->GetDeclaringClass(), replacement)
+              << "different classes! " << repl->second->GetDeclaringClass()->PrettyClass()
+              << " vs " << replacement->PrettyClass();
+          VLOG(plugin) << "Updating jfieldID " << reinterpret_cast<uintptr_t>(fid) << " from "
+                       << (*field)->PrettyField() << " to " << repl->second->PrettyField();
+          *field = repl->second;
+          if (repl->second->IsStatic()) {
+            replacement->GetExtData()->GetStaticJFieldIDs()->SetElementPtrSize(
+                art::ArraySlice<art::ArtField>(replacement->GetSFieldsPtr()).OffsetOf(repl->second),
+                fid,
+                art::kRuntimePointerSize);
+          } else {
+            replacement->GetExtData()->GetInstanceJFieldIDs()->SetElementPtrSize(
+                art::ArraySlice<art::ArtField>(replacement->GetIFieldsPtr()).OffsetOf(repl->second),
+                fid,
+                art::kRuntimePointerSize);
+          }
+        }
+      });
+  // Copy the lock-word
+  replacement->SetLockWord(orig->GetLockWord(false), false);
+  orig->SetLockWord(art::LockWord::Default(), false);
+  // Fix up java.lang.reflect.{Method,Field} and java.lang.invoke.{Method,FieldVar}Handle objects
+  // TODO Performing 2 stack-walks back to back isn't the greatest. We might want to try to combine
+  // it with the one ReplaceReferences does. Doing so would be rather complicated though.
+  // TODO We maybe should just give the Heap the ability to do this.
+  VisitReflectiveObjects(
+      driver_->self_,
+      driver_->runtime_->GetHeap(),
+      [&](art::ArtField* f, const auto& info) REQUIRES(art::Locks::mutator_lock_) {
+        auto it = field_map.find(f);
+        if (it == field_map.end()) {
+          return f;
+        }
+        VLOG(plugin) << "Updating " << info << " object for (field) " << it->second->PrettyField();
+        return it->second;
+      },
+      [&](art::ArtMethod* m, const auto& info) REQUIRES(art::Locks::mutator_lock_) {
+        auto it = method_map.find(m);
+        if (it == method_map.end()) {
+          return m;
+        }
+        VLOG(plugin) << "Updating " << info << " object for (method) " << it->second->PrettyMethod();
+        return it->second;
+      });
+
+  // Force every frame of every thread to deoptimize (any frame might have eg offsets compiled in).
+  driver_->runtime_->GetInstrumentation()->DeoptimizeAllThreadFrames();
+
+  // Actually perform the general replacement. This doesn't affect ArtMethod/ArtFields.
+  // This replaces the mirror::Class in 'holder' as well. It's magic!
+  HeapExtensions::ReplaceReference(driver_->self_, orig, replacement);
+
+  // Save the old class so that the JIT gc doesn't get confused by it being collected before the
+  // jit code. This is also needed to keep the dex-caches of any obsolete methods live.
+  replacement->GetExtData()->SetObsoleteClass(orig);
+
+  // Clear the static fields of the old-class.
+  for (art::ArtField& f : orig->GetSFields()) {
+    switch (f.GetTypeAsPrimitiveType()) {
+    #define UPDATE_FIELD(TYPE)            \
+      case art::Primitive::kPrim ## TYPE: \
+        f.Set ## TYPE <false>(orig, 0);   \
+        break
+
+      UPDATE_FIELD(Int);
+      UPDATE_FIELD(Float);
+      UPDATE_FIELD(Long);
+      UPDATE_FIELD(Double);
+      UPDATE_FIELD(Short);
+      UPDATE_FIELD(Char);
+      UPDATE_FIELD(Byte);
+      UPDATE_FIELD(Boolean);
+      case art::Primitive::kPrimNot:
+        f.SetObject<false>(orig, nullptr);
+        break;
+      case art::Primitive::kPrimVoid:
+        LOG(FATAL) << "Unexpected field with type void found!";
+        UNREACHABLE();
+    #undef UPDATE_FIELD
+    }
+  }
+
+  // Update dex-caches to point to new fields. We wait until here so that the new-class is known by
+  // the linker. At the same time reset all methods to have interpreter entrypoints, anything jitted
+  // might encode field/method offsets.
+  FuncVisitor fv([&](art::ClassVisitor& thiz,
+                     art::ObjPtr<art::mirror::Class> klass) REQUIRES(art::Locks::mutator_lock_) {
+    // Code to actually update a dex-cache. Since non-structural obsolete methods can lead to a
+    // single class having several dex-caches associated with it we factor this out a bit.
+    auto update_dex_cache = [&](art::ObjPtr<art::mirror::DexCache> dc,
+                                auto describe) REQUIRES(art::Locks::mutator_lock_) {
+      // Clear dex-cache. We don't need to do anything with resolved-types since those are already
+      // handled by ReplaceReferences.
+      if (dc.IsNull()) {
+        // We don't need to do anything if the class doesn't have a dex-cache. This is the case for
+        // things like arrays and primitives.
+        return;
+      }
+      for (size_t i = 0; art::kIsDebugBuild && i < dc->NumResolvedTypes(); i++) {
+        DCHECK_NE(dc->GetResolvedTypes()[i].load().object.Read(), orig)
+            << "Obsolete reference found in dex-cache of class " << klass->PrettyClass() << "!";
+      }
+      for (size_t i = 0; i < dc->NumResolvedFields(); i++) {
+        auto pair(dc->GetNativePairPtrSize(dc->GetResolvedFields(), i, art::kRuntimePointerSize));
+        auto new_val = field_map.find(pair.object);
+        if (new_val != field_map.end()) {
+          VLOG(plugin) << "Updating field dex-cache entry " << i << " of class "
+                       << klass->PrettyClass() << " dex cache " << describe();
+          pair.object = new_val->second;
+          dc->SetNativePairPtrSize(dc->GetResolvedFields(), i, pair, art::kRuntimePointerSize);
+        }
+      }
+      for (size_t i = 0; i < dc->NumResolvedMethods(); i++) {
+        auto pair(
+            dc->GetNativePairPtrSize(dc->GetResolvedMethods(), i, art::kRuntimePointerSize));
+        auto new_val = method_map.find(pair.object);
+        if (new_val != method_map.end()) {
+          VLOG(plugin) << "Updating method dex-cache entry " << i << " of class "
+                       << klass->PrettyClass() << " dex cache " << describe();
+          pair.object = new_val->second;
+          dc->SetNativePairPtrSize(dc->GetResolvedMethods(), i, pair, art::kRuntimePointerSize);
+        }
+      }
+    };
+    // Clear our own dex-cache.
+    update_dex_cache(klass->GetDexCache(), []() { return "Primary"; });
+    // Clear all the normal obsolete dex-caches.
+    art::ObjPtr<art::mirror::ClassExt> ext(klass->GetExtData());
+    if (!ext.IsNull()) {
+      art::ObjPtr<art::mirror::ObjectArray<art::mirror::DexCache>> obsolete_caches(
+          ext->GetObsoleteDexCaches());
+      // This contains the dex-cache associated with each obsolete method. Since each redefinition
+      // could cause many methods to become obsolete a single dex-cache might be in the array
+      // multiple times. We always add new obsoletes onto the end of this array so identical
+      // dex-caches are all right next to one another.
+      art::ObjPtr<art::mirror::DexCache> prev(nullptr);
+      for (int32_t i = 0; !obsolete_caches.IsNull() && i < obsolete_caches->GetLength(); i++) {
+        art::ObjPtr<art::mirror::DexCache> cur(obsolete_caches->Get(i));
+        if (!cur.IsNull() && cur != prev) {
+          prev = cur;
+          VLOG(plugin) << "Clearing obsolete dex cache " << i << " of " << klass->PrettyClass();
+          update_dex_cache(cur, [&i]() { return StringPrintf("Obsolete[%d]", i); });
+        }
+      }
+      if (!ext->GetObsoleteClass().IsNull()) {
+        VLOG(plugin) << "Recuring on obsolete class " << ext->GetObsoleteClass()->PrettyClass();
+        // Recur on any obsolete-classes. These aren't known about by the class-linker anymore so
+        // we need to visit it manually.
+        thiz(ext->GetObsoleteClass());
+      }
+    }
+    return true;
+  });
+  // TODO Rewrite VisitClasses to be able to take a lambda directly.
+  driver_->runtime_->GetClassLinker()->VisitClasses(&fv);
+
+  art::jit::Jit* jit = driver_->runtime_->GetJit();
+  if (jit != nullptr) {
+    // Clear jit.
+    // TODO We might want to have some way to tell the JIT not to wait the kJitSamplesBatchSize
+    // invokes to start compiling things again.
+    jit->GetCodeCache()->InvalidateAllCompiledCode();
+  }
+
+  // Clear thread caches
+  {
+    // TODO We might be able to avoid doing this but given the rather unstructured nature of the
+    // interpreter cache it's probably not worth the effort.
+    art::MutexLock mu(driver_->self_, *art::Locks::thread_list_lock_);
+    driver_->runtime_->GetThreadList()->ForEach(
+        [](art::Thread* t) { t->GetInterpreterCache()->Clear(t); });
+  }
+
+  if (art::kIsDebugBuild) {
+    // Just make sure we didn't screw up any of the now obsolete methods or fields. We need their
+    // declaring-class to still be the obolete class
+    orig->VisitMethods([&](art::ArtMethod* method) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+      DCHECK_EQ(method->GetDeclaringClass(), orig) << method->GetDeclaringClass()->PrettyClass()
+                                                   << " vs " << orig->PrettyClass();
+    }, art::kRuntimePointerSize);
+    orig->VisitFields([&](art::ArtField* field) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+      DCHECK_EQ(field->GetDeclaringClass(), orig) << field->GetDeclaringClass()->PrettyClass()
+                                                  << " vs " << orig->PrettyClass();
+    });
+  }
+}
+
+// Redefines the class in place
+void Redefiner::ClassRedefinition::UpdateClassInPlace(const RedefinitionDataIter& holder) {
+  art::ObjPtr<art::mirror::Class> mclass(holder.GetMirrorClass());
+  // TODO Rewrite so we don't do a stack walk for each and every class.
+  FindAndAllocateObsoleteMethods(mclass);
+  art::ObjPtr<art::mirror::DexCache> new_dex_cache(holder.GetNewDexCache());
+  art::ObjPtr<art::mirror::Object> original_dex_file(holder.GetOriginalDexFile());
   DCHECK_EQ(dex_file_->NumClassDefs(), 1u);
   const art::dex::ClassDef& class_def = dex_file_->GetClassDef(0);
   UpdateMethods(mclass, class_def);
@@ -1562,10 +2324,23 @@
   }
 }
 
+// Performs final updates to class for redefinition.
+void Redefiner::ClassRedefinition::UpdateClass(const RedefinitionDataIter& holder) {
+  if (IsStructuralRedefinition()) {
+    UpdateClassStructurally(holder);
+  } else {
+    UpdateClassInPlace(holder);
+  }
+}
+
 // Restores the old obsolete methods maps if it turns out they weren't needed (ie there were no new
 // obsolete methods).
 void Redefiner::ClassRedefinition::RestoreObsoleteMethodMapsIfUnneeded(
     const RedefinitionDataIter* cur_data) {
+  if (IsStructuralRedefinition()) {
+    // We didn't touch these in this case.
+    return;
+  }
   art::ObjPtr<art::mirror::Class> klass = GetMirrorClass();
   art::ObjPtr<art::mirror::ClassExt> ext = klass->GetExtData();
   art::ObjPtr<art::mirror::PointerArray> methods = ext->GetObsoleteMethods();
@@ -1574,7 +2349,10 @@
   int32_t expected_length =
       old_length + klass->NumDirectMethods() + klass->NumDeclaredVirtualMethods();
   // Check to make sure we are only undoing this one.
-  if (expected_length == methods->GetLength()) {
+  if (methods.IsNull()) {
+    // No new obsolete methods! We can get rid of the maps.
+    ext->SetObsoleteArrays(cur_data->GetOldObsoleteMethods(), cur_data->GetOldDexCaches());
+  } else if (expected_length == methods->GetLength()) {
     for (int32_t i = 0; i < expected_length; i++) {
       art::ArtMethod* expected = nullptr;
       if (i < old_length) {
@@ -1612,20 +2390,22 @@
     RecordFailure(ERR(OUT_OF_MEMORY), "Could not allocate ClassExt");
     return false;
   }
-  // First save the old values of the 2 arrays that make up the obsolete methods maps.  Then
-  // allocate the 2 arrays that make up the obsolete methods map.  Since the contents of the arrays
-  // are only modified when all threads (other than the modifying one) are suspended we don't need
-  // to worry about missing the unsyncronized writes to the array. We do synchronize when setting it
-  // however, since that can happen at any time.
-  cur_data->SetOldObsoleteMethods(ext->GetObsoleteMethods());
-  cur_data->SetOldDexCaches(ext->GetObsoleteDexCaches());
-  if (!art::mirror::ClassExt::ExtendObsoleteArrays(
-          ext, driver_->self_, klass->GetDeclaredMethodsSlice(art::kRuntimePointerSize).size())) {
-    // OOM. Clear exception and return error.
-    driver_->self_->AssertPendingOOMException();
-    driver_->self_->ClearException();
-    RecordFailure(ERR(OUT_OF_MEMORY), "Unable to allocate/extend obsolete methods map");
-    return false;
+  if (!IsStructuralRedefinition()) {
+    // First save the old values of the 2 arrays that make up the obsolete methods maps. Then
+    // allocate the 2 arrays that make up the obsolete methods map. Since the contents of the arrays
+    // are only modified when all threads (other than the modifying one) are suspended we don't need
+    // to worry about missing the unsyncronized writes to the array. We do synchronize when setting
+    // it however, since that can happen at any time.
+    cur_data->SetOldObsoleteMethods(ext->GetObsoleteMethods());
+    cur_data->SetOldDexCaches(ext->GetObsoleteDexCaches());
+    if (!art::mirror::ClassExt::ExtendObsoleteArrays(
+            ext, driver_->self_, klass->GetDeclaredMethodsSlice(art::kRuntimePointerSize).size())) {
+      // OOM. Clear exception and return error.
+      driver_->self_->AssertPendingOOMException();
+      driver_->self_->ClearException();
+      RecordFailure(ERR(OUT_OF_MEMORY), "Unable to allocate/extend obsolete methods map");
+      return false;
+    }
   }
   return true;
 }
diff --git a/openjdkjvmti/ti_redefine.h b/openjdkjvmti/ti_redefine.h
index f55a2b8..732d3e4 100644
--- a/openjdkjvmti/ti_redefine.h
+++ b/openjdkjvmti/ti_redefine.h
@@ -32,24 +32,28 @@
 #ifndef ART_OPENJDKJVMTI_TI_REDEFINE_H_
 #define ART_OPENJDKJVMTI_TI_REDEFINE_H_
 
+#include <functional>
 #include <string>
 
 #include <jni.h>
 
+#include "art_field.h"
 #include "art_jvmti.h"
 #include "base/array_ref.h"
 #include "base/globals.h"
+#include "dex/dex_file.h"
+#include "dex/dex_file_structs.h"
 #include "jni/jni_env_ext-inl.h"
 #include "jvmti.h"
 #include "mirror/array.h"
 #include "mirror/class.h"
+#include "mirror/dex_cache.h"
 #include "obj_ptr.h"
 
 namespace art {
 namespace dex {
 struct ClassDef;
 }  // namespace dex
-class DexFile;
 }  // namespace art
 
 namespace openjdkjvmti {
@@ -58,6 +62,11 @@
 class RedefinitionDataHolder;
 class RedefinitionDataIter;
 
+enum class RedefinitionType {
+  kStructural,
+  kNormal,
+};
+
 // Class that can redefine a single class's methods.
 class Redefiner {
  public:
@@ -69,6 +78,7 @@
                                           art::Runtime* runtime,
                                           art::Thread* self,
                                           const std::vector<ArtClassDefinition>& definitions,
+                                          RedefinitionType type,
                                           /*out*/std::string* error_msg);
 
   // Redefine the given classes with the given dex data. Note this function does not take ownership
@@ -83,15 +93,24 @@
                                     /*out*/std::string* error_msg);
 
   static jvmtiError IsModifiableClass(jvmtiEnv* env, jclass klass, jboolean* is_redefinable);
+  static jvmtiError IsStructurallyModifiableClass(jvmtiEnv* env,
+                                                  jclass klass,
+                                                  jboolean* is_redefinable);
 
   static art::MemMap MoveDataToMemMap(const std::string& original_location,
                                       art::ArrayRef<const unsigned char> data,
                                       std::string* error_msg);
 
   // Helper for checking if redefinition/retransformation is allowed.
+  template<RedefinitionType kType = RedefinitionType::kNormal>
   static jvmtiError GetClassRedefinitionError(jclass klass, /*out*/std::string* error_msg)
       REQUIRES(!art::Locks::mutator_lock_);
 
+  static jvmtiError StructurallyRedefineClassDirect(jvmtiEnv* env,
+                                                    jclass klass,
+                                                    const unsigned char* data,
+                                                    jint data_size);
+
  private:
   class ClassRedefinition {
    public:
@@ -146,6 +165,12 @@
     void FindAndAllocateObsoleteMethods(art::ObjPtr<art::mirror::Class> art_klass)
         REQUIRES(art::Locks::mutator_lock_);
 
+    art::ObjPtr<art::mirror::Class> AllocateNewClassObject(art::Handle<art::mirror::DexCache> cache)
+        REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+    uint32_t GetNewClassSize(bool with_embedded_tables, art::Handle<art::mirror::Class> old_class)
+        REQUIRES_SHARED(art::Locks::mutator_lock_);
+
     // Checks that the dex file contains only the single expected class and that the top-level class
     // data has not been modified in an incompatible manner.
     bool CheckClass() REQUIRES_SHARED(art::Locks::mutator_lock_);
@@ -165,12 +190,15 @@
     // Checks that the class can even be redefined.
     bool CheckRedefinable() REQUIRES_SHARED(art::Locks::mutator_lock_);
 
-    // Checks that the dex file does not add/remove methods, or change their modifiers or types.
-    bool CheckSameMethods() REQUIRES_SHARED(art::Locks::mutator_lock_);
+    // Checks that the dex file does not add/remove methods, or change their modifiers or types in
+    // illegal ways.
+    bool CheckMethods() REQUIRES_SHARED(art::Locks::mutator_lock_);
 
-    // Checks that the dex file does not modify fields types or modifiers.
-    bool CheckSameFields() REQUIRES_SHARED(art::Locks::mutator_lock_);
+    // Checks that the dex file does not modify fields types or modifiers in illegal ways.
+    bool CheckFields() REQUIRES_SHARED(art::Locks::mutator_lock_);
 
+    // Temporary check that a class undergoing structural redefinition has no instances. This
+    // requirement will be removed in time.
     void UpdateJavaDexFile(art::ObjPtr<art::mirror::Object> java_dex_file,
                            art::ObjPtr<art::mirror::LongArray> new_cookie)
         REQUIRES(art::Locks::mutator_lock_);
@@ -182,9 +210,12 @@
                        const art::dex::ClassDef& class_def)
         REQUIRES(art::Locks::mutator_lock_);
 
-    void UpdateClass(art::ObjPtr<art::mirror::Class> mclass,
-                     art::ObjPtr<art::mirror::DexCache> new_dex_cache,
-                     art::ObjPtr<art::mirror::Object> original_dex_file)
+    void UpdateClass(const RedefinitionDataIter& cur_data)
+        REQUIRES(art::Locks::mutator_lock_);
+
+    void CollectNewFieldAndMethodMappings(const RedefinitionDataIter& data,
+                                          std::map<art::ArtMethod*, art::ArtMethod*>* method_map,
+                                          std::map<art::ArtField*, art::ArtField*>* field_map)
         REQUIRES(art::Locks::mutator_lock_);
 
     void RestoreObsoleteMethodMapsIfUnneeded(const RedefinitionDataIter* cur_data)
@@ -196,18 +227,38 @@
     // This should be done with all threads suspended.
     void UnregisterJvmtiBreakpoints() REQUIRES_SHARED(art::Locks::mutator_lock_);
 
+    void RecordNewMethodAdded();
+    void RecordNewFieldAdded();
+
    private:
+    bool IsStructuralRedefinition() const {
+      DCHECK(!(added_fields_ || added_methods_) || driver_->IsStructuralRedefinition())
+          << "added_fields_: " << added_fields_ << " added_methods_: " << added_methods_
+          << " driver_->IsStructuralRedefinition(): " << driver_->IsStructuralRedefinition();
+      return driver_->IsStructuralRedefinition() && (added_fields_ || added_methods_);
+    }
+
+    void UpdateClassStructurally(const RedefinitionDataIter& cur_data)
+        REQUIRES(art::Locks::mutator_lock_);
+
+    void UpdateClassInPlace(const RedefinitionDataIter& cur_data)
+        REQUIRES(art::Locks::mutator_lock_);
+
     Redefiner* driver_;
     jclass klass_;
     std::unique_ptr<const art::DexFile> dex_file_;
     std::string class_sig_;
     art::ArrayRef<const unsigned char> original_dex_file_;
+
+    bool added_fields_ = false;
+    bool added_methods_ = false;
   };
 
   ArtJvmTiEnv* env_;
   jvmtiError result_;
   art::Runtime* runtime_;
   art::Thread* self_;
+  RedefinitionType type_;
   std::vector<ClassRedefinition> redefinitions_;
   // Kept as a jclass since we have weird run-state changes that make keeping it around as a
   // mirror::Class difficult and confusing.
@@ -216,17 +267,23 @@
   Redefiner(ArtJvmTiEnv* env,
             art::Runtime* runtime,
             art::Thread* self,
+            RedefinitionType type,
             std::string* error_msg)
       : env_(env),
         result_(ERR(INTERNAL)),
         runtime_(runtime),
         self_(self),
+        type_(type),
         redefinitions_(),
         error_msg_(error_msg) { }
 
   jvmtiError AddRedefinition(ArtJvmTiEnv* env, const ArtClassDefinition& def)
       REQUIRES_SHARED(art::Locks::mutator_lock_);
 
+  template<RedefinitionType kType = RedefinitionType::kNormal>
+  static jvmtiError IsModifiableClassGeneric(jvmtiEnv* env, jclass klass, jboolean* is_redefinable);
+
+  template<RedefinitionType kType = RedefinitionType::kNormal>
   static jvmtiError GetClassRedefinitionError(art::Handle<art::mirror::Class> klass,
                                               /*out*/std::string* error_msg)
       REQUIRES_SHARED(art::Locks::mutator_lock_);
@@ -247,6 +304,10 @@
   void RestoreObsoleteMethodMapsIfUnneeded(RedefinitionDataHolder& holder)
       REQUIRES(art::Locks::mutator_lock_);
 
+  bool IsStructuralRedefinition() const {
+    return type_ == RedefinitionType::kStructural;
+  }
+
   void RecordFailure(jvmtiError result, const std::string& class_sig, const std::string& error_msg);
   void RecordFailure(jvmtiError result, const std::string& error_msg) {
     RecordFailure(result, "NO CLASS", error_msg);
diff --git a/openjdkjvmti/transform.cc b/openjdkjvmti/transform.cc
index 27f04b7..34aec09 100644
--- a/openjdkjvmti/transform.cc
+++ b/openjdkjvmti/transform.cc
@@ -330,7 +330,8 @@
   if (res != OK) {
     return res;
   }
-  return Redefiner::RedefineClassesDirect(env, runtime, self, definitions, error_msg);
+  return Redefiner::RedefineClassesDirect(
+      env, runtime, self, definitions, RedefinitionType::kNormal, error_msg);
 }
 
 // TODO Move this somewhere else, ti_class?
diff --git a/runtime/art_method.cc b/runtime/art_method.cc
index d8bcb02..646f73d 100644
--- a/runtime/art_method.cc
+++ b/runtime/art_method.cc
@@ -16,12 +16,15 @@
 
 #include "art_method.h"
 
+#include <algorithm>
 #include <cstddef>
 
 #include "android-base/stringprintf.h"
 
 #include "arch/context.h"
 #include "art_method-inl.h"
+#include "base/enums.h"
+#include "base/stl_util.h"
 #include "class_linker-inl.h"
 #include "class_root.h"
 #include "debugger.h"
@@ -106,26 +109,32 @@
 }
 
 ObjPtr<mirror::DexCache> ArtMethod::GetObsoleteDexCache() {
+  PointerSize pointer_size = kRuntimePointerSize;
   DCHECK(!Runtime::Current()->IsAotCompiler()) << PrettyMethod();
   DCHECK(IsObsolete());
   ObjPtr<mirror::ClassExt> ext(GetDeclaringClass()->GetExtData());
-  CHECK(!ext.IsNull());
-  ObjPtr<mirror::PointerArray> obsolete_methods(ext->GetObsoleteMethods());
-  CHECK(!obsolete_methods.IsNull());
-  DCHECK(ext->GetObsoleteDexCaches() != nullptr);
-  int32_t len = obsolete_methods->GetLength();
-  DCHECK_EQ(len, ext->GetObsoleteDexCaches()->GetLength());
+  ObjPtr<mirror::PointerArray> obsolete_methods(ext.IsNull() ? nullptr : ext->GetObsoleteMethods());
+  int32_t len = (obsolete_methods.IsNull() ? 0 : obsolete_methods->GetLength());
+  DCHECK(len == 0 || len == ext->GetObsoleteDexCaches()->GetLength())
+      << "len=" << len << " ext->GetObsoleteDexCaches()=" << ext->GetObsoleteDexCaches();
   // Using kRuntimePointerSize (instead of using the image's pointer size) is fine since images
   // should never have obsolete methods in them so they should always be the same.
-  PointerSize pointer_size = kRuntimePointerSize;
-  DCHECK_EQ(kRuntimePointerSize, Runtime::Current()->GetClassLinker()->GetImagePointerSize());
+  DCHECK_EQ(pointer_size, Runtime::Current()->GetClassLinker()->GetImagePointerSize());
   for (int32_t i = 0; i < len; i++) {
     if (this == obsolete_methods->GetElementPtrSize<ArtMethod*>(i, pointer_size)) {
       return ext->GetObsoleteDexCaches()->Get(i);
     }
   }
-  LOG(FATAL) << "This method does not appear in the obsolete map of its class!";
-  UNREACHABLE();
+  CHECK(GetDeclaringClass()->IsObsoleteObject())
+      << "This non-structurally obsolete method does not appear in the obsolete map of its class: "
+      << GetDeclaringClass()->PrettyClass() << " Searched " << len << " caches.";
+  CHECK_EQ(this,
+           std::clamp(this,
+                      &(*GetDeclaringClass()->GetMethods(pointer_size).begin()),
+                      &(*GetDeclaringClass()->GetMethods(pointer_size).end())))
+      << "class is marked as structurally obsolete method but not found in normal obsolete-map "
+      << "despite not being the original method pointer for " << GetDeclaringClass()->PrettyClass();
+  return GetDeclaringClass()->GetDexCache();
 }
 
 uint16_t ArtMethod::FindObsoleteDexClassDefIndex() {
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc
index 9d9abe3..10b9da1 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -6146,7 +6146,7 @@
     // Update CHA info based on whether we override methods.
     // Have to do this before setting the class as resolved which allows
     // instantiation of klass.
-    if (cha_ != nullptr) {
+    if (LIKELY(descriptor != nullptr) && cha_ != nullptr) {
       cha_->UpdateAfterLoadingOf(klass);
     }
 
@@ -6177,7 +6177,7 @@
     ObjectLock<mirror::Class> lock(self, h_new_class);
     FixupTemporaryDeclaringClass(klass.Get(), h_new_class.Get());
 
-    {
+    if (LIKELY(descriptor != nullptr)) {
       WriterMutexLock mu(self, *Locks::classlinker_classes_lock_);
       const ObjPtr<mirror::ClassLoader> class_loader = h_new_class.Get()->GetClassLoader();
       ClassTable* const table = InsertClassTableForClassLoader(class_loader);
@@ -6197,7 +6197,7 @@
     // Update CHA info based on whether we override methods.
     // Have to do this before setting the class as resolved which allows
     // instantiation of klass.
-    if (cha_ != nullptr) {
+    if (LIKELY(descriptor != nullptr) && cha_ != nullptr) {
       cha_->UpdateAfterLoadingOf(h_new_class);
     }
 
diff --git a/runtime/class_linker.h b/runtime/class_linker.h
index dd9f56f..792f7b7 100644
--- a/runtime/class_linker.h
+++ b/runtime/class_linker.h
@@ -20,6 +20,7 @@
 #include <list>
 #include <set>
 #include <string>
+#include <type_traits>
 #include <unordered_map>
 #include <unordered_set>
 #include <utility>
@@ -479,6 +480,38 @@
                                                       LinearAlloc* allocator,
                                                       size_t length);
 
+  // Convenience AllocClass() overload that uses mirror::Class::InitializeClassVisitor
+  // for the class initialization and uses the `java_lang_Class` from class roots
+  // instead of an explicit argument.
+  ObjPtr<mirror::Class> AllocClass(Thread* self, uint32_t class_size)
+      REQUIRES_SHARED(Locks::mutator_lock_)
+      REQUIRES(!Roles::uninterruptible_);
+
+  // Setup the classloader, class def index, type idx so that we can insert this class in the class
+  // table.
+  void SetupClass(const DexFile& dex_file,
+                  const dex::ClassDef& dex_class_def,
+                  Handle<mirror::Class> klass,
+                  ObjPtr<mirror::ClassLoader> class_loader)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
+  void LoadClass(Thread* self,
+                 const DexFile& dex_file,
+                 const dex::ClassDef& dex_class_def,
+                 Handle<mirror::Class> klass)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
+  // Link the class and place it into the class-table using the given descriptor. NB if the
+  // descriptor is null the class will not be placed in any class-table. This is useful implementing
+  // obsolete classes and should not be used otherwise.
+  bool LinkClass(Thread* self,
+                 const char* descriptor,
+                 Handle<mirror::Class> klass,
+                 Handle<mirror::ObjectArray<mirror::Class>> interfaces,
+                 MutableHandle<mirror::Class>* h_new_class_out)
+      REQUIRES_SHARED(Locks::mutator_lock_)
+      REQUIRES(!Locks::classlinker_classes_lock_);
+
   ObjPtr<mirror::PointerArray> AllocPointerArray(Thread* self, size_t length)
       REQUIRES_SHARED(Locks::mutator_lock_)
       REQUIRES(!Roles::uninterruptible_);
@@ -829,13 +862,6 @@
       REQUIRES_SHARED(Locks::mutator_lock_)
       REQUIRES(!Roles::uninterruptible_);
 
-  // Convenience AllocClass() overload that uses mirror::Class::InitializeClassVisitor
-  // for the class initialization and uses the `java_lang_Class` from class roots
-  // instead of an explicit argument.
-  ObjPtr<mirror::Class> AllocClass(Thread* self, uint32_t class_size)
-      REQUIRES_SHARED(Locks::mutator_lock_)
-      REQUIRES(!Roles::uninterruptible_);
-
   // Allocate a primitive array class and store it in appropriate class root.
   void AllocPrimitiveArrayClass(Thread* self,
                                 ClassRoot primitive_root,
@@ -889,20 +915,6 @@
   uint32_t SizeOfClassWithoutEmbeddedTables(const DexFile& dex_file,
                                             const dex::ClassDef& dex_class_def);
 
-  // Setup the classloader, class def index, type idx so that we can insert this class in the class
-  // table.
-  void SetupClass(const DexFile& dex_file,
-                  const dex::ClassDef& dex_class_def,
-                  Handle<mirror::Class> klass,
-                  ObjPtr<mirror::ClassLoader> class_loader)
-      REQUIRES_SHARED(Locks::mutator_lock_);
-
-  void LoadClass(Thread* self,
-                 const DexFile& dex_file,
-                 const dex::ClassDef& dex_class_def,
-                 Handle<mirror::Class> klass)
-      REQUIRES_SHARED(Locks::mutator_lock_);
-
   void LoadField(const ClassAccessor::Field& field, Handle<mirror::Class> klass, ArtField* dst)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
@@ -1049,14 +1061,6 @@
                                                      ObjPtr<mirror::Class> klass2)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
-  bool LinkClass(Thread* self,
-                 const char* descriptor,
-                 Handle<mirror::Class> klass,
-                 Handle<mirror::ObjectArray<mirror::Class>> interfaces,
-                 MutableHandle<mirror::Class>* h_new_class_out)
-      REQUIRES_SHARED(Locks::mutator_lock_)
-      REQUIRES(!Locks::classlinker_classes_lock_);
-
   bool LinkSuperClass(Handle<mirror::Class> klass)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
diff --git a/runtime/class_linker_test.cc b/runtime/class_linker_test.cc
index 4377d7e..6299e3f 100644
--- a/runtime/class_linker_test.cc
+++ b/runtime/class_linker_test.cc
@@ -613,6 +613,7 @@
   ClassExtOffsets() : CheckOffsets<mirror::ClassExt>(false, "Ldalvik/system/ClassExt;") {
     addOffset(OFFSETOF_MEMBER(mirror::ClassExt, instance_jfield_ids_), "instanceJfieldIDs");
     addOffset(OFFSETOF_MEMBER(mirror::ClassExt, jmethod_ids_), "jmethodIDs");
+    addOffset(OFFSETOF_MEMBER(mirror::ClassExt, obsolete_class_), "obsoleteClass");
     addOffset(OFFSETOF_MEMBER(mirror::ClassExt, obsolete_dex_caches_), "obsoleteDexCaches");
     addOffset(OFFSETOF_MEMBER(mirror::ClassExt, obsolete_methods_), "obsoleteMethods");
     addOffset(OFFSETOF_MEMBER(mirror::ClassExt, original_dex_file_), "originalDexFile");
diff --git a/runtime/instrumentation.cc b/runtime/instrumentation.cc
index 76065a3..ca48955 100644
--- a/runtime/instrumentation.cc
+++ b/runtime/instrumentation.cc
@@ -156,7 +156,8 @@
 }
 
 Instrumentation::Instrumentation()
-    : instrumentation_stubs_installed_(false),
+    : current_force_deopt_id_(0),
+      instrumentation_stubs_installed_(false),
       entry_exit_stubs_installed_(false),
       interpreter_stubs_installed_(false),
       interpret_only_(false),
@@ -282,16 +283,20 @@
 // deoptimization of quick frames to interpreter frames.
 // Since we may already have done this previously, we need to push new instrumentation frame before
 // existing instrumentation frames.
-static void InstrumentationInstallStack(Thread* thread, void* arg)
+void InstrumentationInstallStack(Thread* thread, void* arg)
     REQUIRES_SHARED(Locks::mutator_lock_) {
   struct InstallStackVisitor final : public StackVisitor {
-    InstallStackVisitor(Thread* thread_in, Context* context, uintptr_t instrumentation_exit_pc)
+    InstallStackVisitor(Thread* thread_in,
+                        Context* context,
+                        uintptr_t instrumentation_exit_pc,
+                        uint64_t force_deopt_id)
         : StackVisitor(thread_in, context, kInstrumentationStackWalk),
           instrumentation_stack_(thread_in->GetInstrumentationStack()),
           instrumentation_exit_pc_(instrumentation_exit_pc),
-          reached_existing_instrumentation_frames_(false), instrumentation_stack_depth_(0),
-          last_return_pc_(0) {
-    }
+          reached_existing_instrumentation_frames_(false),
+          instrumentation_stack_depth_(0),
+          last_return_pc_(0),
+          force_deopt_id_(force_deopt_id) {}
 
     bool VisitFrame() override REQUIRES_SHARED(Locks::mutator_lock_) {
       ArtMethod* m = GetMethod();
@@ -308,7 +313,8 @@
                                                         m,
                                                         /*return_pc=*/ 0,
                                                         GetFrameId(),
-                                                        interpreter_frame);
+                                                        interpreter_frame,
+                                                        force_deopt_id_);
         if (kVerboseInstrumentation) {
           LOG(INFO) << "Pushing shadow frame " << instrumentation_frame.Dump();
         }
@@ -375,7 +381,8 @@
             m,
             return_pc,
             GetFrameId(),    // A runtime method still gets a frame id.
-            false);
+            false,
+            force_deopt_id_);
         if (kVerboseInstrumentation) {
           LOG(INFO) << "Pushing frame " << instrumentation_frame.Dump();
         }
@@ -409,6 +416,7 @@
     bool reached_existing_instrumentation_frames_;
     size_t instrumentation_stack_depth_;
     uintptr_t last_return_pc_;
+    uint64_t force_deopt_id_;
   };
   if (kVerboseInstrumentation) {
     std::string thread_name;
@@ -419,7 +427,8 @@
   Instrumentation* instrumentation = reinterpret_cast<Instrumentation*>(arg);
   std::unique_ptr<Context> context(Context::Create());
   uintptr_t instrumentation_exit_pc = reinterpret_cast<uintptr_t>(GetQuickInstrumentationExitPc());
-  InstallStackVisitor visitor(thread, context.get(), instrumentation_exit_pc);
+  InstallStackVisitor visitor(
+      thread, context.get(), instrumentation_exit_pc, instrumentation->current_force_deopt_id_);
   visitor.WalkStack(true);
   CHECK_EQ(visitor.dex_pcs_.size(), thread->GetInstrumentationStack()->size());
 
@@ -542,6 +551,17 @@
   }
 }
 
+void Instrumentation::DeoptimizeAllThreadFrames() {
+  Thread* self = Thread::Current();
+  MutexLock mu(self, *Locks::thread_list_lock_);
+  ThreadList* tl = Runtime::Current()->GetThreadList();
+  tl->ForEach([&](Thread* t) {
+    Locks::mutator_lock_->AssertExclusiveHeld(self);
+    InstrumentThreadStack(t);
+  });
+  current_force_deopt_id_++;
+}
+
 static bool HasEvent(Instrumentation::InstrumentationEvent expected, uint32_t events) {
   return (events & expected) != 0;
 }
@@ -803,10 +823,24 @@
     }
     if (empty) {
       MutexLock mu(self, *Locks::thread_list_lock_);
-      Runtime::Current()->GetThreadList()->ForEach(InstrumentationRestoreStack, this);
-      // Only do this after restoring, as walking the stack when restoring will see
-      // the instrumentation exit pc.
-      instrumentation_stubs_installed_ = false;
+      bool no_remaining_deopts = true;
+      // Check that there are no other forced deoptimizations. Do it here so we only need to lock
+      // thread_list_lock once.
+      runtime->GetThreadList()->ForEach([&](Thread* t) {
+        no_remaining_deopts =
+            no_remaining_deopts && !t->IsForceInterpreter() &&
+            std::all_of(t->GetInstrumentationStack()->cbegin(),
+                        t->GetInstrumentationStack()->cend(),
+                        [&](const auto& frame) REQUIRES_SHARED(Locks::mutator_lock_) {
+                          return frame.force_deopt_id_ == current_force_deopt_id_;
+                        });
+      });
+      if (no_remaining_deopts) {
+        Runtime::Current()->GetThreadList()->ForEach(InstrumentationRestoreStack, this);
+        // Only do this after restoring, as walking the stack when restoring will see
+        // the instrumentation exit pc.
+        instrumentation_stubs_installed_ = false;
+      }
     }
   }
 }
@@ -1401,8 +1435,8 @@
   DCHECK(!self->IsExceptionPending());
   size_t frame_id = StackVisitor::ComputeNumFrames(self, kInstrumentationStackWalk);
 
-  instrumentation::InstrumentationStackFrame instrumentation_frame(h_this.Get(), method, lr,
-                                                                   frame_id, interpreter_entry);
+  instrumentation::InstrumentationStackFrame instrumentation_frame(
+      h_this.Get(), method, lr, frame_id, interpreter_entry, current_force_deopt_id_);
   stack->push_front(instrumentation_frame);
 }
 
@@ -1563,6 +1597,13 @@
   bool deoptimize = (visitor.caller != nullptr) &&
                     (interpreter_stubs_installed_ || IsDeoptimized(visitor.caller) ||
                     self->IsForceInterpreter() ||
+                    // NB Since structurally obsolete compiled methods might have the offsets of
+                    // methods/fields compiled in we need to go back to interpreter whenever we hit
+                    // them.
+                    visitor.caller->GetDeclaringClass()->IsObsoleteObject() ||
+                    // Check if we forced all threads to deoptimize in the time between this frame
+                    // being created and now.
+                    instrumentation_frame.force_deopt_id_ != current_force_deopt_id_ ||
                     Dbg::IsForcedInterpreterNeededForUpcall(self, visitor.caller));
   if (is_ref) {
     // Restore the return value if it's a reference since it might have moved.
@@ -1628,7 +1669,8 @@
 std::string InstrumentationStackFrame::Dump() const {
   std::ostringstream os;
   os << "Frame " << frame_id_ << " " << ArtMethod::PrettyMethod(method_) << ":"
-      << reinterpret_cast<void*>(return_pc_) << " this=" << reinterpret_cast<void*>(this_object_);
+      << reinterpret_cast<void*>(return_pc_) << " this=" << reinterpret_cast<void*>(this_object_)
+      << " force_deopt_id=" << force_deopt_id_;
   return os.str();
 }
 
diff --git a/runtime/instrumentation.h b/runtime/instrumentation.h
index a7907c8..82e1a13 100644
--- a/runtime/instrumentation.h
+++ b/runtime/instrumentation.h
@@ -536,6 +536,10 @@
   void InstrumentThreadStack(Thread* thread)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
+  // Force all currently running frames to be deoptimized back to interpreter. This should only be
+  // used in cases where basically all compiled code has been invalidated.
+  void DeoptimizeAllThreadFrames() REQUIRES(art::Locks::mutator_lock_);
+
   static size_t ComputeFrameId(Thread* self,
                                size_t frame_depth,
                                size_t inlined_frames_before_frame)
@@ -643,6 +647,11 @@
     return deoptimized_methods_lock_.get();
   }
 
+  // A counter that's incremented every time a DeoptimizeAllFrames. We check each
+  // InstrumentationStackFrames creation id against this number and if they differ we deopt even if
+  // we could otherwise continue running.
+  uint64_t current_force_deopt_id_ GUARDED_BY(Locks::mutator_lock_);
+
   // Have we hijacked ArtMethod::code_ so that it calls instrumentation/interpreter code?
   bool instrumentation_stubs_installed_;
 
@@ -746,6 +755,7 @@
 
   friend class InstrumentationTest;  // For GetCurrentInstrumentationLevel and ConfigureStubs.
   friend class InstrumentationStackPopper;  // For popping instrumentation frames.
+  friend void InstrumentationInstallStack(Thread*, void*);
 
   DISALLOW_COPY_AND_ASSIGN(Instrumentation);
 };
@@ -758,12 +768,14 @@
                             ArtMethod* method,
                             uintptr_t return_pc,
                             size_t frame_id,
-                            bool interpreter_entry)
+                            bool interpreter_entry,
+                            uint64_t force_deopt_id)
       : this_object_(this_object),
         method_(method),
         return_pc_(return_pc),
         frame_id_(frame_id),
-        interpreter_entry_(interpreter_entry) {
+        interpreter_entry_(interpreter_entry),
+        force_deopt_id_(force_deopt_id) {
   }
 
   std::string Dump() const REQUIRES_SHARED(Locks::mutator_lock_);
@@ -773,6 +785,7 @@
   uintptr_t return_pc_;
   size_t frame_id_;
   bool interpreter_entry_;
+  uint64_t force_deopt_id_;
 };
 
 }  // namespace instrumentation
diff --git a/runtime/jit/jit_code_cache.cc b/runtime/jit/jit_code_cache.cc
index c0342ba..ecfe9b6 100644
--- a/runtime/jit/jit_code_cache.cc
+++ b/runtime/jit/jit_code_cache.cc
@@ -1686,6 +1686,28 @@
   }
 }
 
+void JitCodeCache::InvalidateAllCompiledCode() {
+  art::MutexLock mu(Thread::Current(), *Locks::jit_lock_);
+  size_t cnt = profiling_infos_.size();
+  size_t osr_size = osr_code_map_.size();
+  for (ProfilingInfo* pi : profiling_infos_) {
+    // NB Due to OSR we might run this on some methods multiple times but this should be fine.
+    ArtMethod* meth = pi->GetMethod();
+    pi->SetSavedEntryPoint(nullptr);
+    // We had a ProfilingInfo so we must be warm.
+    ClearMethodCounter(meth, /*was_warm=*/true);
+    ClassLinker* linker = Runtime::Current()->GetClassLinker();
+    if (meth->IsObsolete()) {
+      linker->SetEntryPointsForObsoleteMethod(meth);
+    } else {
+      linker->SetEntryPointsToInterpreter(meth);
+    }
+  }
+  osr_code_map_.clear();
+  VLOG(jit) << "Invalidated the compiled code of " << (cnt - osr_size) << " methods and "
+            << osr_size << " OSRs.";
+}
+
 void JitCodeCache::InvalidateCompiledCodeFor(ArtMethod* method,
                                              const OatQuickMethodHeader* header) {
   DCHECK(!method->IsNative());
diff --git a/runtime/jit/jit_code_cache.h b/runtime/jit/jit_code_cache.h
index 64607b6..154700f 100644
--- a/runtime/jit/jit_code_cache.h
+++ b/runtime/jit/jit_code_cache.h
@@ -274,6 +274,10 @@
       REQUIRES(!Locks::jit_lock_)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
+  void InvalidateAllCompiledCode()
+      REQUIRES(!Locks::jit_lock_)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
   void InvalidateCompiledCodeFor(ArtMethod* method, const OatQuickMethodHeader* code)
       REQUIRES(!Locks::jit_lock_)
       REQUIRES_SHARED(Locks::mutator_lock_);
diff --git a/runtime/jni/jni_id_manager.cc b/runtime/jni/jni_id_manager.cc
index 88c6ba1..9ae8c89 100644
--- a/runtime/jni/jni_id_manager.cc
+++ b/runtime/jni/jni_id_manager.cc
@@ -209,6 +209,7 @@
   }
 }
 
+// TODO need to fix races in here with visitors
 template <typename ArtType> uintptr_t JniIdManager::EncodeGenericId(ArtType* t) {
   Runtime* runtime = Runtime::Current();
   JniIdType id_type = runtime->GetJniIdType();
@@ -309,6 +310,22 @@
   return res;
 }
 
+void JniIdManager::VisitIds(Thread* self, JniIdManager::IdVisitor* visitor) {
+  art::WriterMutexLock mu(self, *Locks::jni_id_lock_);
+  if (visitor->ShouldVisitFields()) {
+    for (auto it = field_id_map_.begin(); it != field_id_map_.end(); ++it) {
+      visitor->VisitFieldId(
+          reinterpret_cast<jfieldID>(IndexToId(std::distance(field_id_map_.begin(), it))), &*it);
+    }
+  }
+  if (visitor->ShouldVisitMethods()) {
+    for (auto it = method_id_map_.begin(); it != method_id_map_.end(); ++it) {
+      visitor->VisitMethodId(
+          reinterpret_cast<jmethodID>(IndexToId(std::distance(method_id_map_.begin(), it))), &*it);
+    }
+  }
+}
+
 template <typename ArtType> ArtType* JniIdManager::DecodeGenericId(uintptr_t t) {
   if (Runtime::Current()->GetJniIdType() == JniIdType::kIndices && (t % 2) == 1) {
     ReaderMutexLock mu(Thread::Current(), *Locks::jni_id_lock_);
diff --git a/runtime/jni/jni_id_manager.h b/runtime/jni/jni_id_manager.h
index d294815..7b2f3c4 100644
--- a/runtime/jni/jni_id_manager.h
+++ b/runtime/jni/jni_id_manager.h
@@ -32,6 +32,22 @@
 class ScopedEnableSuspendAllJniIdQueries;
 class JniIdManager {
  public:
+  class IdVisitor {
+   public:
+    virtual ~IdVisitor() {}
+    virtual void VisitMethodId(jmethodID id, ArtMethod** method) = 0;
+    virtual void VisitFieldId(jfieldID id, ArtField** field) = 0;
+    virtual bool ShouldVisitFields() = 0;
+    virtual bool ShouldVisitMethods() = 0;
+  };
+
+  template <typename T,
+            typename = typename std::enable_if<std::is_same_v<T, jmethodID> ||
+                                               std::is_same_v<T, jfieldID>>>
+  static constexpr bool IsIndexId(T val) {
+    return val == nullptr || reinterpret_cast<uintptr_t>(val) % 2 == 1;
+  }
+
   ArtMethod* DecodeMethodId(jmethodID method) REQUIRES(!Locks::jni_id_lock_);
   ArtField* DecodeFieldId(jfieldID field) REQUIRES(!Locks::jni_id_lock_);
   jmethodID EncodeMethodId(ArtMethod* method) REQUIRES(!Locks::jni_id_lock_)
@@ -39,6 +55,34 @@
   jfieldID EncodeFieldId(ArtField* field) REQUIRES(!Locks::jni_id_lock_)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
+  void VisitIds(Thread* self, IdVisitor* visitor);
+
+  template<typename MethodVisitor, typename FieldVisitor>
+  void VisitIds(Thread* self, MethodVisitor m, FieldVisitor f) REQUIRES(!Locks::jni_id_lock_) {
+    struct FuncVisitor : public IdVisitor {
+     public:
+      FuncVisitor(MethodVisitor m, FieldVisitor f) : m_(m), f_(f) {}
+      bool ShouldVisitFields() override {
+        return true;
+      }
+      bool ShouldVisitMethods() override {
+        return true;
+      }
+      void VisitMethodId(jmethodID mid, ArtMethod** am) NO_THREAD_SAFETY_ANALYSIS override {
+        m_(mid, am);
+      }
+      void VisitFieldId(jfieldID fid, ArtField** af) NO_THREAD_SAFETY_ANALYSIS override {
+        f_(fid, af);
+      }
+
+     private:
+      MethodVisitor m_;
+      FieldVisitor f_;
+    };
+    FuncVisitor fv(m, f);
+    VisitIds(self, &fv);
+  }
+
  private:
   template <typename ArtType>
   uintptr_t EncodeGenericId(ArtType* t) REQUIRES(!Locks::jni_id_lock_)
diff --git a/runtime/mirror/class-refvisitor-inl.h b/runtime/mirror/class-refvisitor-inl.h
index 263b774..8c85387 100644
--- a/runtime/mirror/class-refvisitor-inl.h
+++ b/runtime/mirror/class-refvisitor-inl.h
@@ -53,20 +53,14 @@
 
 template<ReadBarrierOption kReadBarrierOption, class Visitor>
 void Class::VisitNativeRoots(Visitor& visitor, PointerSize pointer_size) {
-  for (ArtField& field : GetSFieldsUnchecked()) {
-    // Visit roots first in case the declaring class gets moved.
-    field.VisitRoots(visitor);
+  VisitFields<kReadBarrierOption>([&](ArtField* field) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    field->VisitRoots(visitor);
     if (kIsDebugBuild && IsResolved()) {
-      CHECK_EQ(field.GetDeclaringClass<kReadBarrierOption>(), this) << GetStatus();
+      CHECK_EQ(field->GetDeclaringClass<kReadBarrierOption>(), this)
+          << GetStatus() << field->GetDeclaringClass()->PrettyClass() << " != " << PrettyClass();
     }
-  }
-  for (ArtField& field : GetIFieldsUnchecked()) {
-    // Visit roots first in case the declaring class gets moved.
-    field.VisitRoots(visitor);
-    if (kIsDebugBuild && IsResolved()) {
-      CHECK_EQ(field.GetDeclaringClass<kReadBarrierOption>(), this) << GetStatus();
-    }
-  }
+  });
+  // Don't use VisitMethods because we don't want to hit the class-ext methods twice.
   for (ArtMethod& method : GetMethods(pointer_size)) {
     method.VisitRoots<kReadBarrierOption>(visitor, pointer_size);
   }
@@ -76,6 +70,27 @@
   }
 }
 
+template<ReadBarrierOption kReadBarrierOption, class Visitor>
+void Class::VisitMethods(Visitor visitor, PointerSize pointer_size) {
+  for (ArtMethod& method : GetMethods(pointer_size)) {
+    visitor(&method);
+  }
+  ObjPtr<ClassExt> ext(GetExtData<kDefaultVerifyFlags, kReadBarrierOption>());
+  if (!ext.IsNull()) {
+    ext->VisitMethods<kReadBarrierOption, Visitor>(visitor, pointer_size);
+  }
+}
+
+template<ReadBarrierOption kReadBarrierOption, class Visitor>
+void Class::VisitFields(Visitor visitor) {
+  for (ArtField& sfield : GetSFieldsUnchecked()) {
+    visitor(&sfield);
+  }
+  for (ArtField& ifield : GetIFieldsUnchecked()) {
+    visitor(&ifield);
+  }
+}
+
 }  // namespace mirror
 }  // namespace art
 
diff --git a/runtime/mirror/class.cc b/runtime/mirror/class.cc
index 7dff9df..455f98d 100644
--- a/runtime/mirror/class.cc
+++ b/runtime/mirror/class.cc
@@ -1539,6 +1539,12 @@
 
 std::string Class::PrettyClass() {
   std::string result;
+  if (IsObsoleteObject()) {
+    result += "(Obsolete)";
+  }
+  if (IsRetired()) {
+    result += "(Retired)";
+  }
   result += "java.lang.Class<";
   result += PrettyDescriptor();
   result += ">";
diff --git a/runtime/mirror/class.h b/runtime/mirror/class.h
index 960f49c..6ed20ed 100644
--- a/runtime/mirror/class.h
+++ b/runtime/mirror/class.h
@@ -313,6 +313,17 @@
     }
   }
 
+  bool IsObsoleteObject() REQUIRES_SHARED(Locks::mutator_lock_) {
+    return (GetAccessFlags() & kAccObsoleteObject) != 0;
+  }
+
+  void SetObsoleteObject() REQUIRES_SHARED(Locks::mutator_lock_) {
+    uint32_t flags = GetField32(OFFSET_OF_OBJECT_MEMBER(Class, access_flags_));
+    if ((flags & kAccObsoleteObject) == 0) {
+      SetAccessFlags(flags | kAccObsoleteObject);
+    }
+  }
+
   template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags>
   bool IsTypeOfReferenceClass() REQUIRES_SHARED(Locks::mutator_lock_) {
     return (GetClassFlags<kVerifyFlags>() & kClassFlagReference) != 0;
@@ -1110,6 +1121,15 @@
   void VisitNativeRoots(Visitor& visitor, PointerSize pointer_size)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
+  // Visit ArtMethods directly owned by this class.
+  template<ReadBarrierOption kReadBarrierOption = kWithReadBarrier, class Visitor>
+  void VisitMethods(Visitor visitor, PointerSize pointer_size)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
+  // Visit ArtFields directly owned by this class.
+  template<ReadBarrierOption kReadBarrierOption = kWithReadBarrier, class Visitor>
+  void VisitFields(Visitor visitor) REQUIRES_SHARED(Locks::mutator_lock_);
+
   // Get one of the primitive classes.
   static ObjPtr<mirror::Class> GetPrimitiveClass(ObjPtr<mirror::String> name)
       REQUIRES_SHARED(Locks::mutator_lock_);
diff --git a/runtime/mirror/class_ext-inl.h b/runtime/mirror/class_ext-inl.h
index ead02ee..fd81a2a 100644
--- a/runtime/mirror/class_ext-inl.h
+++ b/runtime/mirror/class_ext-inl.h
@@ -21,8 +21,11 @@
 
 #include "array-inl.h"
 #include "art_method-inl.h"
+#include "base/enums.h"
 #include "handle_scope.h"
+#include "mirror/object.h"
 #include "object-inl.h"
+#include "verify_object.h"
 
 namespace art {
 namespace mirror {
@@ -89,6 +92,12 @@
 }
 
 template <VerifyObjectFlags kVerifyFlags, ReadBarrierOption kReadBarrierOption>
+inline ObjPtr<Class> ClassExt::GetObsoleteClass() {
+  return GetFieldObject<Class, kVerifyFlags, kReadBarrierOption>(
+      OFFSET_OF_OBJECT_MEMBER(ClassExt, obsolete_class_));
+}
+
+template <VerifyObjectFlags kVerifyFlags, ReadBarrierOption kReadBarrierOption>
 inline ObjPtr<PointerArray> ClassExt::GetJMethodIDs() {
   return GetFieldObject<PointerArray, kVerifyFlags, kReadBarrierOption>(
       OFFSET_OF_OBJECT_MEMBER(ClassExt, jmethod_ids_));
@@ -116,15 +125,58 @@
 
 template<ReadBarrierOption kReadBarrierOption, class Visitor>
 void ClassExt::VisitNativeRoots(Visitor& visitor, PointerSize pointer_size) {
+  VisitMethods<kReadBarrierOption>([&](ArtMethod* method) {
+    method->VisitRoots<kReadBarrierOption>(visitor, pointer_size);
+  }, pointer_size);
+}
+
+template<ReadBarrierOption kReadBarrierOption, class Visitor>
+void ClassExt::VisitMethods(Visitor visitor, PointerSize pointer_size) {
   ObjPtr<PointerArray> arr(GetObsoleteMethods<kDefaultVerifyFlags, kReadBarrierOption>());
-  if (arr.IsNull()) {
-    return;
+  if (!arr.IsNull()) {
+    int32_t len = arr->GetLength();
+    for (int32_t i = 0; i < len; i++) {
+      ArtMethod* method = arr->GetElementPtrSize<ArtMethod*>(i, pointer_size);
+      if (method != nullptr) {
+        visitor(method);
+      }
+    }
   }
-  int32_t len = arr->GetLength();
-  for (int32_t i = 0; i < len; i++) {
-    ArtMethod* method = arr->GetElementPtrSize<ArtMethod*, kDefaultVerifyFlags>(i, pointer_size);
-    if (method != nullptr) {
-      method->VisitRoots<kReadBarrierOption>(visitor, pointer_size);
+}
+
+template<ReadBarrierOption kReadBarrierOption, class Visitor>
+void ClassExt::VisitJMethodIDs(Visitor v) {
+  ObjPtr<PointerArray> marr(GetJMethodIDs<kDefaultVerifyFlags, kReadBarrierOption>());
+  if (!marr.IsNull()) {
+    int32_t len = marr->GetLength();
+    for (int32_t i = 0; i < len; i++) {
+      jmethodID id = marr->GetElementPtrSize<jmethodID>(i, kRuntimePointerSize);
+      if (id != nullptr) {
+        v(id, i);
+      }
+    }
+  }
+}
+template<ReadBarrierOption kReadBarrierOption, class Visitor>
+void ClassExt::VisitJFieldIDs(Visitor v) {
+  ObjPtr<PointerArray> sarr(GetStaticJFieldIDs<kDefaultVerifyFlags, kReadBarrierOption>());
+  if (!sarr.IsNull()) {
+    int32_t len = sarr->GetLength();
+    for (int32_t i = 0; i < len; i++) {
+      jfieldID id = sarr->GetElementPtrSize<jfieldID>(i, kRuntimePointerSize);
+      if (id != nullptr) {
+        v(id, i, true);
+      }
+    }
+  }
+  ObjPtr<PointerArray> iarr(GetInstanceJFieldIDs<kDefaultVerifyFlags, kReadBarrierOption>());
+  if (!iarr.IsNull()) {
+    int32_t len = iarr->GetLength();
+    for (int32_t i = 0; i < len; i++) {
+      jfieldID id = iarr->GetElementPtrSize<jfieldID>(i, kRuntimePointerSize);
+      if (id != nullptr) {
+        v(id, i, false);
+      }
     }
   }
 }
diff --git a/runtime/mirror/class_ext.cc b/runtime/mirror/class_ext.cc
index 4c6cb4d..27dcea8 100644
--- a/runtime/mirror/class_ext.cc
+++ b/runtime/mirror/class_ext.cc
@@ -25,6 +25,8 @@
 #include "class_root.h"
 #include "dex/dex_file-inl.h"
 #include "gc/accounting/card_table-inl.h"
+#include "mirror/object.h"
+#include "mirror/object_array.h"
 #include "object-inl.h"
 #include "object_array-alloc-inl.h"
 #include "object_array-inl.h"
@@ -101,6 +103,10 @@
   return true;
 }
 
+void ClassExt::SetObsoleteClass(ObjPtr<Class> klass) {
+  SetFieldObject<false>(OFFSET_OF_OBJECT_MEMBER(ClassExt, obsolete_class_), klass);
+}
+
 ObjPtr<ClassExt> ClassExt::Alloc(Thread* self) {
   return ObjPtr<ClassExt>::DownCast(GetClassRoot<ClassExt>()->AllocObject(self));
 }
diff --git a/runtime/mirror/class_ext.h b/runtime/mirror/class_ext.h
index 6fb225f..eb4047b 100644
--- a/runtime/mirror/class_ext.h
+++ b/runtime/mirror/class_ext.h
@@ -106,8 +106,26 @@
   inline void VisitNativeRoots(Visitor& visitor, PointerSize pointer_size)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
+  template<ReadBarrierOption kReadBarrierOption = kWithReadBarrier, class Visitor>
+  inline void VisitMethods(Visitor visitor, PointerSize pointer_size)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
   static ObjPtr<ClassExt> Alloc(Thread* self) REQUIRES_SHARED(Locks::mutator_lock_);
 
+  // TODO Save the obsolete class, if we have one.
+  // TODO We need this so jit-cleanup can work. the obsolete class might get cleaned up early
+  // otherwise. We should remove the need for this.
+  template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags,
+           ReadBarrierOption kReadBarrierOption = kWithReadBarrier>
+  ObjPtr<Class> GetObsoleteClass() REQUIRES_SHARED(Locks::mutator_lock_);
+  void SetObsoleteClass(ObjPtr<Class> classes) REQUIRES_SHARED(Locks::mutator_lock_);
+
+  template<ReadBarrierOption kReadBarrierOption = kWithReadBarrier, typename Visitor>
+  inline void VisitJFieldIDs(Visitor v) REQUIRES_SHARED(Locks::mutator_lock_);
+
+  template<ReadBarrierOption kReadBarrierOption = kWithReadBarrier, typename Visitor>
+  inline void VisitJMethodIDs(Visitor v) REQUIRES_SHARED(Locks::mutator_lock_);
+
  private:
   template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags,
            ReadBarrierOption kReadBarrierOption = kWithReadBarrier>
@@ -123,6 +141,9 @@
   // the classes methods_ array or '0' if no id has been assigned to that method yet.
   HeapReference<PointerArray> jmethod_ids_;
 
+  // If set this is the Class object that was being used before a structural redefinition occurred.
+  HeapReference<Class> obsolete_class_;
+
   HeapReference<ObjectArray<DexCache>> obsolete_dex_caches_;
 
   HeapReference<PointerArray> obsolete_methods_;
@@ -137,8 +158,8 @@
   HeapReference<Object> verify_error_;
 
   // Native pointer to DexFile and ClassDef index of this class before it was JVMTI-redefined.
-  int32_t pre_redefine_class_def_index_;
   int64_t pre_redefine_dex_file_ptr_;
+  int32_t pre_redefine_class_def_index_;
 
   friend struct art::ClassExtOffsets;  // for verifying offset information
   DISALLOW_IMPLICIT_CONSTRUCTORS(ClassExt);
diff --git a/runtime/mirror/executable-inl.h b/runtime/mirror/executable-inl.h
index 6d4b46a..ce35d6e 100644
--- a/runtime/mirror/executable-inl.h
+++ b/runtime/mirror/executable-inl.h
@@ -20,6 +20,7 @@
 #include "executable.h"
 
 #include "object-inl.h"
+#include "verify_object.h"
 
 namespace art {
 namespace mirror {
@@ -36,6 +37,17 @@
   return GetFieldObject<mirror::Class>(DeclaringClassOffset());
 }
 
+template<typename Visitor, VerifyObjectFlags kVerifiyFlags>
+inline void Executable::VisitTarget(Visitor&& v) {
+  ArtMethod* orig = GetArtMethod<kVerifiyFlags>();
+  ArtMethod* new_target = v(orig);
+  if (orig != new_target) {
+    SetArtMethod(new_target);
+    SetDexMethodIndex(new_target->GetDexMethodIndex());
+    SetDeclaringClass(new_target->GetDeclaringClass());
+  }
+}
+
 }  // namespace mirror
 }  // namespace art
 
diff --git a/runtime/mirror/executable.h b/runtime/mirror/executable.h
index a99c3ec..8eca206 100644
--- a/runtime/mirror/executable.h
+++ b/runtime/mirror/executable.h
@@ -41,6 +41,10 @@
     return reinterpret_cast64<ArtMethod*>(GetField64<kVerifyFlags>(ArtMethodOffset()));
   }
 
+  template <typename Visitor,
+            VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags>
+  inline void VisitTarget(Visitor&& v) REQUIRES(Locks::mutator_lock_);
+
   template <bool kTransactionActive = false,
             bool kCheckTransaction = true,
             VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags>
@@ -61,6 +65,21 @@
   uint32_t access_flags_;
   uint32_t dex_method_index_;
 
+  template<bool kTransactionActive = false>
+  void SetDeclaringClass(ObjPtr<mirror::Class> klass) REQUIRES_SHARED(Locks::mutator_lock_) {
+    SetFieldObject<kTransactionActive>(DeclaringClassOffset(), klass);
+  }
+
+  template<bool kTransactionActive = false>
+  void SetAccessFlags(uint32_t flags) REQUIRES_SHARED(Locks::mutator_lock_) {
+    SetField32<kTransactionActive>(AccessFlagsOffset(), flags);
+  }
+
+  template<bool kTransactionActive = false>
+  void SetDexMethodIndex(uint32_t idx) REQUIRES_SHARED(Locks::mutator_lock_) {
+    SetField32<kTransactionActive>(DexMethodIndexOffset(), idx);
+  }
+
   static MemberOffset DeclaringClassOffset() {
     return MemberOffset(OFFSETOF_MEMBER(Executable, declaring_class_));
   }
diff --git a/runtime/mirror/field-inl.h b/runtime/mirror/field-inl.h
index ac11be1..6e82d6d 100644
--- a/runtime/mirror/field-inl.h
+++ b/runtime/mirror/field-inl.h
@@ -104,6 +104,18 @@
   SetFieldObject<kTransactionActive>(OFFSET_OF_OBJECT_MEMBER(Field, type_), type);
 }
 
+template<typename Visitor>
+inline void Field::VisitTarget(Visitor&& v) {
+  ArtField* orig = GetArtField(/*use_dex_cache*/false);
+  ArtField* new_value = v(orig);
+  if (orig != new_value) {
+    SetDexFieldIndex<false>(new_value->GetDexFieldIndex());
+    SetOffset<false>(new_value->GetOffset().Int32Value());
+    SetDeclaringClass<false>(new_value->GetDeclaringClass());
+  }
+  DCHECK_EQ(new_value, GetArtField(/*use_dex_cache*/false));
+}
+
 }  // namespace mirror
 }  // namespace art
 
diff --git a/runtime/mirror/field.cc b/runtime/mirror/field.cc
index f4d1e73..9a40006 100644
--- a/runtime/mirror/field.cc
+++ b/runtime/mirror/field.cc
@@ -24,7 +24,7 @@
 namespace art {
 namespace mirror {
 
-ArtField* Field::GetArtField() {
+ArtField* Field::GetArtField(bool use_dex_cache) {
   ObjPtr<mirror::Class> declaring_class = GetDeclaringClass();
   if (UNLIKELY(declaring_class->IsProxyClass())) {
     DCHECK(IsStatic());
@@ -38,7 +38,9 @@
     }
   }
   const ObjPtr<mirror::DexCache> dex_cache = declaring_class->GetDexCache();
-  ArtField* art_field = dex_cache->GetResolvedField(GetDexFieldIndex(), kRuntimePointerSize);
+  ArtField* art_field = use_dex_cache
+                            ? dex_cache->GetResolvedField(GetDexFieldIndex(), kRuntimePointerSize)
+                            : nullptr;
   if (UNLIKELY(art_field == nullptr)) {
     if (IsStatic()) {
       art_field = declaring_class->FindDeclaredStaticField(dex_cache, GetDexFieldIndex());
@@ -46,7 +48,9 @@
       art_field = declaring_class->FindInstanceField(dex_cache, GetDexFieldIndex());
     }
     CHECK(art_field != nullptr);
-    dex_cache->SetResolvedField(GetDexFieldIndex(), art_field, kRuntimePointerSize);
+    if (use_dex_cache) {
+      dex_cache->SetResolvedField(GetDexFieldIndex(), art_field, kRuntimePointerSize);
+    }
   }
   CHECK_EQ(declaring_class, art_field->GetDeclaringClass());
   return art_field;
diff --git a/runtime/mirror/field.h b/runtime/mirror/field.h
index 6ba8dc6..89c86e3 100644
--- a/runtime/mirror/field.h
+++ b/runtime/mirror/field.h
@@ -68,8 +68,10 @@
     return GetField32(OFFSET_OF_OBJECT_MEMBER(Field, offset_));
   }
 
-  // Slow, try to use only for PrettyField and such.
-  ArtField* GetArtField() REQUIRES_SHARED(Locks::mutator_lock_);
+  // Slow, try to use only for PrettyField and such. Set use-dex-cache to false to not utilize the
+  // dex-cache when finding the art-field. This is useful for cases where the dex-cache might be
+  // temporarally invalid.
+  ArtField* GetArtField(bool use_dex_cache = true) REQUIRES_SHARED(Locks::mutator_lock_);
 
   template <PointerSize kPointerSize, bool kTransactionActive = false>
   static ObjPtr<mirror::Field> CreateFromArtField(Thread* self,
@@ -77,6 +79,12 @@
                                                   bool force_resolve)
       REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!Roles::uninterruptible_);
 
+
+  // Used to modify the target of this Field object, if required for structural redefinition or some
+  // other purpose.
+  template<typename Visitor>
+  inline void VisitTarget(Visitor&& v) REQUIRES(Locks::mutator_lock_);
+
  private:
   // Padding required for matching alignment with the Java peer.
   uint8_t padding_[2];
diff --git a/runtime/mirror/method_handle_impl-inl.h b/runtime/mirror/method_handle_impl-inl.h
index 932b434..0085642 100644
--- a/runtime/mirror/method_handle_impl-inl.h
+++ b/runtime/mirror/method_handle_impl-inl.h
@@ -39,6 +39,20 @@
       GetTargetMethod()->GetDeclaringClass() : GetTargetField()->GetDeclaringClass();
 }
 
+template<typename Visitor>
+inline void MethodHandle::VisitTarget(Visitor&& v) {
+  void* target = GetTargetField();
+  void* result;
+  if (GetHandleKind() < kFirstAccessorKind) {
+    result = v(GetTargetMethod());
+  } else {
+    result = v(GetTargetField());
+  }
+  if (result != target) {
+    SetField64<false>(ArtFieldOrMethodOffset(), reinterpret_cast<uintptr_t>(result));
+  }
+}
+
 }  // namespace mirror
 }  // namespace art
 
diff --git a/runtime/mirror/method_handle_impl.h b/runtime/mirror/method_handle_impl.h
index c973a24..357ec9d 100644
--- a/runtime/mirror/method_handle_impl.h
+++ b/runtime/mirror/method_handle_impl.h
@@ -87,6 +87,11 @@
   // supported.
   static const char* GetReturnTypeDescriptor(const char* invoke_method_name);
 
+  // Used when classes become structurally obsolete to change the MethodHandle to refer to the new
+  // method or field.
+  template<typename Visitor>
+  void VisitTarget(Visitor&& v) REQUIRES(Locks::mutator_lock_);
+
  protected:
   void Initialize(uintptr_t art_field_or_method, Kind kind, Handle<MethodType> method_type)
       REQUIRES_SHARED(Locks::mutator_lock_);
diff --git a/runtime/mirror/var_handle-inl.h b/runtime/mirror/var_handle-inl.h
new file mode 100644
index 0000000..d3f582d
--- /dev/null
+++ b/runtime/mirror/var_handle-inl.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ART_RUNTIME_MIRROR_VAR_HANDLE_INL_H_
+#define ART_RUNTIME_MIRROR_VAR_HANDLE_INL_H_
+
+#include "var_handle.h"
+
+namespace art {
+class ArtField;
+
+namespace mirror {
+
+template<typename Visitor>
+inline void FieldVarHandle::VisitTarget(Visitor&& v) {
+  ArtField* orig = GetField();
+  ArtField* new_value = v(orig);
+  if (orig != new_value) {
+    SetField64</*kTransactionActive*/ false>(ArtFieldOffset(),
+                                             reinterpret_cast<uintptr_t>(new_value));
+  }
+}
+
+}  // namespace mirror
+}  // namespace art
+
+#endif  // ART_RUNTIME_MIRROR_VAR_HANDLE_INL_H_
diff --git a/runtime/mirror/var_handle.h b/runtime/mirror/var_handle.h
index a46b466..ac78d98 100644
--- a/runtime/mirror/var_handle.h
+++ b/runtime/mirror/var_handle.h
@@ -197,6 +197,10 @@
 
   ArtField* GetField() REQUIRES_SHARED(Locks::mutator_lock_);
 
+  // Used for updating var-handles to obsolete fields.
+  template<typename Visitor>
+  inline void VisitTarget(Visitor&& v) REQUIRES(Locks::mutator_lock_);
+
  private:
   static MemberOffset ArtFieldOffset() {
     return MemberOffset(OFFSETOF_MEMBER(FieldVarHandle, art_field_));
diff --git a/runtime/native/java_lang_Class.cc b/runtime/native/java_lang_Class.cc
index 4967f9e..4516d1b 100644
--- a/runtime/native/java_lang_Class.cc
+++ b/runtime/native/java_lang_Class.cc
@@ -218,6 +218,10 @@
   ScopedFastNativeObjectAccess soa(env);
   StackHandleScope<1> hs(soa.Self());
   Handle<mirror::Class> klass = hs.NewHandle(DecodeClass(soa, javaThis));
+  if (klass->IsObsoleteObject()) {
+    ThrowRuntimeException("Obsolete Object!");
+    return nullptr;
+  }
 
   if (klass->IsProxyClass()) {
     StackHandleScope<1> hs2(soa.Self());
@@ -262,6 +266,10 @@
     ObjPtr<mirror::Class> klass,
     bool public_only,
     bool force_resolve) REQUIRES_SHARED(Locks::mutator_lock_) {
+  if (UNLIKELY(klass->IsObsoleteObject())) {
+    ThrowRuntimeException("Obsolete Object!");
+    return nullptr;
+  }
   StackHandleScope<1> hs(self);
   IterationRange<StrideIterator<ArtField>> ifields = klass->GetIFields();
   IterationRange<StrideIterator<ArtField>> sfields = klass->GetSFields();
@@ -386,6 +394,10 @@
                                                                    ObjPtr<mirror::Class> c,
                                                                    ObjPtr<mirror::String> name)
     REQUIRES_SHARED(Locks::mutator_lock_) {
+  if (UNLIKELY(c->IsObsoleteObject())) {
+    ThrowRuntimeException("Obsolete Object!");
+    return nullptr;
+  }
   ArtField* art_field = FindFieldByName(name, c->GetIFieldsPtr());
   if (art_field != nullptr) {
     return mirror::Field::CreateFromArtField<kRuntimePointerSize>(self, art_field, true);
@@ -404,6 +416,10 @@
   DCHECK(name != nullptr);
   DCHECK(self != nullptr);
 
+  if (UNLIKELY(clazz->IsObsoleteObject())) {
+    ThrowRuntimeException("Obsolete Object!");
+    return nullptr;
+  }
   StackHandleScope<2> hs(self);
   MutableHandle<mirror::Class> h_clazz(hs.NewHandle(clazz));
   Handle<mirror::String> h_name(hs.NewHandle(name));
@@ -501,10 +517,15 @@
   DCHECK(!Runtime::Current()->IsActiveTransaction());
 
   StackHandleScope<1> hs(soa.Self());
+  ObjPtr<mirror::Class> klass = DecodeClass(soa, javaThis);
+  if (UNLIKELY(klass->IsObsoleteObject())) {
+    ThrowRuntimeException("Obsolete Object!");
+    return nullptr;
+  }
   Handle<mirror::Constructor> result = hs.NewHandle(
       mirror::Class::GetDeclaredConstructorInternal<kRuntimePointerSize, false>(
       soa.Self(),
-      DecodeClass(soa, javaThis),
+      klass,
       soa.Decode<mirror::ObjectArray<mirror::Class>>(args)));
   if (result == nullptr || ShouldDenyAccessToMember(result->GetArtMethod(), soa.Self())) {
     return nullptr;
@@ -529,6 +550,10 @@
   bool public_only = (publicOnly != JNI_FALSE);
   hiddenapi::AccessContext hiddenapi_context = GetReflectionCaller(soa.Self());
   Handle<mirror::Class> h_klass = hs.NewHandle(DecodeClass(soa, javaThis));
+  if (UNLIKELY(h_klass->IsObsoleteObject())) {
+    ThrowRuntimeException("Obsolete Object!");
+    return nullptr;
+  }
   size_t constructor_count = 0;
   // Two pass approach for speed.
   for (auto& m : h_klass->GetDirectMethods(kRuntimePointerSize)) {
@@ -563,10 +588,15 @@
   StackHandleScope<1> hs(soa.Self());
   DCHECK_EQ(Runtime::Current()->GetClassLinker()->GetImagePointerSize(), kRuntimePointerSize);
   DCHECK(!Runtime::Current()->IsActiveTransaction());
+  ObjPtr<mirror::Class> klass = DecodeClass(soa, javaThis);
+  if (UNLIKELY(klass->IsObsoleteObject())) {
+    ThrowRuntimeException("Obsolete Object!");
+    return nullptr;
+  }
   Handle<mirror::Method> result = hs.NewHandle(
       mirror::Class::GetDeclaredMethodInternal<kRuntimePointerSize, false>(
           soa.Self(),
-          DecodeClass(soa, javaThis),
+          klass,
           soa.Decode<mirror::String>(name),
           soa.Decode<mirror::ObjectArray<mirror::Class>>(args),
           GetHiddenapiAccessContextFunction(soa.Self())));
@@ -585,6 +615,10 @@
   bool public_only = (publicOnly != JNI_FALSE);
 
   Handle<mirror::Class> klass = hs.NewHandle(DecodeClass(soa, javaThis));
+  if (klass->IsObsoleteObject()) {
+    ThrowRuntimeException("Obsolete Object!");
+    return nullptr;
+  }
   size_t num_methods = 0;
   for (ArtMethod& m : klass->GetDeclaredMethods(kRuntimePointerSize)) {
     uint32_t modifiers = m.GetAccessFlags();
@@ -623,6 +657,10 @@
   ScopedFastNativeObjectAccess soa(env);
   StackHandleScope<2> hs(soa.Self());
   Handle<mirror::Class> klass(hs.NewHandle(DecodeClass(soa, javaThis)));
+  if (klass->IsObsoleteObject()) {
+    ThrowRuntimeException("Obsolete Object!");
+    return nullptr;
+  }
 
   // Handle public contract to throw NPE if the "annotationClass" argument was null.
   if (UNLIKELY(annotationClass == nullptr)) {
@@ -642,6 +680,10 @@
   ScopedFastNativeObjectAccess soa(env);
   StackHandleScope<1> hs(soa.Self());
   Handle<mirror::Class> klass(hs.NewHandle(DecodeClass(soa, javaThis)));
+  if (klass->IsObsoleteObject()) {
+    ThrowRuntimeException("Obsolete Object!");
+    return nullptr;
+  }
   if (klass->IsProxyClass() || klass->GetDexCache() == nullptr) {
     // Return an empty array instead of a null pointer.
     ObjPtr<mirror::Class>  annotation_array_class =
@@ -659,6 +701,10 @@
   ScopedFastNativeObjectAccess soa(env);
   StackHandleScope<1> hs(soa.Self());
   Handle<mirror::Class> klass(hs.NewHandle(DecodeClass(soa, javaThis)));
+  if (klass->IsObsoleteObject()) {
+    ThrowRuntimeException("Obsolete Object!");
+    return nullptr;
+  }
   ObjPtr<mirror::ObjectArray<mirror::Class>> classes = nullptr;
   if (!klass->IsProxyClass() && klass->GetDexCache() != nullptr) {
     classes = annotations::GetDeclaredClasses(klass);
@@ -682,6 +728,10 @@
   ScopedFastNativeObjectAccess soa(env);
   StackHandleScope<1> hs(soa.Self());
   Handle<mirror::Class> klass(hs.NewHandle(DecodeClass(soa, javaThis)));
+  if (klass->IsObsoleteObject()) {
+    ThrowRuntimeException("Obsolete Object!");
+    return nullptr;
+  }
   if (klass->IsProxyClass() || klass->GetDexCache() == nullptr) {
     return nullptr;
   }
@@ -692,6 +742,10 @@
   ScopedFastNativeObjectAccess soa(env);
   StackHandleScope<1> hs(soa.Self());
   Handle<mirror::Class> klass(hs.NewHandle(DecodeClass(soa, javaThis)));
+  if (klass->IsObsoleteObject()) {
+    ThrowRuntimeException("Obsolete Object!");
+    return nullptr;
+  }
   if (klass->IsProxyClass() || klass->GetDexCache() == nullptr) {
     return nullptr;
   }
@@ -708,6 +762,10 @@
   ScopedFastNativeObjectAccess soa(env);
   StackHandleScope<1> hs(soa.Self());
   Handle<mirror::Class> klass(hs.NewHandle(DecodeClass(soa, javaThis)));
+  if (klass->IsObsoleteObject()) {
+    ThrowRuntimeException("Obsolete Object!");
+    return nullptr;
+  }
   if (klass->IsProxyClass() || klass->GetDexCache() == nullptr) {
     return nullptr;
   }
@@ -724,6 +782,10 @@
   ScopedFastNativeObjectAccess soa(env);
   StackHandleScope<1> hs(soa.Self());
   Handle<mirror::Class> klass(hs.NewHandle(DecodeClass(soa, javaThis)));
+  if (klass->IsObsoleteObject()) {
+    ThrowRuntimeException("Obsolete Object!");
+    return 0;
+  }
   return mirror::Class::GetInnerClassFlags(klass, defaultValue);
 }
 
@@ -731,6 +793,10 @@
   ScopedFastNativeObjectAccess soa(env);
   StackHandleScope<1> hs(soa.Self());
   Handle<mirror::Class> klass(hs.NewHandle(DecodeClass(soa, javaThis)));
+  if (klass->IsObsoleteObject()) {
+    ThrowRuntimeException("Obsolete Object!");
+    return nullptr;
+  }
   if (klass->IsProxyClass() || klass->GetDexCache() == nullptr) {
     return nullptr;
   }
@@ -745,6 +811,10 @@
   ScopedFastNativeObjectAccess soa(env);
   StackHandleScope<1> hs(soa.Self());
   Handle<mirror::Class> klass(hs.NewHandle(DecodeClass(soa, javaThis)));
+  if (klass->IsObsoleteObject()) {
+    ThrowRuntimeException("Obsolete Object!");
+    return nullptr;
+  }
   if (klass->IsProxyClass() || klass->GetDexCache() == nullptr) {
     return nullptr;
   }
@@ -756,6 +826,10 @@
   ScopedFastNativeObjectAccess soa(env);
   StackHandleScope<1> hs(soa.Self());
   Handle<mirror::Class> klass(hs.NewHandle(DecodeClass(soa, javaThis)));
+  if (klass->IsObsoleteObject()) {
+    ThrowRuntimeException("Obsolete Object!");
+    return 0;
+  }
   if (klass->IsProxyClass() || klass->GetDexCache() == nullptr) {
     return false;
   }
@@ -771,6 +845,10 @@
   ScopedFastNativeObjectAccess soa(env);
   StackHandleScope<2> hs(soa.Self());
   Handle<mirror::Class> klass(hs.NewHandle(DecodeClass(soa, javaThis)));
+  if (klass->IsObsoleteObject()) {
+    ThrowRuntimeException("Obsolete Object!");
+    return false;
+  }
   if (klass->IsProxyClass() || klass->GetDexCache() == nullptr) {
     return false;
   }
@@ -782,6 +860,10 @@
   ScopedFastNativeObjectAccess soa(env);
   StackHandleScope<1> hs(soa.Self());
   Handle<mirror::Class> klass(hs.NewHandle(DecodeClass(soa, javaThis)));
+  if (klass->IsObsoleteObject()) {
+    ThrowRuntimeException("Obsolete Object!");
+    return nullptr;
+  }
   if (klass->IsProxyClass() || klass->GetDexCache() == nullptr) {
     return nullptr;
   }
@@ -796,6 +878,10 @@
   ScopedFastNativeObjectAccess soa(env);
   StackHandleScope<4> hs(soa.Self());
   Handle<mirror::Class> klass = hs.NewHandle(DecodeClass(soa, javaThis));
+  if (klass->IsObsoleteObject()) {
+    ThrowRuntimeException("Obsolete Object!");
+    return nullptr;
+  }
   if (UNLIKELY(klass->GetPrimitiveType() != 0 || klass->IsInterface() || klass->IsArrayClass() ||
                klass->IsAbstract())) {
     soa.Self()->ThrowNewExceptionF("Ljava/lang/InstantiationException;",
diff --git a/test/1975-hello-structural-transformation/expected.txt b/test/1975-hello-structural-transformation/expected.txt
new file mode 100644
index 0000000..07d3ac2
--- /dev/null
+++ b/test/1975-hello-structural-transformation/expected.txt
@@ -0,0 +1,98 @@
+Saving Field object (ID: 0) public static java.lang.Class art.Transform1975.CUR_CLASS for later
+Saving Field object (ID: 1) public static byte[] art.Transform1975.REDEFINED_DEX_BYTES for later
+Saving MethodHandle object (ID: 2) MethodHandle()Class for later
+Saving MethodHandle object (ID: 3) MethodHandle()byte[] for later
+Saving writable MethodHandle (ID: 4) MethodHandle(Class)void for later
+Reading fields before redefinition
+Reading with reflection.
+public static java.lang.Class art.Transform1975.CUR_CLASS = (ID: 5) class art.Transform1975
+public static byte[] art.Transform1975.REDEFINED_DEX_BYTES = (ID: 6) [100, 101, 120, 10, 48, 51, 53, 0, -51, 24, ...]
+Reading normally in same class.
+ORIGINAL VALUE CUR_CLASS: class art.Transform1975
+ORIGINAL VALUE REDEFINED_DEX_BYTES: ZGV4CjAzNQDNGFvYlmyIt+u4bnNv+OyNVekBxlrJi6EgBwAAcAAAAHhWNBIAAAAAAAAAAFwGAAAmAAAAcAAAAAwAAAAIAQAABwAAADgBAAAEAAAAjAEAAAwAAACsAQAAAQAAAAwCAAD0BAAALAIAAGYDAABrAwAAdQMAAH0DAACIAwAAmQMAAKsDAACuAwAAsgMAAMcDAADmAwAA/QMAABAEAAAjBAAANwQAAEsEAABmBAAAegQAAJYEAACqBAAAwQQAANkEAAD6BAAABgUAABsFAAAvBQAAMgUAADYFAAA6BQAAQgUAAE8FAABfBQAAawUAAHAFAAB5BQAAhQUAAI8FAACWBQAACAAAAAkAAAAKAAAACwAAAA0AAAAOAAAADwAAABAAAAARAAAAEgAAABkAAAAbAAAABgAAAAUAAAAAAAAABwAAAAUAAABQAwAABwAAAAYAAABYAwAABwAAAAYAAABgAwAABgAAAAgAAAAAAAAAGQAAAAoAAAAAAAAAGgAAAAoAAABgAwAAAAADAAMAAAAAAAUAFgAAAAAACwAXAAAABwACACAAAAAAAAUAAQAAAAAABQACAAAAAAAFAB0AAAAAAAUAIgAAAAIABgAhAAAABAAFAAIAAAAGAAUAAgAAAAYAAgAcAAAABgADABwAAAAGAAAAIwAAAAgAAQAeAAAACQAEAB8AAAAAAAAAAQAAAAQAAAAAAAAAGAAAAEQGAAAYBgAAAAAAAAAAAAAAAAAAMgMAAAEAAAAOAAAAAQABAAEAAAA2AwAABAAAAHAQBQAAAA4AAgAAAAIAAAA6AwAADAAAAGIAAwAaAQQAbiAEABAAGgAFAGkAAQAOAAQAAAACAAAAQAMAAFEAAABiAAMAYgEAACICBgBwEAYAAgAaAxMAbiAIADIAbiAHABIAbhAJAAIADAFuIAQAEABiAAMAcQALAAAADAFiAgIAbiAKACEADAEiAgYAcBAGAAIAGgMVAG4gCAAyAG4gCAASAG4QCQACAAwBbiAEABAAYgADAGIBAQAiAgYAcBAGAAIAGgMUAG4gCAAyAG4gCAASAG4QCQACAAwBbiAEABAADgAEAA4AAwAOAAkADnhLAA0ADgEYDwEgDwEYDwAAAAABAAAACwAAAAEAAAAEAAAAAQAAAAUAAyo+OwAIPGNsaW5pdD4ABjxpbml0PgAJQ1VSX0NMQVNTAA9Eb2luZyBzb21ldGhpbmcAEEkgZGlkIHNvbWV0aGluZyEAAUwAAkxMABNMYXJ0L1RyYW5zZm9ybTE5NzU7AB1MZGFsdmlrL2Fubm90YXRpb24vU2lnbmF0dXJlOwAVTGphdmEvaW8vUHJpbnRTdHJlYW07ABFMamF2YS9sYW5nL0NsYXNzOwARTGphdmEvbGFuZy9DbGFzczwAEkxqYXZhL2xhbmcvT2JqZWN0OwASTGphdmEvbGFuZy9TdHJpbmc7ABlMamF2YS9sYW5nL1N0cmluZ0J1aWxkZXI7ABJMamF2YS9sYW5nL1N5c3RlbTsAGkxqYXZhL3V0aWwvQmFzZTY0JEVuY29kZXI7ABJMamF2YS91dGlsL0Jhc2U2NDsAFU5FVyBWQUxVRSBDVVJfQ0xBU1M6IAAWTkVXIFZBTFVFIE5FV19TVFJJTkc6IAAfTkVXIFZBTFVFIFJFREVGSU5FRF9ERVhfQllURVM6IAAKTkVXX1NUUklORwATUkVERUZJTkVEX0RFWF9CWVRFUwASVHJhbnNmb3JtMTk3NS5qYXZhAAFWAAJWTAACW0IABmFwcGVuZAALZG9Tb21ldGhpbmcADmVuY29kZVRvU3RyaW5nAApnZXRFbmNvZGVyAANvdXQAB3ByaW50bG4ACnJlYWRGaWVsZHMACHRvU3RyaW5nAAV2YWx1ZQB2fn5EOHsiY29tcGlsYXRpb24tbW9kZSI6ImRlYnVnIiwibWluLWFwaSI6MSwic2hhLTEiOiJhODM1MmYyNTQ4ODUzNjJjY2Q4ZDkwOWQzNTI5YzYwMDk0ZGQ4OTZlIiwidmVyc2lvbiI6IjEuNi4yMC1kZXYifQACAQEkHAIXDBcAAwAEAAAJAQkBCQCIgASsBAGBgATABAEJ2AQBCYAFAAAAAAAAAQAAAA4GAAA4BgAAAQAAAAAAAAAAAAAAAAAAADwGAAAQAAAAAAAAAAEAAAAAAAAAAQAAACYAAABwAAAAAgAAAAwAAAAIAQAAAwAAAAcAAAA4AQAABAAAAAQAAACMAQAABQAAAAwAAACsAQAABgAAAAEAAAAMAgAAASAAAAQAAAAsAgAAAyAAAAQAAAAyAwAAARAAAAMAAABQAwAAAiAAACYAAABmAwAABCAAAAEAAAAOBgAAACAAAAEAAAAYBgAAAxAAAAIAAAA4BgAABiAAAAEAAABEBgAAABAAAAEAAABcBgAA
+Reading with native.
+Field public static java.lang.Class art.Transform1975.CUR_CLASS = (ID: 5) class art.Transform1975
+Field public static byte[] art.Transform1975.REDEFINED_DEX_BYTES = (ID: 6) [100, 101, 120, 10, 48, 51, 53, 0, -51, 24, ...]
+Reading normally in other class.
+Read CUR_CLASS field: (ID: 5) class art.Transform1975
+Read REDEFINED_DEX_BYTES field: (ID: 6) [100, 101, 120, 10, 48, 51, 53, 0, -51, 24, ...]
+Reading using method handles.
+(ID: 7) MethodHandle()Class (public static java.lang.Class art.Transform1975.CUR_CLASS) = (ID: 5) class art.Transform1975
+(ID: 8) MethodHandle()byte[] (public static byte[] art.Transform1975.REDEFINED_DEX_BYTES) = (ID: 6) [100, 101, 120, 10, 48, 51, 53, 0, -51, 24, ...]
+Doing modification maybe
+Not doing anything
+Reading with reflection after possible modification.
+public static java.lang.Class art.Transform1975.CUR_CLASS = (ID: 5) class art.Transform1975
+public static byte[] art.Transform1975.REDEFINED_DEX_BYTES = (ID: 6) [100, 101, 120, 10, 48, 51, 53, 0, -51, 24, ...]
+Reading normally in same class after possible modification.
+ORIGINAL VALUE CUR_CLASS: class art.Transform1975
+ORIGINAL VALUE REDEFINED_DEX_BYTES: ZGV4CjAzNQDNGFvYlmyIt+u4bnNv+OyNVekBxlrJi6EgBwAAcAAAAHhWNBIAAAAAAAAAAFwGAAAmAAAAcAAAAAwAAAAIAQAABwAAADgBAAAEAAAAjAEAAAwAAACsAQAAAQAAAAwCAAD0BAAALAIAAGYDAABrAwAAdQMAAH0DAACIAwAAmQMAAKsDAACuAwAAsgMAAMcDAADmAwAA/QMAABAEAAAjBAAANwQAAEsEAABmBAAAegQAAJYEAACqBAAAwQQAANkEAAD6BAAABgUAABsFAAAvBQAAMgUAADYFAAA6BQAAQgUAAE8FAABfBQAAawUAAHAFAAB5BQAAhQUAAI8FAACWBQAACAAAAAkAAAAKAAAACwAAAA0AAAAOAAAADwAAABAAAAARAAAAEgAAABkAAAAbAAAABgAAAAUAAAAAAAAABwAAAAUAAABQAwAABwAAAAYAAABYAwAABwAAAAYAAABgAwAABgAAAAgAAAAAAAAAGQAAAAoAAAAAAAAAGgAAAAoAAABgAwAAAAADAAMAAAAAAAUAFgAAAAAACwAXAAAABwACACAAAAAAAAUAAQAAAAAABQACAAAAAAAFAB0AAAAAAAUAIgAAAAIABgAhAAAABAAFAAIAAAAGAAUAAgAAAAYAAgAcAAAABgADABwAAAAGAAAAIwAAAAgAAQAeAAAACQAEAB8AAAAAAAAAAQAAAAQAAAAAAAAAGAAAAEQGAAAYBgAAAAAAAAAAAAAAAAAAMgMAAAEAAAAOAAAAAQABAAEAAAA2AwAABAAAAHAQBQAAAA4AAgAAAAIAAAA6AwAADAAAAGIAAwAaAQQAbiAEABAAGgAFAGkAAQAOAAQAAAACAAAAQAMAAFEAAABiAAMAYgEAACICBgBwEAYAAgAaAxMAbiAIADIAbiAHABIAbhAJAAIADAFuIAQAEABiAAMAcQALAAAADAFiAgIAbiAKACEADAEiAgYAcBAGAAIAGgMVAG4gCAAyAG4gCAASAG4QCQACAAwBbiAEABAAYgADAGIBAQAiAgYAcBAGAAIAGgMUAG4gCAAyAG4gCAASAG4QCQACAAwBbiAEABAADgAEAA4AAwAOAAkADnhLAA0ADgEYDwEgDwEYDwAAAAABAAAACwAAAAEAAAAEAAAAAQAAAAUAAyo+OwAIPGNsaW5pdD4ABjxpbml0PgAJQ1VSX0NMQVNTAA9Eb2luZyBzb21ldGhpbmcAEEkgZGlkIHNvbWV0aGluZyEAAUwAAkxMABNMYXJ0L1RyYW5zZm9ybTE5NzU7AB1MZGFsdmlrL2Fubm90YXRpb24vU2lnbmF0dXJlOwAVTGphdmEvaW8vUHJpbnRTdHJlYW07ABFMamF2YS9sYW5nL0NsYXNzOwARTGphdmEvbGFuZy9DbGFzczwAEkxqYXZhL2xhbmcvT2JqZWN0OwASTGphdmEvbGFuZy9TdHJpbmc7ABlMamF2YS9sYW5nL1N0cmluZ0J1aWxkZXI7ABJMamF2YS9sYW5nL1N5c3RlbTsAGkxqYXZhL3V0aWwvQmFzZTY0JEVuY29kZXI7ABJMamF2YS91dGlsL0Jhc2U2NDsAFU5FVyBWQUxVRSBDVVJfQ0xBU1M6IAAWTkVXIFZBTFVFIE5FV19TVFJJTkc6IAAfTkVXIFZBTFVFIFJFREVGSU5FRF9ERVhfQllURVM6IAAKTkVXX1NUUklORwATUkVERUZJTkVEX0RFWF9CWVRFUwASVHJhbnNmb3JtMTk3NS5qYXZhAAFWAAJWTAACW0IABmFwcGVuZAALZG9Tb21ldGhpbmcADmVuY29kZVRvU3RyaW5nAApnZXRFbmNvZGVyAANvdXQAB3ByaW50bG4ACnJlYWRGaWVsZHMACHRvU3RyaW5nAAV2YWx1ZQB2fn5EOHsiY29tcGlsYXRpb24tbW9kZSI6ImRlYnVnIiwibWluLWFwaSI6MSwic2hhLTEiOiJhODM1MmYyNTQ4ODUzNjJjY2Q4ZDkwOWQzNTI5YzYwMDk0ZGQ4OTZlIiwidmVyc2lvbiI6IjEuNi4yMC1kZXYifQACAQEkHAIXDBcAAwAEAAAJAQkBCQCIgASsBAGBgATABAEJ2AQBCYAFAAAAAAAAAQAAAA4GAAA4BgAAAQAAAAAAAAAAAAAAAAAAADwGAAAQAAAAAAAAAAEAAAAAAAAAAQAAACYAAABwAAAAAgAAAAwAAAAIAQAAAwAAAAcAAAA4AQAABAAAAAQAAACMAQAABQAAAAwAAACsAQAABgAAAAEAAAAMAgAAASAAAAQAAAAsAgAAAyAAAAQAAAAyAwAAARAAAAMAAABQAwAAAiAAACYAAABmAwAABCAAAAEAAAAOBgAAACAAAAEAAAAYBgAAAxAAAAIAAAA4BgAABiAAAAEAAABEBgAAABAAAAEAAABcBgAA
+Reading with native after possible modification.
+Field public static java.lang.Class art.Transform1975.CUR_CLASS = (ID: 5) class art.Transform1975
+Field public static byte[] art.Transform1975.REDEFINED_DEX_BYTES = (ID: 6) [100, 101, 120, 10, 48, 51, 53, 0, -51, 24, ...]
+Reading normally in other class after possible modification.
+Read CUR_CLASS field: (ID: 5) class art.Transform1975
+Read REDEFINED_DEX_BYTES field: (ID: 6) [100, 101, 120, 10, 48, 51, 53, 0, -51, 24, ...]
+Reading using method handles.
+(ID: 9) MethodHandle()Class (public static java.lang.Class art.Transform1975.CUR_CLASS) = (ID: 5) class art.Transform1975
+(ID: 10) MethodHandle()byte[] (public static byte[] art.Transform1975.REDEFINED_DEX_BYTES) = (ID: 6) [100, 101, 120, 10, 48, 51, 53, 0, -51, 24, ...]
+Reading fields after redefinition
+Reading with reflection.
+public static java.lang.Class art.Transform1975.CUR_CLASS = (ID: 5) class art.Transform1975
+public static java.lang.String art.Transform1975.NEW_STRING = (ID: 11) <NULL>
+public static byte[] art.Transform1975.REDEFINED_DEX_BYTES = (ID: 6) [100, 101, 120, 10, 48, 51, 53, 0, -51, 24, ...]
+Reading normally in same class.
+NEW VALUE CUR_CLASS: class art.Transform1975
+NEW VALUE REDEFINED_DEX_BYTES: ZGV4CjAzNQDNGFvYlmyIt+u4bnNv+OyNVekBxlrJi6EgBwAAcAAAAHhWNBIAAAAAAAAAAFwGAAAmAAAAcAAAAAwAAAAIAQAABwAAADgBAAAEAAAAjAEAAAwAAACsAQAAAQAAAAwCAAD0BAAALAIAAGYDAABrAwAAdQMAAH0DAACIAwAAmQMAAKsDAACuAwAAsgMAAMcDAADmAwAA/QMAABAEAAAjBAAANwQAAEsEAABmBAAAegQAAJYEAACqBAAAwQQAANkEAAD6BAAABgUAABsFAAAvBQAAMgUAADYFAAA6BQAAQgUAAE8FAABfBQAAawUAAHAFAAB5BQAAhQUAAI8FAACWBQAACAAAAAkAAAAKAAAACwAAAA0AAAAOAAAADwAAABAAAAARAAAAEgAAABkAAAAbAAAABgAAAAUAAAAAAAAABwAAAAUAAABQAwAABwAAAAYAAABYAwAABwAAAAYAAABgAwAABgAAAAgAAAAAAAAAGQAAAAoAAAAAAAAAGgAAAAoAAABgAwAAAAADAAMAAAAAAAUAFgAAAAAACwAXAAAABwACACAAAAAAAAUAAQAAAAAABQACAAAAAAAFAB0AAAAAAAUAIgAAAAIABgAhAAAABAAFAAIAAAAGAAUAAgAAAAYAAgAcAAAABgADABwAAAAGAAAAIwAAAAgAAQAeAAAACQAEAB8AAAAAAAAAAQAAAAQAAAAAAAAAGAAAAEQGAAAYBgAAAAAAAAAAAAAAAAAAMgMAAAEAAAAOAAAAAQABAAEAAAA2AwAABAAAAHAQBQAAAA4AAgAAAAIAAAA6AwAADAAAAGIAAwAaAQQAbiAEABAAGgAFAGkAAQAOAAQAAAACAAAAQAMAAFEAAABiAAMAYgEAACICBgBwEAYAAgAaAxMAbiAIADIAbiAHABIAbhAJAAIADAFuIAQAEABiAAMAcQALAAAADAFiAgIAbiAKACEADAEiAgYAcBAGAAIAGgMVAG4gCAAyAG4gCAASAG4QCQACAAwBbiAEABAAYgADAGIBAQAiAgYAcBAGAAIAGgMUAG4gCAAyAG4gCAASAG4QCQACAAwBbiAEABAADgAEAA4AAwAOAAkADnhLAA0ADgEYDwEgDwEYDwAAAAABAAAACwAAAAEAAAAEAAAAAQAAAAUAAyo+OwAIPGNsaW5pdD4ABjxpbml0PgAJQ1VSX0NMQVNTAA9Eb2luZyBzb21ldGhpbmcAEEkgZGlkIHNvbWV0aGluZyEAAUwAAkxMABNMYXJ0L1RyYW5zZm9ybTE5NzU7AB1MZGFsdmlrL2Fubm90YXRpb24vU2lnbmF0dXJlOwAVTGphdmEvaW8vUHJpbnRTdHJlYW07ABFMamF2YS9sYW5nL0NsYXNzOwARTGphdmEvbGFuZy9DbGFzczwAEkxqYXZhL2xhbmcvT2JqZWN0OwASTGphdmEvbGFuZy9TdHJpbmc7ABlMamF2YS9sYW5nL1N0cmluZ0J1aWxkZXI7ABJMamF2YS9sYW5nL1N5c3RlbTsAGkxqYXZhL3V0aWwvQmFzZTY0JEVuY29kZXI7ABJMamF2YS91dGlsL0Jhc2U2NDsAFU5FVyBWQUxVRSBDVVJfQ0xBU1M6IAAWTkVXIFZBTFVFIE5FV19TVFJJTkc6IAAfTkVXIFZBTFVFIFJFREVGSU5FRF9ERVhfQllURVM6IAAKTkVXX1NUUklORwATUkVERUZJTkVEX0RFWF9CWVRFUwASVHJhbnNmb3JtMTk3NS5qYXZhAAFWAAJWTAACW0IABmFwcGVuZAALZG9Tb21ldGhpbmcADmVuY29kZVRvU3RyaW5nAApnZXRFbmNvZGVyAANvdXQAB3ByaW50bG4ACnJlYWRGaWVsZHMACHRvU3RyaW5nAAV2YWx1ZQB2fn5EOHsiY29tcGlsYXRpb24tbW9kZSI6ImRlYnVnIiwibWluLWFwaSI6MSwic2hhLTEiOiJhODM1MmYyNTQ4ODUzNjJjY2Q4ZDkwOWQzNTI5YzYwMDk0ZGQ4OTZlIiwidmVyc2lvbiI6IjEuNi4yMC1kZXYifQACAQEkHAIXDBcAAwAEAAAJAQkBCQCIgASsBAGBgATABAEJ2AQBCYAFAAAAAAAAAQAAAA4GAAA4BgAAAQAAAAAAAAAAAAAAAAAAADwGAAAQAAAAAAAAAAEAAAAAAAAAAQAAACYAAABwAAAAAgAAAAwAAAAIAQAAAwAAAAcAAAA4AQAABAAAAAQAAACMAQAABQAAAAwAAACsAQAABgAAAAEAAAAMAgAAASAAAAQAAAAsAgAAAyAAAAQAAAAyAwAAARAAAAMAAABQAwAAAiAAACYAAABmAwAABCAAAAEAAAAOBgAAACAAAAEAAAAYBgAAAxAAAAIAAAA4BgAABiAAAAEAAABEBgAAABAAAAEAAABcBgAA
+NEW VALUE NEW_STRING: null
+Reading with native.
+Field public static java.lang.Class art.Transform1975.CUR_CLASS = (ID: 5) class art.Transform1975
+Field public static java.lang.String art.Transform1975.NEW_STRING = (ID: 11) <NULL>
+Field public static byte[] art.Transform1975.REDEFINED_DEX_BYTES = (ID: 6) [100, 101, 120, 10, 48, 51, 53, 0, -51, 24, ...]
+Reading normally in other class.
+Read CUR_CLASS field: (ID: 5) class art.Transform1975
+Read REDEFINED_DEX_BYTES field: (ID: 6) [100, 101, 120, 10, 48, 51, 53, 0, -51, 24, ...]
+Read NEW_STRING field: (ID: 11) <NULL>
+Reading using method handles.
+(ID: 12) MethodHandle()Class (public static java.lang.Class art.Transform1975.CUR_CLASS) = (ID: 5) class art.Transform1975
+(ID: 13) MethodHandle()String (public static java.lang.String art.Transform1975.NEW_STRING) = (ID: 11) <NULL>
+(ID: 14) MethodHandle()byte[] (public static byte[] art.Transform1975.REDEFINED_DEX_BYTES) = (ID: 6) [100, 101, 120, 10, 48, 51, 53, 0, -51, 24, ...]
+Doing modification maybe
+Doing something
+Reading with reflection after possible modification.
+public static java.lang.Class art.Transform1975.CUR_CLASS = (ID: 5) class art.Transform1975
+public static java.lang.String art.Transform1975.NEW_STRING = (ID: 15) I did something!
+public static byte[] art.Transform1975.REDEFINED_DEX_BYTES = (ID: 6) [100, 101, 120, 10, 48, 51, 53, 0, -51, 24, ...]
+Reading normally in same class after possible modification.
+NEW VALUE CUR_CLASS: class art.Transform1975
+NEW VALUE REDEFINED_DEX_BYTES: ZGV4CjAzNQDNGFvYlmyIt+u4bnNv+OyNVekBxlrJi6EgBwAAcAAAAHhWNBIAAAAAAAAAAFwGAAAmAAAAcAAAAAwAAAAIAQAABwAAADgBAAAEAAAAjAEAAAwAAACsAQAAAQAAAAwCAAD0BAAALAIAAGYDAABrAwAAdQMAAH0DAACIAwAAmQMAAKsDAACuAwAAsgMAAMcDAADmAwAA/QMAABAEAAAjBAAANwQAAEsEAABmBAAAegQAAJYEAACqBAAAwQQAANkEAAD6BAAABgUAABsFAAAvBQAAMgUAADYFAAA6BQAAQgUAAE8FAABfBQAAawUAAHAFAAB5BQAAhQUAAI8FAACWBQAACAAAAAkAAAAKAAAACwAAAA0AAAAOAAAADwAAABAAAAARAAAAEgAAABkAAAAbAAAABgAAAAUAAAAAAAAABwAAAAUAAABQAwAABwAAAAYAAABYAwAABwAAAAYAAABgAwAABgAAAAgAAAAAAAAAGQAAAAoAAAAAAAAAGgAAAAoAAABgAwAAAAADAAMAAAAAAAUAFgAAAAAACwAXAAAABwACACAAAAAAAAUAAQAAAAAABQACAAAAAAAFAB0AAAAAAAUAIgAAAAIABgAhAAAABAAFAAIAAAAGAAUAAgAAAAYAAgAcAAAABgADABwAAAAGAAAAIwAAAAgAAQAeAAAACQAEAB8AAAAAAAAAAQAAAAQAAAAAAAAAGAAAAEQGAAAYBgAAAAAAAAAAAAAAAAAAMgMAAAEAAAAOAAAAAQABAAEAAAA2AwAABAAAAHAQBQAAAA4AAgAAAAIAAAA6AwAADAAAAGIAAwAaAQQAbiAEABAAGgAFAGkAAQAOAAQAAAACAAAAQAMAAFEAAABiAAMAYgEAACICBgBwEAYAAgAaAxMAbiAIADIAbiAHABIAbhAJAAIADAFuIAQAEABiAAMAcQALAAAADAFiAgIAbiAKACEADAEiAgYAcBAGAAIAGgMVAG4gCAAyAG4gCAASAG4QCQACAAwBbiAEABAAYgADAGIBAQAiAgYAcBAGAAIAGgMUAG4gCAAyAG4gCAASAG4QCQACAAwBbiAEABAADgAEAA4AAwAOAAkADnhLAA0ADgEYDwEgDwEYDwAAAAABAAAACwAAAAEAAAAEAAAAAQAAAAUAAyo+OwAIPGNsaW5pdD4ABjxpbml0PgAJQ1VSX0NMQVNTAA9Eb2luZyBzb21ldGhpbmcAEEkgZGlkIHNvbWV0aGluZyEAAUwAAkxMABNMYXJ0L1RyYW5zZm9ybTE5NzU7AB1MZGFsdmlrL2Fubm90YXRpb24vU2lnbmF0dXJlOwAVTGphdmEvaW8vUHJpbnRTdHJlYW07ABFMamF2YS9sYW5nL0NsYXNzOwARTGphdmEvbGFuZy9DbGFzczwAEkxqYXZhL2xhbmcvT2JqZWN0OwASTGphdmEvbGFuZy9TdHJpbmc7ABlMamF2YS9sYW5nL1N0cmluZ0J1aWxkZXI7ABJMamF2YS9sYW5nL1N5c3RlbTsAGkxqYXZhL3V0aWwvQmFzZTY0JEVuY29kZXI7ABJMamF2YS91dGlsL0Jhc2U2NDsAFU5FVyBWQUxVRSBDVVJfQ0xBU1M6IAAWTkVXIFZBTFVFIE5FV19TVFJJTkc6IAAfTkVXIFZBTFVFIFJFREVGSU5FRF9ERVhfQllURVM6IAAKTkVXX1NUUklORwATUkVERUZJTkVEX0RFWF9CWVRFUwASVHJhbnNmb3JtMTk3NS5qYXZhAAFWAAJWTAACW0IABmFwcGVuZAALZG9Tb21ldGhpbmcADmVuY29kZVRvU3RyaW5nAApnZXRFbmNvZGVyAANvdXQAB3ByaW50bG4ACnJlYWRGaWVsZHMACHRvU3RyaW5nAAV2YWx1ZQB2fn5EOHsiY29tcGlsYXRpb24tbW9kZSI6ImRlYnVnIiwibWluLWFwaSI6MSwic2hhLTEiOiJhODM1MmYyNTQ4ODUzNjJjY2Q4ZDkwOWQzNTI5YzYwMDk0ZGQ4OTZlIiwidmVyc2lvbiI6IjEuNi4yMC1kZXYifQACAQEkHAIXDBcAAwAEAAAJAQkBCQCIgASsBAGBgATABAEJ2AQBCYAFAAAAAAAAAQAAAA4GAAA4BgAAAQAAAAAAAAAAAAAAAAAAADwGAAAQAAAAAAAAAAEAAAAAAAAAAQAAACYAAABwAAAAAgAAAAwAAAAIAQAAAwAAAAcAAAA4AQAABAAAAAQAAACMAQAABQAAAAwAAACsAQAABgAAAAEAAAAMAgAAASAAAAQAAAAsAgAAAyAAAAQAAAAyAwAAARAAAAMAAABQAwAAAiAAACYAAABmAwAABCAAAAEAAAAOBgAAACAAAAEAAAAYBgAAAxAAAAIAAAA4BgAABiAAAAEAAABEBgAAABAAAAEAAABcBgAA
+NEW VALUE NEW_STRING: I did something!
+Reading with native after possible modification.
+Field public static java.lang.Class art.Transform1975.CUR_CLASS = (ID: 5) class art.Transform1975
+Field public static java.lang.String art.Transform1975.NEW_STRING = (ID: 15) I did something!
+Field public static byte[] art.Transform1975.REDEFINED_DEX_BYTES = (ID: 6) [100, 101, 120, 10, 48, 51, 53, 0, -51, 24, ...]
+Reading normally in other class after possible modification.
+Read CUR_CLASS field: (ID: 5) class art.Transform1975
+Read REDEFINED_DEX_BYTES field: (ID: 6) [100, 101, 120, 10, 48, 51, 53, 0, -51, 24, ...]
+Read NEW_STRING field: (ID: 15) I did something!
+Reading using method handles.
+(ID: 16) MethodHandle()Class (public static java.lang.Class art.Transform1975.CUR_CLASS) = (ID: 5) class art.Transform1975
+(ID: 17) MethodHandle()String (public static java.lang.String art.Transform1975.NEW_STRING) = (ID: 15) I did something!
+(ID: 18) MethodHandle()byte[] (public static byte[] art.Transform1975.REDEFINED_DEX_BYTES) = (ID: 6) [100, 101, 120, 10, 48, 51, 53, 0, -51, 24, ...]
+reading reflectively with old reflection objects
+OLD FIELD OBJECT: public static java.lang.Class art.Transform1975.CUR_CLASS = (ID: 5) class art.Transform1975
+OLD FIELD OBJECT: public static byte[] art.Transform1975.REDEFINED_DEX_BYTES = (ID: 6) [100, 101, 120, 10, 48, 51, 53, 0, -51, 24, ...]
+reading natively with old jfieldIDs
+Field public static java.lang.Class art.Transform1975.CUR_CLASS = (ID: 5) class art.Transform1975
+Field public static byte[] art.Transform1975.REDEFINED_DEX_BYTES = (ID: 6) [100, 101, 120, 10, 48, 51, 53, 0, -51, 24, ...]
+reading natively with new jfieldIDs
+Reading with old method handles
+(ID: 2) MethodHandle()Class (public static java.lang.Class art.Transform1975.CUR_CLASS) = (ID: 5) class art.Transform1975
+(ID: 3) MethodHandle()byte[] (public static byte[] art.Transform1975.REDEFINED_DEX_BYTES) = (ID: 6) [100, 101, 120, 10, 48, 51, 53, 0, -51, 24, ...]
+Reading with new method handles
+(ID: 19) MethodHandle()Class (public static java.lang.Class art.Transform1975.CUR_CLASS) = (ID: 5) class art.Transform1975
+(ID: 20) MethodHandle()String (public static java.lang.String art.Transform1975.NEW_STRING) = (ID: 15) I did something!
+(ID: 21) MethodHandle()byte[] (public static byte[] art.Transform1975.REDEFINED_DEX_BYTES) = (ID: 6) [100, 101, 120, 10, 48, 51, 53, 0, -51, 24, ...]
+Writing (ID: 22) class art.Test1975 to CUR_CLASS with old method handle
+Reading changed value
+CUR_CLASS is now (ID: 22) class art.Test1975
diff --git a/test/1975-hello-structural-transformation/info.txt b/test/1975-hello-structural-transformation/info.txt
new file mode 100644
index 0000000..218cf4e
--- /dev/null
+++ b/test/1975-hello-structural-transformation/info.txt
@@ -0,0 +1 @@
+Test adding static fields using structural class redefinition.
diff --git a/test/1975-hello-structural-transformation/run b/test/1975-hello-structural-transformation/run
new file mode 100755
index 0000000..03e41a5
--- /dev/null
+++ b/test/1975-hello-structural-transformation/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/1975-hello-structural-transformation/src/Main.java b/test/1975-hello-structural-transformation/src/Main.java
new file mode 100644
index 0000000..9cfb95b
--- /dev/null
+++ b/test/1975-hello-structural-transformation/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.Test1975.run();
+  }
+}
diff --git a/test/1975-hello-structural-transformation/src/art/Redefinition.java b/test/1975-hello-structural-transformation/src/art/Redefinition.java
new file mode 120000
index 0000000..81eaf31
--- /dev/null
+++ b/test/1975-hello-structural-transformation/src/art/Redefinition.java
@@ -0,0 +1 @@
+../../../jvmti-common/Redefinition.java
\ No newline at end of file
diff --git a/test/1975-hello-structural-transformation/src/art/Test1975.java b/test/1975-hello-structural-transformation/src/art/Test1975.java
new file mode 100644
index 0000000..0009c03
--- /dev/null
+++ b/test/1975-hello-structural-transformation/src/art/Test1975.java
@@ -0,0 +1,264 @@
+/*
+ * 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.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.ref.*;
+import java.lang.reflect.*;
+import java.util.*;
+
+public class Test1975 {
+  public static void run() throws Exception {
+    Redefinition.setTestConfiguration(Redefinition.Config.COMMON_REDEFINE);
+    doTest();
+  }
+
+  private static final boolean PRINT_NONDETERMINISTIC = false;
+
+  public static WeakHashMap<Object, Long> id_nums = new WeakHashMap<>();
+  public static long next_id = 0;
+
+  public static String printGeneric(Object o) {
+    Long id = id_nums.get(o);
+    if (id == null) {
+      id = Long.valueOf(next_id++);
+      id_nums.put(o, id);
+    }
+    if (o == null) {
+      return "(ID: " + id + ") <NULL>";
+    }
+    Class oc = o.getClass();
+    if (oc.isArray() && oc.getComponentType() == Byte.TYPE) {
+      return "(ID: "
+          + id
+          + ") "
+          + Arrays.toString(Arrays.copyOf((byte[]) o, 10)).replace(']', ',')
+          + " ...]";
+    } else {
+      return "(ID: " + id + ") " + o.toString();
+    }
+  }
+
+  // Since we are adding fields we redefine this class with the Transform1975 class to add new
+  // field-reads.
+  public static final class ReadTransformFields implements Runnable {
+    public void run() {
+      System.out.println("Read CUR_CLASS field: " + printGeneric(Transform1975.CUR_CLASS));
+      System.out.println(
+          "Read REDEFINED_DEX_BYTES field: " + printGeneric(Transform1975.REDEFINED_DEX_BYTES));
+    }
+  }
+
+  /* Base64 encoded dex file for:
+   * public static final class ReadTransformFields implements Runnable {
+   *   public void run() {
+   *     System.out.println("Read CUR_CLASS field: " + printGeneric(Transform1975.CUR_CLASS));
+   *     System.out.println("Read REDEFINED_DEX_BYTES field: " + printGeneric(Transform1975.REDEFINED_DEX_BYTES));
+   *     System.out.println("Read NEW_STRING field: " + printGeneric(Transform1975.NEW_STRING));
+   *   }
+   * }
+   */
+  private static final byte[] NEW_READ_BYTES =
+      Base64.getDecoder()
+          .decode(
+              "ZGV4CjAzNQCHIfWvfkMos9E+Snhux5rSGhnDAbiVJlyYBgAAcAAAAHhWNBIAAAAAAAAAANQFAAAk"
+                  + "AAAAcAAAAA4AAAAAAQAABQAAADgBAAAEAAAAdAEAAAgAAACUAQAAAQAAANQBAACkBAAA9AEAAO4C"
+                  + "AAD2AgAAAQMAAAQDAAAIAwAALAMAADwDAABRAwAAdQMAAJUDAACsAwAAvwMAANMDAADpAwAA/QMA"
+                  + "ABgEAAAsBAAAOAQAAE0EAABlBAAAfgQAAKAEAAC1BAAAxAQAAMcEAADLBAAAzwQAANwEAADkBAAA"
+                  + "6gQAAO8EAAD9BAAABgUAAAsFAAAVBQAAHAUAAAQAAAAFAAAABgAAAAcAAAAIAAAACQAAAAoAAAAL"
+                  + "AAAADAAAAA0AAAAOAAAADwAAABcAAAAZAAAAAgAAAAkAAAAAAAAAAwAAAAkAAADgAgAAAwAAAAoA"
+                  + "AADoAgAAFwAAAAwAAAAAAAAAGAAAAAwAAADoAgAAAgAGAAEAAAACAAkAEAAAAAIADQARAAAACwAF"
+                  + "AB0AAAAAAAMAAAAAAAAAAwAgAAAAAQABAB4AAAAFAAQAHwAAAAcAAwAAAAAACgADAAAAAAAKAAIA"
+                  + "GwAAAAoAAAAhAAAAAAAAABEAAAAHAAAA2AIAABYAAADEBQAAowUAAAAAAAABAAEAAQAAAMYCAAAE"
+                  + "AAAAcBAEAAAADgAFAAEAAgAAAMoCAABVAAAAYgADAGIBAABxEAIAAQAMASICCgBwEAUAAgAaAxIA"
+                  + "biAGADIAbiAGABIAbhAHAAIADAFuIAMAEABiAAMAYgECAHEQAgABAAwBIgIKAHAQBQACABoDFABu"
+                  + "IAYAMgBuIAYAEgBuEAcAAgAMAW4gAwAQAGIAAwBiAQEAcRACAAEADAEiAgoAcBAFAAIAGgMTAG4g"
+                  + "BgAyAG4gBgASAG4QBwACAAwBbiADABAADgAEAA4ABgAOARwPARwPARwPAAABAAAACAAAAAEAAAAH"
+                  + "AAAAAQAAAAkABjxpbml0PgAJQ1VSX0NMQVNTAAFMAAJMTAAiTGFydC9UZXN0MTk3NSRSZWFkVHJh"
+                  + "bnNmb3JtRmllbGRzOwAOTGFydC9UZXN0MTk3NTsAE0xhcnQvVHJhbnNmb3JtMTk3NTsAIkxkYWx2"
+                  + "aWsvYW5ub3RhdGlvbi9FbmNsb3NpbmdDbGFzczsAHkxkYWx2aWsvYW5ub3RhdGlvbi9Jbm5lckNs"
+                  + "YXNzOwAVTGphdmEvaW8vUHJpbnRTdHJlYW07ABFMamF2YS9sYW5nL0NsYXNzOwASTGphdmEvbGFu"
+                  + "Zy9PYmplY3Q7ABRMamF2YS9sYW5nL1J1bm5hYmxlOwASTGphdmEvbGFuZy9TdHJpbmc7ABlMamF2"
+                  + "YS9sYW5nL1N0cmluZ0J1aWxkZXI7ABJMamF2YS9sYW5nL1N5c3RlbTsACk5FV19TVFJJTkcAE1JF"
+                  + "REVGSU5FRF9ERVhfQllURVMAFlJlYWQgQ1VSX0NMQVNTIGZpZWxkOiAAF1JlYWQgTkVXX1NUUklO"
+                  + "RyBmaWVsZDogACBSZWFkIFJFREVGSU5FRF9ERVhfQllURVMgZmllbGQ6IAATUmVhZFRyYW5zZm9y"
+                  + "bUZpZWxkcwANVGVzdDE5NzUuamF2YQABVgACVkwAAltCAAthY2Nlc3NGbGFncwAGYXBwZW5kAARu"
+                  + "YW1lAANvdXQADHByaW50R2VuZXJpYwAHcHJpbnRsbgADcnVuAAh0b1N0cmluZwAFdmFsdWUAdn5+"
+                  + "RDh7ImNvbXBpbGF0aW9uLW1vZGUiOiJkZWJ1ZyIsIm1pbi1hcGkiOjEsInNoYS0xIjoiYTgzNTJm"
+                  + "MjU0ODg1MzYyY2NkOGQ5MDlkMzUyOWM2MDA5NGRkODk2ZSIsInZlcnNpb24iOiIxLjYuMjAtZGV2"
+                  + "In0AAgMBIhgBAgQCGgQZHBcVAAABAQCBgAT0AwEBjAQAAAAAAAAAAgAAAJQFAACaBQAAuAUAAAAA"
+                  + "AAAAAAAAAAAAABAAAAAAAAAAAQAAAAAAAAABAAAAJAAAAHAAAAACAAAADgAAAAABAAADAAAABQAA"
+                  + "ADgBAAAEAAAABAAAAHQBAAAFAAAACAAAAJQBAAAGAAAAAQAAANQBAAABIAAAAgAAAPQBAAADIAAA"
+                  + "AgAAAMYCAAABEAAAAwAAANgCAAACIAAAJAAAAO4CAAAEIAAAAgAAAJQFAAAAIAAAAQAAAKMFAAAD"
+                  + "EAAAAgAAALQFAAAGIAAAAQAAAMQFAAAAEAAAAQAAANQFAAA=");
+
+  static void ReadFields() throws Exception {
+    Runnable r = new ReadTransformFields();
+    System.out.println("Reading with reflection.");
+    for (Field f : Transform1975.class.getFields()) {
+      System.out.println(f.toString() + " = " + printGeneric(f.get(null)));
+    }
+    System.out.println("Reading normally in same class.");
+    Transform1975.readFields();
+    System.out.println("Reading with native.");
+    readNativeFields(Transform1975.class, getNativeFields(Transform1975.class.getFields()));
+    System.out.println("Reading normally in other class.");
+    r.run();
+    System.out.println("Reading using method handles.");
+    readMethodHandles(getMethodHandles(Transform1975.class.getFields()));
+    System.out.println("Doing modification maybe");
+    Transform1975.doSomething();
+    System.out.println("Reading with reflection after possible modification.");
+    for (Field f : Transform1975.class.getFields()) {
+      System.out.println(f.toString() + " = " + printGeneric(f.get(null)));
+    }
+    System.out.println("Reading normally in same class after possible modification.");
+    Transform1975.readFields();
+    System.out.println("Reading with native after possible modification.");
+    readNativeFields(Transform1975.class, getNativeFields(Transform1975.class.getFields()));
+    System.out.println("Reading normally in other class after possible modification.");
+    r.run();
+    System.out.println("Reading using method handles.");
+    readMethodHandles(getMethodHandles(Transform1975.class.getFields()));
+  }
+
+  public static final class MethodHandleWrapper {
+    private MethodHandle mh;
+    private Field f;
+    public MethodHandleWrapper(MethodHandle mh, Field f) {
+      this.f = f;
+      this.mh = mh;
+    }
+    public MethodHandle getHandle() {
+      return mh;
+    }
+    public Field getField() {
+      return f;
+    }
+    public Object invoke() throws Throwable {
+      return mh.invoke();
+    }
+    public String toString() {
+      return mh.toString();
+    }
+  }
+
+  public static MethodHandleWrapper[] getMethodHandles(Field[] fields) throws Exception {
+    final MethodHandles.Lookup l = MethodHandles.lookup();
+    MethodHandleWrapper[] res = new MethodHandleWrapper[fields.length];
+    for (int i = 0; i < res.length; i++) {
+      res[i] = new MethodHandleWrapper(l.unreflectGetter(fields[i]), fields[i]);;
+    }
+    return res;
+  }
+
+  public static void readMethodHandles(MethodHandleWrapper[] handles) throws Exception {
+    for (MethodHandleWrapper h : handles) {
+      try {
+        System.out.println(printGeneric(h) + " (" + h.getField() + ") = " + printGeneric(h.invoke()));
+      } catch (Throwable t) {
+        if (t instanceof Exception) {
+          throw (Exception)t;
+        } else if (t instanceof Error) {
+          throw (Error)t;
+        } else {
+          throw new RuntimeException("Unexpected throwable thrown!", t);
+        }
+      }
+    }
+  }
+  public static void doTest() throws Exception {
+    // TODO It would be good to have a test of invoke-custom too but since that requires smali and
+    // internally we just store the resolved MethodHandle this should all be good enough.
+
+    // Grab Field objects from before the transformation.
+    Field[] old_fields = Transform1975.class.getFields();
+    for (Field f : old_fields) {
+      System.out.println("Saving Field object " + printGeneric(f) + " for later");
+    }
+    // Grab jfieldIDs from before the transformation.
+    long[] old_native_fields = getNativeFields(Transform1975.class.getFields());
+    // Grab MethodHandles from before the transformation.
+    MethodHandleWrapper[] handles = getMethodHandles(Transform1975.class.getFields());
+    for (MethodHandleWrapper h : handles) {
+      System.out.println("Saving MethodHandle object " + printGeneric(h) + " for later");
+    }
+    // Grab a 'setter' MethodHandle from before the redefinition.
+    Field cur_class_field = Transform1975.class.getDeclaredField("CUR_CLASS");
+    MethodHandleWrapper write_wrapper = new MethodHandleWrapper(MethodHandles.lookup().unreflectSetter(cur_class_field), cur_class_field);
+    System.out.println("Saving writable MethodHandle " + printGeneric(write_wrapper) + " for later");
+
+    // Read the fields in all possible ways.
+    System.out.println("Reading fields before redefinition");
+    ReadFields();
+    // Redefine the transform class. Also change the ReadTransformFields so we don't have to deal
+    // with annoying compilation stuff.
+    Redefinition.doCommonStructuralClassRedefinition(
+        Transform1975.class, Transform1975.REDEFINED_DEX_BYTES);
+    Redefinition.doCommonClassRedefinition(
+        ReadTransformFields.class, new byte[] {}, NEW_READ_BYTES);
+    // Read the fields in all possible ways.
+    System.out.println("Reading fields after redefinition");
+    ReadFields();
+    // Check that the old Field, jfieldID, and MethodHandle objects were updated.
+    System.out.println("reading reflectively with old reflection objects");
+    for (Field f : old_fields) {
+      System.out.println("OLD FIELD OBJECT: " + f.toString() + " = " + printGeneric(f.get(null)));
+    }
+    System.out.println("reading natively with old jfieldIDs");
+    readNativeFields(Transform1975.class, old_native_fields);
+    // Make sure the fields keep the same id.
+    System.out.println("reading natively with new jfieldIDs");
+    long[] new_fields = getNativeFields(Transform1975.class.getFields());
+    Arrays.sort(old_native_fields);
+    Arrays.sort(new_fields);
+    boolean different = new_fields.length == old_native_fields.length;
+    for (int i = 0; i < old_native_fields.length && !different; i++) {
+      different = different || new_fields[i] != old_native_fields[i];
+    }
+    if (different) {
+      System.out.println(
+          "Missing expected fields! "
+              + Arrays.toString(new_fields)
+              + " vs "
+              + Arrays.toString(old_native_fields));
+    }
+    // Make sure the old handles work.
+    System.out.println("Reading with old method handles");
+    readMethodHandles(handles);
+    System.out.println("Reading with new method handles");
+    readMethodHandles(getMethodHandles(Transform1975.class.getFields()));
+    System.out.println("Writing " + printGeneric(Test1975.class) + " to CUR_CLASS with old method handle");
+    try {
+      write_wrapper.getHandle().invokeExact(Test1975.class);
+    } catch (Throwable t) {
+      throw new RuntimeException("something threw", t);
+    }
+    System.out.println("Reading changed value");
+    System.out.println("CUR_CLASS is now " + printGeneric(Transform1975.CUR_CLASS));
+  }
+
+  private static void printNativeField(long id, Field f, Object value) {
+    System.out.println(
+        "Field" + (PRINT_NONDETERMINISTIC ? " " + id : "") + " " + f + " = " + printGeneric(value));
+  }
+
+  public static native long[] getNativeFields(Field[] fields);
+
+  public static native void readNativeFields(Class<?> field_class, long[] sfields);
+}
diff --git a/test/1975-hello-structural-transformation/src/art/Transform1975.java b/test/1975-hello-structural-transformation/src/art/Transform1975.java
new file mode 100644
index 0000000..415be85
--- /dev/null
+++ b/test/1975-hello-structural-transformation/src/art/Transform1975.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+import java.util.Base64;
+
+public class Transform1975 {
+
+  static {
+  }
+
+  public static Class<?> CUR_CLASS = Transform1975.class;
+
+  /* Dex file for:
+   * // NB The name NEW_STRING ensures the offset for the REDEFINED_DEX_BYTES field is different.
+   * package art;
+   * public class Transform1975 {
+   *  static {}
+   *  public static Class<?> CUR_CLASS;
+   *  public static byte[] REDEFINED_DEX_BYTES;
+   *  public static String NEW_STRING;
+   *  public static void doSomething() {
+   *    System.out.println("Doing something");
+   *    new_string = "I did something!";
+   *  }
+   *  public static void readFields() {
+   *    System.out.println("NEW VALUE CUR_CLASS: " + CUR_CLASS);
+   *    System.out.println("NEW VALUE REDEFINED_DEX_BYTES: " + Base64.getEncoder().encodeToString(REDEFINED_DEX_BYTES));
+   *    System.out.println("NEW VALUE NEW_STRING: " + NEW_STRING);
+   *  }
+   * }
+   */
+  public static byte[] REDEFINED_DEX_BYTES =
+      Base64.getDecoder()
+          .decode(
+              "ZGV4CjAzNQDNGFvYlmyIt+u4bnNv+OyNVekBxlrJi6EgBwAAcAAAAHhWNBIAAAAAAAAAAFwGAAAm"
+                  + "AAAAcAAAAAwAAAAIAQAABwAAADgBAAAEAAAAjAEAAAwAAACsAQAAAQAAAAwCAAD0BAAALAIAAGYD"
+                  + "AABrAwAAdQMAAH0DAACIAwAAmQMAAKsDAACuAwAAsgMAAMcDAADmAwAA/QMAABAEAAAjBAAANwQA"
+                  + "AEsEAABmBAAAegQAAJYEAACqBAAAwQQAANkEAAD6BAAABgUAABsFAAAvBQAAMgUAADYFAAA6BQAA"
+                  + "QgUAAE8FAABfBQAAawUAAHAFAAB5BQAAhQUAAI8FAACWBQAACAAAAAkAAAAKAAAACwAAAA0AAAAO"
+                  + "AAAADwAAABAAAAARAAAAEgAAABkAAAAbAAAABgAAAAUAAAAAAAAABwAAAAUAAABQAwAABwAAAAYA"
+                  + "AABYAwAABwAAAAYAAABgAwAABgAAAAgAAAAAAAAAGQAAAAoAAAAAAAAAGgAAAAoAAABgAwAAAAAD"
+                  + "AAMAAAAAAAUAFgAAAAAACwAXAAAABwACACAAAAAAAAUAAQAAAAAABQACAAAAAAAFAB0AAAAAAAUA"
+                  + "IgAAAAIABgAhAAAABAAFAAIAAAAGAAUAAgAAAAYAAgAcAAAABgADABwAAAAGAAAAIwAAAAgAAQAe"
+                  + "AAAACQAEAB8AAAAAAAAAAQAAAAQAAAAAAAAAGAAAAEQGAAAYBgAAAAAAAAAAAAAAAAAAMgMAAAEA"
+                  + "AAAOAAAAAQABAAEAAAA2AwAABAAAAHAQBQAAAA4AAgAAAAIAAAA6AwAADAAAAGIAAwAaAQQAbiAE"
+                  + "ABAAGgAFAGkAAQAOAAQAAAACAAAAQAMAAFEAAABiAAMAYgEAACICBgBwEAYAAgAaAxMAbiAIADIA"
+                  + "biAHABIAbhAJAAIADAFuIAQAEABiAAMAcQALAAAADAFiAgIAbiAKACEADAEiAgYAcBAGAAIAGgMV"
+                  + "AG4gCAAyAG4gCAASAG4QCQACAAwBbiAEABAAYgADAGIBAQAiAgYAcBAGAAIAGgMUAG4gCAAyAG4g"
+                  + "CAASAG4QCQACAAwBbiAEABAADgAEAA4AAwAOAAkADnhLAA0ADgEYDwEgDwEYDwAAAAABAAAACwAA"
+                  + "AAEAAAAEAAAAAQAAAAUAAyo+OwAIPGNsaW5pdD4ABjxpbml0PgAJQ1VSX0NMQVNTAA9Eb2luZyBz"
+                  + "b21ldGhpbmcAEEkgZGlkIHNvbWV0aGluZyEAAUwAAkxMABNMYXJ0L1RyYW5zZm9ybTE5NzU7AB1M"
+                  + "ZGFsdmlrL2Fubm90YXRpb24vU2lnbmF0dXJlOwAVTGphdmEvaW8vUHJpbnRTdHJlYW07ABFMamF2"
+                  + "YS9sYW5nL0NsYXNzOwARTGphdmEvbGFuZy9DbGFzczwAEkxqYXZhL2xhbmcvT2JqZWN0OwASTGph"
+                  + "dmEvbGFuZy9TdHJpbmc7ABlMamF2YS9sYW5nL1N0cmluZ0J1aWxkZXI7ABJMamF2YS9sYW5nL1N5"
+                  + "c3RlbTsAGkxqYXZhL3V0aWwvQmFzZTY0JEVuY29kZXI7ABJMamF2YS91dGlsL0Jhc2U2NDsAFU5F"
+                  + "VyBWQUxVRSBDVVJfQ0xBU1M6IAAWTkVXIFZBTFVFIE5FV19TVFJJTkc6IAAfTkVXIFZBTFVFIFJF"
+                  + "REVGSU5FRF9ERVhfQllURVM6IAAKTkVXX1NUUklORwATUkVERUZJTkVEX0RFWF9CWVRFUwASVHJh"
+                  + "bnNmb3JtMTk3NS5qYXZhAAFWAAJWTAACW0IABmFwcGVuZAALZG9Tb21ldGhpbmcADmVuY29kZVRv"
+                  + "U3RyaW5nAApnZXRFbmNvZGVyAANvdXQAB3ByaW50bG4ACnJlYWRGaWVsZHMACHRvU3RyaW5nAAV2"
+                  + "YWx1ZQB2fn5EOHsiY29tcGlsYXRpb24tbW9kZSI6ImRlYnVnIiwibWluLWFwaSI6MSwic2hhLTEi"
+                  + "OiJhODM1MmYyNTQ4ODUzNjJjY2Q4ZDkwOWQzNTI5YzYwMDk0ZGQ4OTZlIiwidmVyc2lvbiI6IjEu"
+                  + "Ni4yMC1kZXYifQACAQEkHAIXDBcAAwAEAAAJAQkBCQCIgASsBAGBgATABAEJ2AQBCYAFAAAAAAAA"
+                  + "AQAAAA4GAAA4BgAAAQAAAAAAAAAAAAAAAAAAADwGAAAQAAAAAAAAAAEAAAAAAAAAAQAAACYAAABw"
+                  + "AAAAAgAAAAwAAAAIAQAAAwAAAAcAAAA4AQAABAAAAAQAAACMAQAABQAAAAwAAACsAQAABgAAAAEA"
+                  + "AAAMAgAAASAAAAQAAAAsAgAAAyAAAAQAAAAyAwAAARAAAAMAAABQAwAAAiAAACYAAABmAwAABCAA"
+                  + "AAEAAAAOBgAAACAAAAEAAAAYBgAAAxAAAAIAAAA4BgAABiAAAAEAAABEBgAAABAAAAEAAABcBgAA");
+
+  public static void doSomething() {
+    System.out.println("Not doing anything");
+  }
+
+  public static void readFields() {
+    System.out.println("ORIGINAL VALUE CUR_CLASS: " + CUR_CLASS);
+    System.out.println(
+        "ORIGINAL VALUE REDEFINED_DEX_BYTES: "
+            + Base64.getEncoder().encodeToString(REDEFINED_DEX_BYTES));
+  }
+}
diff --git a/test/1975-hello-structural-transformation/structural_transform.cc b/test/1975-hello-structural-transformation/structural_transform.cc
new file mode 100644
index 0000000..4217045
--- /dev/null
+++ b/test/1975-hello-structural-transformation/structural_transform.cc
@@ -0,0 +1,77 @@
+
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <cstdio>
+#include <memory>
+#include <mutex>
+#include <string>
+#include <vector>
+
+#include "android-base/logging.h"
+#include "android-base/macros.h"
+#include "android-base/stringprintf.h"
+#include "jni.h"
+#include "jvmti.h"
+#include "scoped_local_ref.h"
+#include "scoped_utf_chars.h"
+
+// Test infrastructure
+#include "jni_helper.h"
+#include "jvmti_helper.h"
+#include "test_env.h"
+#include "ti_macros.h"
+
+namespace art {
+namespace Test1975StructuralTransform {
+
+extern "C" JNIEXPORT void JNICALL Java_art_Test1975_readNativeFields(JNIEnv* env,
+                                                                    jclass k,
+                                                                    jclass f_class,
+                                                                    jlongArray f) {
+  jint len = env->GetArrayLength(f);
+  for (jint i = 0; i < len; i++) {
+    jlong fid_val;
+    env->GetLongArrayRegion(f, i, 1, &fid_val);
+    jfieldID fid = reinterpret_cast<jfieldID>(static_cast<intptr_t>(fid_val));
+    // For this test everything is objects and static.
+    jobject val = env->GetStaticObjectField(f_class, fid);
+    env->CallStaticVoidMethod(
+        k,
+        env->GetStaticMethodID(
+            k, "printNativeField", "(JLjava/lang/reflect/Field;Ljava/lang/Object;)V"),
+        fid_val,
+        env->ToReflectedField(f_class, fid, true),
+        val);
+    env->DeleteLocalRef(val);
+  }
+}
+
+extern "C" JNIEXPORT jlongArray JNICALL Java_art_Test1975_getNativeFields(JNIEnv* env,
+                                                                          jclass,
+                                                                          jobjectArray f) {
+  jint len = env->GetArrayLength(f);
+  jlongArray arr = env->NewLongArray(len);
+  for (jint i = 0; i < len; i++) {
+    jfieldID fid = env->FromReflectedField(env->GetObjectArrayElement(f, i));
+    jlong lfid = static_cast<jlong>(reinterpret_cast<intptr_t>(fid));
+    env->SetLongArrayRegion(arr, i, 1, &lfid);
+  }
+  return arr;
+}
+
+}  // namespace Test1975StructuralTransform
+}  // namespace art
diff --git a/test/1976-hello-structural-static-methods/expected.txt b/test/1976-hello-structural-static-methods/expected.txt
new file mode 100644
index 0000000..944cc0b
--- /dev/null
+++ b/test/1976-hello-structural-static-methods/expected.txt
@@ -0,0 +1,70 @@
+Running directly
+Saying everything!
+hello
+Saying hi!
+hello
+Running reflective
+Reflectively invoking public static void art.Transform1976.sayEverything()
+hello
+Reflectively invoking public static void art.Transform1976.sayHi()
+hello
+Running jni
+Running method public static void art.Transform1976.sayEverything() using JNI.
+hello
+Running method public static void art.Transform1976.sayHi() using JNI.
+hello
+Running method handles
+Invoking MethodHandle()void (public static void art.Transform1976.sayEverything())
+hello
+Invoking MethodHandle()void (public static void art.Transform1976.sayHi())
+hello
+Running directly after redef
+Saying everything!
+Not saying hi again!
+Bye
+Saying hi!
+Not saying hi again!
+Saying bye!
+Bye
+Running reflective after redef using old j.l.r.Method
+Reflectively invoking public static void art.Transform1976.sayEverything() on old j.l.r.Method
+Not saying hi again!
+Bye
+Reflectively invoking public static void art.Transform1976.sayHi() on old j.l.r.Method
+Not saying hi again!
+Running reflective after redef using new j.l.r.Method
+Reflectively invoking public static void art.Transform1976.sayBye() on new j.l.r.Method
+Bye
+Reflectively invoking public static void art.Transform1976.sayEverything() on new j.l.r.Method
+Not saying hi again!
+Bye
+Reflectively invoking public static void art.Transform1976.sayHi() on new j.l.r.Method
+Not saying hi again!
+Running jni with old ids
+Running method public static void art.Transform1976.sayEverything() using JNI.
+Not saying hi again!
+Bye
+Running method public static void art.Transform1976.sayHi() using JNI.
+Not saying hi again!
+Running jni with new ids
+Running method public static void art.Transform1976.sayBye() using JNI.
+Bye
+Running method public static void art.Transform1976.sayEverything() using JNI.
+Not saying hi again!
+Bye
+Running method public static void art.Transform1976.sayHi() using JNI.
+Not saying hi again!
+Running method handles using old handles
+Invoking MethodHandle()void (public static void art.Transform1976.sayEverything())
+Not saying hi again!
+Bye
+Invoking MethodHandle()void (public static void art.Transform1976.sayHi())
+Not saying hi again!
+Running method handles using new handles
+Invoking MethodHandle()void (public static void art.Transform1976.sayBye())
+Bye
+Invoking MethodHandle()void (public static void art.Transform1976.sayEverything())
+Not saying hi again!
+Bye
+Invoking MethodHandle()void (public static void art.Transform1976.sayHi())
+Not saying hi again!
diff --git a/test/1976-hello-structural-static-methods/info.txt b/test/1976-hello-structural-static-methods/info.txt
new file mode 100644
index 0000000..e9f9dbb
--- /dev/null
+++ b/test/1976-hello-structural-static-methods/info.txt
@@ -0,0 +1 @@
+Test adding static methods using structural class redefinition.
diff --git a/test/1976-hello-structural-static-methods/run b/test/1976-hello-structural-static-methods/run
new file mode 100755
index 0000000..03e41a5
--- /dev/null
+++ b/test/1976-hello-structural-static-methods/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/1976-hello-structural-static-methods/src/Main.java b/test/1976-hello-structural-static-methods/src/Main.java
new file mode 100644
index 0000000..5abf28f
--- /dev/null
+++ b/test/1976-hello-structural-static-methods/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.Test1976.run();
+  }
+}
diff --git a/test/1976-hello-structural-static-methods/src/art/Redefinition.java b/test/1976-hello-structural-static-methods/src/art/Redefinition.java
new file mode 120000
index 0000000..81eaf31
--- /dev/null
+++ b/test/1976-hello-structural-static-methods/src/art/Redefinition.java
@@ -0,0 +1 @@
+../../../jvmti-common/Redefinition.java
\ No newline at end of file
diff --git a/test/1976-hello-structural-static-methods/src/art/Test1976.java b/test/1976-hello-structural-static-methods/src/art/Test1976.java
new file mode 100644
index 0000000..169d236
--- /dev/null
+++ b/test/1976-hello-structural-static-methods/src/art/Test1976.java
@@ -0,0 +1,203 @@
+/*
+ * 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.lang.ref.*;
+import java.lang.reflect.*;
+import java.lang.invoke.*;
+import java.util.*;
+
+public class Test1976 {
+
+  // The fact that the target is having methods added makes it annoying to test since we cannot
+  // initially call them. To work around this in a simple-ish way just use (non-structural)
+  // redefinition to change the implementation of the caller of Transform1976 after redefining the
+  // target.
+  public static final class RunTransformMethods implements Runnable {
+    public void run() {
+      System.out.println("Saying everything!");
+      Transform1976.sayEverything();
+      System.out.println("Saying hi!");
+      Transform1976.sayHi();
+    }
+  }
+
+  /* Base64 encoded dex bytes of:
+   * public static final class RunTransformMethods implements Runnable {
+   *   public void run() {
+   *    System.out.println("Saying everything!");
+   *    Transform1976.sayEverything();
+   *    System.out.println("Saying hi!");
+   *    Transform1976.sayHi();
+   *    System.out.println("Saying bye!");
+   *    Transform1976.sayBye();
+   *   }
+   * }
+   */
+  public static final byte[] RUN_DEX_BYTES =
+      Base64.getDecoder()
+          .decode(
+              "ZGV4CjAzNQCv3eV8jFcpSsqMGl1ZXRk2iraZO41D0TIgBQAAcAAAAHhWNBIAAAAAAAAAAFwEAAAc"
+                  + "AAAAcAAAAAsAAADgAAAAAgAAAAwBAAABAAAAJAEAAAcAAAAsAQAAAQAAAGQBAACcAwAAhAEAAAYC"
+                  + "AAAOAgAAMgIAAEICAABXAgAAewIAAJsCAACyAgAAxgIAANwCAADwAgAABAMAABkDAAAmAwAAOgMA"
+                  + "AEYDAABVAwAAWAMAAFwDAABpAwAAbwMAAHQDAAB9AwAAggMAAIoDAACZAwAAoAMAAKcDAAABAAAA"
+                  + "AgAAAAMAAAAEAAAABQAAAAYAAAAHAAAACAAAAAkAAAAKAAAAEAAAABAAAAAKAAAAAAAAABEAAAAK"
+                  + "AAAAAAIAAAkABQAUAAAAAAAAAAAAAAAAAAAAFgAAAAIAAAAXAAAAAgAAABgAAAACAAAAGQAAAAUA"
+                  + "AQAVAAAABgAAAAAAAAAAAAAAEQAAAAYAAAD4AQAADwAAAEwEAAAuBAAAAAAAAAEAAQABAAAA6gEA"
+                  + "AAQAAABwEAYAAAAOAAMAAQACAAAA7gEAAB8AAABiAAAAGgENAG4gBQAQAHEAAwAAAGIAAAAaAQ4A"
+                  + "biAFABAAcQAEAAAAYgAAABoBDABuIAUAEABxAAIAAAAOAAYADgAIAA54PHg8eDwAAQAAAAcAAAAB"
+                  + "AAAACAAGPGluaXQ+ACJMYXJ0L1Rlc3QxOTc2JFJ1blRyYW5zZm9ybU1ldGhvZHM7AA5MYXJ0L1Rl"
+                  + "c3QxOTc2OwATTGFydC9UcmFuc2Zvcm0xOTc2OwAiTGRhbHZpay9hbm5vdGF0aW9uL0VuY2xvc2lu"
+                  + "Z0NsYXNzOwAeTGRhbHZpay9hbm5vdGF0aW9uL0lubmVyQ2xhc3M7ABVMamF2YS9pby9QcmludFN0"
+                  + "cmVhbTsAEkxqYXZhL2xhbmcvT2JqZWN0OwAUTGphdmEvbGFuZy9SdW5uYWJsZTsAEkxqYXZhL2xh"
+                  + "bmcvU3RyaW5nOwASTGphdmEvbGFuZy9TeXN0ZW07ABNSdW5UcmFuc2Zvcm1NZXRob2RzAAtTYXlp"
+                  + "bmcgYnllIQASU2F5aW5nIGV2ZXJ5dGhpbmchAApTYXlpbmcgaGkhAA1UZXN0MTk3Ni5qYXZhAAFW"
+                  + "AAJWTAALYWNjZXNzRmxhZ3MABG5hbWUAA291dAAHcHJpbnRsbgADcnVuAAZzYXlCeWUADXNheUV2"
+                  + "ZXJ5dGhpbmcABXNheUhpAAV2YWx1ZQB2fn5EOHsiY29tcGlsYXRpb24tbW9kZSI6ImRlYnVnIiwi"
+                  + "bWluLWFwaSI6MSwic2hhLTEiOiJhODM1MmYyNTQ4ODUzNjJjY2Q4ZDkwOWQzNTI5YzYwMDk0ZGQ4"
+                  + "OTZlIiwidmVyc2lvbiI6IjEuNi4yMC1kZXYifQACAwEaGAECBAISBBkTFwsAAAEBAIGABIQDAQGc"
+                  + "AwAAAAACAAAAHwQAACUEAABABAAAAAAAAAAAAAAAAAAAEAAAAAAAAAABAAAAAAAAAAEAAAAcAAAA"
+                  + "cAAAAAIAAAALAAAA4AAAAAMAAAACAAAADAEAAAQAAAABAAAAJAEAAAUAAAAHAAAALAEAAAYAAAAB"
+                  + "AAAAZAEAAAEgAAACAAAAhAEAAAMgAAACAAAA6gEAAAEQAAACAAAA+AEAAAIgAAAcAAAABgIAAAQg"
+                  + "AAACAAAAHwQAAAAgAAABAAAALgQAAAMQAAACAAAAPAQAAAYgAAABAAAATAQAAAAQAAABAAAAXAQA"
+                  + "AA==");
+
+  public static void run() throws Exception {
+    Redefinition.setTestConfiguration(Redefinition.Config.COMMON_REDEFINE);
+    doTest();
+  }
+
+  private static final boolean PRINT_ID_NUM = false;
+
+  public static void printRun(long id, Method m) {
+    if (PRINT_ID_NUM) {
+      System.out.println("Running method " + id + " " + m + " using JNI.");
+    } else {
+      System.out.println("Running method " + m + " using JNI.");
+    }
+  }
+
+  public static final class MethodHandleWrapper {
+    private MethodHandle mh;
+    private Method m;
+    public MethodHandleWrapper(MethodHandle mh, Method m) {
+      this.m = m;
+      this.mh = mh;
+    }
+    public MethodHandle getHandle() {
+      return mh;
+    }
+    public Method getMethod() {
+      return m;
+    }
+    public Object invoke() throws Throwable {
+      return mh.invoke();
+    }
+    public String toString() {
+      return mh.toString();
+    }
+  }
+
+  public static MethodHandleWrapper[] getMethodHandles(Method[] methods) throws Exception {
+    final MethodHandles.Lookup l = MethodHandles.lookup();
+    ArrayList<MethodHandleWrapper> res = new ArrayList<>();
+    for (Method m : methods) {
+      if (!Modifier.isStatic(m.getModifiers())) {
+        continue;
+      }
+      res.add(new MethodHandleWrapper(l.unreflect(m), m));
+    }
+    return res.toArray(new MethodHandleWrapper[0]);
+  }
+
+  public static void runMethodHandles(MethodHandleWrapper[] handles) throws Exception {
+    for (MethodHandleWrapper h : handles) {
+      try {
+        System.out.println("Invoking " + h + " (" + h.getMethod() + ")");
+        h.invoke();
+      } catch (Throwable t) {
+        if (t instanceof Exception) {
+          throw (Exception)t;
+        } else if (t instanceof Error) {
+          throw (Error)t;
+        } else {
+          throw new RuntimeException("Unexpected throwable thrown!", t);
+        }
+      }
+    }
+  }
+
+  public static void doTest() throws Exception {
+    Runnable r = new RunTransformMethods();
+    System.out.println("Running directly");
+    r.run();
+    System.out.println("Running reflective");
+    Method[] methods = Transform1976.class.getDeclaredMethods();
+    for (Method m : methods) {
+      if (Modifier.isStatic(m.getModifiers())) {
+        System.out.println("Reflectively invoking " + m);
+        m.invoke(null);
+      } else {
+        System.out.println("Not invoking non-static method " + m);
+      }
+    }
+    System.out.println("Running jni");
+    long[] mids = getMethodIds(methods);
+    callNativeMethods(Transform1976.class, mids);
+    MethodHandleWrapper[] handles = getMethodHandles(methods);
+    System.out.println("Running method handles");
+    runMethodHandles(handles);
+    Redefinition.doCommonStructuralClassRedefinition(
+        Transform1976.class, Transform1976.REDEFINED_DEX_BYTES);
+    // Change RunTransformMethods to also call the 'runBye' method. No RI support so no classfile
+    // bytes required.
+    Redefinition.doCommonClassRedefinition(RunTransformMethods.class, new byte[] {}, RUN_DEX_BYTES);
+    System.out.println("Running directly after redef");
+    r.run();
+    System.out.println("Running reflective after redef using old j.l.r.Method");
+    for (Method m : methods) {
+      if (Modifier.isStatic(m.getModifiers())) {
+        System.out.println("Reflectively invoking " + m + " on old j.l.r.Method");
+        m.invoke(null);
+      } else {
+        System.out.println("Not invoking non-static method " + m);
+      }
+    }
+    System.out.println("Running reflective after redef using new j.l.r.Method");
+    for (Method m : Transform1976.class.getDeclaredMethods()) {
+      if (Modifier.isStatic(m.getModifiers())) {
+        System.out.println("Reflectively invoking " + m + " on new j.l.r.Method");
+        m.invoke(null);
+      } else {
+        System.out.println("Not invoking non-static method " + m);
+      }
+    }
+    System.out.println("Running jni with old ids");
+    callNativeMethods(Transform1976.class, mids);
+    System.out.println("Running jni with new ids");
+    callNativeMethods(Transform1976.class, getMethodIds(Transform1976.class.getDeclaredMethods()));
+
+    System.out.println("Running method handles using old handles");
+    runMethodHandles(handles);
+    System.out.println("Running method handles using new handles");
+    runMethodHandles(getMethodHandles(Transform1976.class.getDeclaredMethods()));
+  }
+
+  public static native long[] getMethodIds(Method[] m);
+
+  public static native void callNativeMethods(Class<?> k, long[] smethods);
+}
diff --git a/test/1976-hello-structural-static-methods/src/art/Transform1976.java b/test/1976-hello-structural-static-methods/src/art/Transform1976.java
new file mode 100644
index 0000000..e347711
--- /dev/null
+++ b/test/1976-hello-structural-static-methods/src/art/Transform1976.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+import java.util.Base64;
+
+public class Transform1976 {
+
+  static {
+  }
+
+  /* Dex file for:
+   * package art;
+   * public class Transform1976 {
+   *   static {}
+   *   public static byte[] REDEFINED_DEX_BYTES;
+   *   public static void sayEverything() {
+   *     sayHi();
+   *     sayBye();
+   *   }
+   *   public static void sayBye() {
+   *     System.out.println("Bye");
+   *   }
+   *   public static void sayHi() {
+   *     System.out.println("Not saying hi again!");
+   *   }
+   * }
+   */
+  public static byte[] REDEFINED_DEX_BYTES =
+      Base64.getDecoder()
+          .decode(
+              "ZGV4CjAzNQBHoxOnl1VNY5YvAENBMpZs9rgNOtJjgZFEBAAAcAAAAHhWNBIAAAAAAAAAAJgDAAAU"
+                  + "AAAAcAAAAAcAAADAAAAAAgAAANwAAAACAAAA9AAAAAcAAAAEAQAAAQAAADwBAADoAgAAXAEAAAYC"
+                  + "AAAQAgAAGAIAAB0CAAAyAgAASQIAAF0CAABxAgAAhQIAAJsCAACwAgAAxAIAAMcCAADLAgAAzwIA"
+                  + "ANQCAADdAgAA5QIAAPQCAAD7AgAAAwAAAAQAAAAFAAAABgAAAAcAAAALAAAADQAAAAsAAAAFAAAA"
+                  + "AAAAAAwAAAAFAAAAAAIAAAAABgAJAAAABAABAA4AAAAAAAAAAAAAAAAAAAABAAAAAAAAABAAAAAA"
+                  + "AAAAEQAAAAAAAAASAAAAAQABAA8AAAACAAAAAQAAAAAAAAABAAAAAgAAAAAAAAAKAAAAAAAAAHMD"
+                  + "AAAAAAAAAAAAAAAAAADoAQAAAQAAAA4AAAABAAEAAQAAAOwBAAAEAAAAcBAGAAAADgACAAAAAgAA"
+                  + "APABAAAIAAAAYgABABoBAgBuIAUAEAAOAAAAAAAAAAAA9QEAAAcAAABxAAQAAABxAAIAAAAOAAAA"
+                  + "AgAAAAIAAAD7AQAACAAAAGIAAQAaAQgAbiAFABAADgADAA4AAgAOAAoADngABgAOPDwADQAOeAAB"
+                  + "AAAAAwAIPGNsaW5pdD4ABjxpbml0PgADQnllABNMYXJ0L1RyYW5zZm9ybTE5NzY7ABVMamF2YS9p"
+                  + "by9QcmludFN0cmVhbTsAEkxqYXZhL2xhbmcvT2JqZWN0OwASTGphdmEvbGFuZy9TdHJpbmc7ABJM"
+                  + "amF2YS9sYW5nL1N5c3RlbTsAFE5vdCBzYXlpbmcgaGkgYWdhaW4hABNSRURFRklORURfREVYX0JZ"
+                  + "VEVTABJUcmFuc2Zvcm0xOTc2LmphdmEAAVYAAlZMAAJbQgADb3V0AAdwcmludGxuAAZzYXlCeWUA"
+                  + "DXNheUV2ZXJ5dGhpbmcABXNheUhpAHZ+fkQ4eyJjb21waWxhdGlvbi1tb2RlIjoiZGVidWciLCJt"
+                  + "aW4tYXBpIjoxLCJzaGEtMSI6ImE4MzUyZjI1NDg4NTM2MmNjZDhkOTA5ZDM1MjljNjAwOTRkZDg5"
+                  + "NmUiLCJ2ZXJzaW9uIjoiMS42LjIwLWRldiJ9AAEABQAACQCIgATcAgGBgATwAgEJiAMBCagDAQnI"
+                  + "AwAAAAAAAAAOAAAAAAAAAAEAAAAAAAAAAQAAABQAAABwAAAAAgAAAAcAAADAAAAAAwAAAAIAAADc"
+                  + "AAAABAAAAAIAAAD0AAAABQAAAAcAAAAEAQAABgAAAAEAAAA8AQAAASAAAAUAAABcAQAAAyAAAAUA"
+                  + "AADoAQAAARAAAAEAAAAAAgAAAiAAABQAAAAGAgAAACAAAAEAAABzAwAAAxAAAAEAAACUAwAAABAA"
+                  + "AAEAAACYAwAA");
+
+  public static void sayEverything() {
+    sayHi();
+  }
+
+  public static void sayHi() {
+    System.out.println("hello");
+  }
+}
diff --git a/test/1976-hello-structural-static-methods/structural_transform_methods.cc b/test/1976-hello-structural-static-methods/structural_transform_methods.cc
new file mode 100644
index 0000000..65bcae0
--- /dev/null
+++ b/test/1976-hello-structural-static-methods/structural_transform_methods.cc
@@ -0,0 +1,77 @@
+
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <cstdio>
+#include <memory>
+#include <mutex>
+#include <string>
+#include <vector>
+
+#include "android-base/logging.h"
+#include "android-base/macros.h"
+#include "android-base/stringprintf.h"
+#include "jni.h"
+#include "jvmti.h"
+#include "scoped_local_ref.h"
+#include "scoped_utf_chars.h"
+
+// Test infrastructure
+#include "jni_helper.h"
+#include "jvmti_helper.h"
+#include "test_env.h"
+#include "ti_macros.h"
+
+namespace art {
+namespace Test1976StructuralTransformMethods {
+
+extern "C" JNIEXPORT void JNICALL Java_art_Test1976_callNativeMethods(JNIEnv* env,
+                                                                      jclass k,
+                                                                      jclass m_class,
+                                                                      jlongArray m) {
+  jint len = env->GetArrayLength(m);
+  for (jint i = 0; i < len; i++) {
+    jlong mid_val;
+    env->GetLongArrayRegion(m, i, 1, &mid_val);
+    jmethodID mid = reinterpret_cast<jmethodID>(static_cast<intptr_t>(mid_val));
+    // For this test everything is objects and static.
+    env->CallStaticVoidMethod(
+        k,
+        env->GetStaticMethodID(
+            k, "printRun", "(JLjava/lang/reflect/Method;)V"),
+        mid_val,
+        env->ToReflectedMethod(m_class, mid, true));
+    env->CallStaticVoidMethod(m_class, mid);
+  }
+}
+
+extern "C" JNIEXPORT jlongArray JNICALL Java_art_Test1976_getMethodIds(JNIEnv* env,
+                                                                       jclass,
+                                                                       jobjectArray m) {
+  jint len = env->GetArrayLength(m);
+  jlongArray arr = env->NewLongArray(len);
+  for (jint i = 0; i < len; i++) {
+    env->PushLocalFrame(1);
+    jmethodID fid = env->FromReflectedMethod(env->GetObjectArrayElement(m, i));
+    jlong lmid = static_cast<jlong>(reinterpret_cast<intptr_t>(fid));
+    env->SetLongArrayRegion(arr, i, 1, &lmid);
+    env->PopLocalFrame(nullptr);
+  }
+  return arr;
+}
+
+}  // namespace Test1976StructuralTransformMethods
+}  // namespace art
diff --git a/test/1977-hello-structural-obsolescence/expected.txt b/test/1977-hello-structural-obsolescence/expected.txt
new file mode 100644
index 0000000..ae68bdb
--- /dev/null
+++ b/test/1977-hello-structural-obsolescence/expected.txt
@@ -0,0 +1,9 @@
+hello Alex
+Not doing anything here
+goodbye Alex
+hello Alex
+transforming calling function
+goodbye Alex
+Hello Alex - Transformed
+Not doing anything here
+Goodbye and good luck - Transformed
diff --git a/test/1977-hello-structural-obsolescence/info.txt b/test/1977-hello-structural-obsolescence/info.txt
new file mode 100644
index 0000000..6835227
--- /dev/null
+++ b/test/1977-hello-structural-obsolescence/info.txt
@@ -0,0 +1 @@
+Test interaction between obsolete methods and structural class redefinition.
diff --git a/test/1977-hello-structural-obsolescence/run b/test/1977-hello-structural-obsolescence/run
new file mode 100755
index 0000000..03e41a5
--- /dev/null
+++ b/test/1977-hello-structural-obsolescence/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/1977-hello-structural-obsolescence/src/Main.java b/test/1977-hello-structural-obsolescence/src/Main.java
new file mode 100644
index 0000000..4f94ed5
--- /dev/null
+++ b/test/1977-hello-structural-obsolescence/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.Test1977.run();
+  }
+}
diff --git a/test/1977-hello-structural-obsolescence/src/art/Redefinition.java b/test/1977-hello-structural-obsolescence/src/art/Redefinition.java
new file mode 120000
index 0000000..81eaf31
--- /dev/null
+++ b/test/1977-hello-structural-obsolescence/src/art/Redefinition.java
@@ -0,0 +1 @@
+../../../jvmti-common/Redefinition.java
\ No newline at end of file
diff --git a/test/1977-hello-structural-obsolescence/src/art/Test1977.java b/test/1977-hello-structural-obsolescence/src/art/Test1977.java
new file mode 100644
index 0000000..f4cb9f1
--- /dev/null
+++ b/test/1977-hello-structural-obsolescence/src/art/Test1977.java
@@ -0,0 +1,119 @@
+/*
+ * 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.lang.reflect.Field;
+import java.util.Base64;
+
+import sun.misc.Unsafe;
+
+public class Test1977 {
+
+  // The class we will be transforming.
+  static class Transform {
+    static {
+    }
+
+    public static String sayHiName = " Alex";
+    // Called whenever we do something.
+    public static void somethingHappened() {}
+
+    public static void sayHi(Runnable r) {
+      System.out.println("hello" + sayHiName);
+      r.run();
+      somethingHappened();
+      System.out.println("goodbye" + sayHiName);
+    }
+  }
+
+  // static class Transform {
+  //   static {}
+  //   // NB Due to the ordering of fields offset of sayHiName will change.
+  //   public static String sayHiName;
+  //   public static String sayByeName;
+  //   public static void somethingHappened() {
+  //     sayByeName = " and good luck";
+  //   }
+  //   public static void doSayBye() {
+  //     System.out.println("Goodbye" + sayByeName + " - Transformed");
+  //   }
+  //   public static void doSayHi() {
+  //     System.out.println("Hello" + sayHiName + " - Transformed");
+  //   }
+  //   public static void sayHi(Runnable r) {
+  //     doSayHi();
+  //     r.run();
+  //     somethingHappened();
+  //     doSayBye();
+  //   }
+  // }
+  private static final byte[] DEX_BYTES =
+      Base64.getDecoder()
+          .decode(
+              "ZGV4CjAzNQBNCReVL85UCydGe4wKq3olUYP6Lb8WIlewBgAAcAAAAHhWNBIAAAAAAAAAAOwFAAAl"
+                  + "AAAAcAAAAAsAAAAEAQAABQAAADABAAADAAAAbAEAAAwAAACEAQAAAQAAAOQBAACsBAAABAIAAEID"
+                  + "AABSAwAAYgMAAGwDAAB0AwAAfQMAAIQDAACHAwAAiwMAAKUDAAC1AwAA2QMAAPkDAAAQBAAAJAQA"
+                  + "ADoEAABOBAAAaQQAAH0EAACMBAAAlwQAAJoEAACeBAAAqwQAALMEAAC9BAAAxgQAAMwEAADRBAAA"
+                  + "2gQAAN8EAADrBAAA8gQAAP0EAAAQBQAAGgUAACEFAAAIAAAACQAAAAoAAAALAAAADAAAAA0AAAAO"
+                  + "AAAADwAAABAAAAARAAAAFAAAAAYAAAAHAAAAAAAAAAcAAAAIAAAANAMAABQAAAAKAAAAAAAAABUA"
+                  + "AAAKAAAAPAMAABUAAAAKAAAANAMAAAAABwAeAAAAAAAHACAAAAAJAAQAGwAAAAAAAgACAAAAAAAC"
+                  + "AAMAAAAAAAIAGAAAAAAAAgAZAAAAAAADAB8AAAAAAAIAIQAAAAQABAAcAAAABQACAAMAAAAGAAIA"
+                  + "HQAAAAgAAgADAAAACAABABcAAAAIAAAAIgAAAAAAAAAAAAAABQAAAAAAAAASAAAA3AUAAKgFAAAA"
+                  + "AAAAAAAAAAAAAAAOAwAAAQAAAA4AAAABAAEAAQAAABIDAAAEAAAAcBAHAAAADgAEAAAAAgAAABYD"
+                  + "AAAeAAAAYgACAGIBAAAiAggAcBAJAAIAGgMEAG4gCgAyAG4gCgASABoBAABuIAoAEgBuEAsAAgAM"
+                  + "AW4gBgAQAA4ABAAAAAIAAAAdAwAAHgAAAGIAAgBiAQEAIgIIAHAQCQACABoDBQBuIAoAMgBuIAoA"
+                  + "EgAaAQAAbiAKABIAbhALAAIADAFuIAYAEAAOAAEAAQABAAAAJAMAAA0AAABxAAMAAAByEAgAAABx"
+                  + "AAUAAABxAAIAAAAOAAAAAQAAAAAAAAAtAwAABQAAABoAAQBpAAAADgAHAA4ABgAOAA8ADgEdDwAS"
+                  + "AA4BHQ8AFQEADjw8PDwADAAOSwAAAAEAAAAHAAAAAQAAAAYADiAtIFRyYW5zZm9ybWVkAA4gYW5k"
+                  + "IGdvb2QgbHVjawAIPGNsaW5pdD4ABjxpbml0PgAHR29vZGJ5ZQAFSGVsbG8AAUwAAkxMABhMYXJ0"
+                  + "L1Rlc3QxOTc3JFRyYW5zZm9ybTsADkxhcnQvVGVzdDE5Nzc7ACJMZGFsdmlrL2Fubm90YXRpb24v"
+                  + "RW5jbG9zaW5nQ2xhc3M7AB5MZGFsdmlrL2Fubm90YXRpb24vSW5uZXJDbGFzczsAFUxqYXZhL2lv"
+                  + "L1ByaW50U3RyZWFtOwASTGphdmEvbGFuZy9PYmplY3Q7ABRMamF2YS9sYW5nL1J1bm5hYmxlOwAS"
+                  + "TGphdmEvbGFuZy9TdHJpbmc7ABlMamF2YS9sYW5nL1N0cmluZ0J1aWxkZXI7ABJMamF2YS9sYW5n"
+                  + "L1N5c3RlbTsADVRlc3QxOTc3LmphdmEACVRyYW5zZm9ybQABVgACVkwAC2FjY2Vzc0ZsYWdzAAZh"
+                  + "cHBlbmQACGRvU2F5QnllAAdkb1NheUhpAARuYW1lAANvdXQAB3ByaW50bG4AA3J1bgAKc2F5Qnll"
+                  + "TmFtZQAFc2F5SGkACXNheUhpTmFtZQARc29tZXRoaW5nSGFwcGVuZWQACHRvU3RyaW5nAAV2YWx1"
+                  + "ZQB2fn5EOHsiY29tcGlsYXRpb24tbW9kZSI6ImRlYnVnIiwibWluLWFwaSI6MSwic2hhLTEiOiJh"
+                  + "ODM1MmYyNTQ4ODUzNjJjY2Q4ZDkwOWQzNTI5YzYwMDk0ZGQ4OTZlIiwidmVyc2lvbiI6IjEuNi4y"
+                  + "MC1kZXYifQACAgEjGAECAwIWBAgaFxMCAAYAAAkBCQCIgASEBAGAgASYBAEJsAQBCfwEAQnIBQEJ"
+                  + "9AUAAAAAAgAAAJkFAACfBQAA0AUAAAAAAAAAAAAAAAAAABAAAAAAAAAAAQAAAAAAAAABAAAAJQAA"
+                  + "AHAAAAACAAAACwAAAAQBAAADAAAABQAAADABAAAEAAAAAwAAAGwBAAAFAAAADAAAAIQBAAAGAAAA"
+                  + "AQAAAOQBAAABIAAABgAAAAQCAAADIAAABgAAAA4DAAABEAAAAgAAADQDAAACIAAAJQAAAEIDAAAE"
+                  + "IAAAAgAAAJkFAAAAIAAAAQAAAKgFAAADEAAAAgAAAMwFAAAGIAAAAQAAANwFAAAAEAAAAQAAAOwF"
+                  + "AAA=");
+
+  public static void run() throws Exception {
+    Redefinition.setTestConfiguration(Redefinition.Config.COMMON_REDEFINE);
+    doTest();
+  }
+
+  public static void doTest() throws Exception {
+    Transform.sayHi(
+        () -> {
+          System.out.println("Not doing anything here");
+        });
+    Transform.sayHi(
+        () -> {
+          System.out.println("transforming calling function");
+          Redefinition.doCommonStructuralClassRedefinition(Transform.class, DEX_BYTES);
+        });
+    Transform.sayHi(
+        () -> {
+          System.out.println("Not doing anything here");
+        });
+  }
+}
diff --git a/test/1978-regular-obsolete-then-structural-obsolescence/expected.txt b/test/1978-regular-obsolete-then-structural-obsolescence/expected.txt
new file mode 100644
index 0000000..2bad9f2
--- /dev/null
+++ b/test/1978-regular-obsolete-then-structural-obsolescence/expected.txt
@@ -0,0 +1,21 @@
+hello Alex
+Not doing anything here - op1
+Running after op1 using normal definition
+how do you do Alex
+Not doing anything here - op2
+Running after op2 using normal definition
+goodbye Alex
+hello Alex
+transforming calling function - non-structural
+Running after op1 using non-structural redefinition
+how do you do Alex
+transforming calling function - structural
+Running after op2 using structural redefinition
+goodbye Alex
+Hello Alex - Transformed
+Not doing anything here - op1
+Running after op2 using structural redefinition
+How do you do this fine day - Transformed
+Not doing anything here - op2
+Running after op2 using structural redefinition
+Goodbye and good luck - Transformed
diff --git a/test/1978-regular-obsolete-then-structural-obsolescence/info.txt b/test/1978-regular-obsolete-then-structural-obsolescence/info.txt
new file mode 100644
index 0000000..6835227
--- /dev/null
+++ b/test/1978-regular-obsolete-then-structural-obsolescence/info.txt
@@ -0,0 +1 @@
+Test interaction between obsolete methods and structural class redefinition.
diff --git a/test/1978-regular-obsolete-then-structural-obsolescence/run b/test/1978-regular-obsolete-then-structural-obsolescence/run
new file mode 100755
index 0000000..03e41a5
--- /dev/null
+++ b/test/1978-regular-obsolete-then-structural-obsolescence/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/1978-regular-obsolete-then-structural-obsolescence/src/Main.java b/test/1978-regular-obsolete-then-structural-obsolescence/src/Main.java
new file mode 100644
index 0000000..7d023df
--- /dev/null
+++ b/test/1978-regular-obsolete-then-structural-obsolescence/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.Test1978.run();
+  }
+}
diff --git a/test/1978-regular-obsolete-then-structural-obsolescence/src/art/Redefinition.java b/test/1978-regular-obsolete-then-structural-obsolescence/src/art/Redefinition.java
new file mode 120000
index 0000000..81eaf31
--- /dev/null
+++ b/test/1978-regular-obsolete-then-structural-obsolescence/src/art/Redefinition.java
@@ -0,0 +1 @@
+../../../jvmti-common/Redefinition.java
\ No newline at end of file
diff --git a/test/1978-regular-obsolete-then-structural-obsolescence/src/art/Test1978.java b/test/1978-regular-obsolete-then-structural-obsolescence/src/art/Test1978.java
new file mode 100644
index 0000000..54d9c2d
--- /dev/null
+++ b/test/1978-regular-obsolete-then-structural-obsolescence/src/art/Test1978.java
@@ -0,0 +1,213 @@
+/*
+ * 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;
+
+// TODO Need version where non-structural obsolete method tries to touch a structural obsolete
+// field.
+public class Test1978 {
+
+  // The class we will be transforming.
+  static class Transform {
+    static {
+    }
+
+    public static String sayHiName = " Alex";
+    // Called whenever we do something.
+    public static void somethingHappened1() {
+      System.out.println("Running after op1 using normal definition");
+    }
+
+    public static void somethingHappened2() {
+      System.out.println("Running after op2 using normal definition");
+    }
+
+    public static void sayHi(Runnable r1, Runnable r2) {
+      System.out.println("hello" + sayHiName);
+      r1.run();
+      somethingHappened1();
+      System.out.println("how do you do" + sayHiName);
+      r2.run();
+      somethingHappened2();
+      System.out.println("goodbye" + sayHiName);
+    }
+  }
+
+  // static class Transform {
+  //   static {}
+  //   public static String sayHiName;
+  //   public static void somethingHappened1() {
+  //     System.out.println("Running after op1 using non-structural redefinition");
+  //   }
+  //   public static void somethingHappened2() {
+  //     System.out.println("Running after op2 using non-structural redefinition");
+  //   }
+  //   public static void sayHi(Runnable r1, Runnable r2) {
+  //     System.out.println("TRANSFORMED_NON_STRUCTURAL hello" + sayHiName);
+  //     r1.run();
+  //     somethingHappened1();
+  //     System.out.println("TRANSFORMED_NON_STRUCTURAL how do you do" + sayHiName);
+  //     r2.run();
+  //     somethingHappened2();
+  //     System.out.println("TRANSFORMED_NON_STRUCTURAL goodbye" + sayHiName);
+  //   }
+  // }
+  private static final byte[] NON_STRUCTURAL_DEX_BYTES =
+      Base64.getDecoder()
+          .decode(
+              "ZGV4CjAzNQCrRWiiuda6GysXvxv0iJ+9HUcPBCTU/UVkBwAAcAAAAHhWNBIAAAAAAAAAAKAGAAAl"
+                  + "AAAAcAAAAAsAAAAEAQAABQAAADABAAACAAAAbAEAAAsAAAB8AQAAAQAAANQBAABwBQAA9AEAAFQD"
+                  + "AABeAwAAZgMAAGkDAABtAwAAhwMAAJcDAAC7AwAA2wMAAPIDAAAGBAAAHAQAADAEAABLBAAAXwQA"
+                  + "AJQEAADJBAAA7QQAAA8FAAA5BQAASAUAAFMFAABWBQAAWgUAAF8FAABsBQAAdAUAAHoFAAB/BQAA"
+                  + "iAUAAI0FAACUBQAAnwUAALMFAADHBQAA0QUAANgFAAAEAAAABQAAAAYAAAAHAAAACAAAAAkAAAAK"
+                  + "AAAACwAAAAwAAAANAAAAFQAAAAIAAAAHAAAAAAAAAAMAAAAIAAAARAMAABUAAAAKAAAAAAAAABcA"
+                  + "AAAKAAAATAMAABYAAAAKAAAARAMAAAAABwAfAAAACQAEABsAAAAAAAIAAAAAAAAAAgABAAAAAAAD"
+                  + "AB4AAAAAAAIAIAAAAAAAAgAhAAAABAAEABwAAAAFAAIAAQAAAAYAAgAdAAAACAACAAEAAAAIAAEA"
+                  + "GQAAAAgAAAAiAAAAAAAAAAAAAAAFAAAAAAAAABMAAACQBgAAXwYAAAAAAAAAAAAAAAAAABwDAAAB"
+                  + "AAAADgAAAAEAAQABAAAAIAMAAAQAAABwEAYAAAAOAAYAAgACAAAAJAMAAFUAAABiAAEAYgEAACIC"
+                  + "CABwEAgAAgAaAxEAbiAJADIAbiAJABIAbhAKAAIADAFuIAUAEAByEAcABABxAAMAAABiBAEAYgAA"
+                  + "ACIBCABwEAgAAQAaAhIAbiAJACEAbiAJAAEAbhAKAAEADABuIAUABAByEAcABQBxAAQAAABiBAEA"
+                  + "YgUAACIACABwEAgAAAAaARAAbiAJABAAbiAJAFAAbhAKAAAADAVuIAUAVAAOAAAAAgAAAAIAAAA3"
+                  + "AwAACAAAAGIAAQAaAQ4AbiAFABAADgACAAAAAgAAADwDAAAIAAAAYgABABoBDwBuIAUAEAAOAAcA"
+                  + "DgAGAA4AEAIAAA4BGA88PAEYDzw8ARgPAAoADngADQAOeAAAAAABAAAABwAAAAIAAAAGAAYACDxj"
+                  + "bGluaXQ+AAY8aW5pdD4AAUwAAkxMABhMYXJ0L1Rlc3QxOTc4JFRyYW5zZm9ybTsADkxhcnQvVGVz"
+                  + "dDE5Nzg7ACJMZGFsdmlrL2Fubm90YXRpb24vRW5jbG9zaW5nQ2xhc3M7AB5MZGFsdmlrL2Fubm90"
+                  + "YXRpb24vSW5uZXJDbGFzczsAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwASTGphdmEvbGFuZy9PYmpl"
+                  + "Y3Q7ABRMamF2YS9sYW5nL1J1bm5hYmxlOwASTGphdmEvbGFuZy9TdHJpbmc7ABlMamF2YS9sYW5n"
+                  + "L1N0cmluZ0J1aWxkZXI7ABJMamF2YS9sYW5nL1N5c3RlbTsAM1J1bm5pbmcgYWZ0ZXIgb3AxIHVz"
+                  + "aW5nIG5vbi1zdHJ1Y3R1cmFsIHJlZGVmaW5pdGlvbgAzUnVubmluZyBhZnRlciBvcDIgdXNpbmcg"
+                  + "bm9uLXN0cnVjdHVyYWwgcmVkZWZpbml0aW9uACJUUkFOU0ZPUk1FRF9OT05fU1RSVUNUVVJBTCBn"
+                  + "b29kYnllACBUUkFOU0ZPUk1FRF9OT05fU1RSVUNUVVJBTCBoZWxsbwAoVFJBTlNGT1JNRURfTk9O"
+                  + "X1NUUlVDVFVSQUwgaG93IGRvIHlvdSBkbwANVGVzdDE5NzguamF2YQAJVHJhbnNmb3JtAAFWAAJW"
+                  + "TAADVkxMAAthY2Nlc3NGbGFncwAGYXBwZW5kAARuYW1lAANvdXQAB3ByaW50bG4AA3J1bgAFc2F5"
+                  + "SGkACXNheUhpTmFtZQASc29tZXRoaW5nSGFwcGVuZWQxABJzb21ldGhpbmdIYXBwZW5lZDIACHRv"
+                  + "U3RyaW5nAAV2YWx1ZQB2fn5EOHsiY29tcGlsYXRpb24tbW9kZSI6ImRlYnVnIiwibWluLWFwaSI6"
+                  + "MSwic2hhLTEiOiJhODM1MmYyNTQ4ODUzNjJjY2Q4ZDkwOWQzNTI5YzYwMDk0ZGQ4OTZlIiwidmVy"
+                  + "c2lvbiI6IjEuNi4yMC1kZXYifQACAgEjGAECAwIYBAgaFxQBAAUAAAkAiIAE9AMBgIAEiAQBCaAE"
+                  + "AQncBQEJ/AUAAAAAAAAAAgAAAFAGAABWBgAAhAYAAAAAAAAAAAAAAAAAABAAAAAAAAAAAQAAAAAA"
+                  + "AAABAAAAJQAAAHAAAAACAAAACwAAAAQBAAADAAAABQAAADABAAAEAAAAAgAAAGwBAAAFAAAACwAA"
+                  + "AHwBAAAGAAAAAQAAANQBAAABIAAABQAAAPQBAAADIAAABQAAABwDAAABEAAAAgAAAEQDAAACIAAA"
+                  + "JQAAAFQDAAAEIAAAAgAAAFAGAAAAIAAAAQAAAF8GAAADEAAAAgAAAIAGAAAGIAAAAQAAAJAGAAAA"
+                  + "EAAAAQAAAKAGAAA=");
+
+  // static class Transform {
+  //   static {}
+  //   // NB Due to the ordering of fields offset of sayHiName will change.
+  //   public static String sayHiName;
+  //   public static String sayByeName;
+  //   public static String sayQuery;
+  //   public static void somethingHappened1() {
+  //     System.out.println("Running after op2 using structural redefinition");
+  //     sayQuery = " this fine day";
+  //   }
+  //   public static void somethingHappened2() {
+  //     System.out.println("Running after op2 using structural redefinition");
+  //     sayByeName = " and good luck";
+  //   }
+  //   public static void doSayBye() {
+  //     System.out.println("Goodbye" + sayByeName + " - Transformed");
+  //   }
+  //   public static void doQuery() {
+  //     System.out.println("How do you do" + sayQuery + " - Transformed");
+  //   }
+  //   public static void doSayHi() {
+  //     System.out.println("Hello" + sayHiName + " - Transformed");
+  //   }
+  //   public static void sayHi(Runnable r, Runnable r2) {
+  //     doSayHi();
+  //     r1.run();
+  //     somethingHappened1();
+  //     doQuery();
+  //     r2.run();
+  //     somethingHappened2();
+  //     doSayBye();
+  //   }
+  // }
+  private static final byte[] STRUCTURAL_DEX_BYTES =
+      Base64.getDecoder()
+          .decode(
+              "ZGV4CjAzNQBcSGjP90G9cWx1TjBkAO5SCOfSe5sjsEAUCAAAcAAAAHhWNBIAAAAAAAAAAFAHAAAs"
+                  + "AAAAcAAAAAsAAAAgAQAABQAAAEwBAAAEAAAAiAEAAA4AAACoAQAAAQAAABgCAADcBQAAOAIAABwE"
+                  + "AAAsBAAAPAQAAEwEAABWBAAAXgQAAGcEAABuBAAAfQQAAIAEAACEBAAAngQAAK4EAADSBAAA8gQA"
+                  + "AAkFAAAdBQAAMwUAAEcFAABiBQAAdgUAAKcFAAC2BQAAwQUAAMQFAADIBQAAzQUAANoFAADiBQAA"
+                  + "6wUAAPUFAAD+BQAABAYAAAkGAAASBgAAFwYAACMGAAAqBgAANQYAAD8GAABTBgAAZwYAAHEGAAB4"
+                  + "BgAACgAAAAsAAAAMAAAADQAAAA4AAAAPAAAAEAAAABEAAAASAAAAEwAAABcAAAAIAAAABwAAAAAA"
+                  + "AAAJAAAACAAAAAwEAAAXAAAACgAAAAAAAAAZAAAACgAAABQEAAAYAAAACgAAAAwEAAAAAAcAIwAA"
+                  + "AAAABwAlAAAAAAAHACYAAAAJAAQAIAAAAAAAAgADAAAAAAACAAQAAAAAAAIAHAAAAAAAAgAdAAAA"
+                  + "AAACAB4AAAAAAAMAJAAAAAAAAgAnAAAAAAACACgAAAAEAAQAIQAAAAUAAgAEAAAABgACACIAAAAI"
+                  + "AAIABAAAAAgAAQAbAAAACAAAACkAAAAAAAAAAAAAAAUAAAAAAAAAFQAAAEAHAAD/BgAAAAAAAAAA"
+                  + "AAAAAAAA1AMAAAEAAAAOAAAAAQABAAEAAADYAwAABAAAAHAQCQAAAA4ABAAAAAIAAADcAwAAHgAA"
+                  + "AGIAAwBiAQIAIgIIAHAQCwACABoDBwBuIAwAMgBuIAwAEgAaAQAAbiAMABIAbhANAAIADAFuIAgA"
+                  + "EAAOAAQAAAACAAAA4wMAAB4AAABiAAMAYgEAACICCABwEAsAAgAaAwUAbiAMADIAbiAMABIAGgEA"
+                  + "AG4gDAASAG4QDQACAAwBbiAIABAADgAEAAAAAgAAAOoDAAAeAAAAYgADAGIBAQAiAggAcBALAAIA"
+                  + "GgMGAG4gDAAyAG4gDAASABoBAABuIAwAEgBuEA0AAgAMAW4gCAAQAA4AAgACAAEAAADxAwAAFgAA"
+                  + "AHEABAAAAHIQCgAAAHEABgAAAHEAAgAAAHIQCgABAHEABwAAAHEAAwAAAA4AAgAAAAIAAAD+AwAA"
+                  + "DAAAAGIAAwAaARQAbiAIABAAGgACAGkAAgAOAAIAAAACAAAABAQAAAwAAABiAAMAGgEUAG4gCAAQ"
+                  + "ABoAAQBpAAAADgAHAA4ABgAOABgADgEdDwAVAA4BHQ8AGwAOAR0PAB4CAAAOPDw8PDw8PAANAA54"
+                  + "SwARAA54SwAAAAEAAAAHAAAAAgAAAAYABgAOIC0gVHJhbnNmb3JtZWQADiBhbmQgZ29vZCBsdWNr"
+                  + "AA4gdGhpcyBmaW5lIGRheQAIPGNsaW5pdD4ABjxpbml0PgAHR29vZGJ5ZQAFSGVsbG8ADUhvdyBk"
+                  + "byB5b3UgZG8AAUwAAkxMABhMYXJ0L1Rlc3QxOTc4JFRyYW5zZm9ybTsADkxhcnQvVGVzdDE5Nzg7"
+                  + "ACJMZGFsdmlrL2Fubm90YXRpb24vRW5jbG9zaW5nQ2xhc3M7AB5MZGFsdmlrL2Fubm90YXRpb24v"
+                  + "SW5uZXJDbGFzczsAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwASTGphdmEvbGFuZy9PYmplY3Q7ABRM"
+                  + "amF2YS9sYW5nL1J1bm5hYmxlOwASTGphdmEvbGFuZy9TdHJpbmc7ABlMamF2YS9sYW5nL1N0cmlu"
+                  + "Z0J1aWxkZXI7ABJMamF2YS9sYW5nL1N5c3RlbTsAL1J1bm5pbmcgYWZ0ZXIgb3AyIHVzaW5nIHN0"
+                  + "cnVjdHVyYWwgcmVkZWZpbml0aW9uAA1UZXN0MTk3OC5qYXZhAAlUcmFuc2Zvcm0AAVYAAlZMAANW"
+                  + "TEwAC2FjY2Vzc0ZsYWdzAAZhcHBlbmQAB2RvUXVlcnkACGRvU2F5QnllAAdkb1NheUhpAARuYW1l"
+                  + "AANvdXQAB3ByaW50bG4AA3J1bgAKc2F5QnllTmFtZQAFc2F5SGkACXNheUhpTmFtZQAIc2F5UXVl"
+                  + "cnkAEnNvbWV0aGluZ0hhcHBlbmVkMQASc29tZXRoaW5nSGFwcGVuZWQyAAh0b1N0cmluZwAFdmFs"
+                  + "dWUAdn5+RDh7ImNvbXBpbGF0aW9uLW1vZGUiOiJkZWJ1ZyIsIm1pbi1hcGkiOjEsInNoYS0xIjoi"
+                  + "YTgzNTJmMjU0ODg1MzYyY2NkOGQ5MDlkMzUyOWM2MDA5NGRkODk2ZSIsInZlcnNpb24iOiIxLjYu"
+                  + "MjAtZGV2In0AAgIBKhgBAgMCGgQIHxcWAwAIAAAJAQkBCQCIgAS4BAGAgATMBAEJ5AQBCbAFAQn8"
+                  + "BQEJyAYBCYQHAQmsBwAAAAAAAAACAAAA8AYAAPYGAAA0BwAAAAAAAAAAAAAAAAAAEAAAAAAAAAAB"
+                  + "AAAAAAAAAAEAAAAsAAAAcAAAAAIAAAALAAAAIAEAAAMAAAAFAAAATAEAAAQAAAAEAAAAiAEAAAUA"
+                  + "AAAOAAAAqAEAAAYAAAABAAAAGAIAAAEgAAAIAAAAOAIAAAMgAAAIAAAA1AMAAAEQAAACAAAADAQA"
+                  + "AAIgAAAsAAAAHAQAAAQgAAACAAAA8AYAAAAgAAABAAAA/wYAAAMQAAACAAAAMAcAAAYgAAABAAAA"
+                  + "QAcAAAAQAAABAAAAUAcAAA==");
+
+  public static void run() throws Exception {
+    Redefinition.setTestConfiguration(Redefinition.Config.COMMON_REDEFINE);
+    doTest();
+  }
+
+  public static void doTest() throws Exception {
+    Transform.sayHi(
+        () -> {
+          System.out.println("Not doing anything here - op1");
+        },
+        () -> {
+          System.out.println("Not doing anything here - op2");
+        });
+    Transform.sayHi(
+        () -> {
+          System.out.println("transforming calling function - non-structural");
+          Redefinition.doCommonClassRedefinition(
+              Transform.class, new byte[] {}, NON_STRUCTURAL_DEX_BYTES);
+        },
+        () -> {
+          System.out.println("transforming calling function - structural");
+          Redefinition.doCommonStructuralClassRedefinition(Transform.class, STRUCTURAL_DEX_BYTES);
+        });
+    Transform.sayHi(
+        () -> {
+          System.out.println("Not doing anything here - op1");
+        },
+        () -> {
+          System.out.println("Not doing anything here - op2");
+        });
+  }
+}
diff --git a/test/1979-threaded-structural-transformation/expected.txt b/test/1979-threaded-structural-transformation/expected.txt
new file mode 100644
index 0000000..ffe96bb
--- /dev/null
+++ b/test/1979-threaded-structural-transformation/expected.txt
@@ -0,0 +1,16 @@
+Hitting class class art.Test1979$Transform[FOO: value of <FOO FIELD>, BAR: value of <BAR FIELD>]
+Initial: class art.Test1979$Transform[FOO: value of <FOO FIELD>, BAR: value of <BAR FIELD>]
+Reading with reflection.
+public static java.lang.Object art.Test1979$Transform.BAR = (ID: 0) value of <BAR FIELD>
+public static java.lang.Object art.Test1979$Transform.FOO = (ID: 1) value of <FOO FIELD>
+Reading normally.
+Read BAR field: (ID: 0) value of <BAR FIELD>
+Read FOO field: (ID: 1) value of <FOO FIELD>
+Redefined: class art.Test1979$Transform[FOO: value of <FOO FIELD>, BAR: value of <BAR FIELD>, BAZ: null]
+Reading with reflection after possible modification.
+public static java.lang.Object art.Test1979$Transform.BAR = (ID: 0) value of <BAR FIELD>
+public static java.lang.Object art.Test1979$Transform.BAZ = (ID: 2) <NULL>
+public static java.lang.Object art.Test1979$Transform.FOO = (ID: 1) value of <FOO FIELD>
+Reading normally after possible modification.
+Read FOO field: (ID: 1) value of <FOO FIELD>
+Read BAR field: (ID: 0) value of <BAR FIELD>
diff --git a/test/1979-threaded-structural-transformation/info.txt b/test/1979-threaded-structural-transformation/info.txt
new file mode 100644
index 0000000..dc7623c
--- /dev/null
+++ b/test/1979-threaded-structural-transformation/info.txt
@@ -0,0 +1,2 @@
+Test that remote threads without references to the redefined class are properly deoptimized when
+structural redefinition occurs.
diff --git a/test/1979-threaded-structural-transformation/run b/test/1979-threaded-structural-transformation/run
new file mode 100755
index 0000000..03e41a5
--- /dev/null
+++ b/test/1979-threaded-structural-transformation/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/1979-threaded-structural-transformation/src/Main.java b/test/1979-threaded-structural-transformation/src/Main.java
new file mode 100644
index 0000000..36174eb
--- /dev/null
+++ b/test/1979-threaded-structural-transformation/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.Test1979.run();
+  }
+}
diff --git a/test/1979-threaded-structural-transformation/src/art/Redefinition.java b/test/1979-threaded-structural-transformation/src/art/Redefinition.java
new file mode 120000
index 0000000..81eaf31
--- /dev/null
+++ b/test/1979-threaded-structural-transformation/src/art/Redefinition.java
@@ -0,0 +1 @@
+../../../jvmti-common/Redefinition.java
\ No newline at end of file
diff --git a/test/1979-threaded-structural-transformation/src/art/Test1979.java b/test/1979-threaded-structural-transformation/src/art/Test1979.java
new file mode 100644
index 0000000..ca4027d
--- /dev/null
+++ b/test/1979-threaded-structural-transformation/src/art/Test1979.java
@@ -0,0 +1,172 @@
+/*
+ * 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.lang.ref.*;
+import java.lang.reflect.*;
+import java.util.*;
+import java.util.concurrent.CountDownLatch;
+import java.util.function.Supplier;
+
+public class Test1979 {
+  public static void run() throws Exception {
+    Redefinition.setTestConfiguration(Redefinition.Config.COMMON_REDEFINE);
+    doTest();
+  }
+
+  private static final boolean PRINT_NONDETERMINISTIC = false;
+
+  public static WeakHashMap<Object, Long> id_nums = new WeakHashMap<>();
+  public static long next_id = 0;
+
+  public static String printGeneric(Object o) {
+    Long id = id_nums.get(o);
+    if (id == null) {
+      id = Long.valueOf(next_id++);
+      id_nums.put(o, id);
+    }
+    if (o == null) {
+      return "(ID: " + id + ") <NULL>";
+    }
+    Class oc = o.getClass();
+    if (oc.isArray() && oc.getComponentType() == Byte.TYPE) {
+      return "(ID: "
+          + id
+          + ") "
+          + Arrays.toString(Arrays.copyOf((byte[]) o, 10)).replace(']', ',')
+          + " ...]";
+    } else {
+      return "(ID: " + id + ") " + o.toString();
+    }
+  }
+
+  private static void doRedefinition() {
+    Redefinition.doCommonStructuralClassRedefinition(
+        Transform.class, REDEFINED_DEX_BYTES);
+  }
+
+  private static void readReflective(String msg) throws Exception {
+    System.out.println(msg);
+    for (Field f : Transform.class.getFields()) {
+      System.out.println(f.toString() + " = " + printGeneric(f.get(null)));
+    }
+  }
+
+  public static class Transform {
+    static {}
+    public static Object BAR = new Object() {
+      public String toString() {
+        return "value of <" + this.get() + ">";
+      }
+      public Object get() {
+        return "BAR FIELD";
+      }
+    };
+    public static Object FOO = new Object() {
+      public String toString() {
+        return "value of <" + this.get() + ">";
+      }
+      public Object get() {
+        return "FOO FIELD";
+      }
+    };
+    public static String staticToString() {
+      return Transform.class.toString() + "[FOO: " + FOO + ", BAR: " + BAR + "]";
+    }
+  }
+
+  /* Base64 encoded class of:
+   * public static class Transform {
+   *   static {}
+   *   // NB This is the order the fields will be laid out in memory.
+   *   public static Object BAR;
+   *   public static Object BAZ;
+   *   public static Object FOO;
+   *   public static String staticToString() {
+   *    return Transform.class.toString() + "[FOO: " + FOO + ", BAR: " + BAR + ", BAZ: " + BAZ + "]";
+   *   }
+   * }
+   */
+  private static byte[] REDEFINED_DEX_BYTES = Base64.getDecoder().decode(
+      "ZGV4CjAzNQDrznAlv8Fs6FNeDAHAxiU9uy8DUayd82ZkBQAAcAAAAHhWNBIAAAAAAAAAAKAEAAAd" +
+      "AAAAcAAAAAkAAADkAAAABAAAAAgBAAADAAAAOAEAAAkAAABQAQAAAQAAAJgBAACsAwAAuAEAAHoC" +
+      "AACDAgAAjAIAAJYCAACeAgAAowIAAKgCAACtAgAAsAIAALQCAADOAgAA3gIAAAIDAAAiAwAANQMA" +
+      "AEkDAABdAwAAeAMAAIcDAACSAwAAlQMAAJ0DAACgAwAArQMAALUDAAC7AwAAywMAANUDAADcAwAA" +
+      "CQAAAAoAAAALAAAADAAAAA0AAAAOAAAADwAAABAAAAATAAAABwAAAAYAAAAAAAAACAAAAAcAAABs" +
+      "AgAACAAAAAcAAAB0AgAAEwAAAAgAAAAAAAAAAAAFAAQAAAAAAAUABQAAAAAABQAGAAAAAAADAAIA" +
+      "AAAAAAMAAwAAAAAAAAAZAAAABAAAABoAAAAFAAMAAwAAAAcAAwADAAAABwABABcAAAAHAAIAFwAA" +
+      "AAcAAAAaAAAAAAAAAAEAAAAFAAAAAAAAABEAAACQBAAAYwQAAAAAAAAFAAAAAgAAAGgCAAA2AAAA" +
+      "HAAAAG4QAwAAAAwAYgECAGICAABiAwEAIgQHAHAQBQAEAG4gBwAEABoAFABuIAcABABuIAYAFAAa" +
+      "AAAAbiAHAAQAbiAGACQAGgABAG4gBwAEAG4gBgA0ABoAFQBuIAcABABuEAgABAAMABEAAAAAAAAA" +
+      "AABgAgAAAQAAAA4AAAABAAEAAQAAAGQCAAAEAAAAcBAEAAAADgAIAA4ABwAOAA4ADgABAAAABQAA" +
+      "AAEAAAAGAAcsIEJBUjogAAcsIEJBWjogAAg8Y2xpbml0PgAGPGluaXQ+AANCQVIAA0JBWgADRk9P" +
+      "AAFMAAJMTAAYTGFydC9UZXN0MTk3OSRUcmFuc2Zvcm07AA5MYXJ0L1Rlc3QxOTc5OwAiTGRhbHZp" +
+      "ay9hbm5vdGF0aW9uL0VuY2xvc2luZ0NsYXNzOwAeTGRhbHZpay9hbm5vdGF0aW9uL0lubmVyQ2xh" +
+      "c3M7ABFMamF2YS9sYW5nL0NsYXNzOwASTGphdmEvbGFuZy9PYmplY3Q7ABJMamF2YS9sYW5nL1N0" +
+      "cmluZzsAGUxqYXZhL2xhbmcvU3RyaW5nQnVpbGRlcjsADVRlc3QxOTc5LmphdmEACVRyYW5zZm9y" +
+      "bQABVgAGW0ZPTzogAAFdAAthY2Nlc3NGbGFncwAGYXBwZW5kAARuYW1lAA5zdGF0aWNUb1N0cmlu" +
+      "ZwAIdG9TdHJpbmcABXZhbHVlAHZ+fkQ4eyJjb21waWxhdGlvbi1tb2RlIjoiZGVidWciLCJtaW4t" +
+      "YXBpIjoxLCJzaGEtMSI6ImE4MzUyZjI1NDg4NTM2MmNjZDhkOTA5ZDM1MjljNjAwOTRkZDg5NmUi" +
+      "LCJ2ZXJzaW9uIjoiMS42LjIwLWRldiJ9AAICARsYAQIDAhYECRgXEgMAAwAACQEJAQkAiIAEtAQB" +
+      "gYAEyAQBCbgDAAAAAAAAAAIAAABUBAAAWgQAAIQEAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAEAAAAA" +
+      "AAAAAQAAAB0AAABwAAAAAgAAAAkAAADkAAAAAwAAAAQAAAAIAQAABAAAAAMAAAA4AQAABQAAAAkA" +
+      "AABQAQAABgAAAAEAAACYAQAAASAAAAMAAAC4AQAAAyAAAAMAAABgAgAAARAAAAIAAABsAgAAAiAA" +
+      "AB0AAAB6AgAABCAAAAIAAABUBAAAACAAAAEAAABjBAAAAxAAAAIAAACABAAABiAAAAEAAACQBAAA" +
+      "ABAAAAEAAACgBAAA");
+
+  public interface TRunnable {
+    public void run() throws Exception;
+  }
+
+  public static void doTest() throws Exception {
+    final CountDownLatch cdl = new CountDownLatch(1);
+    final CountDownLatch continueLatch = new CountDownLatch(1);
+    // Make sure the transformed class is already loaded before we start running (and possibly
+    // compiling) the test thread.
+    System.out.println("Hitting class " + Transform.staticToString());
+    Thread t = new Thread(() -> {
+      try {
+        // We don't want to read these in the same method here to ensure that no reference to
+        // Transform is active on this thread at the time the redefinition occurs. To accomplish
+        // this just run the code in a different method, which is good enough.
+        ((TRunnable)() -> {
+          System.out.println("Initial: " + Transform.staticToString());
+          readReflective("Reading with reflection.");
+          System.out.println("Reading normally.");
+          System.out.println("Read BAR field: " + printGeneric(Transform.BAR));
+          System.out.println("Read FOO field: " + printGeneric(Transform.FOO));
+        }).run();
+        cdl.countDown();
+        continueLatch.await();
+        // Now that redefinition has occurred without this frame having any references to the
+        // Transform class we want to make sure we have the correct offsets.
+        System.out.println("Redefined: " + Transform.staticToString());
+        readReflective("Reading with reflection after possible modification.");
+        System.out.println("Reading normally after possible modification.");
+        System.out.println("Read FOO field: " + printGeneric(Transform.FOO));
+        System.out.println("Read BAR field: " + printGeneric(Transform.BAR));
+      } catch (Exception e) {
+        throw new Error(e);
+      }
+    });
+    t.start();
+    cdl.await();
+    doRedefinition();
+    continueLatch.countDown();
+    t.join();
+  }
+}
diff --git a/test/1980-obsolete-object-cleared/expected.txt b/test/1980-obsolete-object-cleared/expected.txt
new file mode 100644
index 0000000..77569ee
--- /dev/null
+++ b/test/1980-obsolete-object-cleared/expected.txt
@@ -0,0 +1,440 @@
+JNI_OnLoad called
+Reading normally.
+	Original secret number is: 42
+	Original secret array is: [1, 2, 3, 4]
+Using unsafe to access values directly from memory.
+	Original secret number is: 42
+	Original secret array is: [1, 2, 3, 4]
+Reading normally post redefinition.
+	Post-redefinition secret number is: 42
+	Post-redefinition secret array is: [1, 2, 3, 4]
+Obsolete class is: class Main$Transform
+Using unsafe to access obsolete values directly from memory.
+	Obsolete secret number is: 0
+	Obsolete secret array is: null
+
+
+Using obsolete class object!
+
+
+Calling public java.lang.Class java.lang.Class.asSubclass(java.lang.Class) with params: [[null, class java.lang.Object, (obsolete)class Main$Transform, class Main$Transform, long, class java.lang.Class]]
+public java.lang.Class java.lang.Class.asSubclass(java.lang.Class) with [null] throws java.lang.reflect.InvocationTargetException: java.lang.NullPointerException: Attempt to invoke virtual method 'boolean java.lang.Class.isAssignableFrom(java.lang.Class)' on a null object reference
+public java.lang.Class java.lang.Class.asSubclass(java.lang.Class) on (obsolete)class Main$Transform with [class java.lang.Object] = (obsolete)class Main$Transform
+public java.lang.Class java.lang.Class.asSubclass(java.lang.Class) on (obsolete)class Main$Transform with [(obsolete)class Main$Transform] = (obsolete)class Main$Transform
+public java.lang.Class java.lang.Class.asSubclass(java.lang.Class) with [class Main$Transform] throws java.lang.reflect.InvocationTargetException: java.lang.ClassCastException: class Main$Transform cannot be cast to Main$Transform
+public java.lang.Class java.lang.Class.asSubclass(java.lang.Class) with [long] throws java.lang.reflect.InvocationTargetException: java.lang.ClassCastException: class Main$Transform cannot be cast to long
+public java.lang.Class java.lang.Class.asSubclass(java.lang.Class) with [class java.lang.Class] throws java.lang.reflect.InvocationTargetException: java.lang.ClassCastException: class Main$Transform cannot be cast to java.lang.Class
+Calling public java.lang.Object java.lang.Class.cast(java.lang.Object) with params: [[null, foo, NOT_USED_STRING, class Main$Transform]]
+public java.lang.Object java.lang.Class.cast(java.lang.Object) on (obsolete)class Main$Transform with [null] = null
+public java.lang.Object java.lang.Class.cast(java.lang.Object) with [foo] throws java.lang.reflect.InvocationTargetException: java.lang.ClassCastException: Cannot cast java.lang.String to Main$Transform
+public java.lang.Object java.lang.Class.cast(java.lang.Object) with [NOT_USED_STRING] throws java.lang.reflect.InvocationTargetException: java.lang.ClassCastException: Cannot cast java.lang.String to Main$Transform
+public java.lang.Object java.lang.Class.cast(java.lang.Object) with [class Main$Transform] throws java.lang.reflect.InvocationTargetException: java.lang.ClassCastException: Cannot cast java.lang.Class to Main$Transform
+Calling public boolean java.lang.Class.desiredAssertionStatus() with params: []
+public boolean java.lang.Class.desiredAssertionStatus() on (obsolete)class Main$Transform with [] = false
+Calling public int java.lang.Class.getAccessFlags() with params: []
+public int java.lang.Class.getAccessFlags() on (obsolete)class Main$Transform with [] = 2621441
+Calling public java.lang.annotation.Annotation java.lang.Class.getAnnotation(java.lang.Class) with params: [[null, class java.lang.Object, (obsolete)class Main$Transform, class Main$Transform, long, class java.lang.Class]]
+public java.lang.annotation.Annotation java.lang.Class.getAnnotation(java.lang.Class) with [null] throws java.lang.reflect.InvocationTargetException: java.lang.NullPointerException
+public java.lang.annotation.Annotation java.lang.Class.getAnnotation(java.lang.Class) with [class java.lang.Object] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+public java.lang.annotation.Annotation java.lang.Class.getAnnotation(java.lang.Class) with [(obsolete)class Main$Transform] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+public java.lang.annotation.Annotation java.lang.Class.getAnnotation(java.lang.Class) with [class Main$Transform] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+public java.lang.annotation.Annotation java.lang.Class.getAnnotation(java.lang.Class) with [long] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+public java.lang.annotation.Annotation java.lang.Class.getAnnotation(java.lang.Class) with [class java.lang.Class] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+Calling public java.lang.annotation.Annotation[] java.lang.Class.getAnnotations() with params: []
+public java.lang.annotation.Annotation[] java.lang.Class.getAnnotations() with [] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+Calling public java.lang.annotation.Annotation[] java.lang.Class.getAnnotationsByType(java.lang.Class) with params: [[null, class java.lang.Object, (obsolete)class Main$Transform, class Main$Transform, long, class java.lang.Class]]
+public java.lang.annotation.Annotation[] java.lang.Class.getAnnotationsByType(java.lang.Class) with [null] throws java.lang.reflect.InvocationTargetException: java.lang.NullPointerException: annotationClass
+public java.lang.annotation.Annotation[] java.lang.Class.getAnnotationsByType(java.lang.Class) with [class java.lang.Object] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+public java.lang.annotation.Annotation[] java.lang.Class.getAnnotationsByType(java.lang.Class) with [(obsolete)class Main$Transform] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+public java.lang.annotation.Annotation[] java.lang.Class.getAnnotationsByType(java.lang.Class) with [class Main$Transform] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+public java.lang.annotation.Annotation[] java.lang.Class.getAnnotationsByType(java.lang.Class) with [long] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+public java.lang.annotation.Annotation[] java.lang.Class.getAnnotationsByType(java.lang.Class) with [class java.lang.Class] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+Calling public java.lang.String java.lang.Class.getCanonicalName() with params: []
+public java.lang.String java.lang.Class.getCanonicalName() with [] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+Calling public java.lang.ClassLoader java.lang.Class.getClassLoader() with params: []
+public java.lang.ClassLoader java.lang.Class.getClassLoader() on (obsolete)class Main$Transform with [] = dalvik.system.PathClassLoader
+Calling public java.lang.Class[] java.lang.Class.getClasses() with params: []
+public java.lang.Class[] java.lang.Class.getClasses() with [] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+Calling public java.lang.Class java.lang.Class.getComponentType() with params: []
+public java.lang.Class java.lang.Class.getComponentType() on (obsolete)class Main$Transform with [] = null
+Calling public java.lang.reflect.Constructor java.lang.Class.getConstructor(java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.SecurityException with params: [[new java.lang.Object[0], new java.lang.Class[0], null]]
+public java.lang.reflect.Constructor java.lang.Class.getConstructor(java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.SecurityException with [new java.lang.Object[0]] throws java.lang.IllegalArgumentException: method java.lang.Class.getConstructor argument 1 has type java.lang.Class[], got java.lang.Object[]: null
+public java.lang.reflect.Constructor java.lang.Class.getConstructor(java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.SecurityException with [new java.lang.Class[0]] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+public java.lang.reflect.Constructor java.lang.Class.getConstructor(java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.SecurityException with [null] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+Calling public java.lang.reflect.Constructor[] java.lang.Class.getConstructors() throws java.lang.SecurityException with params: []
+public java.lang.reflect.Constructor[] java.lang.Class.getConstructors() throws java.lang.SecurityException with [] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+Calling public native java.lang.annotation.Annotation java.lang.Class.getDeclaredAnnotation(java.lang.Class) with params: [[null, class java.lang.Object, (obsolete)class Main$Transform, class Main$Transform, long, class java.lang.Class]]
+public native java.lang.annotation.Annotation java.lang.Class.getDeclaredAnnotation(java.lang.Class) with [null] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+public native java.lang.annotation.Annotation java.lang.Class.getDeclaredAnnotation(java.lang.Class) with [class java.lang.Object] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+public native java.lang.annotation.Annotation java.lang.Class.getDeclaredAnnotation(java.lang.Class) with [(obsolete)class Main$Transform] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+public native java.lang.annotation.Annotation java.lang.Class.getDeclaredAnnotation(java.lang.Class) with [class Main$Transform] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+public native java.lang.annotation.Annotation java.lang.Class.getDeclaredAnnotation(java.lang.Class) with [long] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+public native java.lang.annotation.Annotation java.lang.Class.getDeclaredAnnotation(java.lang.Class) with [class java.lang.Class] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+Calling public native java.lang.annotation.Annotation[] java.lang.Class.getDeclaredAnnotations() with params: []
+public native java.lang.annotation.Annotation[] java.lang.Class.getDeclaredAnnotations() with [] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+Calling public native java.lang.Class[] java.lang.Class.getDeclaredClasses() with params: []
+public native java.lang.Class[] java.lang.Class.getDeclaredClasses() with [] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+Calling public java.lang.reflect.Constructor java.lang.Class.getDeclaredConstructor(java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.SecurityException with params: [[new java.lang.Object[0], new java.lang.Class[0], null]]
+public java.lang.reflect.Constructor java.lang.Class.getDeclaredConstructor(java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.SecurityException with [new java.lang.Object[0]] throws java.lang.IllegalArgumentException: method java.lang.Class.getDeclaredConstructor argument 1 has type java.lang.Class[], got java.lang.Object[]: null
+public java.lang.reflect.Constructor java.lang.Class.getDeclaredConstructor(java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.SecurityException with [new java.lang.Class[0]] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+public java.lang.reflect.Constructor java.lang.Class.getDeclaredConstructor(java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.SecurityException with [null] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+Calling public java.lang.reflect.Constructor[] java.lang.Class.getDeclaredConstructors() throws java.lang.SecurityException with params: []
+public java.lang.reflect.Constructor[] java.lang.Class.getDeclaredConstructors() throws java.lang.SecurityException with [] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+Calling public native java.lang.reflect.Field java.lang.Class.getDeclaredField(java.lang.String) throws java.lang.NoSuchFieldException with params: [[NOT_USED_STRING, foo, SECRET_ARRAY]]
+public native java.lang.reflect.Field java.lang.Class.getDeclaredField(java.lang.String) throws java.lang.NoSuchFieldException with [NOT_USED_STRING] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+public native java.lang.reflect.Field java.lang.Class.getDeclaredField(java.lang.String) throws java.lang.NoSuchFieldException with [foo] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+public native java.lang.reflect.Field java.lang.Class.getDeclaredField(java.lang.String) throws java.lang.NoSuchFieldException with [SECRET_ARRAY] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+Calling public native java.lang.reflect.Field[] java.lang.Class.getDeclaredFields() with params: []
+public native java.lang.reflect.Field[] java.lang.Class.getDeclaredFields() with [] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+Calling public native java.lang.reflect.Field[] java.lang.Class.getDeclaredFieldsUnchecked(boolean) with params: [[true, false]]
+public native java.lang.reflect.Field[] java.lang.Class.getDeclaredFieldsUnchecked(boolean) with [true] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+public native java.lang.reflect.Field[] java.lang.Class.getDeclaredFieldsUnchecked(boolean) with [false] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+Calling public java.lang.reflect.Method java.lang.Class.getDeclaredMethod(java.lang.String,java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.SecurityException with params: [[NOT_USED_STRING, foo, SECRET_ARRAY], [new java.lang.Object[0], new java.lang.Class[0], null]]
+public java.lang.reflect.Method java.lang.Class.getDeclaredMethod(java.lang.String,java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.SecurityException with [NOT_USED_STRING, new java.lang.Object[0]] throws java.lang.IllegalArgumentException: method java.lang.Class.getDeclaredMethod argument 2 has type java.lang.Class[], got java.lang.Object[]: null
+public java.lang.reflect.Method java.lang.Class.getDeclaredMethod(java.lang.String,java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.SecurityException with [NOT_USED_STRING, new java.lang.Class[0]] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+public java.lang.reflect.Method java.lang.Class.getDeclaredMethod(java.lang.String,java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.SecurityException with [NOT_USED_STRING, null] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+public java.lang.reflect.Method java.lang.Class.getDeclaredMethod(java.lang.String,java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.SecurityException with [foo, new java.lang.Object[0]] throws java.lang.IllegalArgumentException: method java.lang.Class.getDeclaredMethod argument 2 has type java.lang.Class[], got java.lang.Object[]: null
+public java.lang.reflect.Method java.lang.Class.getDeclaredMethod(java.lang.String,java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.SecurityException with [foo, new java.lang.Class[0]] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+public java.lang.reflect.Method java.lang.Class.getDeclaredMethod(java.lang.String,java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.SecurityException with [foo, null] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+public java.lang.reflect.Method java.lang.Class.getDeclaredMethod(java.lang.String,java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.SecurityException with [SECRET_ARRAY, new java.lang.Object[0]] throws java.lang.IllegalArgumentException: method java.lang.Class.getDeclaredMethod argument 2 has type java.lang.Class[], got java.lang.Object[]: null
+public java.lang.reflect.Method java.lang.Class.getDeclaredMethod(java.lang.String,java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.SecurityException with [SECRET_ARRAY, new java.lang.Class[0]] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+public java.lang.reflect.Method java.lang.Class.getDeclaredMethod(java.lang.String,java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.SecurityException with [SECRET_ARRAY, null] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+Calling public java.lang.reflect.Method[] java.lang.Class.getDeclaredMethods() throws java.lang.SecurityException with params: []
+public java.lang.reflect.Method[] java.lang.Class.getDeclaredMethods() throws java.lang.SecurityException with [] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+Calling public native java.lang.reflect.Method[] java.lang.Class.getDeclaredMethodsUnchecked(boolean) with params: [[true, false]]
+public native java.lang.reflect.Method[] java.lang.Class.getDeclaredMethodsUnchecked(boolean) with [true] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+public native java.lang.reflect.Method[] java.lang.Class.getDeclaredMethodsUnchecked(boolean) with [false] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+Calling public native java.lang.Class java.lang.Class.getDeclaringClass() with params: []
+public native java.lang.Class java.lang.Class.getDeclaringClass() with [] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+Calling public native java.lang.Class java.lang.Class.getEnclosingClass() with params: []
+public native java.lang.Class java.lang.Class.getEnclosingClass() with [] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+Calling public java.lang.reflect.Constructor java.lang.Class.getEnclosingConstructor() with params: []
+public java.lang.reflect.Constructor java.lang.Class.getEnclosingConstructor() with [] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+Calling public java.lang.reflect.Method java.lang.Class.getEnclosingMethod() with params: []
+public java.lang.reflect.Method java.lang.Class.getEnclosingMethod() with [] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+Calling public java.lang.Object[] java.lang.Class.getEnumConstants() with params: []
+public java.lang.Object[] java.lang.Class.getEnumConstants() with [] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+Calling public java.lang.Object[] java.lang.Class.getEnumConstantsShared() with params: []
+public java.lang.Object[] java.lang.Class.getEnumConstantsShared() with [] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+Calling public java.lang.reflect.Field java.lang.Class.getField(java.lang.String) throws java.lang.NoSuchFieldException with params: [[NOT_USED_STRING, foo, SECRET_ARRAY]]
+public java.lang.reflect.Field java.lang.Class.getField(java.lang.String) throws java.lang.NoSuchFieldException with [NOT_USED_STRING] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+public java.lang.reflect.Field java.lang.Class.getField(java.lang.String) throws java.lang.NoSuchFieldException with [foo] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+public java.lang.reflect.Field java.lang.Class.getField(java.lang.String) throws java.lang.NoSuchFieldException with [SECRET_ARRAY] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+Calling public java.lang.reflect.Field[] java.lang.Class.getFields() throws java.lang.SecurityException with params: []
+public java.lang.reflect.Field[] java.lang.Class.getFields() throws java.lang.SecurityException with [] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+Calling public java.lang.reflect.Type[] java.lang.Class.getGenericInterfaces() with params: []
+public java.lang.reflect.Type[] java.lang.Class.getGenericInterfaces() with [] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+Calling public java.lang.reflect.Type java.lang.Class.getGenericSuperclass() with params: []
+public java.lang.reflect.Type java.lang.Class.getGenericSuperclass() with [] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+Calling public java.lang.reflect.Method java.lang.Class.getInstanceMethod(java.lang.String,java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.IllegalAccessException with params: [[NOT_USED_STRING, foo, SECRET_ARRAY], [new java.lang.Object[0], new java.lang.Class[0], null]]
+public java.lang.reflect.Method java.lang.Class.getInstanceMethod(java.lang.String,java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.IllegalAccessException with [NOT_USED_STRING, new java.lang.Object[0]] throws java.lang.IllegalArgumentException: method java.lang.Class.getInstanceMethod argument 2 has type java.lang.Class[], got java.lang.Object[]: null
+public java.lang.reflect.Method java.lang.Class.getInstanceMethod(java.lang.String,java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.IllegalAccessException with [NOT_USED_STRING, new java.lang.Class[0]] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+public java.lang.reflect.Method java.lang.Class.getInstanceMethod(java.lang.String,java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.IllegalAccessException with [NOT_USED_STRING, null] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+public java.lang.reflect.Method java.lang.Class.getInstanceMethod(java.lang.String,java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.IllegalAccessException with [foo, new java.lang.Object[0]] throws java.lang.IllegalArgumentException: method java.lang.Class.getInstanceMethod argument 2 has type java.lang.Class[], got java.lang.Object[]: null
+public java.lang.reflect.Method java.lang.Class.getInstanceMethod(java.lang.String,java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.IllegalAccessException with [foo, new java.lang.Class[0]] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+public java.lang.reflect.Method java.lang.Class.getInstanceMethod(java.lang.String,java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.IllegalAccessException with [foo, null] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+public java.lang.reflect.Method java.lang.Class.getInstanceMethod(java.lang.String,java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.IllegalAccessException with [SECRET_ARRAY, new java.lang.Object[0]] throws java.lang.IllegalArgumentException: method java.lang.Class.getInstanceMethod argument 2 has type java.lang.Class[], got java.lang.Object[]: null
+public java.lang.reflect.Method java.lang.Class.getInstanceMethod(java.lang.String,java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.IllegalAccessException with [SECRET_ARRAY, new java.lang.Class[0]] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+public java.lang.reflect.Method java.lang.Class.getInstanceMethod(java.lang.String,java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.IllegalAccessException with [SECRET_ARRAY, null] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+Calling public java.lang.Class[] java.lang.Class.getInterfaces() with params: []
+public java.lang.Class[] java.lang.Class.getInterfaces() with [] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+Calling public java.lang.reflect.Method java.lang.Class.getMethod(java.lang.String,java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.SecurityException with params: [[NOT_USED_STRING, foo, SECRET_ARRAY], [new java.lang.Object[0], new java.lang.Class[0], null]]
+public java.lang.reflect.Method java.lang.Class.getMethod(java.lang.String,java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.SecurityException with [NOT_USED_STRING, new java.lang.Object[0]] throws java.lang.IllegalArgumentException: method java.lang.Class.getMethod argument 2 has type java.lang.Class[], got java.lang.Object[]: null
+public java.lang.reflect.Method java.lang.Class.getMethod(java.lang.String,java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.SecurityException with [NOT_USED_STRING, new java.lang.Class[0]] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+public java.lang.reflect.Method java.lang.Class.getMethod(java.lang.String,java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.SecurityException with [NOT_USED_STRING, null] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+public java.lang.reflect.Method java.lang.Class.getMethod(java.lang.String,java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.SecurityException with [foo, new java.lang.Object[0]] throws java.lang.IllegalArgumentException: method java.lang.Class.getMethod argument 2 has type java.lang.Class[], got java.lang.Object[]: null
+public java.lang.reflect.Method java.lang.Class.getMethod(java.lang.String,java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.SecurityException with [foo, new java.lang.Class[0]] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+public java.lang.reflect.Method java.lang.Class.getMethod(java.lang.String,java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.SecurityException with [foo, null] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+public java.lang.reflect.Method java.lang.Class.getMethod(java.lang.String,java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.SecurityException with [SECRET_ARRAY, new java.lang.Object[0]] throws java.lang.IllegalArgumentException: method java.lang.Class.getMethod argument 2 has type java.lang.Class[], got java.lang.Object[]: null
+public java.lang.reflect.Method java.lang.Class.getMethod(java.lang.String,java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.SecurityException with [SECRET_ARRAY, new java.lang.Class[0]] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+public java.lang.reflect.Method java.lang.Class.getMethod(java.lang.String,java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.SecurityException with [SECRET_ARRAY, null] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+Calling public java.lang.reflect.Method[] java.lang.Class.getMethods() throws java.lang.SecurityException with params: []
+public java.lang.reflect.Method[] java.lang.Class.getMethods() throws java.lang.SecurityException with [] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+Calling public int java.lang.Class.getModifiers() with params: []
+public int java.lang.Class.getModifiers() with [] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+Calling public java.lang.String java.lang.Class.getName() with params: []
+public java.lang.String java.lang.Class.getName() on (obsolete)class Main$Transform with [] = Main$Transform
+Calling public java.lang.Package java.lang.Class.getPackage() with params: []
+public java.lang.Package java.lang.Class.getPackage() on (obsolete)class Main$Transform with [] = null
+Calling public java.lang.String java.lang.Class.getPackageName$() with params: []
+public java.lang.String java.lang.Class.getPackageName$() on (obsolete)class Main$Transform with [] = null
+Calling public java.security.ProtectionDomain java.lang.Class.getProtectionDomain() with params: []
+public java.security.ProtectionDomain java.lang.Class.getProtectionDomain() on (obsolete)class Main$Transform with [] = null
+Calling public java.net.URL java.lang.Class.getResource(java.lang.String) with params: [[NOT_USED_STRING, foo, SECRET_ARRAY]]
+public java.net.URL java.lang.Class.getResource(java.lang.String) on (obsolete)class Main$Transform with [NOT_USED_STRING] = null
+public java.net.URL java.lang.Class.getResource(java.lang.String) on (obsolete)class Main$Transform with [foo] = null
+public java.net.URL java.lang.Class.getResource(java.lang.String) on (obsolete)class Main$Transform with [SECRET_ARRAY] = null
+Calling public java.io.InputStream java.lang.Class.getResourceAsStream(java.lang.String) with params: [[NOT_USED_STRING, foo, SECRET_ARRAY]]
+public java.io.InputStream java.lang.Class.getResourceAsStream(java.lang.String) on (obsolete)class Main$Transform with [NOT_USED_STRING] = null
+public java.io.InputStream java.lang.Class.getResourceAsStream(java.lang.String) on (obsolete)class Main$Transform with [foo] = null
+public java.io.InputStream java.lang.Class.getResourceAsStream(java.lang.String) on (obsolete)class Main$Transform with [SECRET_ARRAY] = null
+Calling public java.lang.Object[] java.lang.Class.getSigners() with params: []
+public java.lang.Object[] java.lang.Class.getSigners() on (obsolete)class Main$Transform with [] = null
+Calling public java.lang.String java.lang.Class.getSimpleName() with params: []
+public java.lang.String java.lang.Class.getSimpleName() with [] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+Calling public java.lang.Class java.lang.Class.getSuperclass() with params: []
+public java.lang.Class java.lang.Class.getSuperclass() on (obsolete)class Main$Transform with [] = class java.lang.Object
+Calling public java.lang.String java.lang.Class.getTypeName() with params: []
+public java.lang.String java.lang.Class.getTypeName() on (obsolete)class Main$Transform with [] = Main$Transform
+Calling public synchronized java.lang.reflect.TypeVariable[] java.lang.Class.getTypeParameters() with params: []
+public synchronized java.lang.reflect.TypeVariable[] java.lang.Class.getTypeParameters() with [] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+Calling public boolean java.lang.Class.isAnnotation() with params: []
+public boolean java.lang.Class.isAnnotation() with [] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+Calling public boolean java.lang.Class.isAnnotationPresent(java.lang.Class) with params: [[null, class java.lang.Object, (obsolete)class Main$Transform, class Main$Transform, long, class java.lang.Class]]
+public boolean java.lang.Class.isAnnotationPresent(java.lang.Class) with [null] throws java.lang.reflect.InvocationTargetException: java.lang.NullPointerException: annotationClass == null
+public boolean java.lang.Class.isAnnotationPresent(java.lang.Class) with [class java.lang.Object] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+public boolean java.lang.Class.isAnnotationPresent(java.lang.Class) with [(obsolete)class Main$Transform] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+public boolean java.lang.Class.isAnnotationPresent(java.lang.Class) with [class Main$Transform] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+public boolean java.lang.Class.isAnnotationPresent(java.lang.Class) with [long] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+public boolean java.lang.Class.isAnnotationPresent(java.lang.Class) with [class java.lang.Class] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+Calling public native boolean java.lang.Class.isAnonymousClass() with params: []
+public native boolean java.lang.Class.isAnonymousClass() with [] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+Calling public boolean java.lang.Class.isArray() with params: []
+public boolean java.lang.Class.isArray() on (obsolete)class Main$Transform with [] = false
+Calling public boolean java.lang.Class.isAssignableFrom(java.lang.Class) with params: [[null, class java.lang.Object, (obsolete)class Main$Transform, class Main$Transform, long, class java.lang.Class]]
+public boolean java.lang.Class.isAssignableFrom(java.lang.Class) with [null] throws java.lang.reflect.InvocationTargetException: java.lang.NullPointerException: Attempt to invoke virtual method 'boolean java.lang.Class.isInterface()' on a null object reference
+public boolean java.lang.Class.isAssignableFrom(java.lang.Class) on (obsolete)class Main$Transform with [class java.lang.Object] = false
+public boolean java.lang.Class.isAssignableFrom(java.lang.Class) on (obsolete)class Main$Transform with [(obsolete)class Main$Transform] = true
+public boolean java.lang.Class.isAssignableFrom(java.lang.Class) on (obsolete)class Main$Transform with [class Main$Transform] = false
+public boolean java.lang.Class.isAssignableFrom(java.lang.Class) on (obsolete)class Main$Transform with [long] = false
+public boolean java.lang.Class.isAssignableFrom(java.lang.Class) on (obsolete)class Main$Transform with [class java.lang.Class] = false
+Calling public boolean java.lang.Class.isEnum() with params: []
+public boolean java.lang.Class.isEnum() with [] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+Calling public boolean java.lang.Class.isFinalizable() with params: []
+public boolean java.lang.Class.isFinalizable() with [] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+Calling public boolean java.lang.Class.isInstance(java.lang.Object) with params: [[null, foo, NOT_USED_STRING, class Main$Transform]]
+public boolean java.lang.Class.isInstance(java.lang.Object) on (obsolete)class Main$Transform with [null] = false
+public boolean java.lang.Class.isInstance(java.lang.Object) on (obsolete)class Main$Transform with [foo] = false
+public boolean java.lang.Class.isInstance(java.lang.Object) on (obsolete)class Main$Transform with [NOT_USED_STRING] = false
+public boolean java.lang.Class.isInstance(java.lang.Object) on (obsolete)class Main$Transform with [class Main$Transform] = false
+Calling public boolean java.lang.Class.isInterface() with params: []
+public boolean java.lang.Class.isInterface() on (obsolete)class Main$Transform with [] = false
+Calling public boolean java.lang.Class.isLocalClass() with params: []
+public boolean java.lang.Class.isLocalClass() with [] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+Calling public boolean java.lang.Class.isMemberClass() with params: []
+public boolean java.lang.Class.isMemberClass() with [] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+Calling public boolean java.lang.Class.isPrimitive() with params: []
+public boolean java.lang.Class.isPrimitive() on (obsolete)class Main$Transform with [] = false
+Calling public boolean java.lang.Class.isProxy() with params: []
+public boolean java.lang.Class.isProxy() on (obsolete)class Main$Transform with [] = false
+Calling public boolean java.lang.Class.isSynthetic() with params: []
+public boolean java.lang.Class.isSynthetic() with [] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+Calling public native java.lang.Object java.lang.Class.newInstance() throws java.lang.InstantiationException,java.lang.IllegalAccessException with params: []
+public native java.lang.Object java.lang.Class.newInstance() throws java.lang.InstantiationException,java.lang.IllegalAccessException with [] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+Calling public java.lang.String java.lang.Class.toGenericString() with params: []
+public java.lang.String java.lang.Class.toGenericString() with [] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+Calling public java.lang.String java.lang.Class.toString() with params: []
+public java.lang.String java.lang.Class.toString() on (obsolete)class Main$Transform with [] = class Main$Transform
+
+
+Using non-obsolete class object!
+
+
+Calling public java.lang.Class java.lang.Class.asSubclass(java.lang.Class) with params: [[null, class java.lang.Object, (obsolete)class Main$Transform, class Main$Transform, long, class java.lang.Class]]
+public java.lang.Class java.lang.Class.asSubclass(java.lang.Class) with [null] throws java.lang.reflect.InvocationTargetException: java.lang.NullPointerException: Attempt to invoke virtual method 'boolean java.lang.Class.isAssignableFrom(java.lang.Class)' on a null object reference
+public java.lang.Class java.lang.Class.asSubclass(java.lang.Class) on class Main$Transform with [class java.lang.Object] = class Main$Transform
+public java.lang.Class java.lang.Class.asSubclass(java.lang.Class) with [(obsolete)class Main$Transform] throws java.lang.reflect.InvocationTargetException: java.lang.ClassCastException: class Main$Transform cannot be cast to Main$Transform
+public java.lang.Class java.lang.Class.asSubclass(java.lang.Class) on class Main$Transform with [class Main$Transform] = class Main$Transform
+public java.lang.Class java.lang.Class.asSubclass(java.lang.Class) with [long] throws java.lang.reflect.InvocationTargetException: java.lang.ClassCastException: class Main$Transform cannot be cast to long
+public java.lang.Class java.lang.Class.asSubclass(java.lang.Class) with [class java.lang.Class] throws java.lang.reflect.InvocationTargetException: java.lang.ClassCastException: class Main$Transform cannot be cast to java.lang.Class
+Calling public java.lang.Object java.lang.Class.cast(java.lang.Object) with params: [[null, foo, NOT_USED_STRING, class Main$Transform]]
+public java.lang.Object java.lang.Class.cast(java.lang.Object) on class Main$Transform with [null] = null
+public java.lang.Object java.lang.Class.cast(java.lang.Object) with [foo] throws java.lang.reflect.InvocationTargetException: java.lang.ClassCastException: Cannot cast java.lang.String to Main$Transform
+public java.lang.Object java.lang.Class.cast(java.lang.Object) with [NOT_USED_STRING] throws java.lang.reflect.InvocationTargetException: java.lang.ClassCastException: Cannot cast java.lang.String to Main$Transform
+public java.lang.Object java.lang.Class.cast(java.lang.Object) with [class Main$Transform] throws java.lang.reflect.InvocationTargetException: java.lang.ClassCastException: Cannot cast java.lang.Class to Main$Transform
+Calling public boolean java.lang.Class.desiredAssertionStatus() with params: []
+public boolean java.lang.Class.desiredAssertionStatus() on class Main$Transform with [] = false
+Calling public int java.lang.Class.getAccessFlags() with params: []
+public int java.lang.Class.getAccessFlags() on class Main$Transform with [] = 524289
+Calling public java.lang.annotation.Annotation java.lang.Class.getAnnotation(java.lang.Class) with params: [[null, class java.lang.Object, (obsolete)class Main$Transform, class Main$Transform, long, class java.lang.Class]]
+public java.lang.annotation.Annotation java.lang.Class.getAnnotation(java.lang.Class) with [null] throws java.lang.reflect.InvocationTargetException: java.lang.NullPointerException
+public java.lang.annotation.Annotation java.lang.Class.getAnnotation(java.lang.Class) on class Main$Transform with [class java.lang.Object] = null
+public java.lang.annotation.Annotation java.lang.Class.getAnnotation(java.lang.Class) with [(obsolete)class Main$Transform] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+public java.lang.annotation.Annotation java.lang.Class.getAnnotation(java.lang.Class) on class Main$Transform with [class Main$Transform] = null
+public java.lang.annotation.Annotation java.lang.Class.getAnnotation(java.lang.Class) on class Main$Transform with [long] = null
+public java.lang.annotation.Annotation java.lang.Class.getAnnotation(java.lang.Class) on class Main$Transform with [class java.lang.Class] = null
+Calling public java.lang.annotation.Annotation[] java.lang.Class.getAnnotations() with params: []
+public java.lang.annotation.Annotation[] java.lang.Class.getAnnotations() on class Main$Transform with [] = []
+Calling public java.lang.annotation.Annotation[] java.lang.Class.getAnnotationsByType(java.lang.Class) with params: [[null, class java.lang.Object, (obsolete)class Main$Transform, class Main$Transform, long, class java.lang.Class]]
+public java.lang.annotation.Annotation[] java.lang.Class.getAnnotationsByType(java.lang.Class) with [null] throws java.lang.reflect.InvocationTargetException: java.lang.NullPointerException: annotationClass
+public java.lang.annotation.Annotation[] java.lang.Class.getAnnotationsByType(java.lang.Class) with [class java.lang.Object] throws java.lang.reflect.InvocationTargetException: java.lang.ClassCastException: java.lang.Object[] cannot be cast to java.lang.annotation.Annotation[]
+public java.lang.annotation.Annotation[] java.lang.Class.getAnnotationsByType(java.lang.Class) with [(obsolete)class Main$Transform] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+public java.lang.annotation.Annotation[] java.lang.Class.getAnnotationsByType(java.lang.Class) with [class Main$Transform] throws java.lang.reflect.InvocationTargetException: java.lang.ClassCastException: Main$Transform[] cannot be cast to java.lang.annotation.Annotation[]
+public java.lang.annotation.Annotation[] java.lang.Class.getAnnotationsByType(java.lang.Class) with [long] throws java.lang.reflect.InvocationTargetException: java.lang.ClassCastException: long[] cannot be cast to java.lang.annotation.Annotation[]
+public java.lang.annotation.Annotation[] java.lang.Class.getAnnotationsByType(java.lang.Class) with [class java.lang.Class] throws java.lang.reflect.InvocationTargetException: java.lang.ClassCastException: java.lang.Class[] cannot be cast to java.lang.annotation.Annotation[]
+Calling public java.lang.String java.lang.Class.getCanonicalName() with params: []
+public java.lang.String java.lang.Class.getCanonicalName() on class Main$Transform with [] = Main.Transform
+Calling public java.lang.ClassLoader java.lang.Class.getClassLoader() with params: []
+public java.lang.ClassLoader java.lang.Class.getClassLoader() on class Main$Transform with [] = dalvik.system.PathClassLoader
+Calling public java.lang.Class[] java.lang.Class.getClasses() with params: []
+public java.lang.Class[] java.lang.Class.getClasses() on class Main$Transform with [] = []
+Calling public java.lang.Class java.lang.Class.getComponentType() with params: []
+public java.lang.Class java.lang.Class.getComponentType() on class Main$Transform with [] = null
+Calling public java.lang.reflect.Constructor java.lang.Class.getConstructor(java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.SecurityException with params: [[new java.lang.Object[0], new java.lang.Class[0], null]]
+public java.lang.reflect.Constructor java.lang.Class.getConstructor(java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.SecurityException with [new java.lang.Object[0]] throws java.lang.IllegalArgumentException: method java.lang.Class.getConstructor argument 1 has type java.lang.Class[], got java.lang.Object[]: null
+public java.lang.reflect.Constructor java.lang.Class.getConstructor(java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.SecurityException on class Main$Transform with [new java.lang.Class[0]] = public Main$Transform()
+public java.lang.reflect.Constructor java.lang.Class.getConstructor(java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.SecurityException on class Main$Transform with [null] = public Main$Transform()
+Calling public java.lang.reflect.Constructor[] java.lang.Class.getConstructors() throws java.lang.SecurityException with params: []
+public java.lang.reflect.Constructor[] java.lang.Class.getConstructors() throws java.lang.SecurityException on class Main$Transform with [] = [public Main$Transform()]
+Calling public native java.lang.annotation.Annotation java.lang.Class.getDeclaredAnnotation(java.lang.Class) with params: [[null, class java.lang.Object, (obsolete)class Main$Transform, class Main$Transform, long, class java.lang.Class]]
+public native java.lang.annotation.Annotation java.lang.Class.getDeclaredAnnotation(java.lang.Class) with [null] throws java.lang.reflect.InvocationTargetException: java.lang.NullPointerException: annotationClass
+public native java.lang.annotation.Annotation java.lang.Class.getDeclaredAnnotation(java.lang.Class) on class Main$Transform with [class java.lang.Object] = null
+public native java.lang.annotation.Annotation java.lang.Class.getDeclaredAnnotation(java.lang.Class) on class Main$Transform with [(obsolete)class Main$Transform] = null
+public native java.lang.annotation.Annotation java.lang.Class.getDeclaredAnnotation(java.lang.Class) on class Main$Transform with [class Main$Transform] = null
+public native java.lang.annotation.Annotation java.lang.Class.getDeclaredAnnotation(java.lang.Class) on class Main$Transform with [long] = null
+public native java.lang.annotation.Annotation java.lang.Class.getDeclaredAnnotation(java.lang.Class) on class Main$Transform with [class java.lang.Class] = null
+Calling public native java.lang.annotation.Annotation[] java.lang.Class.getDeclaredAnnotations() with params: []
+public native java.lang.annotation.Annotation[] java.lang.Class.getDeclaredAnnotations() on class Main$Transform with [] = []
+Calling public native java.lang.Class[] java.lang.Class.getDeclaredClasses() with params: []
+public native java.lang.Class[] java.lang.Class.getDeclaredClasses() on class Main$Transform with [] = []
+Calling public java.lang.reflect.Constructor java.lang.Class.getDeclaredConstructor(java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.SecurityException with params: [[new java.lang.Object[0], new java.lang.Class[0], null]]
+public java.lang.reflect.Constructor java.lang.Class.getDeclaredConstructor(java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.SecurityException with [new java.lang.Object[0]] throws java.lang.IllegalArgumentException: method java.lang.Class.getDeclaredConstructor argument 1 has type java.lang.Class[], got java.lang.Object[]: null
+public java.lang.reflect.Constructor java.lang.Class.getDeclaredConstructor(java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.SecurityException on class Main$Transform with [new java.lang.Class[0]] = public Main$Transform()
+public java.lang.reflect.Constructor java.lang.Class.getDeclaredConstructor(java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.SecurityException on class Main$Transform with [null] = public Main$Transform()
+Calling public java.lang.reflect.Constructor[] java.lang.Class.getDeclaredConstructors() throws java.lang.SecurityException with params: []
+public java.lang.reflect.Constructor[] java.lang.Class.getDeclaredConstructors() throws java.lang.SecurityException on class Main$Transform with [] = [public Main$Transform()]
+Calling public native java.lang.reflect.Field java.lang.Class.getDeclaredField(java.lang.String) throws java.lang.NoSuchFieldException with params: [[NOT_USED_STRING, foo, SECRET_ARRAY]]
+public native java.lang.reflect.Field java.lang.Class.getDeclaredField(java.lang.String) throws java.lang.NoSuchFieldException with [NOT_USED_STRING] throws java.lang.reflect.InvocationTargetException: java.lang.NoSuchFieldException: No field NOT_USED_STRING in class LMain$Transform; (declaration of 'Main$Transform' appears in <transformed-jar>)
+public native java.lang.reflect.Field java.lang.Class.getDeclaredField(java.lang.String) throws java.lang.NoSuchFieldException with [foo] throws java.lang.reflect.InvocationTargetException: java.lang.NoSuchFieldException: No field foo in class LMain$Transform; (declaration of 'Main$Transform' appears in <transformed-jar>)
+public native java.lang.reflect.Field java.lang.Class.getDeclaredField(java.lang.String) throws java.lang.NoSuchFieldException on class Main$Transform with [SECRET_ARRAY] = public static java.lang.Object Main$Transform.SECRET_ARRAY
+Calling public native java.lang.reflect.Field[] java.lang.Class.getDeclaredFields() with params: []
+public native java.lang.reflect.Field[] java.lang.Class.getDeclaredFields() on class Main$Transform with [] = [public static java.lang.Object Main$Transform.AAA_PADDING, public static java.lang.Object Main$Transform.SECRET_ARRAY, public static long Main$Transform.SECRET_NUMBER]
+Calling public native java.lang.reflect.Field[] java.lang.Class.getDeclaredFieldsUnchecked(boolean) with params: [[true, false]]
+public native java.lang.reflect.Field[] java.lang.Class.getDeclaredFieldsUnchecked(boolean) on class Main$Transform with [true] = [public static java.lang.Object Main$Transform.AAA_PADDING, public static java.lang.Object Main$Transform.SECRET_ARRAY, public static long Main$Transform.SECRET_NUMBER]
+public native java.lang.reflect.Field[] java.lang.Class.getDeclaredFieldsUnchecked(boolean) on class Main$Transform with [false] = [public static java.lang.Object Main$Transform.AAA_PADDING, public static java.lang.Object Main$Transform.SECRET_ARRAY, public static long Main$Transform.SECRET_NUMBER]
+Calling public java.lang.reflect.Method java.lang.Class.getDeclaredMethod(java.lang.String,java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.SecurityException with params: [[NOT_USED_STRING, foo, SECRET_ARRAY], [new java.lang.Object[0], new java.lang.Class[0], null]]
+public java.lang.reflect.Method java.lang.Class.getDeclaredMethod(java.lang.String,java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.SecurityException with [NOT_USED_STRING, new java.lang.Object[0]] throws java.lang.IllegalArgumentException: method java.lang.Class.getDeclaredMethod argument 2 has type java.lang.Class[], got java.lang.Object[]: null
+public java.lang.reflect.Method java.lang.Class.getDeclaredMethod(java.lang.String,java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.SecurityException with [NOT_USED_STRING, new java.lang.Class[0]] throws java.lang.reflect.InvocationTargetException: java.lang.NoSuchMethodException: Main$Transform.NOT_USED_STRING []
+public java.lang.reflect.Method java.lang.Class.getDeclaredMethod(java.lang.String,java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.SecurityException with [NOT_USED_STRING, null] throws java.lang.reflect.InvocationTargetException: java.lang.NoSuchMethodException: Main$Transform.NOT_USED_STRING []
+public java.lang.reflect.Method java.lang.Class.getDeclaredMethod(java.lang.String,java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.SecurityException with [foo, new java.lang.Object[0]] throws java.lang.IllegalArgumentException: method java.lang.Class.getDeclaredMethod argument 2 has type java.lang.Class[], got java.lang.Object[]: null
+public java.lang.reflect.Method java.lang.Class.getDeclaredMethod(java.lang.String,java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.SecurityException on class Main$Transform with [foo, new java.lang.Class[0]] = public static void Main$Transform.foo()
+public java.lang.reflect.Method java.lang.Class.getDeclaredMethod(java.lang.String,java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.SecurityException on class Main$Transform with [foo, null] = public static void Main$Transform.foo()
+public java.lang.reflect.Method java.lang.Class.getDeclaredMethod(java.lang.String,java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.SecurityException with [SECRET_ARRAY, new java.lang.Object[0]] throws java.lang.IllegalArgumentException: method java.lang.Class.getDeclaredMethod argument 2 has type java.lang.Class[], got java.lang.Object[]: null
+public java.lang.reflect.Method java.lang.Class.getDeclaredMethod(java.lang.String,java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.SecurityException with [SECRET_ARRAY, new java.lang.Class[0]] throws java.lang.reflect.InvocationTargetException: java.lang.NoSuchMethodException: Main$Transform.SECRET_ARRAY []
+public java.lang.reflect.Method java.lang.Class.getDeclaredMethod(java.lang.String,java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.SecurityException with [SECRET_ARRAY, null] throws java.lang.reflect.InvocationTargetException: java.lang.NoSuchMethodException: Main$Transform.SECRET_ARRAY []
+Calling public java.lang.reflect.Method[] java.lang.Class.getDeclaredMethods() throws java.lang.SecurityException with params: []
+public java.lang.reflect.Method[] java.lang.Class.getDeclaredMethods() throws java.lang.SecurityException on class Main$Transform with [] = [public static void Main$Transform.bar(), public static void Main$Transform.foo()]
+Calling public native java.lang.reflect.Method[] java.lang.Class.getDeclaredMethodsUnchecked(boolean) with params: [[true, false]]
+public native java.lang.reflect.Method[] java.lang.Class.getDeclaredMethodsUnchecked(boolean) on class Main$Transform with [true] = [public static void Main$Transform.bar(), public static void Main$Transform.foo()]
+public native java.lang.reflect.Method[] java.lang.Class.getDeclaredMethodsUnchecked(boolean) on class Main$Transform with [false] = [public static void Main$Transform.bar(), public static void Main$Transform.foo()]
+Calling public native java.lang.Class java.lang.Class.getDeclaringClass() with params: []
+public native java.lang.Class java.lang.Class.getDeclaringClass() on class Main$Transform with [] = class Main
+Calling public native java.lang.Class java.lang.Class.getEnclosingClass() with params: []
+public native java.lang.Class java.lang.Class.getEnclosingClass() on class Main$Transform with [] = class Main
+Calling public java.lang.reflect.Constructor java.lang.Class.getEnclosingConstructor() with params: []
+public java.lang.reflect.Constructor java.lang.Class.getEnclosingConstructor() on class Main$Transform with [] = null
+Calling public java.lang.reflect.Method java.lang.Class.getEnclosingMethod() with params: []
+public java.lang.reflect.Method java.lang.Class.getEnclosingMethod() on class Main$Transform with [] = null
+Calling public java.lang.Object[] java.lang.Class.getEnumConstants() with params: []
+public java.lang.Object[] java.lang.Class.getEnumConstants() on class Main$Transform with [] = null
+Calling public java.lang.Object[] java.lang.Class.getEnumConstantsShared() with params: []
+public java.lang.Object[] java.lang.Class.getEnumConstantsShared() on class Main$Transform with [] = null
+Calling public java.lang.reflect.Field java.lang.Class.getField(java.lang.String) throws java.lang.NoSuchFieldException with params: [[NOT_USED_STRING, foo, SECRET_ARRAY]]
+public java.lang.reflect.Field java.lang.Class.getField(java.lang.String) throws java.lang.NoSuchFieldException with [NOT_USED_STRING] throws java.lang.reflect.InvocationTargetException: java.lang.NoSuchFieldException: NOT_USED_STRING
+public java.lang.reflect.Field java.lang.Class.getField(java.lang.String) throws java.lang.NoSuchFieldException with [foo] throws java.lang.reflect.InvocationTargetException: java.lang.NoSuchFieldException: foo
+public java.lang.reflect.Field java.lang.Class.getField(java.lang.String) throws java.lang.NoSuchFieldException on class Main$Transform with [SECRET_ARRAY] = public static java.lang.Object Main$Transform.SECRET_ARRAY
+Calling public java.lang.reflect.Field[] java.lang.Class.getFields() throws java.lang.SecurityException with params: []
+public java.lang.reflect.Field[] java.lang.Class.getFields() throws java.lang.SecurityException on class Main$Transform with [] = [public static java.lang.Object Main$Transform.AAA_PADDING, public static java.lang.Object Main$Transform.SECRET_ARRAY, public static long Main$Transform.SECRET_NUMBER]
+Calling public java.lang.reflect.Type[] java.lang.Class.getGenericInterfaces() with params: []
+public java.lang.reflect.Type[] java.lang.Class.getGenericInterfaces() on class Main$Transform with [] = []
+Calling public java.lang.reflect.Type java.lang.Class.getGenericSuperclass() with params: []
+public java.lang.reflect.Type java.lang.Class.getGenericSuperclass() on class Main$Transform with [] = class java.lang.Object
+Calling public java.lang.reflect.Method java.lang.Class.getInstanceMethod(java.lang.String,java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.IllegalAccessException with params: [[NOT_USED_STRING, foo, SECRET_ARRAY], [new java.lang.Object[0], new java.lang.Class[0], null]]
+public java.lang.reflect.Method java.lang.Class.getInstanceMethod(java.lang.String,java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.IllegalAccessException with [NOT_USED_STRING, new java.lang.Object[0]] throws java.lang.IllegalArgumentException: method java.lang.Class.getInstanceMethod argument 2 has type java.lang.Class[], got java.lang.Object[]: null
+public java.lang.reflect.Method java.lang.Class.getInstanceMethod(java.lang.String,java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.IllegalAccessException on class Main$Transform with [NOT_USED_STRING, new java.lang.Class[0]] = null
+public java.lang.reflect.Method java.lang.Class.getInstanceMethod(java.lang.String,java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.IllegalAccessException on class Main$Transform with [NOT_USED_STRING, null] = null
+public java.lang.reflect.Method java.lang.Class.getInstanceMethod(java.lang.String,java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.IllegalAccessException with [foo, new java.lang.Object[0]] throws java.lang.IllegalArgumentException: method java.lang.Class.getInstanceMethod argument 2 has type java.lang.Class[], got java.lang.Object[]: null
+public java.lang.reflect.Method java.lang.Class.getInstanceMethod(java.lang.String,java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.IllegalAccessException on class Main$Transform with [foo, new java.lang.Class[0]] = null
+public java.lang.reflect.Method java.lang.Class.getInstanceMethod(java.lang.String,java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.IllegalAccessException on class Main$Transform with [foo, null] = null
+public java.lang.reflect.Method java.lang.Class.getInstanceMethod(java.lang.String,java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.IllegalAccessException with [SECRET_ARRAY, new java.lang.Object[0]] throws java.lang.IllegalArgumentException: method java.lang.Class.getInstanceMethod argument 2 has type java.lang.Class[], got java.lang.Object[]: null
+public java.lang.reflect.Method java.lang.Class.getInstanceMethod(java.lang.String,java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.IllegalAccessException on class Main$Transform with [SECRET_ARRAY, new java.lang.Class[0]] = null
+public java.lang.reflect.Method java.lang.Class.getInstanceMethod(java.lang.String,java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.IllegalAccessException on class Main$Transform with [SECRET_ARRAY, null] = null
+Calling public java.lang.Class[] java.lang.Class.getInterfaces() with params: []
+public java.lang.Class[] java.lang.Class.getInterfaces() on class Main$Transform with [] = []
+Calling public java.lang.reflect.Method java.lang.Class.getMethod(java.lang.String,java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.SecurityException with params: [[NOT_USED_STRING, foo, SECRET_ARRAY], [new java.lang.Object[0], new java.lang.Class[0], null]]
+public java.lang.reflect.Method java.lang.Class.getMethod(java.lang.String,java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.SecurityException with [NOT_USED_STRING, new java.lang.Object[0]] throws java.lang.IllegalArgumentException: method java.lang.Class.getMethod argument 2 has type java.lang.Class[], got java.lang.Object[]: null
+public java.lang.reflect.Method java.lang.Class.getMethod(java.lang.String,java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.SecurityException with [NOT_USED_STRING, new java.lang.Class[0]] throws java.lang.reflect.InvocationTargetException: java.lang.NoSuchMethodException: Main$Transform.NOT_USED_STRING []
+public java.lang.reflect.Method java.lang.Class.getMethod(java.lang.String,java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.SecurityException with [NOT_USED_STRING, null] throws java.lang.reflect.InvocationTargetException: java.lang.NoSuchMethodException: Main$Transform.NOT_USED_STRING []
+public java.lang.reflect.Method java.lang.Class.getMethod(java.lang.String,java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.SecurityException with [foo, new java.lang.Object[0]] throws java.lang.IllegalArgumentException: method java.lang.Class.getMethod argument 2 has type java.lang.Class[], got java.lang.Object[]: null
+public java.lang.reflect.Method java.lang.Class.getMethod(java.lang.String,java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.SecurityException on class Main$Transform with [foo, new java.lang.Class[0]] = public static void Main$Transform.foo()
+public java.lang.reflect.Method java.lang.Class.getMethod(java.lang.String,java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.SecurityException on class Main$Transform with [foo, null] = public static void Main$Transform.foo()
+public java.lang.reflect.Method java.lang.Class.getMethod(java.lang.String,java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.SecurityException with [SECRET_ARRAY, new java.lang.Object[0]] throws java.lang.IllegalArgumentException: method java.lang.Class.getMethod argument 2 has type java.lang.Class[], got java.lang.Object[]: null
+public java.lang.reflect.Method java.lang.Class.getMethod(java.lang.String,java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.SecurityException with [SECRET_ARRAY, new java.lang.Class[0]] throws java.lang.reflect.InvocationTargetException: java.lang.NoSuchMethodException: Main$Transform.SECRET_ARRAY []
+public java.lang.reflect.Method java.lang.Class.getMethod(java.lang.String,java.lang.Class[]) throws java.lang.NoSuchMethodException,java.lang.SecurityException with [SECRET_ARRAY, null] throws java.lang.reflect.InvocationTargetException: java.lang.NoSuchMethodException: Main$Transform.SECRET_ARRAY []
+Calling public java.lang.reflect.Method[] java.lang.Class.getMethods() throws java.lang.SecurityException with params: []
+public java.lang.reflect.Method[] java.lang.Class.getMethods() throws java.lang.SecurityException on class Main$Transform with [] = [public static void Main$Transform.bar(), public boolean java.lang.Object.equals(java.lang.Object), public static void Main$Transform.foo(), public final java.lang.Class java.lang.Object.getClass(), public int java.lang.Object.hashCode(), public final native void java.lang.Object.notify(), public final native void java.lang.Object.notifyAll(), public java.lang.String java.lang.Object.toString(), public final void java.lang.Object.wait() throws java.lang.InterruptedException, public final void java.lang.Object.wait(long) throws java.lang.InterruptedException, public final native void java.lang.Object.wait(long,int) throws java.lang.InterruptedException]
+Calling public int java.lang.Class.getModifiers() with params: []
+public int java.lang.Class.getModifiers() on class Main$Transform with [] = 9
+Calling public java.lang.String java.lang.Class.getName() with params: []
+public java.lang.String java.lang.Class.getName() on class Main$Transform with [] = Main$Transform
+Calling public java.lang.Package java.lang.Class.getPackage() with params: []
+public java.lang.Package java.lang.Class.getPackage() on class Main$Transform with [] = null
+Calling public java.lang.String java.lang.Class.getPackageName$() with params: []
+public java.lang.String java.lang.Class.getPackageName$() on class Main$Transform with [] = null
+Calling public java.security.ProtectionDomain java.lang.Class.getProtectionDomain() with params: []
+public java.security.ProtectionDomain java.lang.Class.getProtectionDomain() on class Main$Transform with [] = null
+Calling public java.net.URL java.lang.Class.getResource(java.lang.String) with params: [[NOT_USED_STRING, foo, SECRET_ARRAY]]
+public java.net.URL java.lang.Class.getResource(java.lang.String) on class Main$Transform with [NOT_USED_STRING] = null
+public java.net.URL java.lang.Class.getResource(java.lang.String) on class Main$Transform with [foo] = null
+public java.net.URL java.lang.Class.getResource(java.lang.String) on class Main$Transform with [SECRET_ARRAY] = null
+Calling public java.io.InputStream java.lang.Class.getResourceAsStream(java.lang.String) with params: [[NOT_USED_STRING, foo, SECRET_ARRAY]]
+public java.io.InputStream java.lang.Class.getResourceAsStream(java.lang.String) on class Main$Transform with [NOT_USED_STRING] = null
+public java.io.InputStream java.lang.Class.getResourceAsStream(java.lang.String) on class Main$Transform with [foo] = null
+public java.io.InputStream java.lang.Class.getResourceAsStream(java.lang.String) on class Main$Transform with [SECRET_ARRAY] = null
+Calling public java.lang.Object[] java.lang.Class.getSigners() with params: []
+public java.lang.Object[] java.lang.Class.getSigners() on class Main$Transform with [] = null
+Calling public java.lang.String java.lang.Class.getSimpleName() with params: []
+public java.lang.String java.lang.Class.getSimpleName() on class Main$Transform with [] = Transform
+Calling public java.lang.Class java.lang.Class.getSuperclass() with params: []
+public java.lang.Class java.lang.Class.getSuperclass() on class Main$Transform with [] = class java.lang.Object
+Calling public java.lang.String java.lang.Class.getTypeName() with params: []
+public java.lang.String java.lang.Class.getTypeName() on class Main$Transform with [] = Main$Transform
+Calling public synchronized java.lang.reflect.TypeVariable[] java.lang.Class.getTypeParameters() with params: []
+public synchronized java.lang.reflect.TypeVariable[] java.lang.Class.getTypeParameters() on class Main$Transform with [] = []
+Calling public boolean java.lang.Class.isAnnotation() with params: []
+public boolean java.lang.Class.isAnnotation() on class Main$Transform with [] = false
+Calling public boolean java.lang.Class.isAnnotationPresent(java.lang.Class) with params: [[null, class java.lang.Object, (obsolete)class Main$Transform, class Main$Transform, long, class java.lang.Class]]
+public boolean java.lang.Class.isAnnotationPresent(java.lang.Class) with [null] throws java.lang.reflect.InvocationTargetException: java.lang.NullPointerException: annotationClass == null
+public boolean java.lang.Class.isAnnotationPresent(java.lang.Class) on class Main$Transform with [class java.lang.Object] = false
+public boolean java.lang.Class.isAnnotationPresent(java.lang.Class) with [(obsolete)class Main$Transform] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+public boolean java.lang.Class.isAnnotationPresent(java.lang.Class) on class Main$Transform with [class Main$Transform] = false
+public boolean java.lang.Class.isAnnotationPresent(java.lang.Class) on class Main$Transform with [long] = false
+public boolean java.lang.Class.isAnnotationPresent(java.lang.Class) on class Main$Transform with [class java.lang.Class] = false
+Calling public native boolean java.lang.Class.isAnonymousClass() with params: []
+public native boolean java.lang.Class.isAnonymousClass() on class Main$Transform with [] = false
+Calling public boolean java.lang.Class.isArray() with params: []
+public boolean java.lang.Class.isArray() on class Main$Transform with [] = false
+Calling public boolean java.lang.Class.isAssignableFrom(java.lang.Class) with params: [[null, class java.lang.Object, (obsolete)class Main$Transform, class Main$Transform, long, class java.lang.Class]]
+public boolean java.lang.Class.isAssignableFrom(java.lang.Class) with [null] throws java.lang.reflect.InvocationTargetException: java.lang.NullPointerException: Attempt to invoke virtual method 'boolean java.lang.Class.isInterface()' on a null object reference
+public boolean java.lang.Class.isAssignableFrom(java.lang.Class) on class Main$Transform with [class java.lang.Object] = false
+public boolean java.lang.Class.isAssignableFrom(java.lang.Class) on class Main$Transform with [(obsolete)class Main$Transform] = false
+public boolean java.lang.Class.isAssignableFrom(java.lang.Class) on class Main$Transform with [class Main$Transform] = true
+public boolean java.lang.Class.isAssignableFrom(java.lang.Class) on class Main$Transform with [long] = false
+public boolean java.lang.Class.isAssignableFrom(java.lang.Class) on class Main$Transform with [class java.lang.Class] = false
+Calling public boolean java.lang.Class.isEnum() with params: []
+public boolean java.lang.Class.isEnum() on class Main$Transform with [] = false
+Calling public boolean java.lang.Class.isFinalizable() with params: []
+public boolean java.lang.Class.isFinalizable() on class Main$Transform with [] = false
+Calling public boolean java.lang.Class.isInstance(java.lang.Object) with params: [[null, foo, NOT_USED_STRING, class Main$Transform]]
+public boolean java.lang.Class.isInstance(java.lang.Object) on class Main$Transform with [null] = false
+public boolean java.lang.Class.isInstance(java.lang.Object) on class Main$Transform with [foo] = false
+public boolean java.lang.Class.isInstance(java.lang.Object) on class Main$Transform with [NOT_USED_STRING] = false
+public boolean java.lang.Class.isInstance(java.lang.Object) on class Main$Transform with [class Main$Transform] = false
+Calling public boolean java.lang.Class.isInterface() with params: []
+public boolean java.lang.Class.isInterface() on class Main$Transform with [] = false
+Calling public boolean java.lang.Class.isLocalClass() with params: []
+public boolean java.lang.Class.isLocalClass() on class Main$Transform with [] = false
+Calling public boolean java.lang.Class.isMemberClass() with params: []
+public boolean java.lang.Class.isMemberClass() on class Main$Transform with [] = true
+Calling public boolean java.lang.Class.isPrimitive() with params: []
+public boolean java.lang.Class.isPrimitive() on class Main$Transform with [] = false
+Calling public boolean java.lang.Class.isProxy() with params: []
+public boolean java.lang.Class.isProxy() on class Main$Transform with [] = false
+Calling public boolean java.lang.Class.isSynthetic() with params: []
+public boolean java.lang.Class.isSynthetic() on class Main$Transform with [] = false
+Calling public native java.lang.Object java.lang.Class.newInstance() throws java.lang.InstantiationException,java.lang.IllegalAccessException with params: []
+public native java.lang.Object java.lang.Class.newInstance() throws java.lang.InstantiationException,java.lang.IllegalAccessException on class Main$Transform with [] = Transform Instance
+Calling public java.lang.String java.lang.Class.toGenericString() with params: []
+public java.lang.String java.lang.Class.toGenericString() on class Main$Transform with [] = public static class Main$Transform
+Calling public java.lang.String java.lang.Class.toString() with params: []
+public java.lang.String java.lang.Class.toString() on class Main$Transform with [] = class Main$Transform
diff --git a/test/1980-obsolete-object-cleared/info.txt b/test/1980-obsolete-object-cleared/info.txt
new file mode 100644
index 0000000..1c5ca82
--- /dev/null
+++ b/test/1980-obsolete-object-cleared/info.txt
@@ -0,0 +1,2 @@
+Test that obsoleted classes have their fields cleared and cannot be used to obtain
+obsolete/invalid reflection objects.
diff --git a/test/1980-obsolete-object-cleared/run b/test/1980-obsolete-object-cleared/run
new file mode 100755
index 0000000..03e41a5
--- /dev/null
+++ b/test/1980-obsolete-object-cleared/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/1980-obsolete-object-cleared/src/Main.java b/test/1980-obsolete-object-cleared/src/Main.java
new file mode 100644
index 0000000..514defc
--- /dev/null
+++ b/test/1980-obsolete-object-cleared/src/Main.java
@@ -0,0 +1,304 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import art.*;
+import java.lang.ref.*;
+import java.lang.reflect.*;
+import java.util.*;
+import java.util.function.Consumer;
+import sun.misc.Unsafe;
+
+public class Main {
+  public static class Transform {
+    static {
+    }
+
+    public static Object SECRET_ARRAY = new byte[] {1, 2, 3, 4};
+    public static long SECRET_NUMBER = 42;
+
+    public static void foo() {}
+  }
+
+  /* Base64 for
+   * public static class Trasform {
+   *   static {}
+   *   public static Object AAA_PADDING;
+   *   public static Object SECRET_ARRAY;
+   *   public static long SECRET_NUMBER;
+   *   public static void foo() {}
+   *   public static void bar() {}
+   * }
+   */
+  public static final byte[] REDEFINED_DEX_FILE =
+      Base64.getDecoder()
+          .decode(
+              "ZGV4CjAzNQDdmsOAlizFD4Ogb6+/mfSdVzhmL8e/mRcYBAAAcAAAAHhWNBIAAAAAAAAAAGADAAAU"
+                  + "AAAAcAAAAAcAAADAAAAAAQAAANwAAAADAAAA6AAAAAUAAAAAAQAAAQAAACgBAADQAgAASAEAAKwB"
+                  + "AAC2AQAAvgEAAMsBAADOAQAA4AEAAOgBAAAMAgAALAIAAEACAABLAgAAWQIAAGgCAABzAgAAdgIA"
+                  + "AIMCAACIAgAAjQIAAJMCAACaAgAAAwAAAAQAAAAFAAAABgAAAAcAAAAIAAAADQAAAA0AAAAGAAAA"
+                  + "AAAAAAEABQACAAAAAQAFAAoAAAABAAAACwAAAAEAAAAAAAAAAQAAAAEAAAABAAAADwAAAAEAAAAQ"
+                  + "AAAABQAAAAEAAAABAAAAAQAAAAUAAAAAAAAACQAAAFADAAAhAwAAAAAAAAAAAAAAAAAAmgEAAAEA"
+                  + "AAAOAAAAAQABAAEAAACeAQAABAAAAHAQBAAAAA4AAAAAAAAAAACiAQAAAQAAAA4AAAAAAAAAAAAA"
+                  + "AKYBAAABAAAADgAHAA4ABgAOAAsADgAKAA4AAAAIPGNsaW5pdD4ABjxpbml0PgALQUFBX1BBRERJ"
+                  + "TkcAAUoAEExNYWluJFRyYW5zZm9ybTsABkxNYWluOwAiTGRhbHZpay9hbm5vdGF0aW9uL0VuY2xv"
+                  + "c2luZ0NsYXNzOwAeTGRhbHZpay9hbm5vdGF0aW9uL0lubmVyQ2xhc3M7ABJMamF2YS9sYW5nL09i"
+                  + "amVjdDsACU1haW4uamF2YQAMU0VDUkVUX0FSUkFZAA1TRUNSRVRfTlVNQkVSAAlUcmFuc2Zvcm0A"
+                  + "AVYAC2FjY2Vzc0ZsYWdzAANiYXIAA2ZvbwAEbmFtZQAFdmFsdWUAdn5+RDh7ImNvbXBpbGF0aW9u"
+                  + "LW1vZGUiOiJkZWJ1ZyIsIm1pbi1hcGkiOjEsInNoYS0xIjoiYTgzNTJmMjU0ODg1MzYyY2NkOGQ5"
+                  + "MDlkMzUyOWM2MDA5NGRkODk2ZSIsInZlcnNpb24iOiIxLjYuMjAtZGV2In0AAgMBEhgCAgQCDgQJ"
+                  + "ERcMAwAEAAAJAQkBCQCIgATIAgGBgATcAgEJ9AIBCYgDAAAAAAACAAAAEgMAABgDAABEAwAAAAAA"
+                  + "AAAAAAAAAAAADwAAAAAAAAABAAAAAAAAAAEAAAAUAAAAcAAAAAIAAAAHAAAAwAAAAAMAAAABAAAA"
+                  + "3AAAAAQAAAADAAAA6AAAAAUAAAAFAAAAAAEAAAYAAAABAAAAKAEAAAEgAAAEAAAASAEAAAMgAAAE"
+                  + "AAAAmgEAAAIgAAAUAAAArAEAAAQgAAACAAAAEgMAAAAgAAABAAAAIQMAAAMQAAACAAAAQAMAAAYg"
+                  + "AAABAAAAUAMAAAAQAAABAAAAYAMAAA==");
+
+  private interface TConsumer<T> {
+    public void accept(T t) throws Exception;
+  }
+
+  private interface ResetIterator<T> extends Iterator<T> {
+    public void reset();
+  }
+
+  private static final class BaseResetIter implements ResetIterator<Object[]> {
+    private boolean have_next = true;
+
+    public Object[] next() {
+      if (have_next) {
+        have_next = false;
+        return new Object[0];
+      } else {
+        throw new NoSuchElementException("only one element");
+      }
+    }
+
+    public boolean hasNext() {
+      return have_next;
+    }
+
+    public void reset() {
+      have_next = true;
+    }
+  }
+
+  public static void main(String[] args) throws Exception {
+    System.loadLibrary(args[0]);
+    Redefinition.setTestConfiguration(Redefinition.Config.COMMON_REDEFINE);
+
+    // Get the Unsafe object.
+    Field f = Unsafe.class.getDeclaredField("THE_ONE");
+    f.setAccessible(true);
+    Unsafe u = (Unsafe) f.get(null);
+
+    // Get the offsets into the original Transform class of the fields
+    long off_secret_array = genericFieldOffset(Transform.class.getDeclaredField("SECRET_ARRAY"));
+    long off_secret_number = genericFieldOffset(Transform.class.getDeclaredField("SECRET_NUMBER"));
+
+    System.out.println("Reading normally.");
+    System.out.println("\tOriginal secret number is: " + Transform.SECRET_NUMBER);
+    System.out.println("\tOriginal secret array is: " + Arrays.toString((byte[])Transform.SECRET_ARRAY));
+    System.out.println("Using unsafe to access values directly from memory.");
+    System.out.println(
+        "\tOriginal secret number is: " + u.getLong(Transform.class, off_secret_number));
+    System.out.println(
+        "\tOriginal secret array is: "
+            + Arrays.toString((byte[]) u.getObject(Transform.class, off_secret_array)));
+
+    // Redefine in a way that changes the offsets.
+    Redefinition.doCommonStructuralClassRedefinition(Transform.class, REDEFINED_DEX_FILE);
+
+    // Make sure the value is the same.
+    System.out.println("Reading normally post redefinition.");
+    System.out.println("\tPost-redefinition secret number is: " + Transform.SECRET_NUMBER);
+    System.out.println("\tPost-redefinition secret array is: " + Arrays.toString((byte[])Transform.SECRET_ARRAY));
+
+    // Get the (old) obsolete class from the ClassExt
+    Field ext_field = Class.class.getDeclaredField("extData");
+    ext_field.setAccessible(true);
+    Object ext_data = ext_field.get(Transform.class);
+    Field oc_field = ext_data.getClass().getDeclaredField("obsoleteClass");
+    oc_field.setAccessible(true);
+    Class<?> obsolete_class = (Class<?>) oc_field.get(ext_data);
+
+    // Try reading the fields directly out of memory using unsafe.
+    System.out.println("Obsolete class is: " + obsolete_class);
+    System.out.println("Using unsafe to access obsolete values directly from memory.");
+    System.out.println(
+        "\tObsolete secret number is: " + u.getLong(obsolete_class, off_secret_number));
+    System.out.println(
+        "\tObsolete secret array is: "
+            + Arrays.toString((byte[]) u.getObject(obsolete_class, off_secret_array)));
+
+    // Try calling all the public, non-static methods on the obsolete class. Make sure we cannot get
+    // j.l.r.{Method,Field} objects or instances.
+    TConsumer<Class> cc =
+        (Class c) -> {
+          for (Method m : Class.class.getDeclaredMethods()) {
+            if (Modifier.isPublic(m.getModifiers()) && !Modifier.isStatic(m.getModifiers())) {
+              Iterable<Object[]> iter = CollectParameterValues(m, obsolete_class);
+              System.out.println("Calling " + m + " with params: " + iter);
+              for (Object[] arr : iter) {
+                try {
+                  System.out.println(
+                      m
+                          + " on "
+                          + safePrint(c)
+                          + " with "
+                          + deepPrint(arr)
+                          + " = "
+                          + safePrint(m.invoke(c, arr)));
+                } catch (Throwable e) {
+                  System.out.println(
+                      m + " with " + deepPrint(arr) + " throws " + safePrint(e) + ": " + safePrint(e.getCause()));
+                }
+              }
+            }
+          }
+        };
+    System.out.println("\n\nUsing obsolete class object!\n\n");
+    cc.accept(obsolete_class);
+    System.out.println("\n\nUsing non-obsolete class object!\n\n");
+    cc.accept(Transform.class);
+  }
+
+  public static Iterable<Object[]> CollectParameterValues(Method m, Class<?> obsolete_class) throws Exception {
+    Class<?>[] types = m.getParameterTypes();
+    final Object[][] params = new Object[types.length][];
+    for (int i = 0; i < types.length; i++) {
+      if (types[i].equals(Class.class)) {
+        params[i] =
+            new Object[] {
+              null, Object.class, obsolete_class, Transform.class, Long.TYPE, Class.class
+            };
+      } else if (types[i].equals(Boolean.TYPE)) {
+        params[i] = new Object[] {Boolean.TRUE, Boolean.FALSE};
+      } else if (types[i].equals(String.class)) {
+        params[i] = new Object[] {"NOT_USED_STRING", "foo", "SECRET_ARRAY"};
+      } else if (types[i].equals(Object.class)) {
+        params[i] = new Object[] {null, "foo", "NOT_USED_STRING", Transform.class};
+      } else if (types[i].isArray()) {
+        params[i] = new Object[] {new Object[0], new Class[0], null};
+      } else {
+        throw new Exception("Unknown type " + types[i] + " at " + i + " in " + m);
+      }
+    }
+    // Build the reset-iter.
+    ResetIterator<Object[]> iter = new BaseResetIter();
+    for (int i = params.length - 1; i >= 0; i--) {
+      iter = new ComboIter(Arrays.asList(params[i]), iter);
+    }
+    final Iterator<Object[]> fiter = iter;
+    // Wrap in an iterator with a useful toString method.
+    return new Iterable<Object[]>() {
+      public Iterator<Object[]> iterator() { return fiter; }
+      public String toString() { return deepPrint(params); }
+    };
+  }
+
+  public static String deepPrint(Object[] o) {
+    return Arrays.toString(
+        Arrays.stream(o)
+            .map(
+                (x) -> {
+                  if (x == null) {
+                    return "null";
+                  } else if (x.getClass().isArray()) {
+                    if (((Object[]) x).length == 0) {
+                      return "new " + x.getClass().getComponentType().getName() + "[0]";
+                    } else {
+                      return deepPrint((Object[]) x);
+                    }
+                  } else {
+                    return safePrint(x);
+                  }
+                })
+            .toArray());
+  }
+
+  public static String safePrint(Object o) {
+    if (o instanceof ClassLoader) {
+      return o.getClass().getName();
+    } else if (o == null) {
+      return "null";
+    } else if (o instanceof Exception) {
+      String res = o.toString();
+      if (res.endsWith("-transformed)")) {
+        res = res.substring(0, res.lastIndexOf(" ")) + " <transformed-jar>)";
+      } else if (res.endsWith(".jar)")) {
+        res = res.substring(0, res.lastIndexOf(" ")) + " <original-jar>)";
+      }
+      return res;
+    } else if (o instanceof Transform) {
+      return "Transform Instance";
+    } else if (o instanceof Class && isObsoleteObject((Class) o)) {
+      return "(obsolete)" + o.toString();
+    } else if (o.getClass().isArray()) {
+      return Arrays.toString((Object[])o);
+    } else {
+      return o.toString();
+    }
+  }
+
+  private static class ComboIter implements ResetIterator<Object[]> {
+    private ResetIterator<Object[]> next;
+    private Object cur;
+    private boolean first;
+    private Iterator<Object> my_vals;
+    private Iterable<Object> my_vals_reset;
+
+    public Object[] next() {
+      if (!next.hasNext()) {
+        cur = my_vals.next();
+        first = false;
+        if (next != null) {
+          next.reset();
+        }
+      }
+      if (first) {
+        first = false;
+        cur = my_vals.next();
+      }
+      Object[] nv = next.next();
+      Object[] res = new Object[nv.length + 1];
+      res[0] = cur;
+      for (int i = 0; i < nv.length; i++) {
+        res[i + 1] = nv[i];
+      }
+      return res;
+    }
+
+    public boolean hasNext() {
+      return next.hasNext() || my_vals.hasNext();
+    }
+
+    public void reset() {
+      my_vals = my_vals_reset.iterator();
+      next.reset();
+      cur = null;
+      first = true;
+    }
+
+    public ComboIter(Iterable<Object> this_reset, ResetIterator<Object[]> next_reset) {
+      my_vals_reset = this_reset;
+      next = next_reset;
+      reset();
+    }
+  }
+
+  public static native long genericFieldOffset(Field f);
+
+  public static native boolean isObsoleteObject(Class c);
+}
diff --git a/test/1980-obsolete-object-cleared/src/art/Redefinition.java b/test/1980-obsolete-object-cleared/src/art/Redefinition.java
new file mode 120000
index 0000000..81eaf31
--- /dev/null
+++ b/test/1980-obsolete-object-cleared/src/art/Redefinition.java
@@ -0,0 +1 @@
+../../../jvmti-common/Redefinition.java
\ No newline at end of file
diff --git a/test/1981-structural-redef-private-method-handles/build b/test/1981-structural-redef-private-method-handles/build
new file mode 100755
index 0000000..c80d7ad
--- /dev/null
+++ b/test/1981-structural-redef-private-method-handles/build
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Make us exit on a failure
+set -e
+
+./default-build "$@" --experimental var-handles
diff --git a/test/1981-structural-redef-private-method-handles/expected.txt b/test/1981-structural-redef-private-method-handles/expected.txt
new file mode 100644
index 0000000..0c0ac6e
--- /dev/null
+++ b/test/1981-structural-redef-private-method-handles/expected.txt
@@ -0,0 +1,37 @@
+Initial: class art.Test1981$Transform[FOO: value of <FOO FIELD>, BAR: value of <BAR FIELD>]
+Reading field FOO using (ID: 0) MethodHandle()Object = (ID: 1) value of <FOO FIELD>
+Reading field FOO using (ID: 2) java.lang.invoke.FieldVarHandle()->java.lang.Object = (ID: 1) value of <FOO FIELD>
+Reading field BAR using (ID: 3) MethodHandle()Object = (ID: 4) value of <BAR FIELD>
+Reading field BAR using (ID: 5) java.lang.invoke.FieldVarHandle()->java.lang.Object = (ID: 4) value of <BAR FIELD>
+Redefining Transform class
+Post redefinition : class art.Test1981$Transform[FOO: value of <FOO FIELD>, BAR: value of <BAR FIELD>, BAZ: null]
+Reading field FOO using (ID: 0) MethodHandle()Object = (ID: 1) value of <FOO FIELD>
+Reading field FOO using (ID: 2) java.lang.invoke.FieldVarHandle()->java.lang.Object = (ID: 1) value of <FOO FIELD>
+Reading field BAR using (ID: 3) MethodHandle()Object = (ID: 4) value of <BAR FIELD>
+Reading field BAR using (ID: 5) java.lang.invoke.FieldVarHandle()->java.lang.Object = (ID: 4) value of <BAR FIELD>
+Reading new field BAZ using (ID: 6) MethodHandle()Object = (ID: 7) <NULL>
+Reading new field BAZ using (ID: 8) java.lang.invoke.FieldVarHandle()->java.lang.Object = (ID: 7) <NULL>
+Reading new field FOO using (ID: 9) MethodHandle()Object = (ID: 1) value of <FOO FIELD>
+Reading new field FOO using (ID: 10) java.lang.invoke.FieldVarHandle()->java.lang.Object = (ID: 1) value of <FOO FIELD>
+Reading new field BAR using (ID: 11) MethodHandle()Object = (ID: 4) value of <BAR FIELD>
+Reading new field BAR using (ID: 12) java.lang.invoke.FieldVarHandle()->java.lang.Object = (ID: 4) value of <BAR FIELD>
+Setting BAZ to (ID: 13) foo with new mh.
+Post set with new mh: class art.Test1981$Transform[FOO: value of <FOO FIELD>, BAR: value of <BAR FIELD>, BAZ: foo]
+Setting FOO to (ID: 14) class art.Test1981$Transform with old mh.
+Post set with old mh: class art.Test1981$Transform[FOO: class art.Test1981$Transform, BAR: value of <BAR FIELD>, BAZ: foo]
+Setting FOO to '(ID: 15) new_value object' with old varhandle.
+Post set with new varhandle: class art.Test1981$Transform[FOO: new_value object, BAR: value of <BAR FIELD>, BAZ: foo]
+Setting BAZ to 'bar' with new varhandle.
+Post set with old varhandle: class art.Test1981$Transform[FOO: new_value object, BAR: value of <BAR FIELD>, BAZ: bar]
+Using mh to call new private method.
+Post reinit with mh: class art.Test1981$Transform[FOO: new_value object, BAR: value of <BAR FIELD>, BAZ: 42]
+Reading field FOO using (ID: 0) MethodHandle()Object = (ID: 15) new_value object
+Reading field FOO using (ID: 2) java.lang.invoke.FieldVarHandle()->java.lang.Object = (ID: 15) new_value object
+Reading field BAR using (ID: 3) MethodHandle()Object = (ID: 4) value of <BAR FIELD>
+Reading field BAR using (ID: 5) java.lang.invoke.FieldVarHandle()->java.lang.Object = (ID: 4) value of <BAR FIELD>
+Reading new field BAZ using (ID: 6) MethodHandle()Object = (ID: 16) 42
+Reading new field BAZ using (ID: 8) java.lang.invoke.FieldVarHandle()->java.lang.Object = (ID: 16) 42
+Reading new field FOO using (ID: 9) MethodHandle()Object = (ID: 15) new_value object
+Reading new field FOO using (ID: 10) java.lang.invoke.FieldVarHandle()->java.lang.Object = (ID: 15) new_value object
+Reading new field BAR using (ID: 11) MethodHandle()Object = (ID: 4) value of <BAR FIELD>
+Reading new field BAR using (ID: 12) java.lang.invoke.FieldVarHandle()->java.lang.Object = (ID: 4) value of <BAR FIELD>
diff --git a/test/1981-structural-redef-private-method-handles/info.txt b/test/1981-structural-redef-private-method-handles/info.txt
new file mode 100644
index 0000000..1c5ca82
--- /dev/null
+++ b/test/1981-structural-redef-private-method-handles/info.txt
@@ -0,0 +1,2 @@
+Test that obsoleted classes have their fields cleared and cannot be used to obtain
+obsolete/invalid reflection objects.
diff --git a/test/1981-structural-redef-private-method-handles/run b/test/1981-structural-redef-private-method-handles/run
new file mode 100755
index 0000000..03e41a5
--- /dev/null
+++ b/test/1981-structural-redef-private-method-handles/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/1981-structural-redef-private-method-handles/src/Main.java b/test/1981-structural-redef-private-method-handles/src/Main.java
new file mode 100644
index 0000000..9d6f74d
--- /dev/null
+++ b/test/1981-structural-redef-private-method-handles/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.Test1981.run();
+  }
+}
diff --git a/test/1981-structural-redef-private-method-handles/src/art/Redefinition.java b/test/1981-structural-redef-private-method-handles/src/art/Redefinition.java
new file mode 120000
index 0000000..81eaf31
--- /dev/null
+++ b/test/1981-structural-redef-private-method-handles/src/art/Redefinition.java
@@ -0,0 +1 @@
+../../../jvmti-common/Redefinition.java
\ No newline at end of file
diff --git a/test/1981-structural-redef-private-method-handles/src/art/Test1981.java b/test/1981-structural-redef-private-method-handles/src/art/Test1981.java
new file mode 100644
index 0000000..2c19d8b
--- /dev/null
+++ b/test/1981-structural-redef-private-method-handles/src/art/Test1981.java
@@ -0,0 +1,302 @@
+/*
+ * 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.lang.invoke.*;
+import java.lang.ref.*;
+import java.lang.reflect.*;
+import java.util.*;
+
+public class Test1981 {
+  public static void run() throws Exception {
+    Redefinition.setTestConfiguration(Redefinition.Config.COMMON_REDEFINE);
+    doTest();
+  }
+
+  private static final boolean PRINT_NONDETERMINISTIC = false;
+
+  public static WeakHashMap<Object, Long> id_nums = new WeakHashMap<>();
+  public static long next_id = 0;
+
+  public static String printGeneric(Object o) {
+    Long id = id_nums.get(o);
+    if (id == null) {
+      id = Long.valueOf(next_id++);
+      id_nums.put(o, id);
+    }
+    if (o == null) {
+      return "(ID: " + id + ") <NULL>";
+    }
+    Class oc = o.getClass();
+    if (oc.isArray() && oc.getComponentType() == Byte.TYPE) {
+      return "(ID: "
+          + id
+          + ") "
+          + Arrays.toString(Arrays.copyOf((byte[]) o, 10)).replace(']', ',')
+          + " ...]";
+    } else if (o instanceof VarHandle) {
+      // These don't have a good to-string. Give them one.
+      VarHandle v = (VarHandle)o;
+      return "(ID: " + id + ") " + o.getClass().getName() + "()->" + v.varType().getName();
+    } else {
+      return "(ID: " + id + ") " + o.toString();
+    }
+  }
+
+  public static class Transform {
+    static {
+    }
+
+    private static Object BAR =
+        new Object() {
+          public String toString() {
+            return "value of <" + this.get() + ">";
+          }
+
+          public Object get() {
+            return "BAR FIELD";
+          }
+        };
+    private static Object FOO =
+        new Object() {
+          public String toString() {
+            return "value of <" + this.get() + ">";
+          }
+
+          public Object get() {
+            return "FOO FIELD";
+          }
+        };
+
+    public static MethodHandles.Lookup getLookup() {
+      return MethodHandles.lookup();
+    }
+
+    public static String staticToString() {
+      return Transform.class.toString() + "[FOO: " + FOO + ", BAR: " + BAR + "]";
+    }
+  }
+
+  /* Base64 encoded class of:
+   * public static class Transform {
+   *   static {}
+   *   // NB This is the order the fields will be laid out in memory.
+   *   private static Object BAR;
+   *   private static Object BAZ;
+   *   private static Object FOO;
+   *   public static MethodHandles.Lookup getLookup() { return null; }
+   *   private static void reinitialize() {
+   *     BAZ = 42;
+   *   }
+   *   public static String staticToString() {
+   *    return Transform.class.toString() + "[FOO: " + FOO + ", BAR: " + BAR + ", BAZ: " + BAZ + "]";
+   *   }
+   * }
+   */
+  private static byte[] REDEFINED_DEX_BYTES =
+      Base64.getDecoder()
+          .decode(
+              "ZGV4CjAzNQDY+Vd3k8SVBE6A35RavIBzYN76h51YIqVwBgAAcAAAAHhWNBIAAAAAAAAAAKwFAAAk"
+                  + "AAAAcAAAAAwAAAAAAQAABgAAADABAAADAAAAeAEAAAwAAACQAQAAAQAAAPABAABgBAAAEAIAABoD"
+                  + "AAAjAwAALAMAADYDAAA+AwAAQwMAAEgDAABNAwAAUAMAAFMDAABXAwAAWwMAAHUDAACFAwAAqQMA"
+                  + "AMkDAADcAwAA8QMAAAUEAAAZBAAANAQAAF0EAABsBAAAdwQAAHoEAACCBAAAhQQAAJIEAACaBAAA"
+                  + "pQQAAKsEAAC5BAAAyQQAANMEAADaBAAA4wQAAAcAAAALAAAADAAAAA0AAAAOAAAADwAAABAAAAAR"
+                  + "AAAAEgAAABMAAAAUAAAAFwAAAAkAAAAGAAAABAMAAAgAAAAIAAAAAAAAAAoAAAAJAAAADAMAAAoA"
+                  + "AAAJAAAAFAMAAAgAAAAKAAAAAAAAABcAAAALAAAAAAAAAAEABwAEAAAAAQAHAAUAAAABAAcABgAA"
+                  + "AAEABQACAAAAAQAFAAMAAAABAAQAHAAAAAEABQAeAAAAAQABAB8AAAAFAAEAIAAAAAYAAAAiAAAA"
+                  + "BwAFAAMAAAAJAAUAAwAAAAkAAgAbAAAACQADABsAAAAJAAEAIAAAAAEAAAABAAAABwAAAAAAAAAV"
+                  + "AAAAnAUAAGoFAAAAAAAABQAAAAIAAAD/AgAANgAAABwAAQBuEAUAAAAMAGIBAgBiAgAAYgMBACIE"
+                  + "CQBwEAgABABuIAoABAAaABgAbiAKAAQAbiAJABQAGgAAAG4gCgAEAG4gCQAkABoAAQBuIAoABABu"
+                  + "IAkANAAaABkAbiAKAAQAbhALAAQADAARAAEAAAAAAAAA9gIAAAIAAAASABEAAAAAAAAAAADuAgAA"
+                  + "AQAAAA4AAAABAAEAAQAAAPICAAAEAAAAcBAHAAAADgABAAAAAQAAAPoCAAAJAAAAEwAqAHEQBgAA"
+                  + "AAwAaQABAA4ACgAOAAkADgAPAA4AEQAOhwAUAA4AAAEAAAAAAAAAAQAAAAcAAAABAAAACAAHLCBC"
+                  + "QVI6IAAHLCBCQVo6IAAIPGNsaW5pdD4ABjxpbml0PgADQkFSAANCQVoAA0ZPTwABSQABTAACTEkA"
+                  + "AkxMABhMYXJ0L1Rlc3QxOTgxJFRyYW5zZm9ybTsADkxhcnQvVGVzdDE5ODE7ACJMZGFsdmlrL2Fu"
+                  + "bm90YXRpb24vRW5jbG9zaW5nQ2xhc3M7AB5MZGFsdmlrL2Fubm90YXRpb24vSW5uZXJDbGFzczsA"
+                  + "EUxqYXZhL2xhbmcvQ2xhc3M7ABNMamF2YS9sYW5nL0ludGVnZXI7ABJMamF2YS9sYW5nL09iamVj"
+                  + "dDsAEkxqYXZhL2xhbmcvU3RyaW5nOwAZTGphdmEvbGFuZy9TdHJpbmdCdWlsZGVyOwAnTGphdmEv"
+                  + "bGFuZy9pbnZva2UvTWV0aG9kSGFuZGxlcyRMb29rdXA7AA1UZXN0MTk4MS5qYXZhAAlUcmFuc2Zv"
+                  + "cm0AAVYABltGT086IAABXQALYWNjZXNzRmxhZ3MABmFwcGVuZAAJZ2V0TG9va3VwAARuYW1lAAxy"
+                  + "ZWluaXRpYWxpemUADnN0YXRpY1RvU3RyaW5nAAh0b1N0cmluZwAFdmFsdWUAB3ZhbHVlT2YAdn5+"
+                  + "RDh7ImNvbXBpbGF0aW9uLW1vZGUiOiJkZWJ1ZyIsIm1pbi1hcGkiOjEsInNoYS0xIjoiYTgzNTJm"
+                  + "MjU0ODg1MzYyY2NkOGQ5MDlkMzUyOWM2MDA5NGRkODk2ZSIsInZlcnNpb24iOiIxLjYuMjAtZGV2"
+                  + "In0AAgMBIRgCAgQCGgQJHRcWAwAFAAAKAQoBCgCIgASgBQGBgAS0BQEJjAUBCswFAQmQBAAAAAAC"
+                  + "AAAAWwUAAGEFAACQBQAAAAAAAAAAAAAAAAAAEAAAAAAAAAABAAAAAAAAAAEAAAAkAAAAcAAAAAIA"
+                  + "AAAMAAAAAAEAAAMAAAAGAAAAMAEAAAQAAAADAAAAeAEAAAUAAAAMAAAAkAEAAAYAAAABAAAA8AEA"
+                  + "AAEgAAAFAAAAEAIAAAMgAAAFAAAA7gIAAAEQAAADAAAABAMAAAIgAAAkAAAAGgMAAAQgAAACAAAA"
+                  + "WwUAAAAgAAABAAAAagUAAAMQAAACAAAAjAUAAAYgAAABAAAAnAUAAAAQAAABAAAArAUAAA==");
+
+  public static void doTest() throws Exception {
+    try {
+      System.out.println("Initial: " + Transform.staticToString());
+      MethodHandles.Lookup lookup = Transform.getLookup();
+      String[] names = new String[] { "FOO", "BAR", };
+      MethodHandle[] handles =
+          new MethodHandle[] {
+            lookup.findStaticGetter(Transform.class, "FOO", Object.class),
+            lookup.findStaticGetter(Transform.class, "BAR", Object.class),
+          };
+      VarHandle foo_handle = lookup.findStaticVarHandle(Transform.class, "FOO", Object.class);
+      VarHandle[] var_handles =
+          new VarHandle[] {
+            foo_handle,
+            lookup.findStaticVarHandle(Transform.class, "BAR", Object.class),
+          };
+
+      for (int i = 0; i < names.length; i++) {
+        System.out.println(
+            "Reading field "
+                + names[i]
+                + " using "
+                + printGeneric(handles[i])
+                + " = "
+                + printGeneric(handles[i].invoke()));
+        System.out.println(
+            "Reading field "
+                + names[i]
+                + " using "
+                + printGeneric(var_handles[i])
+                + " = "
+                + printGeneric(var_handles[i].get()));
+      }
+      MethodHandle old_field_write = lookup.findStaticSetter(Transform.class, "FOO", Object.class);
+
+      System.out.println("Redefining Transform class");
+      Redefinition.doCommonStructuralClassRedefinition(Transform.class, REDEFINED_DEX_BYTES);
+      System.out.println("Post redefinition : " + Transform.staticToString());
+
+      String[] new_names = new String[] { "BAZ", "FOO", "BAR", };
+      MethodHandle[] new_handles =
+          new MethodHandle[] {
+            lookup.findStaticGetter(Transform.class, "BAZ", Object.class),
+            lookup.findStaticGetter(Transform.class, "FOO", Object.class),
+            lookup.findStaticGetter(Transform.class, "BAR", Object.class),
+          };
+      VarHandle baz_handle = lookup.findStaticVarHandle(Transform.class, "BAZ", Object.class);
+      VarHandle[] new_var_handles =
+          new VarHandle[] {
+            baz_handle,
+            lookup.findStaticVarHandle(Transform.class, "FOO", Object.class),
+            lookup.findStaticVarHandle(Transform.class, "BAR", Object.class),
+          };
+
+      for (int i = 0; i < names.length; i++) {
+        System.out.println(
+            "Reading field "
+                + names[i]
+                + " using "
+                + printGeneric(handles[i])
+                + " = "
+                + printGeneric(handles[i].invoke()));
+        System.out.println(
+            "Reading field "
+                + names[i]
+                + " using "
+                + printGeneric(var_handles[i])
+                + " = "
+                + printGeneric(var_handles[i].get()));
+      }
+
+      for (int i = 0; i < new_names.length; i++) {
+        System.out.println(
+            "Reading new field "
+                + new_names[i]
+                + " using "
+                + printGeneric(new_handles[i])
+                + " = "
+                + printGeneric(new_handles[i].invoke()));
+        System.out.println(
+            "Reading new field "
+                + new_names[i]
+                + " using "
+                + printGeneric(new_var_handles[i])
+                + " = "
+                + printGeneric(new_var_handles[i].get()));
+      }
+
+      String val = "foo";
+      System.out.println("Setting BAZ to " + printGeneric(val) + " with new mh.");
+      lookup.findStaticSetter(Transform.class, "BAZ", Object.class).invoke(val);
+      System.out.println("Post set with new mh: " + Transform.staticToString());
+
+      System.out.println("Setting FOO to " + printGeneric(Transform.class) + " with old mh.");
+      old_field_write.invoke(Transform.class);
+      System.out.println("Post set with old mh: " + Transform.staticToString());
+
+      Object new_val = new Object() {
+        public String toString() {
+          return "new_value object";
+        }
+      };
+      System.out.println("Setting FOO to '" + printGeneric(new_val) + "' with old varhandle.");
+      foo_handle.set(new_val);
+      System.out.println("Post set with new varhandle: " + Transform.staticToString());
+
+      System.out.println("Setting BAZ to 'bar' with new varhandle.");
+      baz_handle.set("bar");
+      System.out.println("Post set with old varhandle: " + Transform.staticToString());
+
+      System.out.println("Using mh to call new private method.");
+      MethodHandle reinit =
+          lookup.findStatic(Transform.class, "reinitialize", MethodType.methodType(Void.TYPE));
+      reinit.invoke();
+      System.out.println("Post reinit with mh: " + Transform.staticToString());
+
+
+      for (int i = 0; i < names.length; i++) {
+        System.out.println(
+            "Reading field "
+                + names[i]
+                + " using "
+                + printGeneric(handles[i])
+                + " = "
+                + printGeneric(handles[i].invoke()));
+        System.out.println(
+            "Reading field "
+                + names[i]
+                + " using "
+                + printGeneric(var_handles[i])
+                + " = "
+                + printGeneric(var_handles[i].get()));
+      }
+      for (int i = 0; i < new_names.length; i++) {
+        System.out.println(
+            "Reading new field "
+                + new_names[i]
+                + " using "
+                + printGeneric(new_handles[i])
+                + " = "
+                + printGeneric(new_handles[i].invoke()));
+        System.out.println(
+            "Reading new field "
+                + new_names[i]
+                + " using "
+                + printGeneric(new_var_handles[i])
+                + " = "
+                + printGeneric(new_var_handles[i].get()));
+      }
+    } catch (Throwable t) {
+      if (t instanceof Exception) {
+        throw (Exception) t;
+      } else if (t instanceof Error) {
+        throw (Error) t;
+      } else {
+        throw new RuntimeException("Unexpected throwable!", t);
+      }
+    }
+  }
+}
diff --git a/test/1982-no-virtuals-structural-redefinition/expected.txt b/test/1982-no-virtuals-structural-redefinition/expected.txt
new file mode 100644
index 0000000..604145d
--- /dev/null
+++ b/test/1982-no-virtuals-structural-redefinition/expected.txt
@@ -0,0 +1,30 @@
+Reading with reflection.
+public static java.lang.Object art.Test1982$Transform.BAR on (ID: 0) <NULL> = (ID: 1) value of <BAR FIELD>
+public static java.lang.Object art.Test1982$Transform.FOO on (ID: 0) <NULL> = (ID: 2) value of <FOO FIELD>
+Reading with reflection on subtransform instance.
+public static java.lang.Object art.Test1982$Transform.BAR on (ID: 3) SuperTransform { id: 2, class: class art.Test1982$SubTransform } = (ID: 1) value of <BAR FIELD>
+public static java.lang.Object art.Test1982$Transform.FOO on (ID: 3) SuperTransform { id: 2, class: class art.Test1982$SubTransform } = (ID: 2) value of <FOO FIELD>
+public int art.Test1982$SuperTransform.id on (ID: 3) SuperTransform { id: 2, class: class art.Test1982$SubTransform } = (ID: 4) 2
+Reading normally.
+Read BAR field: (ID: 1) value of <BAR FIELD>
+Read FOO field: (ID: 2) value of <FOO FIELD>
+t1 is (ID: 5) SuperTransform { id: 1, class: class art.Test1982$Transform }
+t2 is (ID: 3) SuperTransform { id: 2, class: class art.Test1982$SubTransform }
+Redefined: class art.Test1982$Transform[FOO: value of <FOO FIELD>, BAR: value of <BAR FIELD>, BAZ: null]
+Reading with reflection after redefinition.
+public static java.lang.Object art.Test1982$Transform.BAR on (ID: 0) <NULL> = (ID: 1) value of <BAR FIELD>
+public static java.lang.Object art.Test1982$Transform.BAZ on (ID: 0) <NULL> = (ID: 0) <NULL>
+public static java.lang.Object art.Test1982$Transform.FOO on (ID: 0) <NULL> = (ID: 2) value of <FOO FIELD>
+Reading with reflection after redefinition on subtransform instance.
+public static java.lang.Object art.Test1982$Transform.BAR on (ID: 3) SuperTransform { id: 2, class: class art.Test1982$SubTransform } = (ID: 1) value of <BAR FIELD>
+public static java.lang.Object art.Test1982$Transform.BAZ on (ID: 3) SuperTransform { id: 2, class: class art.Test1982$SubTransform } = (ID: 0) <NULL>
+public static java.lang.Object art.Test1982$Transform.FOO on (ID: 3) SuperTransform { id: 2, class: class art.Test1982$SubTransform } = (ID: 2) value of <FOO FIELD>
+public int art.Test1982$SuperTransform.id on (ID: 3) SuperTransform { id: 2, class: class art.Test1982$SubTransform } = (ID: 4) 2
+Reading normally after possible modification.
+Read FOO field: (ID: 2) value of <FOO FIELD>
+Read BAR field: (ID: 1) value of <BAR FIELD>
+t1 is (ID: 5) SuperTransform { id: 1, class: class art.Test1982$Transform }
+t2 is (ID: 3) SuperTransform { id: 2, class: class art.Test1982$SubTransform }
+new SubTransform is (ID: 6) SuperTransform { id: 1003, class: class art.Test1982$SubTransform }
+myToString of (ID: 6) SuperTransform { id: 1003, class: class art.Test1982$SubTransform } is SubTransform (subclass of: class art.Test1982$Transform[FOO: value of <FOO FIELD>, BAR: value of <BAR FIELD>, BAZ: null]) { id: 1003 }
+Creating new transform from t1 class = (ID: 7) SuperTransform { id: 1004, class: class art.Test1982$Transform }
diff --git a/test/1982-no-virtuals-structural-redefinition/info.txt b/test/1982-no-virtuals-structural-redefinition/info.txt
new file mode 100644
index 0000000..5921e53
--- /dev/null
+++ b/test/1982-no-virtuals-structural-redefinition/info.txt
@@ -0,0 +1,2 @@
+Test that structural redefinition works on classes with super-types and subtypes with virtual
+fields and methods so long as the target does not have any.
diff --git a/test/1982-no-virtuals-structural-redefinition/run b/test/1982-no-virtuals-structural-redefinition/run
new file mode 100755
index 0000000..03e41a5
--- /dev/null
+++ b/test/1982-no-virtuals-structural-redefinition/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/1982-no-virtuals-structural-redefinition/src/Main.java b/test/1982-no-virtuals-structural-redefinition/src/Main.java
new file mode 100644
index 0000000..19c56f8
--- /dev/null
+++ b/test/1982-no-virtuals-structural-redefinition/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.Test1982.run();
+  }
+}
diff --git a/test/1982-no-virtuals-structural-redefinition/src/art/Redefinition.java b/test/1982-no-virtuals-structural-redefinition/src/art/Redefinition.java
new file mode 120000
index 0000000..81eaf31
--- /dev/null
+++ b/test/1982-no-virtuals-structural-redefinition/src/art/Redefinition.java
@@ -0,0 +1 @@
+../../../jvmti-common/Redefinition.java
\ No newline at end of file
diff --git a/test/1982-no-virtuals-structural-redefinition/src/art/Test1982.java b/test/1982-no-virtuals-structural-redefinition/src/art/Test1982.java
new file mode 100644
index 0000000..3336c64
--- /dev/null
+++ b/test/1982-no-virtuals-structural-redefinition/src/art/Test1982.java
@@ -0,0 +1,200 @@
+/*
+ * 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.lang.ref.*;
+import java.lang.reflect.*;
+import java.util.*;
+
+public class Test1982 {
+  public static void run() throws Exception {
+    Redefinition.setTestConfiguration(Redefinition.Config.COMMON_REDEFINE);
+    doTest();
+  }
+
+  private static final boolean PRINT_NONDETERMINISTIC = false;
+
+  public static WeakHashMap<Object, Long> id_nums = new WeakHashMap<>();
+  public static long next_id = 0;
+
+  public static String printGeneric(Object o) {
+    Long id = id_nums.get(o);
+    if (id == null) {
+      id = Long.valueOf(next_id++);
+      id_nums.put(o, id);
+    }
+    if (o == null) {
+      return "(ID: " + id + ") <NULL>";
+    }
+    Class oc = o.getClass();
+    if (oc.isArray() && oc.getComponentType() == Byte.TYPE) {
+      return "(ID: "
+          + id
+          + ") "
+          + Arrays.toString(Arrays.copyOf((byte[]) o, 10)).replace(']', ',')
+          + " ...]";
+    } else {
+      return "(ID: " + id + ") " + o.toString();
+    }
+  }
+
+  private static void doRedefinition() {
+    Redefinition.doCommonStructuralClassRedefinition(Transform.class, REDEFINED_DEX_BYTES);
+  }
+
+  private static void readReflective(String msg, Field[] fields, Object recv) throws Exception {
+    System.out.println(msg);
+    for (Field f : fields) {
+      System.out.println(
+          f.toString() + " on " + printGeneric(recv) + " = " + printGeneric(f.get(recv)));
+    }
+  }
+
+  public static class SuperTransform {
+    public int id;
+
+    public SuperTransform(int id) {
+      this.id = id;
+    }
+
+    public String toString() {
+      return "SuperTransform { id: " + id + ", class: " + getClass() + " }";
+    }
+  }
+
+  public static class Transform extends SuperTransform {
+    static {
+    }
+
+    public static Object BAR =
+        new Object() {
+          public String toString() {
+            return "value of <" + this.get() + ">";
+          }
+
+          public Object get() {
+            return "BAR FIELD";
+          }
+        };
+    public static Object FOO =
+        new Object() {
+          public String toString() {
+            return "value of <" + this.get() + ">";
+          }
+
+          public Object get() {
+            return "FOO FIELD";
+          }
+        };
+    // This class has no virtual fields or methods. This means we can structurally redefine it
+    // without having to change the size of any instances.
+    public Transform(int id) {
+      super(id);
+    }
+
+    public static String staticToString() {
+      return Transform.class.toString() + "[FOO: " + FOO + ", BAR: " + BAR + "]";
+    }
+  }
+
+  public static class SubTransform extends Transform {
+    public SubTransform(int id) {
+      super(id);
+    }
+
+    public String myToString() {
+      return "SubTransform (subclass of: " + staticToString() + ") { id: " + id + " }";
+    }
+  }
+
+  /* Base64 encoded class of:
+   * public static class Transform extends SuperTransform {
+   *   static {}
+   *   public Transform(int id) { super(id + 1000); }
+   *   // NB This is the order the fields will be laid out in memory.
+   *   public static Object BAR;
+   *   public static Object BAZ;
+   *   public static Object FOO;
+   *   public static String staticToString() {
+   *    return Transform.class.toString() + "[FOO: " + FOO + ", BAR: " + BAR + ", BAZ: " + BAZ + "]";
+   *   }
+   * }
+   */
+  private static byte[] REDEFINED_DEX_BYTES =
+      Base64.getDecoder()
+          .decode(
+              "ZGV4CjAzNQAV5GctNSI+SEKJDaJIQLEac9ClAxZUSZq4BQAAcAAAAHhWNBIAAAAAAAAAAPQEAAAg"
+                  + "AAAAcAAAAAsAAADwAAAABQAAABwBAAADAAAAWAEAAAkAAABwAQAAAQAAALgBAADgAwAA2AEAAKoC"
+                  + "AACzAgAAvAIAAMYCAADOAgAA0wIAANgCAADdAgAA4AIAAOMCAADnAgAABgMAACADAAAwAwAAVAMA"
+                  + "AHQDAACHAwAAmwMAAK8DAADKAwAA2QMAAOQDAADnAwAA6wMAAPMDAAD2AwAAAwQAAAsEAAARBAAA"
+                  + "IQQAACsEAAAyBAAABwAAAAoAAAALAAAADAAAAA0AAAAOAAAADwAAABAAAAARAAAAEgAAABUAAAAI"
+                  + "AAAACAAAAAAAAAAJAAAACQAAAJQCAAAJAAAACQAAAJwCAAAVAAAACgAAAAAAAAAWAAAACgAAAKQC"
+                  + "AAACAAcABAAAAAIABwAFAAAAAgAHAAYAAAABAAQAAwAAAAIAAwACAAAAAgAEAAMAAAACAAAAHAAA"
+                  + "AAYAAAAdAAAACQADAAMAAAAJAAEAGgAAAAkAAgAaAAAACQAAAB0AAAACAAAAAQAAAAEAAAAAAAAA"
+                  + "EwAAAOQEAAC5BAAAAAAAAAUAAAACAAAAjQIAADYAAAAcAAIAbhAEAAAADABiAQIAYgIAAGIDAQAi"
+                  + "BAkAcBAFAAQAbiAHAAQAGgAXAG4gBwAEAG4gBgAUABoAAABuIAcABABuIAYAJAAaAAEAbiAHAAQA"
+                  + "biAGADQAGgAYAG4gBwAEAG4QCAAEAAwAEQAAAAAAAAAAAIQCAAABAAAADgAAAAIAAgACAAAAiAIA"
+                  + "AAYAAADQEegDcCAAABAADgAKAA4ACwEADgARAA4AAAAAAQAAAAcAAAABAAAACAAAAAEAAAAAAAcs"
+                  + "IEJBUjogAAcsIEJBWjogAAg8Y2xpbml0PgAGPGluaXQ+AANCQVIAA0JBWgADRk9PAAFJAAFMAAJM"
+                  + "TAAdTGFydC9UZXN0MTk4MiRTdXBlclRyYW5zZm9ybTsAGExhcnQvVGVzdDE5ODIkVHJhbnNmb3Jt"
+                  + "OwAOTGFydC9UZXN0MTk4MjsAIkxkYWx2aWsvYW5ub3RhdGlvbi9FbmNsb3NpbmdDbGFzczsAHkxk"
+                  + "YWx2aWsvYW5ub3RhdGlvbi9Jbm5lckNsYXNzOwARTGphdmEvbGFuZy9DbGFzczsAEkxqYXZhL2xh"
+                  + "bmcvT2JqZWN0OwASTGphdmEvbGFuZy9TdHJpbmc7ABlMamF2YS9sYW5nL1N0cmluZ0J1aWxkZXI7"
+                  + "AA1UZXN0MTk4Mi5qYXZhAAlUcmFuc2Zvcm0AAVYAAlZJAAZbRk9POiAAAV0AC2FjY2Vzc0ZsYWdz"
+                  + "AAZhcHBlbmQABG5hbWUADnN0YXRpY1RvU3RyaW5nAAh0b1N0cmluZwAFdmFsdWUAdn5+RDh7ImNv"
+                  + "bXBpbGF0aW9uLW1vZGUiOiJkZWJ1ZyIsIm1pbi1hcGkiOjEsInNoYS0xIjoiYTgzNTJmMjU0ODg1"
+                  + "MzYyY2NkOGQ5MDlkMzUyOWM2MDA5NGRkODk2ZSIsInZlcnNpb24iOiIxLjYuMjAtZGV2In0AAgQB"
+                  + "HhgDAgUCGQQJGxcUAwADAAAJAQkBCQGIgATUBAGBgAToBAEJ2AMAAAAAAAIAAACqBAAAsAQAANgE"
+                  + "AAAAAAAAAAAAAAAAAAAQAAAAAAAAAAEAAAAAAAAAAQAAACAAAABwAAAAAgAAAAsAAADwAAAAAwAA"
+                  + "AAUAAAAcAQAABAAAAAMAAABYAQAABQAAAAkAAABwAQAABgAAAAEAAAC4AQAAASAAAAMAAADYAQAA"
+                  + "AyAAAAMAAACEAgAAARAAAAMAAACUAgAAAiAAACAAAACqAgAABCAAAAIAAACqBAAAACAAAAEAAAC5"
+                  + "BAAAAxAAAAIAAADUBAAABiAAAAEAAADkBAAAABAAAAEAAAD0BAAA");
+
+  public static void doTest() throws Exception {
+    Transform t1 = new Transform(1);
+    SuperTransform t2 = new SubTransform(2);
+    readReflective("Reading with reflection.", Transform.class.getDeclaredFields(), null);
+    readReflective(
+        "Reading with reflection on subtransform instance.", SubTransform.class.getFields(), t2);
+    System.out.println("Reading normally.");
+    System.out.println("Read BAR field: " + printGeneric(Transform.BAR));
+    System.out.println("Read FOO field: " + printGeneric(Transform.FOO));
+    System.out.println("t1 is " + printGeneric(t1));
+    System.out.println("t2 is " + printGeneric(t2));
+    doRedefinition();
+    System.out.println("Redefined: " + Transform.staticToString());
+    readReflective(
+        "Reading with reflection after redefinition.", Transform.class.getDeclaredFields(), null);
+    readReflective(
+        "Reading with reflection after redefinition on subtransform instance.",
+        SubTransform.class.getFields(),
+        t2);
+    System.out.println("Reading normally after possible modification.");
+    System.out.println("Read FOO field: " + printGeneric(Transform.FOO));
+    System.out.println("Read BAR field: " + printGeneric(Transform.BAR));
+    System.out.println("t1 is " + printGeneric(t1));
+    System.out.println("t2 is " + printGeneric(t2));
+    SubTransform t3 = new SubTransform(3);
+    System.out.println("new SubTransform is " + printGeneric(t3));
+    System.out.println("myToString of " + printGeneric(t3) + " is " + t3.myToString());
+    // We verified in test 1980 that getDeclaredConstructor will throw if the class is obsolete.
+    // This therefore is a reasonable test that the t1 object's declaring class was updated.
+    System.out.println(
+        "Creating new transform from t1 class = "
+            + printGeneric(t1.getClass().getDeclaredConstructor(Integer.TYPE).newInstance(4)));
+  }
+}
diff --git a/test/1983-structural-redefinition-failures/build b/test/1983-structural-redefinition-failures/build
new file mode 100755
index 0000000..c80d7ad
--- /dev/null
+++ b/test/1983-structural-redefinition-failures/build
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Make us exit on a failure
+set -e
+
+./default-build "$@" --experimental var-handles
diff --git a/test/1983-structural-redefinition-failures/expected.txt b/test/1983-structural-redefinition-failures/expected.txt
new file mode 100644
index 0000000..54e1bcc
--- /dev/null
+++ b/test/1983-structural-redefinition-failures/expected.txt
@@ -0,0 +1,36 @@
+Checking mirror'd classes
+Is Structurally modifiable class java.lang.reflect.AccessibleObject false
+Is Structurally modifiable class java.lang.invoke.CallSite false
+Is Structurally modifiable class dalvik.system.ClassExt false
+Is Structurally modifiable class java.lang.ClassLoader false
+Is Structurally modifiable class java.lang.Class false
+Is Structurally modifiable class java.lang.reflect.Constructor false
+Is Structurally modifiable class java.lang.DexCache false
+Is Structurally modifiable class dalvik.system.EmulatedStackFrame false
+Is Structurally modifiable class java.lang.reflect.Executable false
+Is Structurally modifiable class java.lang.reflect.Field false
+Is Structurally modifiable class java.lang.ref.FinalizerReference false
+Is Structurally modifiable class java.lang.invoke.MethodHandle false
+Is Structurally modifiable class java.lang.invoke.MethodHandles$Lookup false
+Is Structurally modifiable class java.lang.invoke.MethodType false
+Is Structurally modifiable class java.lang.reflect.Method false
+Is Structurally modifiable class java.lang.Object false
+Is Structurally modifiable class java.lang.reflect.Proxy false
+Is Structurally modifiable class java.lang.ref.Reference false
+Is Structurally modifiable class java.lang.StackTraceElement false
+Is Structurally modifiable class java.lang.String false
+Is Structurally modifiable class java.lang.Thread false
+Is Structurally modifiable class java.lang.Throwable false
+Is Structurally modifiable class java.lang.invoke.VarHandle false
+Is Structurally modifiable class java.lang.invoke.FieldVarHandle false
+Checking non-mirror'd classes
+Is Structurally modifiable class java.util.ArrayList false
+Is Structurally modifiable class java.util.Objects true
+Is Structurally modifiable class java.util.Arrays true
+Is Structurally modifiable class [Ljava.lang.Object; false
+Is Structurally modifiable class java.lang.Integer false
+Is Structurally modifiable class java.lang.Number false
+Is Structurally modifiable class art.Test1983$NoVirtuals true
+Is Structurally modifiable class art.Test1983$WithVirtuals false
+Is Structurally modifiable class art.Test1983$SubWithVirtuals false
+Is Structurally modifiable class java.lang.invoke.MethodHandles true
diff --git a/test/1983-structural-redefinition-failures/info.txt b/test/1983-structural-redefinition-failures/info.txt
new file mode 100644
index 0000000..794f8eb
--- /dev/null
+++ b/test/1983-structural-redefinition-failures/info.txt
@@ -0,0 +1 @@
+Sanity check for isStructurallyModifiable.
diff --git a/test/1983-structural-redefinition-failures/run b/test/1983-structural-redefinition-failures/run
new file mode 100755
index 0000000..03e41a5
--- /dev/null
+++ b/test/1983-structural-redefinition-failures/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/1983-structural-redefinition-failures/src/Main.java b/test/1983-structural-redefinition-failures/src/Main.java
new file mode 100644
index 0000000..4a336a3
--- /dev/null
+++ b/test/1983-structural-redefinition-failures/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.Test1983.run();
+  }
+}
diff --git a/test/1983-structural-redefinition-failures/src/art/Redefinition.java b/test/1983-structural-redefinition-failures/src/art/Redefinition.java
new file mode 120000
index 0000000..81eaf31
--- /dev/null
+++ b/test/1983-structural-redefinition-failures/src/art/Redefinition.java
@@ -0,0 +1 @@
+../../../jvmti-common/Redefinition.java
\ No newline at end of file
diff --git a/test/1983-structural-redefinition-failures/src/art/Test1983.java b/test/1983-structural-redefinition-failures/src/art/Test1983.java
new file mode 100644
index 0000000..d6e09f7
--- /dev/null
+++ b/test/1983-structural-redefinition-failures/src/art/Test1983.java
@@ -0,0 +1,105 @@
+/*
+ * 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.lang.ref.*;
+import java.lang.reflect.*;
+import java.lang.invoke.*;
+import java.util.*;
+
+public class Test1983 {
+  public static void run() throws Exception {
+    Redefinition.setTestConfiguration(Redefinition.Config.COMMON_REDEFINE);
+    doTest();
+  }
+
+  public static void Check(Class[] klasses) {
+    for (Class k : klasses) {
+      try {
+        boolean res = Redefinition.isStructurallyModifiable(k);
+        System.out.println("Is Structurally modifiable " + k + " " + res);
+      } catch (Exception e) {
+        System.out.println("Got exception " + e + " during check modifiablity of " + k);
+        e.printStackTrace(System.out);
+      }
+    }
+  }
+
+  public static class WithVirtuals {
+    public Object o;
+    public void foobar() {}
+  }
+  public static class NoVirtuals extends WithVirtuals {
+    public static Object o;
+    public static void foo() {}
+  }
+  public static class SubWithVirtuals extends NoVirtuals {
+    public Object j;
+    public void bar() {}
+  }
+
+  public static void doTest() throws Exception {
+    Class[] mirrord_classes = new Class[] {
+      AccessibleObject.class,
+      CallSite.class,
+      // ClassExt is not on the compile classpath.
+      Class.forName("dalvik.system.ClassExt"),
+      ClassLoader.class,
+      Class.class,
+      Constructor.class,
+      // DexCache is not on the compile classpath
+      Class.forName("java.lang.DexCache"),
+      // EmulatedStackFrame is not on the compile classpath
+      Class.forName("dalvik.system.EmulatedStackFrame"),
+      Executable.class,
+      Field.class,
+      FinalizerReference.class,
+      MethodHandle.class,
+      MethodHandles.Lookup.class,
+      MethodType.class,
+      Method.class,
+      Object.class,
+      Proxy.class,
+      Reference.class,
+      StackTraceElement.class,
+      String.class,
+      Thread.class,
+      Throwable.class,
+      VarHandle.class,
+      // TODO all the var handle types.
+      Class.forName("java.lang.invoke.FieldVarHandle"),
+    };
+    System.out.println("Checking mirror'd classes");
+    Check(mirrord_classes);
+    // The results of some of these will change as we improve structural class redefinition. Any
+    // that are true should always remain so though.
+    Class[] non_mirrord_classes = new Class[] {
+      ArrayList.class,
+      Objects.class,
+      Arrays.class,
+      new Object[0].getClass(),
+      Integer.class,
+      Number.class,
+      NoVirtuals.class,
+      WithVirtuals.class,
+      SubWithVirtuals.class,
+      MethodHandles.class,
+    };
+    System.out.println("Checking non-mirror'd classes");
+    Check(non_mirrord_classes);
+  }
+}
diff --git a/test/Android.bp b/test/Android.bp
index 7199f47..86d1857 100644
--- a/test/Android.bp
+++ b/test/Android.bp
@@ -301,6 +301,8 @@
         "1969-force-early-return-void/force_early_return_void.cc",
         "1970-force-early-return-long/force_early_return_long.cc",
         "1974-resize-array/resize_array.cc",
+        "1975-hello-structural-transformation/structural_transform.cc",
+        "1976-hello-structural-static-methods/structural_transform_methods.cc",
     ],
     // Use NDK-compatible headers for ctstiagent.
     header_libs: [
diff --git a/test/common/runtime_state.cc b/test/common/runtime_state.cc
index 88d7a9f..2c1a43a 100644
--- a/test/common/runtime_state.cc
+++ b/test/common/runtime_state.cc
@@ -19,6 +19,7 @@
 #include <android-base/logging.h>
 #include <android-base/macros.h>
 
+#include "art_field.h"
 #include "art_method-inl.h"
 #include "base/enums.h"
 #include "common_throws.h"
@@ -29,12 +30,14 @@
 #include "jit/profiling_info.h"
 #include "jni/jni_internal.h"
 #include "mirror/class-inl.h"
+#include "mirror/class.h"
 #include "nativehelper/ScopedUtfChars.h"
 #include "oat_file.h"
 #include "oat_quick_method_header.h"
 #include "profile/profile_compilation_info.h"
 #include "runtime.h"
 #include "scoped_thread_state_change-inl.h"
+#include "scoped_thread_state_change.h"
 #include "thread-current-inl.h"
 
 namespace art {
@@ -378,4 +381,16 @@
   Runtime::Current()->SetTargetSdkVersion(static_cast<uint32_t>(version));
 }
 
+extern "C" JNIEXPORT jlong JNICALL Java_Main_genericFieldOffset(JNIEnv* env, jclass, jobject fld) {
+  jfieldID fid = env->FromReflectedField(fld);
+  ScopedObjectAccess soa(env);
+  ArtField* af = jni::DecodeArtField(fid);
+  return af->GetOffset().Int32Value();
+}
+
+extern "C" JNIEXPORT jboolean JNICALL Java_Main_isObsoleteObject(JNIEnv* env, jclass, jclass c) {
+  ScopedObjectAccess soa(env);
+  return soa.Decode<mirror::Class>(c)->IsObsoleteObject();
+}
+
 }  // namespace art
diff --git a/test/jvmti-common/Redefinition.java b/test/jvmti-common/Redefinition.java
index 56d2938..2ebce17 100644
--- a/test/jvmti-common/Redefinition.java
+++ b/test/jvmti-common/Redefinition.java
@@ -88,4 +88,7 @@
   public static native void addCommonTransformationResult(String target_name,
                                                           byte[] class_bytes,
                                                           byte[] dex_bytes);
+
+  public static native void doCommonStructuralClassRedefinition(Class<?> target, byte[] dex_file);
+  public static native boolean isStructurallyModifiable(Class<?> target);
 }
diff --git a/test/knownfailures.json b/test/knownfailures.json
index 2611077..42126d8 100644
--- a/test/knownfailures.json
+++ b/test/knownfailures.json
@@ -1121,7 +1121,16 @@
                   "1957-error-ext",
                   "1972-jni-id-swap-indices",
                   "1973-jni-id-swap-pointer",
-                  "1974-resize-array"
+                  "1974-resize-array",
+                  "1975-hello-structural-transformation",
+                  "1976-hello-structural-static-methods",
+                  "1977-hello-structural-obsolescence",
+                  "1978-regular-obsolete-then-structural-obsolescence",
+                  "1979-threaded-structural-transformation",
+                  "1980-obsolete-object-cleared",
+                  "1981-structural-redef-private-method-handles",
+                  "1982-no-virtuals-structural-redefinition",
+                  "1983-structural-redefinition-failures"
                 ],
         "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 bceaa6b..ae1a8d3 100644
--- a/test/ti-agent/jvmti_helper.cc
+++ b/test/ti-agent/jvmti_helper.cc
@@ -15,6 +15,7 @@
  */
 
 #include "jvmti_helper.h"
+#include "jvmti.h"
 #include "test_env.h"
 
 #include <dlfcn.h>
@@ -231,4 +232,32 @@
   __builtin_unreachable();
 }
 
+void DeallocParams(jvmtiEnv* env, jvmtiParamInfo* params, jint n_params) {
+  for (jint i = 0; i < n_params; i++) {
+    Dealloc(env, params[i].name);
+  }
+}
+
+void* GetExtensionFunctionVoid(JNIEnv* env, jvmtiEnv* jvmti, const std::string_view& name) {
+  jint n_ext = 0;
+  void* res = nullptr;
+  jvmtiExtensionFunctionInfo* infos = nullptr;
+  if (JvmtiErrorToException(env, jvmti, jvmti->GetExtensionFunctions(&n_ext, &infos))) {
+    return nullptr;
+  }
+  for (jint i = 0; i < n_ext; i++) {
+    const jvmtiExtensionFunctionInfo& info = infos[i];
+    if (name == info.id) {
+      res = reinterpret_cast<void*>(info.func);
+    }
+    DeallocParams(jvmti, info.params, info.param_count);
+    Dealloc(jvmti, info.short_description, info.errors, info.id, info.params);
+  }
+  Dealloc(jvmti, infos);
+  if (res == nullptr) {
+    JvmtiErrorToException(env, jvmti, JVMTI_ERROR_NOT_FOUND);
+  }
+  return res;
+}
+
 }  // namespace art
diff --git a/test/ti-agent/jvmti_helper.h b/test/ti-agent/jvmti_helper.h
index a47a402..a3b9535 100644
--- a/test/ti-agent/jvmti_helper.h
+++ b/test/ti-agent/jvmti_helper.h
@@ -79,6 +79,21 @@
 // To print jvmtiError. Does not rely on GetErrorName, so is an approximation.
 std::ostream& operator<<(std::ostream& os, const jvmtiError& rhs);
 
+template <typename T> void Dealloc(jvmtiEnv* env, T* t) {
+  env->Deallocate(reinterpret_cast<unsigned char*>(t));
+}
+
+template <typename T, typename... Rest> void Dealloc(jvmtiEnv* env, T* t, Rest... rs) {
+  Dealloc(env, t);
+  Dealloc(env, rs...);
+}
+
+void* GetExtensionFunctionVoid(JNIEnv* env, jvmtiEnv* jvmti, const std::string_view& name);
+
+template<typename T> T GetExtensionFunction(JNIEnv* env, jvmtiEnv* jvmti, const std::string_view& name) {
+  return reinterpret_cast<T>(GetExtensionFunctionVoid(env, jvmti, 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 0e4b1bd..9d9f13f 100644
--- a/test/ti-agent/redefinition_helper.cc
+++ b/test/ti-agent/redefinition_helper.cc
@@ -129,6 +129,36 @@
   return DoMultiClassRedefine(jvmti_env, env, 1, &target, &class_file_bytes, &dex_file_bytes);
 }
 
+extern "C" JNIEXPORT jboolean JNICALL
+Java_art_Redefinition_isStructurallyModifiable(JNIEnv* env, jclass, jclass target) {
+  using ArtCanStructurallyRedefineClass =
+      jvmtiError (*)(jvmtiEnv * env, jclass k, jboolean * result);
+  ArtCanStructurallyRedefineClass can_redef = GetExtensionFunction<ArtCanStructurallyRedefineClass>(
+      env, jvmti_env, "com.android.art.class.is_structurally_modifiable_class");
+  if (can_redef == nullptr || env->ExceptionCheck()) {
+    return false;
+  }
+  jboolean result = false;
+  JvmtiErrorToException(env, jvmti_env, can_redef(jvmti_env, target, &result));
+  return result;
+}
+
+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));
+}
+
 // 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(