Consolidate updating of reflective Field/Method references

Previously we used several different visitors to update Field &
Method references for structural redefinition. We also did not visit
or update JVMTI based references.

This consolidates all the visitors to a single
Runtime::VisitReflectiveTargets function with a single
ReflectiveTargetVisitor type. This simplifies the code around
structural redefinition and ensures that the reflective value holders
are in charge of the actual replacement.

Support was also added for walking internal openjdkjvmti references
for things like field-read/modification events.

Test: ./test.py --host
Bug: 134162467

Change-Id: Ic5fc1db7db0a30f947a1a67259dc024e149ebd57
diff --git a/openjdkjvmti/OpenjdkJvmTi.cc b/openjdkjvmti/OpenjdkJvmTi.cc
index 0300c11..b49762c 100644
--- a/openjdkjvmti/OpenjdkJvmTi.cc
+++ b/openjdkjvmti/OpenjdkJvmTi.cc
@@ -1536,6 +1536,8 @@
   HeapExtensions::Register(gEventHandler);
   SearchUtil::Register();
   HeapUtil::Register();
+  FieldUtil::Register(gEventHandler);
+  BreakpointUtil::Register(gEventHandler);
   Transformer::Setup();
 
   {
@@ -1559,6 +1561,8 @@
   MethodUtil::Unregister();
   SearchUtil::Unregister();
   HeapUtil::Unregister();
+  FieldUtil::Unregister();
+  BreakpointUtil::Unregister();
 
   // TODO It would be good to delete the gEventHandler and gDeoptManager here but we cannot since
   // daemon threads might be suspended and we want to make sure that even if they wake up briefly
diff --git a/openjdkjvmti/ti_breakpoint.cc b/openjdkjvmti/ti_breakpoint.cc
index 813aa8e..13d8db7 100644
--- a/openjdkjvmti/ti_breakpoint.cc
+++ b/openjdkjvmti/ti_breakpoint.cc
@@ -53,6 +53,69 @@
 
 namespace openjdkjvmti {
 
+class JvmtiBreakpointReflectionSource : public art::ReflectionSourceInfo {
+ public:
+  JvmtiBreakpointReflectionSource(size_t pc, art::ArtMethod* m)
+      : art::ReflectionSourceInfo(art::ReflectionSourceType::kSourceMiscInternal),
+        pc_(pc),
+        m_(m) {}
+
+  void Describe(std::ostream& os) const override REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    art::ReflectionSourceInfo::Describe(os);
+    os << " jvmti Breakpoint Method=" << m_->PrettyMethod() << " PC=" << pc_;
+  }
+
+ private:
+  size_t pc_;
+  art::ArtMethod* m_;
+};
+
+class BreakpointReflectiveValueCallback : public art::ReflectiveValueVisitCallback {
+ public:
+  void VisitReflectiveTargets(art::ReflectiveValueVisitor* visitor)
+      REQUIRES(art::Locks::mutator_lock_) {
+    art::Thread* self = art::Thread::Current();
+    eh_->ForEachEnv(self, [&](ArtJvmTiEnv* env) NO_THREAD_SAFETY_ANALYSIS {
+      art::Locks::mutator_lock_->AssertExclusiveHeld(self);
+      art::WriterMutexLock mu(self, env->event_info_mutex_);
+      std::vector<std::pair<Breakpoint, Breakpoint>> updated_breakpoints;
+      for (auto it : env->breakpoints) {
+        art::ArtMethod* orig_method = it.GetMethod();
+        art::ArtMethod* am = visitor->VisitMethod(
+            orig_method, JvmtiBreakpointReflectionSource(it.GetLocation(), orig_method));
+        if (am != orig_method) {
+          updated_breakpoints.push_back({ Breakpoint { am, it.GetLocation() }, it });
+        }
+      }
+      for (auto it : updated_breakpoints) {
+        DCHECK(env->breakpoints.find(it.second) != env->breakpoints.end());
+        env->breakpoints.erase(it.second);
+        env->breakpoints.insert(it.first);
+      }
+    });
+  }
+
+  EventHandler* eh_;
+};
+
+static BreakpointReflectiveValueCallback gReflectiveValueCallback;
+void BreakpointUtil::Register(EventHandler* eh) {
+  gReflectiveValueCallback.eh_ = eh;
+  art::ScopedThreadStateChange stsc(art::Thread::Current(),
+                                    art::ThreadState::kWaitingForDebuggerToAttach);
+  art::ScopedSuspendAll ssa("Add breakpoint reflective value visit callback");
+  art::RuntimeCallbacks* callbacks = art::Runtime::Current()->GetRuntimeCallbacks();
+  callbacks->AddReflectiveValueVisitCallback(&gReflectiveValueCallback);
+}
+
+void BreakpointUtil::Unregister() {
+  art::ScopedThreadStateChange stsc(art::Thread::Current(),
+                                    art::ThreadState::kWaitingForDebuggerToAttach);
+  art::ScopedSuspendAll ssa("Remove reflective value visit callback");
+  art::RuntimeCallbacks* callbacks = art::Runtime::Current()->GetRuntimeCallbacks();
+  callbacks->RemoveReflectiveValueVisitCallback(&gReflectiveValueCallback);
+}
+
 size_t Breakpoint::hash() const {
   return std::hash<uintptr_t> {}(reinterpret_cast<uintptr_t>(method_))
       ^ std::hash<jlocation> {}(location_);
diff --git a/openjdkjvmti/ti_breakpoint.h b/openjdkjvmti/ti_breakpoint.h
index 7aa33ae..96610c3 100644
--- a/openjdkjvmti/ti_breakpoint.h
+++ b/openjdkjvmti/ti_breakpoint.h
@@ -47,6 +47,7 @@
 namespace openjdkjvmti {
 
 struct ArtJvmTiEnv;
+class EventHandler;
 
 class Breakpoint {
  public:
@@ -74,6 +75,9 @@
 
 class BreakpointUtil {
  public:
+  static void Register(EventHandler* eh);
+  static void Unregister();
+
   static jvmtiError SetBreakpoint(jvmtiEnv* env, jmethodID method, jlocation location);
   static jvmtiError ClearBreakpoint(jvmtiEnv* env, jmethodID method, jlocation location);
   // Used by class redefinition to remove breakpoints on redefined classes.
diff --git a/openjdkjvmti/ti_field.cc b/openjdkjvmti/ti_field.cc
index 2a860d9..e5b9637 100644
--- a/openjdkjvmti/ti_field.cc
+++ b/openjdkjvmti/ti_field.cc
@@ -30,19 +30,100 @@
  */
 
 #include "ti_field.h"
+#include <unordered_map>
 
+#include "android-base/thread_annotations.h"
 #include "art_field-inl.h"
+#include "art_field.h"
 #include "art_jvmti.h"
 #include "base/enums.h"
+#include "base/locks.h"
 #include "dex/dex_file_annotations.h"
 #include "dex/modifiers.h"
 #include "jni/jni_internal.h"
 #include "mirror/object_array-inl.h"
+#include "reflective_value_visitor.h"
+#include "runtime.h"
+#include "runtime_callbacks.h"
 #include "scoped_thread_state_change-inl.h"
 #include "thread-current-inl.h"
 
 namespace openjdkjvmti {
 
+class JvmtiFieldReflectionSource : public art::ReflectionSourceInfo {
+ public:
+  JvmtiFieldReflectionSource(bool is_access, art::ArtField* f)
+      : art::ReflectionSourceInfo(art::ReflectionSourceType::kSourceMiscInternal),
+        is_access_(is_access),
+        f_(f) {}
+  void Describe(std::ostream& os) const override REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    art::ReflectionSourceInfo::Describe(os);
+    os << " jvmti Field" << (is_access_ ? "Access" : "Modification")
+       << "Watch Target=" << f_->PrettyField();
+  }
+
+ private:
+  bool is_access_;
+  art::ArtField* f_;
+};
+struct FieldReflectiveValueCallback : public art::ReflectiveValueVisitCallback {
+ public:
+  void VisitReflectiveTargets(art::ReflectiveValueVisitor* visitor)
+      REQUIRES(art::Locks::mutator_lock_) {
+    art::Thread* self = art::Thread::Current();
+    event_handler->ForEachEnv(self, [&](ArtJvmTiEnv* env) NO_THREAD_SAFETY_ANALYSIS {
+      art::Locks::mutator_lock_->AssertExclusiveHeld(self);
+      art::WriterMutexLock mu(self, env->event_info_mutex_);
+      std::vector<std::pair<art::ArtField*, art::ArtField*>> updated_access_fields;
+      for (auto it : env->access_watched_fields) {
+        art::ArtField* af =
+            visitor->VisitField(it, JvmtiFieldReflectionSource(/*is_access=*/true, it));
+        if (af != it) {
+          updated_access_fields.push_back({ af, it });
+        }
+      }
+      for (auto it : updated_access_fields) {
+        DCHECK(env->access_watched_fields.find(it.second) != env->access_watched_fields.end());
+        env->access_watched_fields.erase(it.second);
+        env->access_watched_fields.insert(it.first);
+      }
+      std::vector<std::pair<art::ArtField*, art::ArtField*>> updated_modify_fields;
+      for (auto it : env->modify_watched_fields) {
+        art::ArtField* af =
+            visitor->VisitField(it, JvmtiFieldReflectionSource(/*is_access=*/false, it));
+        if (af != it) {
+          updated_modify_fields.push_back({ af, it });
+        }
+      }
+      for (auto it : updated_modify_fields) {
+        DCHECK(env->modify_watched_fields.find(it.second) != env->modify_watched_fields.end());
+        env->modify_watched_fields.erase(it.second);
+        env->modify_watched_fields.insert(it.first);
+      }
+    });
+  }
+
+  EventHandler* event_handler = nullptr;
+};
+
+static FieldReflectiveValueCallback gReflectiveValueCallback;
+
+void FieldUtil::Register(EventHandler* eh) {
+  gReflectiveValueCallback.event_handler = eh;
+  art::ScopedThreadStateChange stsc(art::Thread::Current(),
+                                    art::ThreadState::kWaitingForDebuggerToAttach);
+  art::ScopedSuspendAll ssa("Add reflective value visit callback");
+  art::RuntimeCallbacks* callbacks = art::Runtime::Current()->GetRuntimeCallbacks();
+  callbacks->AddReflectiveValueVisitCallback(&gReflectiveValueCallback);
+}
+
+void FieldUtil::Unregister() {
+  art::ScopedThreadStateChange stsc(art::Thread::Current(),
+                                    art::ThreadState::kWaitingForDebuggerToAttach);
+  art::ScopedSuspendAll ssa("Remove reflective value visit callback");
+  art::RuntimeCallbacks* callbacks = art::Runtime::Current()->GetRuntimeCallbacks();
+  callbacks->RemoveReflectiveValueVisitCallback(&gReflectiveValueCallback);
+}
 // Note: For all these functions, we could do a check that the field actually belongs to the given
 //       class. But the spec seems to assume a certain encoding of the field ID, and so doesn't
 //       specify any errors.
diff --git a/openjdkjvmti/ti_field.h b/openjdkjvmti/ti_field.h
index 3cf29f0..073c6cc 100644
--- a/openjdkjvmti/ti_field.h
+++ b/openjdkjvmti/ti_field.h
@@ -71,6 +71,9 @@
       REQUIRES(!ArtJvmTiEnv::event_info_mutex_);
   static jvmtiError ClearFieldAccessWatch(jvmtiEnv* env, jclass klass, jfieldID field)
       REQUIRES(!ArtJvmTiEnv::event_info_mutex_);
+
+  static void Register(EventHandler* eh);
+  static void Unregister();
 };
 
 }  // namespace openjdkjvmti
diff --git a/openjdkjvmti/ti_redefine.cc b/openjdkjvmti/ti_redefine.cc
index 97cf98b..1ddc34b 100644
--- a/openjdkjvmti/ti_redefine.cc
+++ b/openjdkjvmti/ti_redefine.cc
@@ -107,7 +107,7 @@
 #include "mirror/object_array-inl.h"
 #include "mirror/object_array.h"
 #include "mirror/string.h"
-#include "mirror/var_handle-inl.h"
+#include "mirror/var_handle.h"
 #include "nativehelper/scoped_local_ref.h"
 #include "non_debuggable_classes.h"
 #include "obj_ptr.h"
@@ -1967,65 +1967,6 @@
   }
 }
 
-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.
@@ -2082,63 +2023,13 @@
     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
+  // Update live pointers in ART code.
   // 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(),
+  driver_->runtime_->VisitReflectiveTargets(
       [&](art::ArtField* f, const auto& info) REQUIRES(art::Locks::mutator_lock_) {
         auto it = field_map.find(f);
         if (it == field_map.end()) {
@@ -2152,7 +2043,8 @@
         if (it == method_map.end()) {
           return m;
         }
-        VLOG(plugin) << "Updating " << info << " object for (method) " << it->second->PrettyMethod();
+        VLOG(plugin) << "Updating " << info << " object for (method) "
+                     << it->second->PrettyMethod();
         return it->second;
       });
 
@@ -2193,80 +2085,6 @@
     }
   }
 
