--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/hotspot/jtreg/serviceability/jvmti/FieldAccessWatch/FieldAccessWatch.java Mon Mar 12 14:11:54 2018 -0700
@@ -0,0 +1,137 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * @test
+ * @bug 8193369
+ * @summary Tests that all FieldAccess and FieldModification notifications
+ are generated.
+ * @compile FieldAccessWatch.java
+ * @run main/othervm/native -agentlib:FieldAccessWatch FieldAccessWatch
+ */
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+
+public class FieldAccessWatch {
+
+ private static final String agentLib = "FieldAccessWatch";
+
+ private static class MyItem {
+ }
+
+ private static class MyList {
+ public List<MyItem> items = new ArrayList<>();
+ }
+
+ public static void main(String[] args) throws Exception {
+ try {
+ System.loadLibrary(agentLib);
+ } catch (UnsatisfiedLinkError ex) {
+ System.err.println("Failed to load " + agentLib + " lib");
+ System.err.println("java.library.path: " + System.getProperty("java.library.path"));
+ throw ex;
+ }
+
+ if (!initWatchers(MyList.class, MyList.class.getDeclaredField("items"))) {
+ throw new RuntimeException("Watchers initializations error");
+ }
+
+ MyList list = new MyList();
+
+ test("[1]items.add(0, object)",() -> list.items.add(0, new MyItem()));
+ test("[2]items.add(object)", () -> list.items.add(new MyItem()));
+ test("[3]items.add(1, object)", () -> list.items.add(1, new MyItem()));
+ test("[4]items.add(object)", () -> list.items.add(new MyItem()));
+ test("[5]items.add(1, object)", () -> list.items.add(1, new MyItem()));
+ }
+
+ private static void log(String msg) {
+ System.out.println(msg);
+ System.out.flush();
+ }
+
+ // For every access/modify notification native part tries to locate
+ // boolean "<field_name>_access"/"<field_name>_modify" field and sets it to true
+ private static class TestResult {
+ // MyList.items
+ public boolean items_access;
+
+ // AbstractList.modCount
+ public boolean modCount_access;
+ public boolean modCount_modify;
+
+ // ArrayList.size
+ public boolean size_access;
+ public boolean size_modify;
+
+ // ArrayList.elementData
+ public boolean elementData_access;
+
+ // verify that all fields are set to true
+ public void verify() {
+ Arrays.stream(this.getClass().getDeclaredFields()).forEach(f -> verify(f));
+ }
+
+ private void verify(Field f) {
+ try {
+ if (!f.getBoolean(this)) {
+ throw new RuntimeException(f.getName() + " notification is missed");
+ }
+ } catch (IllegalAccessException ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+ }
+
+ @FunctionalInterface
+ private interface TestAction {
+ void apply();
+ }
+
+ private static void test(String descr, TestAction action) throws Exception {
+ log(descr + ": starting");
+ TestResult result = new TestResult();
+ if (!startTest(result)) {
+ throw new RuntimeException("startTest failed");
+ }
+ action.apply();
+ // wait some time to ensure all posted events are handled
+ Thread.sleep(500);
+
+ stopTest();
+
+ // check the results
+ result.verify();
+
+ log(descr + ": OK");
+ }
+
+ private static native boolean initWatchers(Class cls, Field field);
+ private static native boolean startTest(TestResult results);
+ private static native void stopTest();
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/hotspot/jtreg/serviceability/jvmti/FieldAccessWatch/libFieldAccessWatch.c Mon Mar 12 14:11:54 2018 -0700
@@ -0,0 +1,309 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include "jvmti.h"
+#include "jni.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+static jvmtiEnv *jvmti = NULL;
+
+// valid while a test is executed
+static JNIEnv *javaEnv = NULL;
+static jobject testResultObject = NULL;
+static jclass testResultClass = NULL;
+
+
+static void reportError(const char *msg, int err) {
+ printf("%s, error: %d\n", msg, err);
+}
+
+
+// logs the notification and updates currentTestResult
+static void handleNotification(jmethodID method,
+ jfieldID field,
+ jclass field_klass,
+ int modified,
+ jlocation location)
+{
+ jvmtiError err;
+ char *name = NULL;
+ char *mname = NULL;
+ char *mgensig = NULL;
+ jclass methodClass = NULL;
+ char *csig = NULL;
+
+ if (testResultObject == NULL) {
+ // we are out of test
+ return;
+ }
+
+ err = (*jvmti)->GetFieldName(jvmti, field_klass, field, &name, NULL, NULL);
+ if (err != JVMTI_ERROR_NONE) {
+ reportError("GetFieldName failed", err);
+ return;
+ }
+
+ err = (*jvmti)->GetMethodName(jvmti, method, &mname, NULL, &mgensig);
+ if (err != JVMTI_ERROR_NONE) {
+ reportError("GetMethodName failed", err);
+ return;
+ }
+
+ err = (*jvmti)->GetMethodDeclaringClass(jvmti, method, &methodClass);
+ if (err != JVMTI_ERROR_NONE) {
+ reportError("GetMethodDeclaringClass failed", err);
+ return;
+ }
+
+ err = (*jvmti)->GetClassSignature(jvmti, methodClass, &csig, NULL);
+ if (err != JVMTI_ERROR_NONE) {
+ reportError("GetClassSignature failed", err);
+ return;
+ }
+
+ printf("\"class: %s method: %s%s\" %s field: \"%s\", location: %d\n",
+ csig, mname, mgensig, modified ? "modified" : "accessed", name, (int)location);
+
+ // set TestResult
+ if (javaEnv != NULL && testResultObject != NULL && testResultClass != NULL) {
+ jfieldID fieldID;
+ // field names in TestResult are "<field_name>_access"/"<field_name>_modify"
+ char *fieldName = (char *)malloc(strlen(name) + 16);
+ strcpy(fieldName, name);
+ strcat(fieldName, modified ? "_modify" : "_access");
+
+ fieldID = (*javaEnv)->GetFieldID(javaEnv, testResultClass, fieldName, "Z");
+ if (fieldID != NULL) {
+ (*javaEnv)->SetBooleanField(javaEnv, testResultObject, fieldID, JNI_TRUE);
+ } else {
+ // the field is not interesting for the test
+ }
+ // clear any possible exception
+ (*javaEnv)->ExceptionClear(javaEnv);
+
+ free(fieldName);
+ }
+
+ (*jvmti)->Deallocate(jvmti, (unsigned char*)csig);
+ (*jvmti)->Deallocate(jvmti, (unsigned char*)mname);
+ (*jvmti)->Deallocate(jvmti, (unsigned char*)mgensig);
+ (*jvmti)->Deallocate(jvmti, (unsigned char*)name);
+}
+
+// recursively sets access and modification watchers for all
+// fields of the object specified.
+void setWatchers(JNIEnv *jni_env, const jobject obj)
+{
+ jclass klass;
+
+ if (obj == NULL) {
+ return;
+ }
+
+ klass = (*jni_env)->GetObjectClass(jni_env, obj);
+ do {
+ jfieldID* klassFields = NULL;
+ jint fieldCount = 0;
+ int i;
+ jvmtiError err = (*jvmti)->GetClassFields(jvmti, klass, &fieldCount, &klassFields);
+ if (err != JVMTI_ERROR_NONE) {
+ reportError("Failed to get class fields", err);
+ return;
+ }
+
+ for (i = 0; i < fieldCount; ++i) {
+ char *sig = NULL;
+ err = (*jvmti)->SetFieldModificationWatch(jvmti, klass, klassFields[i]);
+ if (err != JVMTI_ERROR_NONE && err != JVMTI_ERROR_DUPLICATE) {
+ reportError("Failed to set field modification", err);
+ return;
+ }
+
+ err = (*jvmti)->SetFieldAccessWatch(jvmti, klass, klassFields[i]);
+ if (err != JVMTI_ERROR_NONE && err != JVMTI_ERROR_DUPLICATE) {
+ reportError("Failed to set field access", err);
+ return;
+ }
+
+ err = (*jvmti)->GetFieldName(jvmti, klass, klassFields[i], NULL, &sig, NULL);
+ if (sig) {
+ if (sig[0] == 'L') {
+ jobject fieldVal = (*jni_env)->GetObjectField(jni_env, obj, klassFields[i]);
+ setWatchers(jni_env, fieldVal);
+ }
+ (*jvmti)->Deallocate(jvmti, (unsigned char*)sig);
+ }
+ }
+
+ (*jvmti)->Deallocate(jvmti, (unsigned char*)klassFields);
+
+ klass = (*jni_env)->GetSuperclass(jni_env, klass);
+ } while (klass != NULL);
+}
+
+
+static void JNICALL
+onFieldAccess(jvmtiEnv *jvmti_env,
+ JNIEnv* jni_env,
+ jthread thread,
+ jmethodID method,
+ jlocation location,
+ jclass field_klass,
+ jobject object,
+ jfieldID field)
+{
+ handleNotification(method, field, field_klass, 0, location);
+}
+
+
+static void JNICALL
+onFieldModification(jvmtiEnv *jvmti_env,
+ JNIEnv* jni_env,
+ jthread thread,
+ jmethodID method,
+ jlocation location,
+ jclass field_klass,
+ jobject object,
+ jfieldID field,
+ char signature_type,
+ jvalue new_value)
+{
+ handleNotification(method, field, field_klass, 1, location);
+
+ if (signature_type == 'L') {
+ jobject newObject = new_value.l;
+ setWatchers(jni_env, newObject);
+ }
+}
+
+
+JNIEXPORT jint JNICALL
+Agent_OnLoad(JavaVM *jvm, char *options, void *reserved)
+{
+ jvmtiError err;
+ jvmtiCapabilities caps = {0};
+ jvmtiEventCallbacks callbacks = {0};
+ jint res = (*jvm)->GetEnv(jvm, (void **) &jvmti, JVMTI_VERSION_1_1);
+ if (res != JNI_OK || jvmti == NULL) {
+ reportError("GetEnv failed", res);
+ return JNI_ERR;
+ }
+
+ caps.can_generate_field_modification_events = 1;
+ caps.can_generate_field_access_events = 1;
+ caps.can_tag_objects = 1;
+ err = (*jvmti)->AddCapabilities(jvmti, &caps);
+ if (err != JVMTI_ERROR_NONE) {
+ reportError("Failed to set capabilities", err);
+ return JNI_ERR;
+ }
+
+ callbacks.FieldModification = &onFieldModification;
+ callbacks.FieldAccess = &onFieldAccess;
+
+ err = (*jvmti)->SetEventCallbacks(jvmti, &callbacks, sizeof(callbacks));
+ if (err != JVMTI_ERROR_NONE) {
+ reportError("Failed to set event callbacks", err);
+ return JNI_ERR;
+ }
+
+ err = (*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE, JVMTI_EVENT_FIELD_ACCESS, NULL);
+ if (err != JVMTI_ERROR_NONE) {
+ reportError("Failed to set access notifications", err);
+ return JNI_ERR;
+ }
+
+ err = (*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE, JVMTI_EVENT_FIELD_MODIFICATION, NULL);
+ if (err != JVMTI_ERROR_NONE) {
+ reportError("Failed to set modification notifications", err);
+ return JNI_ERR;
+ }
+ setbuf(stdout, NULL);
+ return JNI_OK;
+}
+
+
+JNIEXPORT jboolean JNICALL
+Java_FieldAccessWatch_initWatchers(JNIEnv *env, jclass thisClass, jclass cls, jobject field)
+{
+ jfieldID fieldId;
+ jvmtiError err;
+
+ if (jvmti == NULL) {
+ reportError("jvmti is NULL", 0);
+ return JNI_FALSE;
+ }
+
+ fieldId = (*env)->FromReflectedField(env, field);
+
+ err = (*jvmti)->SetFieldModificationWatch(jvmti, cls, fieldId);
+ if (err != JVMTI_ERROR_NONE) {
+ reportError("SetFieldModificationWatch failed", err);
+ return JNI_FALSE;
+ }
+
+ err = (*jvmti)->SetFieldAccessWatch(jvmti, cls, fieldId);
+ if (err != JVMTI_ERROR_NONE) {
+ reportError("SetFieldAccessWatch failed", err);
+ return JNI_FALSE;
+ }
+
+ return JNI_TRUE;
+}
+
+
+JNIEXPORT jboolean JNICALL
+Java_FieldAccessWatch_startTest(JNIEnv *env, jclass thisClass, jobject testResults)
+{
+ javaEnv = env;
+ testResultObject = (*javaEnv)->NewGlobalRef(javaEnv, testResults);
+ testResultClass = (jclass)(*javaEnv)->NewGlobalRef(javaEnv, (*javaEnv)->GetObjectClass(javaEnv, testResultObject));
+
+ return JNI_TRUE;
+}
+
+JNIEXPORT void JNICALL
+Java_FieldAccessWatch_stopTest(JNIEnv *env, jclass thisClass)
+{
+ if (testResultObject != NULL) {
+ (*env)->DeleteGlobalRef(env, testResultObject);
+ testResultObject = NULL;
+ }
+ if (testResultClass != NULL) {
+ (*env)->DeleteGlobalRef(env, testResultClass);
+ testResultClass = NULL;
+ }
+}
+
+
+#ifdef __cplusplus
+}
+#endif
+