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 nullptr", 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