-  // 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.
diff --git a/runtime/Android.bp b/runtime/Android.bp
index 477362b..3809541 100644
--- a/runtime/Android.bp
+++ b/runtime/Android.bp
@@ -189,6 +189,7 @@
         "read_barrier.cc",
         "reference_table.cc",
         "reflection.cc",
+        "reflective_value_visitor.cc",
         "runtime.cc",
         "runtime_callbacks.cc",
         "runtime_common.cc",
@@ -498,6 +499,7 @@
         "oat.h",
         "object_callbacks.h",
         "process_state.h",
+        "reflective_value_visitor.h",
         "stack.h",
         "suspend_reason.h",
         "thread.h",
diff --git a/runtime/gc/heap.cc b/runtime/gc/heap.cc
index 8f1508a..b3a46ca 100644
--- a/runtime/gc/heap.cc
+++ b/runtime/gc/heap.cc
@@ -41,6 +41,7 @@
 #include "base/systrace.h"
 #include "base/time_utils.h"
 #include "base/utils.h"
+#include "class_root.h"
 #include "common_throws.h"
 #include "debugger.h"
 #include "dex/dex_file-inl.h"
@@ -80,10 +81,14 @@
 #include "jit/jit_code_cache.h"
 #include "jni/java_vm_ext.h"
 #include "mirror/class-inl.h"
+#include "mirror/executable-inl.h"
+#include "mirror/field.h"
+#include "mirror/method_handle_impl.h"
 #include "mirror/object-inl.h"
 #include "mirror/object-refvisitor-inl.h"
 #include "mirror/object_array-inl.h"
 #include "mirror/reference-inl.h"
+#include "mirror/var_handle.h"
 #include "nativehelper/scoped_local_ref.h"
 #include "obj_ptr-inl.h"
 #include "reflection.h"
