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