Skip to content

Commit

Permalink
review: add a unit test covering JVMTI ClearAllFramePops
Browse files Browse the repository at this point in the history
  • Loading branch information
sspitsyn committed Dec 18, 2024
1 parent 0e4ddcc commit 156c158
Show file tree
Hide file tree
Showing 2 changed files with 261 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* Copyright (c) 2024, 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 id=platform
* @summary Verifies JVMTI ClearAllFramePops clears all FramePop requests
* @library /test/lib
* @run main/othervm/native -agentlib:ClearAllFramePops ClearAllFramePops platform
*/
/*
* @test id=virtual
* @summary Verifies JVMTI ClearAllFramePops clears all FramePop requests
* @library /test/lib
* @run main/othervm/native -agentlib:ClearAllFramePops ClearAllFramePops virtual
*/

public class ClearAllFramePops {

final static int MAX_THREADS_LIMIT = 10;
final static int NESTING_DEPTH = 5;
final static String TEST_THREAD_NAME_BASE = "Test Thread #";

native static void clearAllFramePops();
native static void getReady();
native static void check();

public static void main(String args[]) {
boolean isVirtual = args.length > 0 && args[0].equals("virtual");
final int THREADS_LIMIT = Math.min(Runtime.getRuntime().availableProcessors() + 1, MAX_THREADS_LIMIT);
Thread[] t = new Thread[THREADS_LIMIT];
getReady();
Thread.Builder builder = (isVirtual ? Thread.ofVirtual() : Thread.ofPlatform())
.name(TEST_THREAD_NAME_BASE, 0);
for (int i = 0; i < THREADS_LIMIT; i++) {
t[i] = builder.start(new TestTask());
}
for (int i = 0; i < THREADS_LIMIT; i++) {
try {
t[i].join();
} catch (InterruptedException e) {
throw new Error("Unexpected: " + e);
}
}
check();
}

static class TestTask implements Runnable {
int nestingCount = 0;

public void run() {
if (nestingCount < NESTING_DEPTH) {
nestingCount++;
run();
} else {
clearAllFramePops();
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
/*
* Copyright (c) 2024, 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 <stdlib.h>
#include <string.h>
#include <time.h>
#include "jvmti.h"
#include "jvmti_common.hpp"


extern "C" {

static jvmtiEnv *jvmti;
static jvmtiEventCallbacks callbacks;
static jrawMonitorID event_lock;
static jboolean watch_events = JNI_FALSE;
static int pop_count;
static const char* TEST_THREAD_NAME_BASE = "Test Thread";
static const char* TEST_CLASS_SIG = "LClearAllFramePops$TestTask;";

static
bool isTestThread(JNIEnv *jni, jvmtiEnv *jvmti, jthread thr) {
jvmtiThreadInfo inf = get_thread_info(jvmti, jni, thr);
bool result = strncmp(inf.name, TEST_THREAD_NAME_BASE, strlen(TEST_THREAD_NAME_BASE)) == 0;
deallocate(jvmti, jni, inf.name);

return result;
}

static
void printInfo(JNIEnv *jni, jvmtiEnv *jvmti, jthread thr, jmethodID method, int depth) {
jclass cls;
char *mname, *msig, *csig;
jvmtiThreadInfo inf = get_thread_info(jvmti, jni, thr);

check_jvmti_status(jni, jvmti->GetMethodDeclaringClass(method, &cls), "Error in GetMethodDeclaringClass.");
check_jvmti_status(jni, jvmti->GetClassSignature(cls, &csig, nullptr), "Error in GetClassSignature.");
check_jvmti_status(jni, jvmti->GetMethodName(method, &mname, &msig, nullptr), "Error in GetMethodName.");

LOG(" %s: %s.%s%s, depth = %d\n", inf.name, csig, mname, msig, depth);

deallocate(jvmti, jni, inf.name);
deallocate(jvmti, jni, mname);
deallocate(jvmti, jni, msig);
deallocate(jvmti, jni, csig);
}

void JNICALL MethodEntry(jvmtiEnv *jvmti, JNIEnv *jni,
jthread thr, jmethodID method) {
RawMonitorLocker rml(jvmti, jni, event_lock);

if (watch_events == JNI_FALSE) {
return;
}
if (!isTestThread(jni, jvmti, thr)) {
return; // not a tested thread
}
jclass cls;
char *csig;
jvmtiThreadInfo inf = get_thread_info(jvmti, jni, thr);

check_jvmti_status(jni, jvmti->GetMethodDeclaringClass(method, &cls), "Error in GetMethodDeclaringClass.");
check_jvmti_status(jni, jvmti->GetClassSignature(cls, &csig, nullptr), "Error in GetClassSignature.");

if (strcmp(csig, TEST_CLASS_SIG) != 0 ||
strcmp(get_method_name(jvmti, jni, method), "run") != 0) {
return; // not a tested method
}
LOG("\n>>>Method entry event:");
printInfo(jni, jvmti, thr, method, get_frame_count(jvmti, jni, thr));

check_jvmti_status(jni, jvmti->NotifyFramePop(thr, 0), "Error in NotifyFramePop.");
deallocate(jvmti, jni, csig);
}

void JNICALL FramePop(jvmtiEnv *jvmti, JNIEnv *jni,
jthread thr, jmethodID method, jboolean wasPopedByException) {
RawMonitorLocker rml(jvmti, jni, event_lock);

jint frameCount = get_frame_count(jvmti, jni, thr);

LOG("\n>>> Frame Pop event:");
printInfo(jni, jvmti, thr, method, frameCount);
pop_count++;
}

JNIEXPORT jint JNICALL
Agent_OnLoad(JavaVM *jvm, char *options, void *reserved) {
jvmtiCapabilities caps;
jvmtiError err;

jint res = jvm->GetEnv((void **) &jvmti, JVMTI_VERSION_1_1);
if (res != JNI_OK || jvmti == nullptr) {
LOG("Failed: Wrong result of a valid call to GetEnv!\n");
return JNI_ERR;
}
event_lock = create_raw_monitor(jvmti, "_event_lock");

memset(&caps, 0, sizeof(jvmtiCapabilities));
caps.can_generate_frame_pop_events = 1;
caps.can_generate_method_entry_events = 1;
caps.can_support_virtual_threads = 1;

callbacks.MethodEntry = &MethodEntry;
callbacks.FramePop = &FramePop;

err = jvmti->AddCapabilities(&caps);
if (err != JVMTI_ERROR_NONE) {
LOG("(AddCapabilities) unexpected error: %s (%d)\n", TranslateError(err), err);
return JNI_ERR;
}
err = jvmti->GetCapabilities(&caps);
if (err != JVMTI_ERROR_NONE) {
LOG("(GetCapabilities) unexpected error: %s (%d)\n", TranslateError(err), err);
return JNI_ERR;
}
err = jvmti->SetEventCallbacks(&callbacks, sizeof(callbacks));
if (err != JVMTI_ERROR_NONE) {
LOG("(SetEventCallbacks) unexpected error: %s (%d)\n", TranslateError(err), err);
return JNI_ERR;
}
err = set_event_notification_mode(jvmti, JVMTI_ENABLE, JVMTI_EVENT_VM_DEATH, nullptr);
if (err != JVMTI_ERROR_NONE) {
return JNI_ERR;
}
return JNI_OK;
}

JNIEXPORT void JNICALL Java_ClearAllFramePops_clearAllFramePops(JNIEnv *jni, jclass cls) {
RawMonitorLocker rml(jvmti, jni, event_lock);

char* tname = get_thread_name(jvmti, jni, nullptr);

check_jvmti_status(jni, jvmti->ClearAllFramePops(nullptr), "Error in ClearAllFramePops");
LOG("Called ClearAllFramePops for thread: %s\n", tname);

deallocate(jvmti, jni, tname);
}

JNIEXPORT void JNICALL Java_ClearAllFramePops_getReady(JNIEnv *jni, jclass cls) {
RawMonitorLocker rml(jvmti, jni, event_lock);

watch_events = JNI_TRUE;
set_event_notification_mode(jvmti, jni, JVMTI_ENABLE, JVMTI_EVENT_METHOD_ENTRY, nullptr);
set_event_notification_mode(jvmti, jni, JVMTI_ENABLE, JVMTI_EVENT_FRAME_POP, nullptr);
}

JNIEXPORT void JNICALL Java_ClearAllFramePops_check(JNIEnv *jni, jclass cls) {
RawMonitorLocker rml(jvmti, jni, event_lock);

watch_events = JNI_FALSE;
set_event_notification_mode(jvmti, jni, JVMTI_DISABLE, JVMTI_EVENT_METHOD_ENTRY, nullptr);
set_event_notification_mode(jvmti, jni, JVMTI_DISABLE, JVMTI_EVENT_FRAME_POP, nullptr);
LOG("\n>>> Total frame pops: %d\n", pop_count);

if (pop_count > 0) {
fatal(jni, "Failed: FramePop events are not expected");
}
}

}

0 comments on commit 156c158

Please sign in to comment.