8193369: post_field_access does not work for some functions, possibly related to fast_getfield
authoramenkov
Mon, 12 Mar 2018 14:11:54 -0700
changeset 49379 2d1d0c66966b
parent 49378 1873626a5d67
child 49380 74518f9ca4b4
8193369: post_field_access does not work for some functions, possibly related to fast_getfield Reviewed-by: sspitsyn, cjplummer
make/test/JtregNativeHotspot.gmk
src/hotspot/share/prims/jvmtiManageCapabilities.cpp
test/hotspot/jtreg/serviceability/jvmti/FieldAccessWatch/FieldAccessWatch.java
test/hotspot/jtreg/serviceability/jvmti/FieldAccessWatch/libFieldAccessWatch.c
--- a/make/test/JtregNativeHotspot.gmk	Mon Mar 12 13:54:55 2018 -0700
+++ b/make/test/JtregNativeHotspot.gmk	Mon Mar 12 14:11:54 2018 -0700
@@ -68,6 +68,7 @@
     $(TOPDIR)/test/hotspot/jtreg/compiler/runtime/criticalnatives/lookup \
     $(TOPDIR)/test/hotspot/jtreg/compiler/runtime/criticalnatives/argumentcorruption \
     $(TOPDIR)/test/hotspot/jtreg/serviceability/jvmti/CanGenerateAllClassHook \
+    $(TOPDIR)/test/hotspot/jtreg/serviceability/jvmti/FieldAccessWatch \
     $(TOPDIR)/test/hotspot/jtreg/serviceability/jvmti/GetOwnedMonitorInfo \
     $(TOPDIR)/test/hotspot/jtreg/serviceability/jvmti/GetOwnedMonitorStackDepthInfo \
     $(TOPDIR)/test/hotspot/jtreg/serviceability/jvmti/GetNamedModule \
@@ -103,6 +104,7 @@
     BUILD_HOTSPOT_JTREG_LIBRARIES_LIBS_liboverflow := -lc
     BUILD_HOTSPOT_JTREG_LIBRARIES_LIBS_libSimpleClassFileLoadHook := -lc
     BUILD_HOTSPOT_JTREG_LIBRARIES_LIBS_libCanGenerateAllClassHook := -lc
+    BUILD_HOTSPOT_JTREG_LIBRARIES_LIBS_libFieldAccessWatch := -lc
     BUILD_HOTSPOT_JTREG_LIBRARIES_LIBS_libGetOwnedMonitorInfoTest := -lc
     BUILD_HOTSPOT_JTREG_LIBRARIES_LIBS_libGetOwnedMonitorStackDepthInfoTest := -lc
     BUILD_HOTSPOT_JTREG_LIBRARIES_LIBS_libGetNamedModuleTest := -lc
--- a/src/hotspot/share/prims/jvmtiManageCapabilities.cpp	Mon Mar 12 13:54:55 2018 -0700
+++ b/src/hotspot/share/prims/jvmtiManageCapabilities.cpp	Mon Mar 12 14:11:54 2018 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2003, 2016, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2003, 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
@@ -312,7 +312,10 @@
   }
 #endif // ZERO
 
-  if (avail.can_generate_breakpoint_events) {
+  if (avail.can_generate_breakpoint_events
+       || avail.can_generate_field_access_events
+       || avail.can_generate_field_modification_events)
+  {
     RewriteFrequentPairs = false;
   }
 
--- /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
+