1 /*
  2  * Copyright (c) 2021, 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 
 35 static jvmtiEnv *jvmti = nullptr;
 36 
 37 // valid while a test is executed
 38 static jobject testResultObject = nullptr;
 39 static jclass testResultClass = nullptr;
 40 // we log object values handling FieldModification event and this cause FieldAccess events are triggered.
 41 // The flag to disable FieldAccess handling.
 42 static bool disableAccessEvent = false;
 43 
 44 static void reportError(const char *msg, int err) {
 45     printf("%s, error: %d\n", msg, err);
 46 }
 47 
 48 static void printJValue(const char *prefix, JNIEnv *jni_env, char signature_type, jvalue value) {
 49     // print new_value to ensure the value is valid
 50     // use String.valueOf(...) to get string representation
 51     /*
 52     Z boolean
 53     B byte
 54     C char
 55     S short
 56     I int
 57     J long
 58     F float
 59     D double
 60     L fully-qualified-class ;   fully-qualified-class
 61     [ type                      type[]
 62     */
 63     char signature[64] = {};
 64     if (signature_type == 'Q' || signature_type == 'L') {
 65         snprintf(signature, sizeof(signature), "(Ljava/lang/Object;)Ljava/lang/String;");
 66     } else {
 67         snprintf(signature, sizeof(signature), "(%c)Ljava/lang/String;", signature_type);
 68     }
 69 
 70     jclass clsString = jni_env->FindClass("java/lang/String");
 71     jmethodID mid = jni_env->GetStaticMethodID(clsString, "valueOf", signature);
 72     jstring objJStr = (jstring)jni_env->CallStaticObjectMethodA(clsString, mid, &value);
 73 
 74     const char* objStr = "UNKNOWN";
 75     if (objJStr != nullptr) {
 76         objStr = jni_env->GetStringUTFChars(objJStr, nullptr);
 77     }
 78 
 79     printf("    %s is: '%s'\n", prefix, objStr);
 80     fflush(0);
 81 
 82     if (objJStr != nullptr) {
 83         jni_env->ReleaseStringUTFChars(objJStr, objStr);
 84     }
 85 }
 86 
 87 
 88 // logs the notification and updates currentTestResult
 89 static void handleNotification(jvmtiEnv *jvmti, JNIEnv *jni_env,
 90     jmethodID method,
 91     jobject object,
 92     jfieldID field,
 93     jclass field_klass,
 94     bool modified,
 95     jlocation location)
 96 {
 97     jvmtiError err;
 98     char *name = nullptr;
 99     char *signature = nullptr;
100     char *mname = nullptr;
101     char *mgensig = nullptr;
102     jclass methodClass = nullptr;
103     char *csig = nullptr;
104 
105     if (testResultObject == nullptr) {
106         // we are out of test
107         return;
108     }
109 
110     err = jvmti->GetFieldName(field_klass, field, &name, &signature, nullptr);
111     if (err != JVMTI_ERROR_NONE) {
112         reportError("GetFieldName failed", err);
113         return;
114     }
115 
116     err = jvmti->GetMethodName(method, &mname, nullptr, &mgensig);
117     if (err != JVMTI_ERROR_NONE) {
118         reportError("GetMethodName failed", err);
119         return;
120     }
121 
122     err = jvmti->GetMethodDeclaringClass(method, &methodClass);
123     if (err != JVMTI_ERROR_NONE) {
124         reportError("GetMethodDeclaringClass failed", err);
125         return;
126     }
127 
128     err = jvmti->GetClassSignature(methodClass, &csig, nullptr);
129     if (err != JVMTI_ERROR_NONE) {
130         reportError("GetClassSignature failed", err);
131         return;
132     }
133 
134     printf("  \"class: %s method: %s%s\" %s field: \"%s\" (type '%s'), location: %d\n",
135         csig, mname, mgensig, modified ? "modified" : "accessed", name, signature, (int)location);
136 
137     // For FieldModification event print current value.
138     // Note: this will cause FieldAccess event.
139     if (modified) {
140         jvalue curValue = {};
141         switch (signature[0]) {
142         case 'L':
143         case 'Q':
144             curValue.l = jni_env->GetObjectField(object, field); break;
145         case 'Z':   // boolean
146             curValue.z = jni_env->GetBooleanField(object, field); break;
147         case 'B':   // byte
148             curValue.b = jni_env->GetByteField(object, field); break;
149         case 'C':   // char
150             curValue.c = jni_env->GetCharField(object, field); break;
151         case 'S':   // short
152             curValue.s = jni_env->GetShortField(object, field); break;
153         case 'I':   // int
154             curValue.i = jni_env->GetIntField(object, field); break;
155         case 'J':   // long
156             curValue.j = jni_env->GetLongField(object, field); break;
157         case 'F':   // float
158             curValue.f = jni_env->GetFloatField(object, field); break;
159         case 'D':   // double
160             curValue.d = jni_env->GetDoubleField(object, field); break;
161         default:
162             printf("ERROR: unexpected signature: %s\n", signature);
163             return;
164         }
165         printJValue("current value: ", jni_env, signature[0], curValue);
166     }
167 
168     // set TestResult
169     if (testResultObject != nullptr && testResultClass != nullptr) {
170         jfieldID fieldID;
171         // field names in TestResult are "<field_name>_access"/"<field_name>_modify"
172         char *fieldName = (char *)malloc(strlen(name) + 16);
173         strcpy(fieldName, name);
174         strcat(fieldName, modified ? "_modify" : "_access");
175 
176         fieldID = jni_env->GetFieldID(testResultClass, fieldName, "Z");
177         if (fieldID != nullptr) {
178             jni_env->SetBooleanField(testResultObject, fieldID, JNI_TRUE);
179         } else {
180             // the field is not interesting for the test
181         }
182         // clear any possible exception
183         jni_env->ExceptionClear();
184 
185         free(fieldName);
186     }
187 
188     jvmti->Deallocate((unsigned char*)csig);
189     jvmti->Deallocate((unsigned char*)mname);
190     jvmti->Deallocate((unsigned char*)mgensig);
191     jvmti->Deallocate((unsigned char*)name);
192     jvmti->Deallocate((unsigned char*)signature);
193 }
194 
195 static void JNICALL
196 onFieldAccess(jvmtiEnv *jvmti_env,
197             JNIEnv* jni_env,
198             jthread thread,
199             jmethodID method,
200             jlocation location,
201             jclass field_klass,
202             jobject object,
203             jfieldID field)
204 {
205     if (disableAccessEvent) {
206         return;
207     }
208     handleNotification(jvmti_env, jni_env, method, object, field, field_klass, false, location);
209 }
210 
211 static void JNICALL
212 onFieldModification(jvmtiEnv *jvmti_env,
213             JNIEnv* jni_env,
214             jthread thread,
215             jmethodID method,
216             jlocation location,
217             jclass field_klass,
218             jobject object,
219             jfieldID field,
220             char signature_type,
221             jvalue new_value)
222 {
223     disableAccessEvent = true;
224 
225     handleNotification(jvmti_env, jni_env, method, object, field, field_klass, true, location);
226 
227     printJValue("new value", jni_env, signature_type, new_value);
228 
229     disableAccessEvent = false;
230 }
231 
232 
233 JNIEXPORT jint JNICALL
234 Agent_OnLoad(JavaVM *jvm, char *options, void *reserved)
235 {
236     jvmtiError err;
237     jvmtiCapabilities caps = {};
238     jvmtiEventCallbacks callbacks = {};
239     jint res = jvm->GetEnv((void **) &jvmti, JVMTI_VERSION_1_1);
240     if (res != JNI_OK || jvmti == nullptr) {
241         reportError("GetEnv failed", res);
242         return JNI_ERR;
243     }
244 
245     caps.can_generate_field_modification_events = 1;
246     caps.can_generate_field_access_events = 1;
247     caps.can_tag_objects = 1;
248     err = jvmti->AddCapabilities(&caps);
249     if (err != JVMTI_ERROR_NONE) {
250         reportError("Failed to set capabilities", err);
251         return JNI_ERR;
252     }
253 
254     callbacks.FieldModification = &onFieldModification;
255     callbacks.FieldAccess = &onFieldAccess;
256 
257     err = jvmti->SetEventCallbacks(&callbacks, sizeof(callbacks));
258     if (err != JVMTI_ERROR_NONE) {
259         reportError("Failed to set event callbacks", err);
260         return JNI_ERR;
261     }
262 
263     err = jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_FIELD_ACCESS, nullptr);
264     if (err != JVMTI_ERROR_NONE) {
265         reportError("Failed to set access notifications", err);
266         return JNI_ERR;
267     }
268 
269     err = jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_FIELD_MODIFICATION, nullptr);
270     if (err != JVMTI_ERROR_NONE) {
271         reportError("Failed to set modification notifications", err);
272         return JNI_ERR;
273     }
274     setbuf(stdout, nullptr);
275     return JNI_OK;
276 }
277 
278 
279 JNIEXPORT jboolean JNICALL
280 Java_FieldAccessModify_initWatchers(JNIEnv *env, jclass thisClass, jclass cls, jobject field)
281 {
282     jfieldID fieldId;
283     jvmtiError err;
284 
285     if (jvmti == nullptr) {
286         reportError("jvmti is NULL", 0);
287         return JNI_FALSE;
288     }
289 
290     fieldId = env->FromReflectedField(field);
291 
292     err = jvmti->SetFieldModificationWatch(cls, fieldId);
293     if (err != JVMTI_ERROR_NONE) {
294         reportError("SetFieldModificationWatch failed", err);
295         return JNI_FALSE;
296     }
297 
298     err = jvmti->SetFieldAccessWatch(cls, fieldId);
299     if (err != JVMTI_ERROR_NONE) {
300         reportError("SetFieldAccessWatch failed", err);
301         return JNI_FALSE;
302     }
303 
304     return JNI_TRUE;
305 }
306 
307 
308 JNIEXPORT jboolean JNICALL
309 Java_FieldAccessModify_startTest(JNIEnv *env, jclass thisClass, jobject testResults)
310 {
311     testResultObject = env->NewGlobalRef(testResults);
312     testResultClass = (jclass)env->NewGlobalRef(env->GetObjectClass(testResultObject));
313 
314     return JNI_TRUE;
315 }
316 
317 JNIEXPORT void JNICALL
318 Java_FieldAccessModify_stopTest(JNIEnv *env, jclass thisClass)
319 {
320     if (testResultObject != nullptr) {
321         env->DeleteGlobalRef(testResultObject);
322         testResultObject = nullptr;
323     }
324     if (testResultClass != nullptr) {
325         env->DeleteGlobalRef(testResultClass);
326         testResultClass = nullptr;
327     }
328 }
329 
330 
331 #ifdef __cplusplus
332 }
333 #endif
334