Add field access & modify JVMTI callbacks

This adds support for the FieldAccess and FieldModification callbacks
in JVMTI and all other functions and behaviors associated with the
can_generate_field_modification_events and
can_generate_field_access_events capabilities.

Tests follow in the next CL.

Bug: 34409228
Test: ./test.py --host -j40

Change-Id: Id18fc53677cc1f96e1460c498ade7607219d5a79
diff --git a/runtime/common_dex_operations.h b/runtime/common_dex_operations.h
index 133ddb0..528db96 100644
--- a/runtime/common_dex_operations.h
+++ b/runtime/common_dex_operations.h
@@ -62,7 +62,7 @@
 }
 
 template<Primitive::Type field_type>
-static ALWAYS_INLINE void DoFieldGetCommon(Thread* self,
+static ALWAYS_INLINE bool DoFieldGetCommon(Thread* self,
                                            const ShadowFrame& shadow_frame,
                                            ObjPtr<mirror::Object> obj,
                                            ArtField* field,
@@ -85,6 +85,9 @@
                                     shadow_frame.GetMethod(),
                                     shadow_frame.GetDexPC(),
                                     field);
+    if (UNLIKELY(self->IsExceptionPending())) {
+      return false;
+    }
   }
 
   switch (field_type) {
@@ -113,6 +116,7 @@
       LOG(FATAL) << "Unreachable " << field_type;
       break;
   }
+  return true;
 }
 
 template<Primitive::Type field_type, bool do_assignability_check, bool transaction_active>
@@ -120,7 +124,7 @@
                                     const ShadowFrame& shadow_frame,
                                     ObjPtr<mirror::Object> obj,
                                     ArtField* field,
