1 /*
  2 * Copyright (c) 2003, 2020, 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 <inttypes.h>
 27 #include "jvmti.h"
 28 #include "jvmti_common.h"
 29 
 30 extern "C" {
 31 
 32 
 33 #define PASSED 0
 34 #define STATUS_FAILED 2
 35 
 36 typedef struct {
 37   const char *cls_sig;
 38   const char *name;
 39   const char *sig;
 40   jlocation loc;
 41 } method_location_info;
 42 
 43 static jvmtiEnv *jvmti = NULL;
 44 static jvmtiEventCallbacks callbacks;
 45 static jint result = PASSED;
 46 static jboolean isVirtualExpected = JNI_FALSE;
 47 static size_t eventsExpected = 0;
 48 static size_t eventsCount = 0;
 49 static method_location_info exits[] = {
 50     { "Lmexit02a;", "chain", "()V", -1 },
 51     { "Lmexit02a;", "dummy", "()V", 3 }
 52 };
 53 
 54 void JNICALL MethodExit(jvmtiEnv *jvmti, JNIEnv *jni,
 55                         jthread thread, jmethodID method,
 56                         jboolean was_poped_by_exc, jvalue return_value) {
 57   jvmtiError err;
 58   char *cls_sig, *name, *sig, *generic;
 59   jclass cls;
 60   jmethodID mid;
 61   jlocation loc;
 62   char buffer[32];
 63 
 64   err = jvmti->GetMethodDeclaringClass(method, &cls);
 65   if (err != JVMTI_ERROR_NONE) {
 66     LOG("(GetMethodDeclaringClass) unexpected error: %s (%d)\n",
 67            TranslateError(err), err);
 68     result = STATUS_FAILED;
 69     return;
 70   }
 71   err = jvmti->GetClassSignature(cls, &cls_sig, &generic);
 72   if (err != JVMTI_ERROR_NONE) {
 73     LOG("(GetClassSignature) unexpected error: %s (%d)\n",
 74            TranslateError(err), err);
 75     result = STATUS_FAILED;
 76     return;
 77   }
 78   if (cls_sig != NULL &&
 79       strcmp(cls_sig, "Lmexit02a;") == 0) {
 80     LOG(">>> retrieving method exit info ...\n");
 81 
 82     err = jvmti->GetMethodName(method,
 83                                    &name, &sig, &generic);
 84     if (err != JVMTI_ERROR_NONE) {
 85       LOG("(GetMethodName) unexpected error: %s (%d)\n",
 86              TranslateError(err), err);
 87       result = STATUS_FAILED;
 88       return;
 89     }
 90     err = jvmti->GetFrameLocation(thread, 0, &mid, &loc);
 91     if (err != JVMTI_ERROR_NONE) {
 92       LOG("(GetFrameLocation) unexpected error: %s (%d)\n",
 93              TranslateError(err), err);
 94       result = STATUS_FAILED;
 95       return;
 96     }
 97     LOG(">>>      class: \"%s\"\n", cls_sig);
 98     LOG(">>>     method: \"%s%s\"\n", name, sig);
 99     LOG(">>>   location: %s\n", jlong_to_string(loc, buffer));
100     LOG(">>> ... done\n");
101 
102     jboolean isVirtual = jni->IsVirtualThread(thread);
103     if (isVirtualExpected != isVirtual) {
104       LOG("The thread IsVirtualThread %d differs from expected %d.\n", isVirtual, isVirtualExpected);
105       result = STATUS_FAILED;
106     }
107 
108     if (eventsCount < sizeof(exits)/sizeof(method_location_info)) {
109       if (cls_sig == NULL ||
110           strcmp(cls_sig, exits[eventsCount].cls_sig) != 0) {
111         LOG("(exit#%" PRIuPTR ") wrong class: \"%s\"",
112                eventsCount, cls_sig);
113         LOG(", expected: \"%s\"\n", exits[eventsCount].cls_sig);
114         result = STATUS_FAILED;
115       }
116       if (name == NULL ||
117           strcmp(name, exits[eventsCount].name) != 0) {
118         LOG("(exit#%" PRIuPTR ") wrong method name: \"%s\"",
119                eventsCount, name);
120         LOG(", expected: \"%s\"\n", exits[eventsCount].name);
121         result = STATUS_FAILED;
122       }
123       if (sig == NULL ||
124           strcmp(sig, exits[eventsCount].sig) != 0) {
125         LOG("(exit#%" PRIuPTR ") wrong method sig: \"%s\"",
126                eventsCount, sig);
127         LOG(", expected: \"%s\"\n", exits[eventsCount].sig);
128         result = STATUS_FAILED;
129       }
130       if (loc != exits[eventsCount].loc) {
131         LOG("(exit#%" PRIuPTR ") wrong location: %s",
132                eventsCount, jlong_to_string(loc, buffer));
133         LOG(", expected: %s\n",
134                jlong_to_string(exits[eventsCount].loc, buffer));
135         result = STATUS_FAILED;
136       }
137     } else {
138       LOG("Unexpected method exit catched:");
139       LOG("     class: \"%s\"\n", cls_sig);
140       LOG("    method: \"%s%s\"\n", name, sig);
141       LOG("  location: %s\n", jlong_to_string(loc, buffer));
142       result = STATUS_FAILED;
143     }
144     eventsCount++;
145   }
146 }
147 
148 #ifdef STATIC_BUILD
149 JNIEXPORT jint JNICALL Agent_OnLoad_mexit02(JavaVM *jvm, char *options, void *reserved) {
150     return Agent_Initialize(jvm, options, reserved);
151 }
152 JNIEXPORT jint JNICALL Agent_OnAttach_mexit02(JavaVM *jvm, char *options, void *reserved) {
153     return Agent_Initialize(jvm, options, reserved);
154 }
155 JNIEXPORT jint JNI_OnLoad_mexit02(JavaVM *jvm, char *options, void *reserved) {
156     return JNI_VERSION_1_8;
157 }
158 #endif
159 jint Agent_Initialize(JavaVM *jvm, char *options, void *reserved) {
160   jvmtiCapabilities caps;
161   jvmtiError err;
162   jint res;
163 
164   res = jvm->GetEnv((void **) &jvmti, JVMTI_VERSION_1_1);
165   if (res != JNI_OK || jvmti == NULL) {
166     LOG("Wrong result of a valid call to GetEnv!\n");
167     return JNI_ERR;
168   }
169 
170   memset(&caps, 0, sizeof(jvmtiCapabilities));
171   caps.can_generate_method_exit_events = 1;
172   caps.can_support_virtual_threads = 1;
173 
174   err = jvmti->AddCapabilities(&caps);
175   if (err != JVMTI_ERROR_NONE) {
176     LOG("(AddCapabilities) unexpected error: %s (%d)\n",
177            TranslateError(err), err);
178     return JNI_ERR;
179   }
180 
181   err = jvmti->GetCapabilities(&caps);
182   if (err != JVMTI_ERROR_NONE) {
183     LOG("(GetCapabilities) unexpected error: %s (%d)\n",
184            TranslateError(err), err);
185     return JNI_ERR;
186   }
187 
188   if (caps.can_generate_method_exit_events) {
189     callbacks.MethodExit = &MethodExit;
190     err = jvmti->SetEventCallbacks(&callbacks, sizeof(callbacks));
191     if (err != JVMTI_ERROR_NONE) {
192       LOG("(SetEventCallbacks) unexpected error: %s (%d)\n",
193              TranslateError(err), err);
194       return JNI_ERR;
195     }
196   } else {
197     LOG("Warning: MethodExit event is not implemented\n");
198   }
199 
200   return JNI_OK;
201 }
202 
203 JNIEXPORT jint JNICALL
204 Java_mexit02_check(JNIEnv *jni, jclass cls) {
205   jvmtiError err;
206   jclass clz;
207   jmethodID mid;
208 
209   if (jvmti == NULL) {
210     LOG("JVMTI client was not properly loaded!\n");
211     return STATUS_FAILED;
212   }
213 
214   jthread thread;
215   err = jvmti->GetCurrentThread(&thread);
216   if (err != JVMTI_ERROR_NONE) {
217     LOG("Failed to get current thread: %s (%d)\n", TranslateError(err), err);
218     result = STATUS_FAILED;
219   }
220   isVirtualExpected = jni->IsVirtualThread(thread);
221 
222   clz = jni->FindClass("mexit02a");
223   if (clz == NULL) {
224     LOG("Failed to find class \"mexit02a\"!\n");
225     return STATUS_FAILED;
226   }
227 
228   mid = jni->GetStaticMethodID(clz, "dummy", "()V");
229   if (mid == NULL) {
230     LOG("Failed to get method \"dummy\"!\n");
231     return STATUS_FAILED;
232   }
233 
234   err = jvmti->SetEventNotificationMode(JVMTI_ENABLE,
235                                         JVMTI_EVENT_METHOD_EXIT, NULL);
236   if (err == JVMTI_ERROR_NONE) {
237     eventsExpected = sizeof(exits)/sizeof(method_location_info);
238     eventsCount = 0;
239   } else {
240     LOG("Failed to enable JVMTI_EVENT_METHOD_EXIT event: %s (%d)\n", TranslateError(err), err);
241     result = STATUS_FAILED;
242   }
243 
244   jni->CallStaticVoidMethod(clz, mid);
245 
246   err = jvmti->SetEventNotificationMode(JVMTI_DISABLE,
247                                         JVMTI_EVENT_METHOD_EXIT, NULL);
248   if (err != JVMTI_ERROR_NONE) {
249     LOG("Failed to disable JVMTI_EVENT_METHOD_EXIT event: %s (%d)\n",
250            TranslateError(err), err);
251     result = STATUS_FAILED;
252   }
253 
254   if (eventsCount != eventsExpected) {
255     LOG("Wrong number of method exit events: %" PRIuPTR ", expected: %" PRIuPTR "\n",
256            eventsCount, eventsExpected);
257     result = STATUS_FAILED;
258   }
259   return result;
260 }
261 
262 JNIEXPORT void JNICALL
263 Java_mexit02a_chain(JNIEnv *jni, jclass cls) {
264   LOG(">>> about to exit method chain ...\n");
265 }
266 
267 JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *jvm, char *options, void *reserved) {
268   return Agent_Initialize(jvm, options, reserved);
269 }
270 
271 JNIEXPORT jint JNICALL Agent_OnAttach(JavaVM *jvm, char *options, void *reserved) {
272   return Agent_Initialize(jvm, options, reserved);
273 }
274 
275 
276 
277 }