@@ -4193,5 +4198,27 @@
   }
 }
 
+void Heap::VisitReflectiveTargets(ReflectiveValueVisitor *visit) {
+  VisitObjectsPaused([&visit](mirror::Object* ref) NO_THREAD_SAFETY_ANALYSIS {
+    art::ObjPtr<mirror::Class> klass(ref->GetClass());
+    // All these classes are in the BootstrapClassLoader.
+    if (!klass->IsBootStrapClassLoaded()) {
+      return;
+    }
+    if (GetClassRoot<mirror::Method>()->IsAssignableFrom(klass) ||
+        GetClassRoot<mirror::Constructor>()->IsAssignableFrom(klass)) {
+      down_cast<mirror::Executable*>(ref)->VisitTarget(visit);
+    } else if (art::GetClassRoot<art::mirror::Field>() == klass) {
+      down_cast<mirror::Field*>(ref)->VisitTarget(visit);
+    } else if (art::GetClassRoot<art::mirror::MethodHandle>()->IsAssignableFrom(klass)) {
+      down_cast<mirror::MethodHandle*>(ref)->VisitTarget(visit);
+    } else if (art::GetClassRoot<art::mirror::FieldVarHandle>()->IsAssignableFrom(klass)) {
+      down_cast<mirror::FieldVarHandle*>(ref)->VisitTarget(visit);
+    } else if (art::GetClassRoot<art::mirror::DexCache>()->IsAssignableFrom(klass)) {
+      down_cast<mirror::DexCache*>(ref)->VisitReflectiveTargets(visit);
+    }
+  });
+}
+
 }  // namespace gc
 }  // namespace art
diff --git a/runtime/gc/heap.h b/runtime/gc/heap.h
index 86d6b82..de94d37 100644
--- a/runtime/gc/heap.h
+++ b/runtime/gc/heap.h
@@ -51,6 +51,7 @@
 enum class InstructionSet;
 class IsMarkedVisitor;
 class Mutex;
+class ReflectiveValueVisitor;
 class RootVisitor;
 class StackVisitor;
 class Thread;
@@ -286,6 +287,9 @@
   ALWAYS_INLINE void VisitObjectsPaused(Visitor&& visitor)
       REQUIRES(Locks::mutator_lock_, !Locks::heap_bitmap_lock_, !*gc_complete_lock_);
 
+  void VisitReflectiveTargets(ReflectiveValueVisitor* visitor)
+      REQUIRES(Locks::mutator_lock_, !Locks::heap_bitmap_lock_, !*gc_complete_lock_);
+
   void CheckPreconditionsForAllocObject(ObjPtr<mirror::Class> c, size_t byte_count)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
diff --git a/runtime/jni/jni_id_manager.cc b/runtime/jni/jni_id_manager.cc
index 9ae8c89..2553fdf 100644
--- a/runtime/jni/jni_id_manager.cc
+++ b/runtime/jni/jni_id_manager.cc
@@ -28,11 +28,13 @@
 #include "jni/jni_internal.h"
 #include "jni_id_type.h"
 #include "mirror/array-inl.h"
+#include "mirror/array.h"
 #include "mirror/class-inl.h"
 #include "mirror/class.h"
-#include "mirror/class_ext.h"
+#include "mirror/class_ext-inl.h"
 #include "mirror/object-inl.h"
 #include "obj_ptr-inl.h"
+#include "reflective_value_visitor.h"
 #include "thread-inl.h"
 #include "thread.h"
 #include <algorithm>
@@ -310,18 +312,80 @@
   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);
+void JniIdManager::VisitReflectiveTargets(ReflectiveValueVisitor* rvv) {
+  art::WriterMutexLock mu(Thread::Current(), *Locks::jni_id_lock_);
+  for (auto it = field_id_map_.begin(); it != field_id_map_.end(); ++it) {
+    ArtField* old_field = *it;
+    uintptr_t id = IndexToId(std::distance(field_id_map_.begin(), it));
+    ArtField* new_field =
+        rvv->VisitField(old_field, JniIdReflectiveSourceInfo(reinterpret_cast<jfieldID>(id)));
+    if (old_field != new_field) {
+      *it = new_field;
+      ObjPtr<mirror::Class> old_class(old_field->GetDeclaringClass());
+      ObjPtr<mirror::Class> new_class(new_field->GetDeclaringClass());
+      ObjPtr<mirror::ClassExt> old_ext_data(old_class->GetExtData());
+      ObjPtr<mirror::ClassExt> new_ext_data(new_class->GetExtData());
+      if (!old_ext_data.IsNull()) {
+        // Clear the old field mapping.
+        if (old_field->IsStatic()) {
+          size_t old_off = ArraySlice<ArtField>(old_class->GetSFieldsPtr()).OffsetOf(old_field);
+          ObjPtr<mirror::PointerArray> old_statics(old_ext_data->GetStaticJFieldIDs());
+          if (!old_statics.IsNull()) {
+            old_statics->SetElementPtrSize(old_off, 0, kRuntimePointerSize);
+          }
+        } else {
+          size_t old_off = ArraySlice<ArtField>(old_class->GetIFieldsPtr()).OffsetOf(old_field);
+          ObjPtr<mirror::PointerArray> old_instances(old_ext_data->GetInstanceJFieldIDs());
+          if (!old_instances.IsNull()) {
+            old_instances->SetElementPtrSize(old_off, 0, kRuntimePointerSize);
+          }
+        }
+      }
+      if (!new_ext_data.IsNull()) {
+        // Set the new field mapping.
+        if (new_field->IsStatic()) {
+          size_t new_off = ArraySlice<ArtField>(new_class->GetSFieldsPtr()).OffsetOf(new_field);
+          ObjPtr<mirror::PointerArray> new_statics(new_ext_data->GetStaticJFieldIDs());
+          if (!new_statics.IsNull()) {
+            new_statics->SetElementPtrSize(new_off, id, kRuntimePointerSize);
+          }
+        } else {
+          size_t new_off = ArraySlice<ArtField>(new_class->GetIFieldsPtr()).OffsetOf(new_field);
+          ObjPtr<mirror::PointerArray> new_instances(new_ext_data->GetInstanceJFieldIDs());
+          if (!new_instances.IsNull()) {
+            new_instances->SetElementPtrSize(new_off, id, kRuntimePointerSize);
+          }
+        }
+      }
     }
   }
-  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);
+  for (auto it = method_id_map_.begin(); it != method_id_map_.end(); ++it) {
+    ArtMethod* old_method = *it;
+    uintptr_t id = IndexToId(std::distance(method_id_map_.begin(), it));
+    ArtMethod* new_method =
+        rvv->VisitMethod(old_method, JniIdReflectiveSourceInfo(reinterpret_cast<jmethodID>(id)));
+    if (old_method != new_method) {
+      *it = new_method;
+      ObjPtr<mirror::Class> old_class(old_method->GetDeclaringClass());
+      ObjPtr<mirror::Class> new_class(new_method->GetDeclaringClass());
+      ObjPtr<mirror::ClassExt> old_ext_data(old_class->GetExtData());
+      ObjPtr<mirror::ClassExt> new_ext_data(new_class->GetExtData());
+      if (!old_ext_data.IsNull()) {
+        // Clear the old method mapping.
+        size_t old_off = ArraySlice<ArtMethod>(old_class->GetMethodsPtr()).OffsetOf(old_method);
+        ObjPtr<mirror::PointerArray> old_methods(old_ext_data->GetJMethodIDs());
+        if (!old_methods.IsNull()) {
+          old_methods->SetElementPtrSize(old_off, 0, kRuntimePointerSize);
+        }
+      }
+      if (!new_ext_data.IsNull()) {
+        // Set the new method mapping.
+        size_t new_off = ArraySlice<ArtMethod>(new_class->GetMethodsPtr()).OffsetOf(new_method);
+        ObjPtr<mirror::PointerArray> new_methods(new_ext_data->GetJMethodIDs());
+        if (!new_methods.IsNull()) {
+          new_methods->SetElementPtrSize(new_off, id, kRuntimePointerSize);
+        }
+      }
     }
   }
 }