-                                    const JValue& value)
+                                    JValue& value)
     REQUIRES_SHARED(Locks::mutator_lock_) {
   field->GetDeclaringClass()->AssertInitializedOrInitializingInThread(self);
 
@@ -128,15 +132,22 @@
   // the field from the base of the object, we need to look for it first.
   instrumentation::Instrumentation* instrumentation = Runtime::Current()->GetInstrumentation();
   if (UNLIKELY(instrumentation->HasFieldWriteListeners())) {
-    StackHandleScope<1> hs(self);
-    // Wrap in handle wrapper in case the listener does thread suspension.
+    StackHandleScope<2> hs(self);
+    // Save this and return value (if needed) in case the instrumentation causes a suspend.
     HandleWrapperObjPtr<mirror::Object> h(hs.NewHandleWrapper(&obj));
     ObjPtr<mirror::Object> this_object = field->IsStatic() ? nullptr : obj;
-    instrumentation->FieldWriteEvent(self, this_object.Ptr(),
+    mirror::Object* fake_root = nullptr;
+    HandleWrapper<mirror::Object> ret(hs.NewHandleWrapper<mirror::Object>(
+        field_type == Primitive::kPrimNot ? value.GetGCRoot() : &fake_root));
+    instrumentation->FieldWriteEvent(self,
+                                     this_object.Ptr(),
                                      shadow_frame.GetMethod(),
                                      shadow_frame.GetDexPC(),
                                      field,
                                      value);
+    if (UNLIKELY(self->IsExceptionPending())) {
+      return false;
+    }
   }
 
   switch (field_type) {
diff --git a/runtime/interpreter/interpreter_common.cc b/runtime/interpreter/interpreter_common.cc
index d06ac23..1b36c3f 100644
--- a/runtime/interpreter/interpreter_common.cc
+++ b/runtime/interpreter/interpreter_common.cc
@@ -66,7 +66,11 @@
   }
 
   JValue result;
-  DoFieldGetCommon<field_type>(self, shadow_frame, obj, f, &result);
+  if (UNLIKELY(!DoFieldGetCommon<field_type>(self, shadow_frame, obj, f, &result))) {
+    // Instrumentation threw an error!
+    CHECK(self->IsExceptionPending());
+    return false;
+  }
   uint32_t vregA = is_static ? inst->VRegA_21c(inst_data) : inst->VRegA_22c(inst_data);
   switch (field_type) {
     case Primitive::kPrimBoolean:
@@ -149,14 +153,18 @@
                                                         field_offset.Uint32Value());
     DCHECK(f != nullptr);
     DCHECK(!f->IsStatic());
-    StackHandleScope<1> hs(Thread::Current());
+    Thread* self = Thread::Current();
+    StackHandleScope<1> hs(self);
     // Save obj in case the instrumentation event has thread suspension.
     HandleWrapperObjPtr<mirror::Object> h = hs.NewHandleWrapper(&obj);
-    instrumentation->FieldReadEvent(Thread::Current(),
+    instrumentation->FieldReadEvent(self,
                                     obj.Ptr(),
                                     shadow_frame.GetMethod(),
                                     shadow_frame.GetDexPC(),
                                     f);
+    if (UNLIKELY(self->IsExceptionPending())) {
+      return false;
+    }
   }
   // Note: iget-x-quick instructions are only for non-volatile fields.
   const uint32_t vregA = inst->VRegA_22c(inst_data);
@@ -322,15 +330,22 @@
     DCHECK(f != nullptr);
     DCHECK(!f->IsStatic());
     JValue field_value = GetFieldValue<field_type>(shadow_frame, vregA);
-    StackHandleScope<1> hs(Thread::Current());
+    Thread* self = Thread::Current();
+    StackHandleScope<2> hs(self);
     // Save obj in case the instrumentation event has thread suspension.
     HandleWrapperObjPtr<mirror::Object> h = hs.NewHandleWrapper(&obj);
-    instrumentation->FieldWriteEvent(Thread::Current(),
+    mirror::Object* fake_root = nullptr;
+    HandleWrapper<mirror::Object> ret(hs.NewHandleWrapper<mirror::Object>(
+        field_type == Primitive::kPrimNot ? field_value.GetGCRoot() : &fake_root));
+    instrumentation->FieldWriteEvent(self,
                                      obj.Ptr(),
                                      shadow_frame.GetMethod(),
                                      shadow_frame.GetDexPC(),
                                      f,
                                      field_value);
+    if (UNLIKELY(self->IsExceptionPending())) {
+      return false;
+    }
   }
   // Note: iput-x-quick instructions are only for non-volatile fields.
   switch (field_type) {
diff --git a/runtime/method_handles.cc b/runtime/method_handles.cc
index 090bac1..f0d3cae 100644
--- a/runtime/method_handles.cc
+++ b/runtime/method_handles.cc
@@ -822,7 +822,7 @@
                                            ObjPtr<mirror::Object>& obj,
                                            ArtField* field,
                                            Primitive::Type field_type,
-                                           const JValue& value)
+                                           JValue& value)
     REQUIRES_SHARED(Locks::mutator_lock_) {
   DCHECK(!Runtime::Current()->IsActiveTransaction());
   static const bool kTransaction = false;         // Not in a transaction.
diff --git a/runtime/openjdkjvmti/OpenjdkJvmTi.cc b/runtime/openjdkjvmti/OpenjdkJvmTi.cc
index 3ec5b32..8daf6f7 100644
--- a/runtime/openjdkjvmti/OpenjdkJvmTi.cc
+++ b/runtime/openjdkjvmti/OpenjdkJvmTi.cc
@@ -635,36 +635,28 @@
     return ERR(NOT_IMPLEMENTED);
   }
 
-  static jvmtiError SetFieldAccessWatch(jvmtiEnv* env,
-                                        jclass klass ATTRIBUTE_UNUSED,
-                                        jfieldID field ATTRIBUTE_UNUSED) {
+  static jvmtiError SetFieldAccessWatch(jvmtiEnv* env, jclass klass, jfieldID field) {
     ENSURE_VALID_ENV(env);
     ENSURE_HAS_CAP(env, can_generate_field_access_events);
-    return ERR(NOT_IMPLEMENTED);
+    return FieldUtil::SetFieldAccessWatch(env, klass, field);
   }
 
-  static jvmtiError ClearFieldAccessWatch(jvmtiEnv* env,
-                                          jclass klass ATTRIBUTE_UNUSED,
-                                          jfieldID field ATTRIBUTE_UNUSED) {
+  static jvmtiError ClearFieldAccessWatch(jvmtiEnv* env, jclass klass, jfieldID field) {
     ENSURE_VALID_ENV(env);
     ENSURE_HAS_CAP(env, can_generate_field_access_events);
-    return ERR(NOT_IMPLEMENTED);
+    return FieldUtil::ClearFieldAccessWatch(env, klass, field);
   }
 
-  static jvmtiError SetFieldModificationWatch(jvmtiEnv* env,
-                                              jclass klass ATTRIBUTE_UNUSED,
-                                              jfieldID field ATTRIBUTE_UNUSED) {
+  static jvmtiError SetFieldModificationWatch(jvmtiEnv* env, jclass klass, jfieldID field) {
     ENSURE_VALID_ENV(env);
     ENSURE_HAS_CAP(env, can_generate_field_modification_events);
-    return ERR(NOT_IMPLEMENTED);
+    return FieldUtil::SetFieldModificationWatch(env, klass, field);
   }
 
-  static jvmtiError ClearFieldModificationWatch(jvmtiEnv* env,
-                                                jclass klass ATTRIBUTE_UNUSED,
-                                                jfieldID field ATTRIBUTE_UNUSED) {
+  static jvmtiError ClearFieldModificationWatch(jvmtiEnv* env, jclass klass, jfieldID field) {
     ENSURE_VALID_ENV(env);
     ENSURE_HAS_CAP(env, can_generate_field_modification_events);
-    return ERR(NOT_IMPLEMENTED);
+    return FieldUtil::ClearFieldModificationWatch(env, klass, field);
   }
 
   static jvmtiError GetLoadedClasses(jvmtiEnv* env, jint* class_count_ptr, jclass** classes_ptr) {
diff --git a/runtime/openjdkjvmti/art_jvmti.h b/runtime/openjdkjvmti/art_jvmti.h
index 369b2d7..8ba3527 100644
--- a/runtime/openjdkjvmti/art_jvmti.h
+++ b/runtime/openjdkjvmti/art_jvmti.h
@@ -34,6 +34,7 @@
 
 #include <memory>
 #include <type_traits>
+#include <unordered_set>
 
 #include <jni.h>
 
@@ -46,6 +47,10 @@
 #include "jni_env_ext.h"
 #include "jvmti.h"
 
+namespace art {
+class ArtField;
+}
+
 namespace openjdkjvmti {
 
 class ObjectTagTable;
@@ -62,6 +67,15 @@
   // Tagging is specific to the jvmtiEnv.
   std::unique_ptr<ObjectTagTable> object_tag_table;
 
+  // Set of watched fields is unique to each jvmtiEnv.
+  // TODO It might be good to follow the RI and only let one jvmtiEnv ever have the watch caps so
+  // we can record this on the field directly. We could do this either using free access-flag bits
+  // or by putting a list in the ClassExt of a field's DeclaringClass.
+  // TODO Maybe just have an extension to let one put a watch on every field, that would probably be
+  // good enough maybe since you probably want either a few or all/almost all of them.
+  std::unordered_set<art::ArtField*> access_watched_fields;
+  std::unordered_set<art::ArtField*> modify_watched_fields;
+
   ArtJvmTiEnv(art::JavaVMExt* runtime, EventHandler* event_handler);
 
   static ArtJvmTiEnv* AsArtJvmTiEnv(jvmtiEnv* env) {
@@ -194,8 +208,8 @@
 
 const jvmtiCapabilities kPotentialCapabilities = {
     .can_tag_objects                                 = 1,
-    .can_generate_field_modification_events          = 0,
-    .can_generate_field_access_events                = 0,
+    .can_generate_field_modification_events          = 1,
+    .can_generate_field_access_events                = 1,
     .can_get_bytecodes                               = 0,
     .can_get_synthetic_attribute                     = 1,
     .can_get_owned_monitor_info                      = 0,
diff --git a/runtime/openjdkjvmti/events-inl.h b/runtime/openjdkjvmti/events-inl.h
index cb7e6a9..af99233 100644
--- a/runtime/openjdkjvmti/events-inl.h
+++ b/runtime/openjdkjvmti/events-inl.h
@@ -20,6 +20,7 @@
 #include <array>
 
 #include "events.h"
+#include "jni_internal.h"
 #include "ScopedLocalRef.h"
 
 #include "art_jvmti.h"
@@ -216,6 +217,71 @@
   }
 }
 
+// Need to give custom specializations for FieldAccess and FieldModification since they need to
+// filter out which particular fields agents want to get notified on.
+// TODO The spec allows us to do shortcuts like only allow one agent to ever set these watches. This
+// could make the system more performant.
+template <>
+inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kFieldModification>(art::Thread* thread,
+                                                                           JNIEnv* jnienv,
+                                                                           jthread jni_thread,
+                                                                           jmethodID method,
+                                                                           jlocation location,
+                                                                           jclass field_klass,
+                                                                           jobject object,
+                                                                           jfieldID field,
+                                                                           char type_char,
+                                                                           jvalue val) const {
+  for (ArtJvmTiEnv* env : envs) {
+    if (env != nullptr &&
+        ShouldDispatch<ArtJvmtiEvent::kFieldModification>(env, thread) &&
+        env->modify_watched_fields.find(
+            art::jni::DecodeArtField(field)) != env->modify_watched_fields.end()) {
+      ScopedLocalRef<jthrowable> thr(jnienv, jnienv->ExceptionOccurred());
+      jnienv->ExceptionClear();
+      auto callback = impl::GetCallback<ArtJvmtiEvent::kFieldModification>(env);
+      (*callback)(env,
+                  jnienv,
+                  jni_thread,
+                  method,
+                  location,
+                  field_klass,
+                  object,
+                  field,
+                  type_char,
+                  val);
+      if (thr.get() != nullptr && !jnienv->ExceptionCheck()) {
+        jnienv->Throw(thr.get());
+      }
+    }
+  }
+}
+
+template <>
+inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kFieldAccess>(art::Thread* thread,
+                                                                     JNIEnv* jnienv,
+                                                                     jthread jni_thread,
+                                                                     jmethodID method,
+                                                                     jlocation location,
+                                                                     jclass field_klass,
+                                                                     jobject object,
+                                                                     jfieldID field) const {
+  for (ArtJvmTiEnv* env : envs) {
+    if (env != nullptr &&
+        ShouldDispatch<ArtJvmtiEvent::kFieldAccess>(env, thread) &&
+        env->access_watched_fields.find(
+            art::jni::DecodeArtField(field)) != env->access_watched_fields.end()) {
+      ScopedLocalRef<jthrowable> thr(jnienv, jnienv->ExceptionOccurred());
+      jnienv->ExceptionClear();
+      auto callback = impl::GetCallback<ArtJvmtiEvent::kFieldAccess>(env);
+      (*callback)(env, jnienv, jni_thread, method, location, field_klass, object, field);
+      if (thr.get() != nullptr && !jnienv->ExceptionCheck()) {
+        jnienv->Throw(thr.get());
+      }
+    }
+  }
+}
+
 // Need to give a custom specialization for NativeMethodBind since it has to deal with an out
 // variable.
 template <>
diff --git a/runtime/openjdkjvmti/events.cc b/runtime/openjdkjvmti/events.cc
index 90bc122..989b9af 100644
--- a/runtime/openjdkjvmti/events.cc
+++ b/runtime/openjdkjvmti/events.cc
@@ -33,6 +33,7 @@
 
 #include "art_jvmti.h"
 #include "art_method-inl.h"
+#include "art_field-inl.h"
 #include "base/logging.h"
 #include "gc/allocation_listener.h"
 #include "gc/gc_pause_listener.h"
@@ -433,24 +434,92 @@
   }
 
   // Call-back for when we read from a field.
-  void FieldRead(art::Thread* self ATTRIBUTE_UNUSED,
-                 art::Handle<art::mirror::Object> this_object ATTRIBUTE_UNUSED,
-                 art::ArtMethod* method ATTRIBUTE_UNUSED,
-                 uint32_t dex_pc ATTRIBUTE_UNUSED,
-                 art::ArtField* field ATTRIBUTE_UNUSED)
+  void FieldRead(art::Thread* self,
+                 art::Handle<art::mirror::Object> this_object,
+                 art::ArtMethod* method,
+                 uint32_t dex_pc,
+                 art::ArtField* field)
       REQUIRES_SHARED(art::Locks::mutator_lock_) OVERRIDE {
-    return;
+    if (event_handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kFieldAccess)) {
+      art::JNIEnvExt* jnienv = self->GetJniEnv();
+      // DCHECK(!self->IsExceptionPending());
+      ScopedLocalRef<jobject> this_ref(jnienv, AddLocalRef<jobject>(jnienv, this_object.Get()));
+      ScopedLocalRef<jobject> fklass(jnienv,
+                                     AddLocalRef<jobject>(jnienv,
+                                                          field->GetDeclaringClass().Ptr()));
+      RunEventCallback<ArtJvmtiEvent::kFieldAccess>(self,
+                                                    jnienv,
+                                                    art::jni::EncodeArtMethod(method),
+                                                    static_cast<jlocation>(dex_pc),
+                                                    static_cast<jclass>(fklass.get()),
+                                                    this_ref.get(),
+                                                    art::jni::EncodeArtField(field));
+    }
+  }
+
+  void FieldWritten(art::Thread* self,
+                    art::Handle<art::mirror::Object> this_object,
+                    art::ArtMethod* method,
+                    uint32_t dex_pc,
+                    art::ArtField* field,
+                    art::Handle<art::mirror::Object> new_val)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) OVERRIDE {
+    if (event_handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kFieldModification)) {
+      art::JNIEnvExt* jnienv = self->GetJniEnv();
+      // DCHECK(!self->IsExceptionPending());
+      ScopedLocalRef<jobject> this_ref(jnienv, AddLocalRef<jobject>(jnienv, this_object.Get()));
+      ScopedLocalRef<jobject> fklass(jnienv,
+                                     AddLocalRef<jobject>(jnienv,
+                                                          field->GetDeclaringClass().Ptr()));
+      ScopedLocalRef<jobject> fval(jnienv, AddLocalRef<jobject>(jnienv, new_val.Get()));
+      jvalue val;
+      val.l = fval.get();
+      RunEventCallback<ArtJvmtiEvent::kFieldModification>(
+          self,
+          jnienv,
+          art::jni::EncodeArtMethod(method),
+          static_cast<jlocation>(dex_pc),
+          static_cast<jclass>(fklass.get()),
+          field->IsStatic() ? nullptr :  this_ref.get(),
+          art::jni::EncodeArtField(field),
+          'L',  // type_char
+          val);
+    }
   }
 
   // Call-back for when we write into a field.
-  void FieldWritten(art::Thread* self ATTRIBUTE_UNUSED,
-                    art::Handle<art::mirror::Object> this_object ATTRIBUTE_UNUSED,
-                    art::ArtMethod* method ATTRIBUTE_UNUSED,
-                    uint32_t dex_pc ATTRIBUTE_UNUSED,
-                    art::ArtField* field ATTRIBUTE_UNUSED,
-                    const art::JValue& field_value ATTRIBUTE_UNUSED)
+  void FieldWritten(art::Thread* self,
+                    art::Handle<art::mirror::Object> this_object,
+                    art::ArtMethod* method,
+                    uint32_t dex_pc,
+                    art::ArtField* field,
+                    const art::JValue& field_value)
       REQUIRES_SHARED(art::Locks::mutator_lock_) OVERRIDE {
-    return;
+    if (event_handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kFieldModification)) {
+      art::JNIEnvExt* jnienv = self->GetJniEnv();
+      DCHECK(!self->IsExceptionPending());
+      ScopedLocalRef<jobject> this_ref(jnienv, AddLocalRef<jobject>(jnienv, this_object.Get()));
+      ScopedLocalRef<jobject> fklass(jnienv,
+                                     AddLocalRef<jobject>(jnienv,
+                                                          field->GetDeclaringClass().Ptr()));
+      char type_char = art::Primitive::Descriptor(field->GetTypeAsPrimitiveType())[0];
+      jvalue val;
+      // 64bit integer is the largest value in the union so we should be fine simply copying it into
+      // the union.
+      val.j = field_value.GetJ();
+      RunEventCallback<ArtJvmtiEvent::kFieldModification>(
+          self,
+          jnienv,
+          art::jni::EncodeArtMethod(method),
+          static_cast<jlocation>(dex_pc),
+          static_cast<jclass>(fklass.get()),
+          field->IsStatic() ? nullptr :  this_ref.get(),  // nb static field modification get given
+                                                          // the class as this_object for some
+                                                          // reason.
+          art::jni::EncodeArtField(field),
+          type_char,
+          val);
+    }
   }
 
   // Call-back when an exception is caught.
@@ -490,15 +559,20 @@
     case ArtJvmtiEvent::kMethodExit:
       return art::instrumentation::Instrumentation::kMethodExited |
              art::instrumentation::Instrumentation::kMethodUnwind;
+    case ArtJvmtiEvent::kFieldModification:
+      return art::instrumentation::Instrumentation::kFieldWritten;
+    case ArtJvmtiEvent::kFieldAccess:
+      return art::instrumentation::Instrumentation::kFieldRead;
     default:
       LOG(FATAL) << "Unknown event ";
       return 0;
   }
 }
 
