1 /*
   2  * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 #include <stdio.h>
  25 #include <string.h>
  26 #include <stdlib.h>
  27 #include "jvmti.h"
  28 #include "jni.h"
  29 
  30 #ifdef __cplusplus
  31 extern "C" {
  32 #endif
  33 
  34 #ifndef JNI_ENV_ARG
  35 
  36 #ifdef __cplusplus
  37 #define JNI_ENV_ARG(x, y) y
  38 #define JNI_ENV_PTR(x) x
  39 #else
  40 #define JNI_ENV_ARG(x,y) x, y
  41 #define JNI_ENV_PTR(x) (*x)
  42 #endif
  43 
  44 #endif
  45 
  46 
  47 static jvmtiEnv *jvmti = NULL;
  48 
  49 // valid while a test is executed
  50 static JNIEnv *javaEnv = NULL;
  51 static jobject currentTestResults = NULL;
  52 static jclass testResultClass = NULL;
  53 
  54 
  55 void reportError(const char *msg, int err) {
  56     printf("%s, error: %d\n", msg, err);
  57 }
  58 
  59 
  60 // logs the notification and updates currentTestResult
  61 static void handleNotification(jmethodID method,
  62     jfieldID field,
  63     jclass field_klass,
  64     int modified,
  65     jlocation location)
  66 {
  67     jvmtiError err;
  68     char *name;
  69     char *mname = NULL;
  70     char *mgensig = NULL;
  71     jclass methodClass = NULL;
  72     char *csig = NULL;
  73 
  74     if (currentTestResults == NULL) {
  75         // we are out of test
  76         return;
  77     }
  78 
  79     err = (*jvmti)->GetFieldName(jvmti, field_klass, field, &name, NULL, NULL);
  80     if (err != JVMTI_ERROR_NONE) {
  81         reportError("GetFieldName failed", err);
  82         return;
  83     }
  84 
  85     err = (*jvmti)->GetMethodName(jvmti, method, &mname, NULL, &mgensig);
  86     if (err != JVMTI_ERROR_NONE) {
  87         reportError("GetMethodName failed", err);
  88         return;
  89     }
  90 
  91     err = (*jvmti)->GetMethodDeclaringClass(jvmti, method, &methodClass);
  92     if (err != JVMTI_ERROR_NONE) {
  93         reportError("GetMethodDeclaringClass failed", err);
  94         return;
  95     }
  96 
  97     err = (*jvmti)->GetClassSignature(jvmti, methodClass, &csig, NULL);
  98     if (err != JVMTI_ERROR_NONE) {
  99         reportError("GetClassSignature failed", err);
 100         return;
 101     }
 102 
 103     printf("\"class: %s method: %s%s\" %s field: \"%s\", location: %d\n",
 104         csig, mname, mgensig, modified ? "modified" : "accessed", name, (int)location);
 105 
 106     // set TestResult
 107     if (javaEnv != NULL && currentTestResults != NULL && testResultClass != NULL) {
 108         jfieldID fieldID;
 109         // field names in TestResult are "<field_name>_access"/"<field_name>_modify"
 110         char *fieldName = (char *)malloc(strlen(name) + 16);
 111         strcpy(fieldName, name);
 112         strcat(fieldName, modified ? "_modify" : "_access");
 113 
 114         fieldID = (*javaEnv)->GetFieldID(javaEnv, testResultClass, fieldName, "Z");
 115         if (fieldID != NULL) {
 116             (*javaEnv)->SetBooleanField(javaEnv, currentTestResults, fieldID, JNI_TRUE);
 117         } else {
 118             // the field is not interesting for the test
 119         }
 120         // clear any possible exception
 121         (*javaEnv)->ExceptionClear(javaEnv);
 122 
 123         free(fieldName);
 124     }
 125 
 126     (*jvmti)->Deallocate(jvmti, (unsigned char*)csig);
 127     (*jvmti)->Deallocate(jvmti, (unsigned char*)mname);
 128     (*jvmti)->Deallocate(jvmti, (unsigned char*)mgensig);
 129     (*jvmti)->Deallocate(jvmti, (unsigned char*)name);
 130 }
 131 
 132 void tagAndWatch(JNIEnv *jni_env, const jobject obj)
 133 {
 134     jclass klass;
 135 
 136     if (obj == NULL) {
 137         return;
 138     }
 139 
 140     klass = (*jni_env)->GetObjectClass(jni_env, obj);
 141     do {
 142         jfieldID* klassFields;
 143         jint fieldCount;
 144         char *sig = NULL;
 145         int i;
 146         jvmtiError err = (*jvmti)->GetClassFields(jvmti, klass, &fieldCount, &klassFields);
 147         if (err != JVMTI_ERROR_NONE) {
 148             reportError("Failed to get class fields", err);
 149         }
 150 
 151         for (i = 0; i < fieldCount; ++i) {
 152             err = (*jvmti)->SetFieldModificationWatch(jvmti, klass, klassFields[i]);
 153             if (err != JVMTI_ERROR_NONE && err != JVMTI_ERROR_DUPLICATE) {
 154                 reportError("Failed to set field modification", err);
 155             }
 156 
 157             err = (*jvmti)->SetFieldAccessWatch(jvmti, klass, klassFields[i]);
 158             if (err != JVMTI_ERROR_NONE && err != JVMTI_ERROR_DUPLICATE) {
 159                 reportError("Failed to set field access", err);
 160             }
 161 
 162             err = (*jvmti)->GetFieldName(jvmti, klass, klassFields[i], NULL, &sig, NULL);
 163             if (sig) {
 164                 if (sig[0] == 'L') {
 165                     jobject fieldVal = (*jni_env)->GetObjectField(jni_env, obj, klassFields[i]);
 166                     tagAndWatch(jni_env, fieldVal);
 167                 }
 168                 (*jvmti)->Deallocate(jvmti, (unsigned char*)sig);
 169             }
 170         }
 171 
 172         err = (*jvmti)->Deallocate(jvmti, (unsigned char*)klassFields);
 173         if (err != JVMTI_ERROR_NONE) {
 174             reportError("Failed to deallocate fields", err);
 175         }
 176 
 177         klass = (*jni_env)->GetSuperclass(jni_env, klass);
 178     } while (klass != NULL);
 179 }
 180 
 181 
 182 static void JNICALL
 183 onFieldAccess(jvmtiEnv *jvmti_env,
 184             JNIEnv* jni_env,
 185             jthread thread,
 186             jmethodID method,
 187             jlocation location,
 188             jclass field_klass,
 189             jobject object,
 190             jfieldID field)
 191 {
 192     handleNotification(method, field, field_klass, 0, location);
 193 }
 194 
 195 
 196 static void JNICALL
 197 onFieldModification(jvmtiEnv *jvmti_env,
 198             JNIEnv* jni_env,
 199             jthread thread,
 200             jmethodID method,
 201             jlocation location,
 202             jclass field_klass,
 203             jobject object,
 204             jfieldID field,
 205             char signature_type,
 206             jvalue new_value)
 207 {
 208     handleNotification(method, field, field_klass, 1, location);
 209 
 210     if (signature_type == 'L') {
 211         jobject newObject = new_value.l;
 212         tagAndWatch(jni_env, newObject);
 213     }
 214 }
 215 
 216 
 217 JNIEXPORT jint JNICALL
 218 Agent_OnLoad(JavaVM *jvm, char *options, void *reserved)
 219 {
 220     jvmtiError err;
 221     jvmtiCapabilities caps = {0};
 222     jvmtiEventCallbacks callbacks = {0};
 223     jint res = JNI_ENV_PTR(jvm)->GetEnv(JNI_ENV_ARG(jvm, (void **) &jvmti), JVMTI_VERSION_1_1);
 224     if (res != JNI_OK || jvmti == NULL) {
 225         reportError("GetEnv failed", res);
 226         return JNI_ERR;
 227     }
 228 
 229     caps.can_generate_field_modification_events = 1;
 230     caps.can_generate_field_access_events = 1;
 231     caps.can_tag_objects = 1;
 232     err = (*jvmti)->AddCapabilities(jvmti, &caps);
 233     if (err != JVMTI_ERROR_NONE) {
 234         reportError("Failed to set capabilities", err);
 235         return JNI_ERR;
 236     }
 237 
 238     callbacks.FieldModification = &onFieldModification;
 239     callbacks.FieldAccess = &onFieldAccess;
 240 
 241     err = (*jvmti)->SetEventCallbacks(jvmti, &callbacks, sizeof(callbacks));
 242     if (err != JVMTI_ERROR_NONE) {
 243         reportError("Failed to set event callbacks", err);
 244         return JNI_ERR;
 245     }
 246 
 247     err = (*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE, JVMTI_EVENT_FIELD_ACCESS, NULL);
 248     if (err != JVMTI_ERROR_NONE) {
 249         reportError("Failed to set access notifications", err);
 250         return JNI_ERR;
 251     }
 252 
 253     err = (*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE, JVMTI_EVENT_FIELD_MODIFICATION, NULL);
 254     if (err != JVMTI_ERROR_NONE) {
 255         reportError("Failed to set modification notifications", err);
 256         return JNI_ERR;
 257     }
 258     setbuf(stdout, NULL);
 259     return JNI_OK;
 260 }
 261 
 262 
 263 JNIEXPORT jboolean JNICALL
 264 Java_FieldAccessWatch_initWatchers(JNIEnv *env, jclass thisClass, jclass cls, jobject field)
 265 {
 266     jfieldID fieldId;
 267     jvmtiError err;
 268 
 269     if (jvmti == NULL) {
 270         reportError("jvmti is NULL", 0);
 271         return JNI_FALSE;
 272     }
 273 
 274     fieldId = (*env)->FromReflectedField(env, field);
 275 
 276     err = (*jvmti)->SetFieldModificationWatch(jvmti, cls, fieldId);
 277     if (err != JVMTI_ERROR_NONE) {
 278         reportError("SetFieldModificationWatch failed", err);
 279         return JNI_FALSE;
 280     }
 281 
 282     err = (*jvmti)->SetFieldAccessWatch(jvmti, cls, fieldId);
 283     if (err != JVMTI_ERROR_NONE) {
 284         reportError("SetFieldAccessWatch failed", err);
 285         return JNI_FALSE;
 286     }
 287 
 288     return JNI_TRUE;
 289 }
 290 
 291 
 292 JNIEXPORT jboolean JNICALL
 293 Java_FieldAccessWatch_startTest(JNIEnv *env, jclass thisClass, jobject testResults)
 294 {
 295     javaEnv = env;
 296     if (currentTestResults != NULL) {
 297         (*javaEnv)->DeleteGlobalRef(javaEnv, currentTestResults);
 298     }
 299     currentTestResults = (*javaEnv)->NewGlobalRef(javaEnv, testResults);
 300     testResultClass = (*javaEnv)->NewGlobalRef(javaEnv, (*javaEnv)->GetObjectClass(javaEnv, currentTestResults));
 301 
 302     return JNI_TRUE;
 303 }
 304 
 305 JNIEXPORT void JNICALL
 306 Java_FieldAccessWatch_stopTest(JNIEnv *env, jclass thisClass)
 307 {
 308     if (currentTestResults != NULL) {
 309         (*env)->DeleteGlobalRef(env, currentTestResults);
 310         currentTestResults = NULL;
 311     }
 312     if (testResultClass != NULL) {
 313         (*env)->DeleteGlobalRef(env, testResultClass);
 314         testResultClass = NULL;
 315     }
 316 }
 317 
 318 
 319 #ifdef __cplusplus
 320 }
 321 #endif
 322