diff --git a/runtime/jni/jni_id_manager.h b/runtime/jni/jni_id_manager.h
index 7b2f3c4..6b43534 100644
--- a/runtime/jni/jni_id_manager.h
+++ b/runtime/jni/jni_id_manager.h
@@ -25,6 +25,7 @@
 #include "art_method.h"
 #include "base/mutex.h"
 #include "jni_id_type.h"
+#include "reflective_value_visitor.h"
 
 namespace art {
 namespace jni {
@@ -32,15 +33,6 @@
 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>>>
@@ -55,33 +47,8 @@
   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);
-  }
+  void VisitReflectiveTargets(ReflectiveValueVisitor* rvv)
+      REQUIRES(Locks::mutator_lock_, !Locks::jni_id_lock_);
 
  private:
   template <typename ArtType>
diff --git a/runtime/mirror/dex_cache.cc b/runtime/mirror/dex_cache.cc
index f97f521..c2c47a5 100644
--- a/runtime/mirror/dex_cache.cc
+++ b/runtime/mirror/dex_cache.cc
@@ -25,6 +25,7 @@
 #include "object-inl.h"
 #include "object.h"
 #include "object_array-inl.h"
+#include "reflective_value_visitor.h"
 #include "runtime.h"
 #include "runtime_globals.h"
 #include "string.h"
@@ -172,6 +173,27 @@
                   dex_file->NumCallSiteIds());
 }
 