-static void SetupMethodTraceListener(JvmtiMethodTraceListener* listener,
-                                     ArtJvmtiEvent event,
-                                     bool enable) {
+static void SetupTraceListener(JvmtiMethodTraceListener* listener,
+                               ArtJvmtiEvent event,
+                               bool enable) {
+  art::ScopedThreadStateChange stsc(art::Thread::Current(), art::ThreadState::kNative);
   uint32_t new_events = GetInstrumentationEventsFor(event);
   art::instrumentation::Instrumentation* instr = art::Runtime::Current()->GetInstrumentation();
   art::gc::ScopedGCCriticalSection gcs(art::Thread::Current(),
@@ -529,7 +603,9 @@
 
     case ArtJvmtiEvent::kMethodEntry:
     case ArtJvmtiEvent::kMethodExit:
-      SetupMethodTraceListener(method_trace_listener_.get(), event, enable);
+    case ArtJvmtiEvent::kFieldAccess:
+    case ArtJvmtiEvent::kFieldModification:
+      SetupTraceListener(method_trace_listener_.get(), event, enable);
       return;
 
     default:
diff --git a/runtime/openjdkjvmti/ti_field.cc b/runtime/openjdkjvmti/ti_field.cc
index 342d8be..32c064e 100644
--- a/runtime/openjdkjvmti/ti_field.cc
+++ b/runtime/openjdkjvmti/ti_field.cc
@@ -187,4 +187,68 @@
   return ERR(NONE);
 }
 
+jvmtiError FieldUtil::SetFieldModificationWatch(jvmtiEnv* jenv, jclass klass, jfieldID field) {
+  ArtJvmTiEnv* env = ArtJvmTiEnv::AsArtJvmTiEnv(jenv);
+  if (klass == nullptr) {
+    return ERR(INVALID_CLASS);
+  }
+  if (field == nullptr) {
+    return ERR(INVALID_FIELDID);
+  }
+  auto res_pair = env->modify_watched_fields.insert(art::jni::DecodeArtField(field));
+  if (!res_pair.second) {
+    // Didn't get inserted because it's already present!
+    return ERR(DUPLICATE);
+  }
+  return OK;
+}
+
+jvmtiError FieldUtil::ClearFieldModificationWatch(jvmtiEnv* jenv, jclass klass, jfieldID field) {
+  ArtJvmTiEnv* env = ArtJvmTiEnv::AsArtJvmTiEnv(jenv);
+  if (klass == nullptr) {
+    return ERR(INVALID_CLASS);
+  }
+  if (field == nullptr) {
+    return ERR(INVALID_FIELDID);
+  }
+  auto pos = env->modify_watched_fields.find(art::jni::DecodeArtField(field));
+  if (pos == env->modify_watched_fields.end()) {
+    return ERR(NOT_FOUND);
+  }
+  env->modify_watched_fields.erase(pos);
+  return OK;
+}
+
+jvmtiError FieldUtil::SetFieldAccessWatch(jvmtiEnv* jenv, jclass klass, jfieldID field) {
+  ArtJvmTiEnv* env = ArtJvmTiEnv::AsArtJvmTiEnv(jenv);
+  if (klass == nullptr) {
+    return ERR(INVALID_CLASS);
+  }
+  if (field == nullptr) {
+    return ERR(INVALID_FIELDID);
+  }
+  auto res_pair = env->access_watched_fields.insert(art::jni::DecodeArtField(field));
+  if (!res_pair.second) {
+    // Didn't get inserted because it's already present!
+    return ERR(DUPLICATE);
+  }
+  return OK;
+}
+
+jvmtiError FieldUtil::ClearFieldAccessWatch(jvmtiEnv* jenv, jclass klass, jfieldID field) {
+  ArtJvmTiEnv* env = ArtJvmTiEnv::AsArtJvmTiEnv(jenv);
+  if (klass == nullptr) {
+    return ERR(INVALID_CLASS);
+  }
+  if (field == nullptr) {
+    return ERR(INVALID_FIELDID);
+  }
+  auto pos = env->access_watched_fields.find(art::jni::DecodeArtField(field));
+  if (pos == env->access_watched_fields.end()) {
+    return ERR(NOT_FOUND);
+  }
+  env->access_watched_fields.erase(pos);
+  return OK;
+}
+
 }  // namespace openjdkjvmti
diff --git a/runtime/openjdkjvmti/ti_field.h b/runtime/openjdkjvmti/ti_field.h
index 9a29f81..880949e 100644
--- a/runtime/openjdkjvmti/ti_field.h
+++ b/runtime/openjdkjvmti/ti_field.h
@@ -60,6 +60,11 @@
                                      jclass klass,
                                      jfieldID field,
                                      jboolean* is_synthetic_ptr);
+
+  static jvmtiError SetFieldModificationWatch(jvmtiEnv* env, jclass klass, jfieldID field);
+  static jvmtiError ClearFieldModificationWatch(jvmtiEnv* env, jclass klass, jfieldID field);
+  static jvmtiError SetFieldAccessWatch(jvmtiEnv* env, jclass klass, jfieldID field);
+  static jvmtiError ClearFieldAccessWatch(jvmtiEnv* env, jclass klass, jfieldID field);
 };
 
 }  // namespace openjdkjvmti