+void DexCache::VisitReflectiveTargets(ReflectiveValueVisitor* visitor) {
+  for (size_t i = 0; i < NumResolvedFields(); i++) {
+    auto pair(GetNativePairPtrSize(GetResolvedFields(), i, kRuntimePointerSize));
+    ArtField* new_val = visitor->VisitField(
+        pair.object, DexCacheSourceInfo(kSourceDexCacheResolvedField, pair.index, this));
+    if (UNLIKELY(new_val != pair.object)) {
+      pair.object = new_val;
+      SetNativePairPtrSize(GetResolvedFields(), i, pair, kRuntimePointerSize);
+    }
+  }
+  for (size_t i = 0; i < NumResolvedMethods(); i++) {
+    auto pair(GetNativePairPtrSize(GetResolvedMethods(), i, kRuntimePointerSize));
+    ArtMethod* new_val = visitor->VisitMethod(
+        pair.object, DexCacheSourceInfo(kSourceDexCacheResolvedMethod, pair.index, this));
+    if (UNLIKELY(new_val != pair.object)) {
+      pair.object = new_val;
+      SetNativePairPtrSize(GetResolvedMethods(), i, pair, kRuntimePointerSize);
+    }
+  }
+}
+
 bool DexCache::AddPreResolvedStringsArray() {
   DCHECK_EQ(NumPreResolvedStrings(), 0u);
   Thread* const self = Thread::Current();
diff --git a/runtime/mirror/dex_cache.h b/runtime/mirror/dex_cache.h
index b41443e..292db14 100644
--- a/runtime/mirror/dex_cache.h
+++ b/runtime/mirror/dex_cache.h
@@ -37,6 +37,7 @@
 class DexFile;
 union JValue;
 class LinearAlloc;
+class ReflectiveValueVisitor;
 class Thread;
 
 namespace mirror {
@@ -476,6 +477,8 @@
   // Returns true if we succeeded in adding the pre-resolved string array.
   bool AddPreResolvedStringsArray() REQUIRES_SHARED(Locks::mutator_lock_);
 
+  void VisitReflectiveTargets(ReflectiveValueVisitor* visitor) REQUIRES(Locks::mutator_lock_);
+
  private:
   void Init(const DexFile* dex_file,
             ObjPtr<String> location,
diff --git a/runtime/mirror/executable-inl.h b/runtime/mirror/executable-inl.h
index ce35d6e..f2d684a 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 "reflective_value_visitor.h"
 #include "verify_object.h"
 
 namespace art {
@@ -37,10 +38,11 @@
   return GetFieldObject<mirror::Class>(DeclaringClassOffset());
 }
 
-template<typename Visitor, VerifyObjectFlags kVerifiyFlags>
-inline void Executable::VisitTarget(Visitor&& v) {
+template<VerifyObjectFlags kVerifiyFlags>
+inline void Executable::VisitTarget(ReflectiveValueVisitor* v) {
+  HeapReflectiveSourceInfo hrsi(kSourceJavaLangReflectExecutable, this);
   ArtMethod* orig = GetArtMethod<kVerifiyFlags>();
-  ArtMethod* new_target = v(orig);
+  ArtMethod* new_target = v->VisitMethod(orig, hrsi);
   if (orig != new_target) {
     SetArtMethod(new_target);
     SetDexMethodIndex(new_target->GetDexMethodIndex());
diff --git a/runtime/mirror/executable.h b/runtime/mirror/executable.h
index 8eca206..750a167 100644
--- a/runtime/mirror/executable.h
+++ b/runtime/mirror/executable.h
@@ -25,6 +25,7 @@
 
 struct ExecutableOffsets;
 class ArtMethod;
+class ReflectiveValueVisitor;
 
 namespace mirror {
 
@@ -41,9 +42,8 @@
     return reinterpret_cast64<ArtMethod*>(GetField64<kVerifyFlags>(ArtMethodOffset()));
   }
 
-  template <typename Visitor,
-            VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags>
-  inline void VisitTarget(Visitor&& v) REQUIRES(Locks::mutator_lock_);
+  template <VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags>
+  inline void VisitTarget(ReflectiveValueVisitor* v) REQUIRES(Locks::mutator_lock_);
 
   template <bool kTransactionActive = false,
             bool kCheckTransaction = true,
diff --git a/runtime/mirror/field-inl.h b/runtime/mirror/field-inl.h
index 6e82d6d..ac11be1 100644
--- a/runtime/mirror/field-inl.h
+++ b/runtime/mirror/field-inl.h
@@ -104,18 +104,6 @@
   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 9a40006..aa071a8 100644
--- a/runtime/mirror/field.cc
+++ b/runtime/mirror/field.cc
@@ -24,6 +24,18 @@
 namespace art {
 namespace mirror {
 
+void Field::VisitTarget(ReflectiveValueVisitor* v) {
+  HeapReflectiveSourceInfo hrsi(kSourceJavaLangReflectField, this);
+  ArtField* orig = GetArtField(/*use_dex_cache*/false);
+  ArtField* new_value = v->VisitField(orig, hrsi);
+  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));
+}
+
 ArtField* Field::GetArtField(bool use_dex_cache) {
   ObjPtr<mirror::Class> declaring_class = GetDeclaringClass();
   if (UNLIKELY(declaring_class->IsProxyClass())) {
diff --git a/runtime/mirror/field.h b/runtime/mirror/field.h
index 89c86e3..2ed3452 100644
--- a/runtime/mirror/field.h
+++ b/runtime/mirror/field.h
@@ -29,6 +29,7 @@
 
 class ArtField;
 struct FieldOffsets;
+class ReflectiveValueVisitor;
 
 namespace mirror {
 
@@ -82,8 +83,7 @@
 
   // 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_);
+  void VisitTarget(ReflectiveValueVisitor* v) REQUIRES(Locks::mutator_lock_);
 
  private:
   // Padding required for matching alignment with the Java peer.
diff --git a/runtime/mirror/method_handle_impl-inl.h b/runtime/mirror/method_handle_impl-inl.h
index 0085642..932b434 100644
--- a/runtime/mirror/method_handle_impl-inl.h
+++ b/runtime/mirror/method_handle_impl-inl.h
@@ -39,20 +39,6 @@
       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.cc b/runtime/mirror/method_handle_impl.cc
index 433d4ba..dd25fc9 100644
--- a/runtime/mirror/method_handle_impl.cc
+++ b/runtime/mirror/method_handle_impl.cc
@@ -54,5 +54,20 @@
   return mh.Get();
 }
 
+void MethodHandle::VisitTarget(ReflectiveValueVisitor* v) {
+  void* target = GetTargetField();
+  void* result;
+  HeapReflectiveSourceInfo hrsi(kSourceJavaLangInvokeMethodHandle, this);
+  if (GetHandleKind() < kFirstAccessorKind) {
+    result = v->VisitMethod(GetTargetMethod(), hrsi);
+  } else {
+    result = v->VisitField(GetTargetField(), hrsi);
+  }
+  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 357ec9d..54aa0c9 100644
--- a/runtime/mirror/method_handle_impl.h
+++ b/runtime/mirror/method_handle_impl.h
@@ -28,6 +28,7 @@
 
 struct MethodHandleOffsets;
 struct MethodHandleImplOffsets;
+class ReflectiveValueVisitor;
 
 namespace mirror {
 
@@ -89,8 +90,7 @@
 
   // 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_);
+  void VisitTarget(ReflectiveValueVisitor* v) REQUIRES(Locks::mutator_lock_);
 
  protected:
   void Initialize(uintptr_t art_field_or_method, Kind kind, Handle<MethodType> method_type)
diff --git a/runtime/mirror/var_handle-inl.h b/runtime/mirror/var_handle-inl.h
deleted file mode 100644
index d3f582d..0000000
--- a/runtime/mirror/var_handle-inl.h
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * 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.cc b/runtime/mirror/var_handle.cc
index d887b5a..6d5ff2c 100644
--- a/runtime/mirror/var_handle.cc
+++ b/runtime/mirror/var_handle.cc
@@ -2032,5 +2032,16 @@
   UNREACHABLE();
 }
 
+void FieldVarHandle::VisitTarget(ReflectiveValueVisitor* v) {
+  ArtField* orig = GetField();
+  ArtField* new_value =
+      v->VisitField(orig, HeapReflectiveSourceInfo(kSourceJavaLangInvokeFieldVarHandle, this));
+  if (orig != new_value) {
+    SetField64</*kTransactionActive*/ false>(ArtFieldOffset(),
+                                             reinterpret_cast<uintptr_t>(new_value));
+  }
+}
+
+
 }  // namespace mirror
 }  // namespace art
diff --git a/runtime/mirror/var_handle.h b/runtime/mirror/var_handle.h
index ac78d98..02a0d8c 100644
--- a/runtime/mirror/var_handle.h
+++ b/runtime/mirror/var_handle.h
@@ -36,6 +36,7 @@
 struct ByteArrayViewVarHandleOffsets;
 struct ByteBufferViewVarHandleOffsets;
 
+class ReflectiveValueVisitor;
 class ShadowFrameGetter;
 
 namespace mirror {
@@ -198,8 +199,7 @@
   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_);
+  void VisitTarget(ReflectiveValueVisitor* v) REQUIRES(Locks::mutator_lock_);
 
  private:
   static MemberOffset ArtFieldOffset() {
diff --git a/runtime/reflective_value_visitor.cc b/runtime/reflective_value_visitor.cc
new file mode 100644
index 0000000..69fd51f
--- /dev/null
+++ b/runtime/reflective_value_visitor.cc
@@ -0,0 +1,45 @@
+/*
+ * 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 "reflective_value_visitor.h"
+#include <sstream>
+
+#include "base/locks.h"
+#include "base/mutex-inl.h"
+#include "mirror/class.h"
+#include "mirror/object-inl.h"
+
+namespace art {
+
+void HeapReflectiveSourceInfo::Describe(std::ostream& os) const {
+  Locks::mutator_lock_->AssertExclusiveHeld(Thread::Current());
+  ReflectionSourceInfo::Describe(os);
+  os << " Class=" << src_->GetClass()->PrettyClass();
+}
+
+template<>
+void JniIdReflectiveSourceInfo<jfieldID>::Describe(std::ostream& os) const {
+  ReflectionSourceInfo::Describe(os);
+  os << " jfieldID=" << reinterpret_cast<uintptr_t>(id_);
+}
+
+template<>
+void JniIdReflectiveSourceInfo<jmethodID>::Describe(std::ostream& os) const {
+  ReflectionSourceInfo::Describe(os);
+  os << " jmethodID=" << reinterpret_cast<uintptr_t>(id_);
+}
+
+}  // namespace art
diff --git a/runtime/reflective_value_visitor.h b/runtime/reflective_value_visitor.h
new file mode 100644
index 0000000..8823fcb
--- /dev/null
+++ b/runtime/reflective_value_visitor.h
@@ -0,0 +1,181 @@
+/*
+ * 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_REFLECTIVE_VALUE_VISITOR_H_
+#define ART_RUNTIME_REFLECTIVE_VALUE_VISITOR_H_
+
+#include <android-base/logging.h>
+
+#include <array>
+#include <compare>
+#include <functional>
+#include <stack>
+
+#include "android-base/macros.h"
+#include "base/enums.h"
+#include "base/globals.h"
+#include "base/locks.h"
+#include "base/macros.h"
+#include "base/value_object.h"
+#include "dex/dex_file.h"
+#include "jni.h"
+#include "mirror/dex_cache.h"
+#include "obj_ptr.h"
+
+namespace art {
+
+class ArtField;
+class ArtMethod;
+class BaseReflectiveHandleScope;
+class Thread;
+
+class ReflectionSourceInfo;
+
+class ReflectiveValueVisitor : public ValueObject {
+ public:
+  virtual ~ReflectiveValueVisitor() {}
+
+  virtual ArtMethod* VisitMethod(ArtMethod* in, const ReflectionSourceInfo& info)
+      REQUIRES_SHARED(Locks::mutator_lock_) = 0;
+  virtual ArtField* VisitField(ArtField* in, const ReflectionSourceInfo& info)
+      REQUIRES_SHARED(Locks::mutator_lock_) = 0;
+
+  // Give it an entrypoint through operator() to interact with things that expect lambda-like things
+  template <typename T,
+            typename = typename std::enable_if<std::is_same_v<T, ArtField> ||
+                                               std::is_same_v<T, ArtMethod>>>
+  T* operator()(T* t, const ReflectionSourceInfo& info) REQUIRES_SHARED(Locks::mutator_lock_) {
+    if constexpr (std::is_same_v<T, ArtField>) {
+      return VisitField(t, info);
+    } else {
+      static_assert(std::is_same_v<T, ArtMethod>, "Expected ArtField or ArtMethod");
+      return VisitMethod(t, info);
+    }
+  }
+};
+
+template <typename FieldVis, typename MethodVis>
+class FunctionReflectiveValueVisitor : public ReflectiveValueVisitor {
+ public:
+  FunctionReflectiveValueVisitor(FieldVis fv, MethodVis mv) : fv_(fv), mv_(mv) {}
+  ArtField* VisitField(ArtField* in, const ReflectionSourceInfo& info) override
+      REQUIRES(Locks::mutator_lock_) {
+    return fv_(in, info);
+  }
+  ArtMethod* VisitMethod(ArtMethod* in, const ReflectionSourceInfo& info) override
+      REQUIRES(Locks::mutator_lock_) {
+    return mv_(in, info);
+  }
+
+ private:
+  FieldVis fv_;
+  MethodVis mv_;
+};
+
+enum ReflectionSourceType {
+  kSourceUnknown = 0,
+  kSourceJavaLangReflectExecutable,
+  kSourceJavaLangReflectField,
+  kSourceJavaLangInvokeMethodHandle,
+  kSourceJavaLangInvokeFieldVarHandle,
+  kSourceThreadHandleScope,
+  kSourceJniFieldId,
+  kSourceJniMethodId,
+  kSourceDexCacheResolvedMethod,
+  kSourceDexCacheResolvedField,
+  kSourceMiscInternal,
+};
+std::ostream& operator<<(std::ostream& os, const ReflectionSourceType& type);
+
+class ReflectionSourceInfo : public ValueObject {
+ public:
+  virtual ~ReflectionSourceInfo() {}
+  // Thread id 0 is for non thread roots.
+  explicit ReflectionSourceInfo(ReflectionSourceType type) : type_(type) {}
+  virtual void Describe(std::ostream& os) const {
+    os << "Type=" << type_;
+  }
+
+ private:
+  const ReflectionSourceType type_;
+
+  DISALLOW_COPY_AND_ASSIGN(ReflectionSourceInfo);
+};
+inline std::ostream& operator<<(std::ostream& os, const ReflectionSourceInfo& info) {
+  info.Describe(os);
+  return os;
+}
+
+class ReflectiveHandleScopeSourceInfo : public ReflectionSourceInfo {
+ public:
+  explicit ReflectiveHandleScopeSourceInfo(BaseReflectiveHandleScope* source)
+      : ReflectionSourceInfo(kSourceThreadHandleScope), source_(source) {}
+
+  void Describe(std::ostream& os) const override {
+    ReflectionSourceInfo::Describe(os);
+    os << " source=" << source_;
+  }
+
+ private:
+  BaseReflectiveHandleScope* source_;
+};
+
+// TODO Maybe give this the ability to retrieve the type and ref, if it's useful.
+class HeapReflectiveSourceInfo : public ReflectionSourceInfo {
+ public:
+  HeapReflectiveSourceInfo(ReflectionSourceType t, mirror::Object* src)
+      : ReflectionSourceInfo(t), src_(src) {}
+  void Describe(std::ostream& os) const override;
+
+ private:
+  ObjPtr<mirror::Object> src_;
+};
+
+// TODO Maybe give this the ability to retrieve the id if it's useful.
+template <typename T,
+          typename = typename std::enable_if_t<std::is_same_v<T, jmethodID> ||
+                                               std::is_same_v<T, jfieldID>>>
+class JniIdReflectiveSourceInfo : public ReflectionSourceInfo {
+ public:
+  explicit JniIdReflectiveSourceInfo(T id)
+      : ReflectionSourceInfo(std::is_same_v<T, jmethodID> ? kSourceJniMethodId : kSourceJniFieldId),
+        id_(id) {}
+  void Describe(std::ostream& os) const override;
+
+ private:
+  T id_;
+};
+
+class DexCacheSourceInfo : public ReflectionSourceInfo {
+ public:
+  explicit DexCacheSourceInfo(ReflectionSourceType type,
+                              size_t index,
+                              ObjPtr<mirror::DexCache> cache)
+      : ReflectionSourceInfo(type), index_(index), cache_(cache) {}
+
+  void Describe(std::ostream& os) const override REQUIRES(Locks::mutator_lock_) {
+    ReflectionSourceInfo::Describe(os);
+    os << " index=" << index_ << " cache_=" << cache_.PtrUnchecked()
+       << " files=" << *cache_->GetDexFile();
+  }
+
+ private:
+  size_t index_;
+  ObjPtr<mirror::DexCache> cache_;
+};
+}  // namespace art
+
+#endif  // ART_RUNTIME_REFLECTIVE_VALUE_VISITOR_H_
diff --git a/runtime/runtime.cc b/runtime/runtime.cc
index 499dbf0..9af2f41 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -2197,6 +2197,12 @@
   VisitConcurrentRoots(visitor, flags);
 }
 
+void Runtime::VisitReflectiveTargets(ReflectiveValueVisitor *visitor) {
+  heap_->VisitReflectiveTargets(visitor);
+  jni_id_manager_->VisitReflectiveTargets(visitor);
+  callbacks_->VisitReflectiveTargets(visitor);
+}
+
 void Runtime::VisitImageRoots(RootVisitor* visitor) {
   for (auto* space : GetHeap()->GetContinuousSpaces()) {
     if (space->IsImageSpace()) {
diff --git a/runtime/runtime.h b/runtime/runtime.h
index 120ca66..0b336c7 100644
--- a/runtime/runtime.h
+++ b/runtime/runtime.h
@@ -43,6 +43,7 @@
 #include "offsets.h"
 #include "process_state.h"
 #include "quick/quick_method_frame_info.h"
+#include "reflective_value_visitor.h"
 #include "runtime_stats.h"
 
 namespace art {
@@ -397,6 +398,17 @@
   void SweepSystemWeaks(IsMarkedVisitor* visitor)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
+  // Walk all reflective objects and visit their targets as well as any method/fields held by the
+  // runtime threads that are marked as being reflective.
+  void VisitReflectiveTargets(ReflectiveValueVisitor* visitor) REQUIRES(Locks::mutator_lock_);
+  // Helper for visiting reflective targets with lambdas for both field and method reflective
+  // targets.
+  template <typename FieldVis, typename MethodVis>
+  void VisitReflectiveTargets(FieldVis&& fv, MethodVis&& mv) REQUIRES(Locks::mutator_lock_) {
+    FunctionReflectiveValueVisitor frvv(fv, mv);
+    VisitReflectiveTargets(&frvv);
+  }
+
   // Returns a special method that calls into a trampoline for runtime method resolution
   ArtMethod* GetResolutionMethod();
 
diff --git a/runtime/runtime_callbacks.cc b/runtime/runtime_callbacks.cc
index 40976c2..ac73364 100644
--- a/runtime/runtime_callbacks.cc
+++ b/runtime/runtime_callbacks.cc
@@ -319,4 +319,20 @@
   }
 }
 
+void RuntimeCallbacks::AddReflectiveValueVisitCallback(ReflectiveValueVisitCallback *cb) {
+  WriterMutexLock mu(Thread::Current(), *callback_lock_);
+  reflective_value_visit_callbacks_.push_back(cb);
+}
+
+void RuntimeCallbacks::RemoveReflectiveValueVisitCallback(ReflectiveValueVisitCallback *cb) {
+  WriterMutexLock mu(Thread::Current(), *callback_lock_);
+  Remove(cb, &reflective_value_visit_callbacks_);
+}
+
+void RuntimeCallbacks::VisitReflectiveTargets(ReflectiveValueVisitor *visitor) {
+  for (ReflectiveValueVisitCallback* cb : COPY(reflective_value_visit_callbacks_)) {
+    cb->VisitReflectiveTargets(visitor);
+  }
+}
+
 }  // namespace art
diff --git a/runtime/runtime_callbacks.h b/runtime/runtime_callbacks.h
index fe7bb0c..7111ba0 100644
--- a/runtime/runtime_callbacks.h
+++ b/runtime/runtime_callbacks.h
@@ -44,6 +44,7 @@
 class Monitor;
 class ReaderWriterMutex;
 class ThreadLifecycleCallback;
+class ReflectiveValueVisitor;
 
 // Note: RuntimeCallbacks uses the mutator lock to synchronize the callback lists. A thread must
 //       hold the exclusive lock to add or remove a listener. A thread must hold the shared lock
@@ -156,6 +157,17 @@
   virtual bool MethodNeedsDebugVersion(ArtMethod* method) REQUIRES_SHARED(Locks::mutator_lock_) = 0;
 };
 
+// Callback to let something request to be notified when reflective objects are being visited and
+// updated to update any bare ArtMethod/ArtField pointers it might have.
+class ReflectiveValueVisitCallback {
+ public:
+  virtual ~ReflectiveValueVisitCallback() {}
+
+  // Called when something visits all reflective values with the update visitor.
+  virtual void VisitReflectiveTargets(ReflectiveValueVisitor* visitor)
+      REQUIRES(Locks::mutator_lock_) = 0;
+};
+
 class RuntimeCallbacks {
  public:
   RuntimeCallbacks();
@@ -257,6 +269,13 @@
   void RemoveDebuggerControlCallback(DebuggerControlCallback* cb)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
+  void VisitReflectiveTargets(ReflectiveValueVisitor* visitor) REQUIRES(Locks::mutator_lock_);
+
+  void AddReflectiveValueVisitCallback(ReflectiveValueVisitCallback* cb)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+  void RemoveReflectiveValueVisitCallback(ReflectiveValueVisitCallback* cb)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
  private:
   std::unique_ptr<ReaderWriterMutex> callback_lock_ BOTTOM_MUTEX_ACQUIRED_AFTER;
 
@@ -280,6 +299,8 @@
       GUARDED_BY(callback_lock_);
   std::vector<DebuggerControlCallback*> debugger_control_callbacks_
       GUARDED_BY(callback_lock_);
+  std::vector<ReflectiveValueVisitCallback*> reflective_value_visit_callbacks_
+      GUARDED_BY(callback_lock_);
 };
 
 }  // namespace art
diff --git a/test/1984-structural-redefine-field-trace/expected.txt b/test/1984-structural-redefine-field-trace/expected.txt
new file mode 100644
index 0000000..6153d7e
--- /dev/null
+++ b/test/1984-structural-redefine-field-trace/expected.txt
@@ -0,0 +1,31 @@
+Dumping fields at start
+public static boolean art.Test1984$Transform.boom=false
+public static int art.Test1984$Transform.count_down=2
+public static boolean art.Test1984$Transform.tock=false
+method: public static void art.Test1984$Transform.tick()	ACCESS: public static boolean art.Test1984$Transform.tock
+method: public static void art.Test1984$Transform.tick()	ACCESS: public static boolean art.Test1984$Transform.tock
+method: public static void art.Test1984$Transform.tick()	MODIFY: public static boolean art.Test1984$Transform.tock	Set to: true
+method: public static void art.Test1984$Transform.tick()	ACCESS: public static int art.Test1984$Transform.count_down
+method: public static void art.Test1984$Transform.tick()	ACCESS: public static boolean art.Test1984$Transform.tock
+method: public static void art.Test1984$Transform.tick()	ACCESS: public static boolean art.Test1984$Transform.tock
+method: public static void art.Test1984$Transform.tick()	MODIFY: public static boolean art.Test1984$Transform.tock	Set to: false
+method: public static void art.Test1984$Transform.tick()	ACCESS: public static int art.Test1984$Transform.count_down
+method: public static void art.Test1984$Transform.tick()	MODIFY: public static int art.Test1984$Transform.count_down	Set to: 1
+method: public static void art.Test1984$Transform.tick()	ACCESS: public static int art.Test1984$Transform.count_down
+REDEFINING TRANSFORM CLASS
+method: public static void art.Test1984$Transform.tick()	ACCESS: public static boolean art.Test1984$Transform.tock
+method: public static void art.Test1984$Transform.tick()	ACCESS: public static boolean art.Test1984$Transform.tock
+method: public static void art.Test1984$Transform.tick()	MODIFY: public static boolean art.Test1984$Transform.tock	Set to: true
+method: public static void art.Test1984$Transform.tick()	ACCESS: public static int art.Test1984$Transform.count_down
+method: public static void art.Test1984$Transform.tick()	ACCESS: public static boolean art.Test1984$Transform.tock
+method: public static void art.Test1984$Transform.tick()	ACCESS: public static boolean art.Test1984$Transform.tock
+method: public static void art.Test1984$Transform.tick()	MODIFY: public static boolean art.Test1984$Transform.tock	Set to: false
+method: public static void art.Test1984$Transform.tick()	ACCESS: public static int art.Test1984$Transform.count_down
+method: public static void art.Test1984$Transform.tick()	MODIFY: public static int art.Test1984$Transform.count_down	Set to: 0
+method: public static void art.Test1984$Transform.tick()	ACCESS: public static int art.Test1984$Transform.count_down
+method: public static void art.Test1984$Transform.tick()	MODIFY: public static boolean art.Test1984$Transform.boom	Set to: true
+Dumping fields at end
+public static int art.Test1984$Transform.aaa_INITIAL=0
+public static boolean art.Test1984$Transform.boom=true
+public static int art.Test1984$Transform.count_down=0
+public static boolean art.Test1984$Transform.tock=false
diff --git a/test/1984-structural-redefine-field-trace/info.txt b/test/1984-structural-redefine-field-trace/info.txt
new file mode 100644
index 0000000..ded28c5
--- /dev/null
+++ b/test/1984-structural-redefine-field-trace/info.txt
@@ -0,0 +1 @@
+Tests field access and modification watches in JVMTI when target is structurally redefined.
diff --git a/test/1984-structural-redefine-field-trace/run b/test/1984-structural-redefine-field-trace/run
new file mode 100755
index 0000000..a36de16
--- /dev/null
+++ b/test/1984-structural-redefine-field-trace/run
@@ -0,0 +1,18 @@
+#!/bin/bash
+#
+# Copyright 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Ask for stack traces to be dumped to a file rather than to stdout.
+./default-run "$@" --jvmti --android-runtime-option -Xopaque-jni-ids:true
diff --git a/test/1984-structural-redefine-field-trace/src/Main.java b/test/1984-structural-redefine-field-trace/src/Main.java
new file mode 100644
index 0000000..415a85e
--- /dev/null
+++ b/test/1984-structural-redefine-field-trace/src/Main.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public class Main {
+  public static void main(String[] args) throws Exception {
+    art.Test1984.run();
+  }
+}
diff --git a/test/1984-structural-redefine-field-trace/src/art/Redefinition.java b/test/1984-structural-redefine-field-trace/src/art/Redefinition.java
new file mode 120000
index 0000000..81eaf31
--- /dev/null
+++ b/test/1984-structural-redefine-field-trace/src/art/Redefinition.java
@@ -0,0 +1 @@
+../../../jvmti-common/Redefinition.java
\ No newline at end of file
diff --git a/test/1984-structural-redefine-field-trace/src/art/Test1984.java b/test/1984-structural-redefine-field-trace/src/art/Test1984.java
new file mode 100644
index 0000000..a69d56e
--- /dev/null
+++ b/test/1984-structural-redefine-field-trace/src/art/Test1984.java
@@ -0,0 +1,134 @@
+/*
+ * 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.lang.reflect.Executable;
+import java.lang.reflect.Field;
+import java.util.Base64;
+
+public class Test1984 {
+  public static void notifyFieldModify(
+      Executable method, long location, Class<?> f_klass, Object target, Field f, Object value) {
+    System.out.println("method: " + method + "\tMODIFY: " + f + "\tSet to: " + value);
+  }
+
+  public static void notifyFieldAccess(
+      Executable method, long location, Class<?> f_klass, Object target, Field f) {
+    System.out.println("method: " + method + "\tACCESS: " + f);
+  }
+
+  public static class Transform {
+    public static int count_down = 2;
+    public static boolean boom = false;
+    public static boolean tock = false;
+
+    public static void tick() {
+      boolean tocked = tock;
+      tock = !tock;
+      if (tocked) {
+        count_down--;
+      }
+      if (count_down == 0) {
+        boom = true;
+      }
+    }
+  }
+
+  /* Base64 encoded dex file for.
+   * // NB The addition of aaa_INITIAL means the fields all have different offsets
+   * public static class Transform {
+   *   public static int aaa_INITIAL = 0;
+   *   public static int count_down = 2;
+   *   public static boolean boom = false;
+   *   public static boolean tock = false;
+   *   public static void tick() {
+   *     boolean tocked = tock;
+   *     tock = !tock;
+   *     if (tocked) {
+   *       count_down--;
+   *     }
+   *     if (count_down == 0) {
+   *       boom = true;
+   *     }
+   *   }
+   * }
+   */
+  public static final byte[] REDEFINED_DEX_BYTES =
+      Base64.getDecoder()
+          .decode(
+              "ZGV4CjAzNQDejZufbnVbJEn1/OfB3XmJPtVbudlWkvnsAwAAcAAAAHhWNBIAAAAAAAAAADQDAAAU"
+                  + "AAAAcAAAAAgAAADAAAAAAQAAAOAAAAAEAAAA7AAAAAQAAAAMAQAAAQAAACwBAACgAgAATAEAAPAB"
+                  + "AAD6AQAAAgIAAAUCAAAfAgAALwIAAFMCAABzAgAAhwIAAJYCAAChAgAApAIAAKcCAAC0AgAAwQIA"
+                  + "AMcCAADTAgAA2QIAAN8CAADlAgAAAgAAAAMAAAAEAAAABQAAAAYAAAAHAAAACgAAAAsAAAAKAAAA"
+                  + "BgAAAAAAAAABAAAADAAAAAEABwAOAAAAAQAAAA8AAAABAAcAEgAAAAEAAAAAAAAAAQAAAAEAAAAB"
+                  + "AAAAEQAAAAUAAAABAAAAAQAAAAEAAAAFAAAAAAAAAAgAAADgAQAAFgMAAAAAAAACAAAABwMAAA0D"
+                  + "AAACAAAAAAAAAOwCAAALAAAAEgFnAQAAEiBnAAIAagEBAGoBAwAOAAAAAQABAAEAAAD0AgAABAAA"
+                  + "AHAQAwAAAA4AAwAAAAAAAAD5AgAAGwAAABIRYwIDAGMAAwA5ABQAARBqAAMAOAIIAGAAAgDYAAD/"
+                  + "ZwACAGAAAgA5AAQAagEBAA4AEgAo7gAATAEAAAAAAAAAAAAAAAAAAAg8Y2xpbml0PgAGPGluaXQ+"
+                  + "AAFJABhMYXJ0L1Rlc3QxOTg0JFRyYW5zZm9ybTsADkxhcnQvVGVzdDE5ODQ7ACJMZGFsdmlrL2Fu"
+                  + "bm90YXRpb24vRW5jbG9zaW5nQ2xhc3M7AB5MZGFsdmlrL2Fubm90YXRpb24vSW5uZXJDbGFzczsA"
+                  + "EkxqYXZhL2xhbmcvT2JqZWN0OwANVGVzdDE5ODQuamF2YQAJVHJhbnNmb3JtAAFWAAFaAAthYWFf"
+                  + "SU5JVElBTAALYWNjZXNzRmxhZ3MABGJvb20ACmNvdW50X2Rvd24ABG5hbWUABHRpY2sABHRvY2sA"
+                  + "BXZhbHVlAAgABx0tPC0ABwAHDgANAAcdLXgtaksuAnkdAAIDARMYAgIEAg0ECRAXCQQAAwAACQEJ"
+                  + "AQkBCQCIgATYAgGBgASAAwEJmAMAAA8AAAAAAAAAAQAAAAAAAAABAAAAFAAAAHAAAAACAAAACAAA"
+                  + "AMAAAAADAAAAAQAAAOAAAAAEAAAABAAAAOwAAAAFAAAABAAAAAwBAAAGAAAAAQAAACwBAAADEAAA"
+                  + "AQAAAEwBAAABIAAAAwAAAFgBAAAGIAAAAQAAAOABAAACIAAAFAAAAPABAAADIAAAAwAAAOwCAAAE"
+                  + "IAAAAgAAAAcDAAAAIAAAAQAAABYDAAAAEAAAAQAAADQDAAA=");
+
+  public static void run() throws Exception {
+    System.out.println("Dumping fields at start");
+    for (Field f : Transform.class.getDeclaredFields()) {
+      System.out.println(f.toString() + "=" + f.get(null));
+    }
+    Trace.disableTracing(Thread.currentThread());
+    Trace.enableFieldTracing(
+        Test1984.class,
+        Test1984.class.getDeclaredMethod(
+            "notifyFieldAccess",
+            Executable.class,
+            Long.TYPE,
+            Class.class,
+            Object.class,
+            Field.class),
+        Test1984.class.getDeclaredMethod(
+            "notifyFieldModify",
+            Executable.class,
+            Long.TYPE,
+            Class.class,
+            Object.class,
+            Field.class,
+            Object.class),
+        Thread.currentThread());
+    for (Field f : Transform.class.getDeclaredFields()) {
+      Trace.watchFieldAccess(f);
+      Trace.watchFieldModification(f);
+    }
+    // count_down = 2
+    Transform.tick(); // count_down = 2
+    Transform.tick(); // count_down = 1
+    System.out.println("REDEFINING TRANSFORM CLASS");
+    Redefinition.doCommonStructuralClassRedefinition(Transform.class, REDEFINED_DEX_BYTES);
+    Transform.tick(); // count_down = 1
+    Transform.tick(); // count_down = 0
+    System.out.println("Dumping fields at end");
+    for (Field f : Transform.class.getDeclaredFields()) {
+      System.out.println(f.toString() + "=" + f.get(null));
+    }
+    // Turn off tracing so we don't have to deal with print internals.
+    Trace.disableTracing(Thread.currentThread());
+  }
+}
diff --git a/test/1984-structural-redefine-field-trace/src/art/Trace.java b/test/1984-structural-redefine-field-trace/src/art/Trace.java
new file mode 120000
index 0000000..5d9b44b
--- /dev/null
+++ b/test/1984-structural-redefine-field-trace/src/art/Trace.java
@@ -0,0 +1 @@
+../../../jvmti-common/Trace.java
\ No newline at end of file