8164086: Checked JNI pending exception check should be cleared when returning to Java frame
Summary: Transitions to Java clear the pending pointer
Reviewed-by: dholmes, neliasso, coleenp
--- a/hotspot/make/test/JtregNative.gmk Tue Sep 13 11:32:45 2016 +0200
+++ b/hotspot/make/test/JtregNative.gmk Tue Sep 13 09:04:44 2016 +0200
@@ -44,6 +44,7 @@
$(HOTSPOT_TOPDIR)/test/native_sanity \
$(HOTSPOT_TOPDIR)/test/runtime/jni/8025979 \
$(HOTSPOT_TOPDIR)/test/runtime/jni/8033445 \
+ $(HOTSPOT_TOPDIR)/test/runtime/jni/checked \
$(HOTSPOT_TOPDIR)/test/runtime/jni/ToStringInInterfaceTest \
$(HOTSPOT_TOPDIR)/test/runtime/modules/getModuleJNI \
$(HOTSPOT_TOPDIR)/test/runtime/SameObject \
--- a/hotspot/src/cpu/aarch64/vm/sharedRuntime_aarch64.cpp Tue Sep 13 11:32:45 2016 +0200
+++ b/hotspot/src/cpu/aarch64/vm/sharedRuntime_aarch64.cpp Tue Sep 13 09:04:44 2016 +0200
@@ -2041,6 +2041,11 @@
__ verify_oop(r0);
}
+ if (CheckJNICalls) {
+ // clear_pending_jni_exception_check
+ __ str(zr, Address(rthread, JavaThread::pending_jni_exception_check_fn_offset()));
+ }
+
if (!is_critical_native) {
// reset handle block
__ ldr(r2, Address(rthread, JavaThread::active_handles_offset()));
--- a/hotspot/src/cpu/aarch64/vm/templateInterpreterGenerator_aarch64.cpp Tue Sep 13 11:32:45 2016 +0200
+++ b/hotspot/src/cpu/aarch64/vm/templateInterpreterGenerator_aarch64.cpp Tue Sep 13 09:04:44 2016 +0200
@@ -1355,6 +1355,11 @@
// reset_last_Java_frame
__ reset_last_Java_frame(true);
+ if (CheckJNICalls) {
+ // clear_pending_jni_exception_check
+ __ str(zr, Address(rthread, JavaThread::pending_jni_exception_check_fn_offset()));
+ }
+
// reset handle block
__ ldr(t, Address(rthread, JavaThread::active_handles_offset()));
__ str(zr, Address(t, JNIHandleBlock::top_offset_in_bytes()));
--- a/hotspot/src/cpu/sparc/vm/sharedRuntime_sparc.cpp Tue Sep 13 11:32:45 2016 +0200
+++ b/hotspot/src/cpu/sparc/vm/sharedRuntime_sparc.cpp Tue Sep 13 09:04:44 2016 +0200
@@ -2765,6 +2765,11 @@
__ verify_oop(I0);
}
+ if (CheckJNICalls) {
+ // clear_pending_jni_exception_check
+ __ st_ptr(G0, G2_thread, JavaThread::pending_jni_exception_check_fn_offset());
+ }
+
if (!is_critical_native) {
// reset handle block
__ ld_ptr(G2_thread, in_bytes(JavaThread::active_handles_offset()), L5);
--- a/hotspot/src/cpu/sparc/vm/templateInterpreterGenerator_sparc.cpp Tue Sep 13 11:32:45 2016 +0200
+++ b/hotspot/src/cpu/sparc/vm/templateInterpreterGenerator_sparc.cpp Tue Sep 13 09:04:44 2016 +0200
@@ -1487,6 +1487,11 @@
__ set(_thread_in_Java, G3_scratch);
__ st(G3_scratch, thread_state);
+ if (CheckJNICalls) {
+ // clear_pending_jni_exception_check
+ __ st_ptr(G0, G2_thread, JavaThread::pending_jni_exception_check_fn_offset());
+ }
+
// reset handle block
__ ld_ptr(G2_thread, JavaThread::active_handles_offset(), G3_scratch);
__ st(G0, G3_scratch, JNIHandleBlock::top_offset_in_bytes());
--- a/hotspot/src/cpu/x86/vm/sharedRuntime_x86_32.cpp Tue Sep 13 11:32:45 2016 +0200
+++ b/hotspot/src/cpu/x86/vm/sharedRuntime_x86_32.cpp Tue Sep 13 09:04:44 2016 +0200
@@ -2236,6 +2236,11 @@
__ verify_oop(rax);
}
+ if (CheckJNICalls) {
+ // clear_pending_jni_exception_check
+ __ movptr(Address(thread, JavaThread::pending_jni_exception_check_fn_offset()), NULL_WORD);
+ }
+
if (!is_critical_native) {
// reset handle block
__ movptr(rcx, Address(thread, JavaThread::active_handles_offset()));
--- a/hotspot/src/cpu/x86/vm/sharedRuntime_x86_64.cpp Tue Sep 13 11:32:45 2016 +0200
+++ b/hotspot/src/cpu/x86/vm/sharedRuntime_x86_64.cpp Tue Sep 13 09:04:44 2016 +0200
@@ -2589,6 +2589,11 @@
__ verify_oop(rax);
}
+ if (CheckJNICalls) {
+ // clear_pending_jni_exception_check
+ __ movptr(Address(r15_thread, JavaThread::pending_jni_exception_check_fn_offset()), NULL_WORD);
+ }
+
if (!is_critical_native) {
// reset handle block
__ movptr(rcx, Address(r15_thread, JavaThread::active_handles_offset()));
--- a/hotspot/src/cpu/x86/vm/templateInterpreterGenerator_x86.cpp Tue Sep 13 11:32:45 2016 +0200
+++ b/hotspot/src/cpu/x86/vm/templateInterpreterGenerator_x86.cpp Tue Sep 13 09:04:44 2016 +0200
@@ -1169,6 +1169,11 @@
// reset_last_Java_frame
__ reset_last_Java_frame(thread, true);
+ if (CheckJNICalls) {
+ // clear_pending_jni_exception_check
+ __ movptr(Address(thread, JavaThread::pending_jni_exception_check_fn_offset()), NULL_WORD);
+ }
+
// reset handle block
__ movptr(t, Address(thread, JavaThread::active_handles_offset()));
__ movl(Address(t, JNIHandleBlock::top_offset_in_bytes()), (int32_t)NULL_WORD);
--- a/hotspot/src/share/vm/prims/whitebox.cpp Tue Sep 13 11:32:45 2016 +0200
+++ b/hotspot/src/share/vm/prims/whitebox.cpp Tue Sep 13 09:04:44 2016 +0200
@@ -894,6 +894,7 @@
}
ThreadToNativeFromVM ttnfv(thread); // can't be in VM when we call JNI
const char* flag_name = env->GetStringUTFChars(name, NULL);
+ CHECK_JNI_EXCEPTION_(env, false);
Flag::Error result = (*TAt)(flag_name, value, true, true);
env->ReleaseStringUTFChars(name, flag_name);
return (result == Flag::SUCCESS);
@@ -906,6 +907,7 @@
}
ThreadToNativeFromVM ttnfv(thread); // can't be in VM when we call JNI
const char* flag_name = env->GetStringUTFChars(name, NULL);
+ CHECK_JNI_EXCEPTION_(env, false);
Flag::Error result = (*TAtPut)(flag_name, value, Flag::INTERNAL);
env->ReleaseStringUTFChars(name, flag_name);
return (result == Flag::SUCCESS);
@@ -944,6 +946,7 @@
static Flag* getVMFlag(JavaThread* thread, JNIEnv* env, jstring name) {
ThreadToNativeFromVM ttnfv(thread); // can't be in VM when we call JNI
const char* flag_name = env->GetStringUTFChars(name, NULL);
+ CHECK_JNI_EXCEPTION_(env, NULL);
Flag* result = Flag::find_flag(flag_name, strlen(flag_name), true, true);
env->ReleaseStringUTFChars(name, flag_name);
return result;
@@ -1084,7 +1087,14 @@
WB_ENTRY(void, WB_SetStringVMFlag(JNIEnv* env, jobject o, jstring name, jstring value))
ThreadToNativeFromVM ttnfv(thread); // can't be in VM when we call JNI
- const char* ccstrValue = (value == NULL) ? NULL : env->GetStringUTFChars(value, NULL);
+ const char* ccstrValue;
+ if (value == NULL) {
+ ccstrValue = NULL;
+ }
+ else {
+ ccstrValue = env->GetStringUTFChars(value, NULL);
+ CHECK_JNI_EXCEPTION(env);
+ }
ccstr ccstrResult = ccstrValue;
bool needFree;
{
@@ -1308,6 +1318,7 @@
jclass clazz = env->FindClass(vmSymbols::java_lang_Object()->as_C_string());
CHECK_JNI_EXCEPTION_(env, NULL);
result = env->NewObjectArray(blobs.length(), clazz, NULL);
+ CHECK_JNI_EXCEPTION_(env, NULL);
if (result == NULL) {
return result;
}
@@ -1317,6 +1328,7 @@
jobjectArray obj = codeBlob2objectArray(thread, env, *it);
CHECK_JNI_EXCEPTION_(env, NULL);
env->SetObjectArrayElement(result, i, obj);
+ CHECK_JNI_EXCEPTION_(env, NULL);
++i;
}
return result;
@@ -1523,6 +1535,7 @@
// can't be in VM when we call JNI
ThreadToNativeFromVM ttnfv(thread);
const char* flag_name = env->GetStringUTFChars(name, NULL);
+ CHECK_JNI_EXCEPTION_(env, false);
bool result = CompilerOracle::has_option_value(mh, flag_name, *value);
env->ReleaseStringUTFChars(name, flag_name);
return result;
@@ -1678,6 +1691,7 @@
// can't be in VM when we call JNI
ThreadToNativeFromVM ttnfv(thread);
const char* dir = env->GetStringUTFChars(compDirect, NULL);
+ CHECK_JNI_EXCEPTION_(env, 0);
int ret;
{
ThreadInVMfromNative ttvfn(thread); // back to VM
--- a/hotspot/src/share/vm/prims/whitebox.hpp Tue Sep 13 11:32:45 2016 +0200
+++ b/hotspot/src/share/vm/prims/whitebox.hpp Tue Sep 13 09:04:44 2016 +0200
@@ -33,9 +33,22 @@
#include "oops/symbol.hpp"
#include "runtime/interfaceSupport.hpp"
+// Unconditionally clear pedantic pending JNI checks
+class ClearPendingJniExcCheck : public StackObj {
+private:
+ JavaThread* _thread;
+public:
+ ClearPendingJniExcCheck(JNIEnv* env) : _thread(JavaThread::thread_from_jni_environment(env)) {}
+ ~ClearPendingJniExcCheck() {
+ _thread->clear_pending_jni_exception_check();
+ }
+};
+
// Entry macro to transition from JNI to VM state.
-#define WB_ENTRY(result_type, header) JNI_ENTRY(result_type, header)
+#define WB_ENTRY(result_type, header) JNI_ENTRY(result_type, header) \
+ ClearPendingJniExcCheck _clearCheck(env);
+
#define WB_END JNI_END
#define WB_METHOD_DECLARE(result_type) extern "C" result_type JNICALL
--- a/hotspot/src/share/vm/runtime/interfaceSupport.hpp Tue Sep 13 11:32:45 2016 +0200
+++ b/hotspot/src/share/vm/runtime/interfaceSupport.hpp Tue Sep 13 09:04:44 2016 +0200
@@ -280,6 +280,7 @@
~ThreadToNativeFromVM() {
trans_from_native(_thread_in_vm);
+ assert(!_thread->is_pending_jni_exception_check(), "Pending JNI Exception Check");
// We don't need to clear_walkable because it will happen automagically when we return to java
}
};
--- a/hotspot/src/share/vm/runtime/thread.hpp Tue Sep 13 11:32:45 2016 +0200
+++ b/hotspot/src/share/vm/runtime/thread.hpp Tue Sep 13 09:04:44 2016 +0200
@@ -1542,6 +1542,9 @@
static ByteSize jmp_ring_offset() { return byte_offset_of(JavaThread, _jmp_ring); }
#endif // PRODUCT
static ByteSize jni_environment_offset() { return byte_offset_of(JavaThread, _jni_environment); }
+ static ByteSize pending_jni_exception_check_fn_offset() {
+ return byte_offset_of(JavaThread, _pending_jni_exception_check_fn);
+ }
static ByteSize last_Java_sp_offset() {
return byte_offset_of(JavaThread, _anchor) + JavaFrameAnchor::last_Java_sp_offset();
}
@@ -1615,7 +1618,11 @@
assert(_jni_active_critical >= 0, "JNI critical nesting problem?");
}
- // Checked JNI, is the programmer required to check for exceptions, specify which function name
+ // Checked JNI: is the programmer required to check for exceptions, if so specify
+ // which function name. Returning to a Java frame should implicitly clear the
+ // pending check, this is done for Native->Java transitions (i.e. user JNI code).
+ // VM->Java transistions are not cleared, it is expected that JNI code enclosed
+ // within ThreadToNativeFromVM makes proper exception checks (i.e. VM internal).
bool is_pending_jni_exception_check() const { return _pending_jni_exception_check_fn != NULL; }
void clear_pending_jni_exception_check() { _pending_jni_exception_check_fn = NULL; }
const char* get_pending_jni_exception_check() const { return _pending_jni_exception_check_fn; }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hotspot/test/runtime/jni/checked/TestCheckedJniExceptionCheck.java Tue Sep 13 09:04:44 2016 +0200
@@ -0,0 +1,209 @@
+/*
+ * Copyright (c) 2016, 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
+ * @bug 8164086
+ * @summary regression tests for 8164086, verify correct warning from checked JNI
+ * @library /test/lib
+ * @modules java.management
+ * @run main/native TestCheckedJniExceptionCheck launch
+ */
+
+import java.util.List;
+import jdk.test.lib.process.ProcessTools;
+import jdk.test.lib.process.OutputAnalyzer;
+
+public class TestCheckedJniExceptionCheck {
+
+ static {
+ System.loadLibrary("TestCheckedJniExceptionCheck");
+ }
+
+ int callableMethodInvokeCount = 0;
+
+ static final String TEST_START = "TEST STARTED";
+ static final String EXPECT_WARNING_START = "EXPECT_WARNING_START";
+ static final String EXPECT_WARNING_END = "EXPECT_WARNING_END";
+
+ static final String JNI_CHECK_EXCEPTION = "WARNING in native method: JNI call made without checking exceptions when required to from";
+
+ static void printExpectWarningStart(int count) {
+ System.out.println(EXPECT_WARNING_START + " " + count);
+ }
+
+ static void printExpectWarningEnd() {
+ System.out.println(EXPECT_WARNING_END);
+ }
+
+ public TestCheckedJniExceptionCheck() {
+ initMethodIds("callableMethod", "()V",
+ "callableNestedMethod", "(IZ)V");
+ System.out.println(TEST_START);
+ }
+
+ public void test() {
+ testSingleCallNoCheck();
+ testSingleCallCheck();
+ testSingleCallNoCheckMultipleTimes();
+
+ testMultipleCallsNoCheck();
+ testMultipleCallsCheck();
+
+ testNestedSingleCallsNoCheck();
+ testNestedSingleCallsCheck();
+ testNestedMultipleCallsNoCheck();
+ testNestedMultipleCallsCheck();
+ }
+
+ public void testSingleCallNoCheck() {
+ System.out.println("testSingleCallNoCheck start");
+ callJavaFromNative(1, false);
+ System.out.println("testSingleCallNoCheck end");
+ }
+
+ public void testSingleCallCheck() {
+ System.out.println("testSingleCallCheck start");
+ callJavaFromNative(1, true);
+ System.out.println("testSingleCallCheck end");
+ }
+
+ public void testSingleCallNoCheckMultipleTimes() {
+ System.out.println("testSingleCallNoCheckMultipleTimes start");
+ callJavaFromNative(1, false);
+ callJavaFromNative(1, false);
+ System.out.println("testSingleCallNoCheckMultipleTimes end");
+ }
+
+ public void testMultipleCallsNoCheck() {
+ System.out.println("testMultipleCallsNoCheck start");
+ printExpectWarningStart(1);
+ callJavaFromNative(2, false);
+ printExpectWarningEnd();
+ System.out.println("testMultipleCallsNoCheck end");
+ }
+
+ public void testMultipleCallsCheck() {
+ System.out.println("testMultipleCallsCheck start");
+ callJavaFromNative(2, true);
+ System.out.println("testMultipleCallsCheck end");
+ }
+
+ public void testNestedSingleCallsNoCheck() {
+ System.out.println("testNestedSingleCallsNoCheck start");
+ callNestedJavaFromNative(1, false);
+ System.out.println("testNestedSingleCallsNoCheck end");
+ }
+
+ public void testNestedSingleCallsCheck() {
+ System.out.println("testNestedSingleCallsCheck start");
+ callNestedJavaFromNative(1, true);
+ System.out.println("testNestedSingleCallsCheck end");
+ }
+
+ public void testNestedMultipleCallsNoCheck() {
+ System.out.println("testNestedMultipleCallsNoCheck start");
+ printExpectWarningStart(3);
+ callNestedJavaFromNative(2, false);
+ printExpectWarningEnd();
+ System.out.println("testNestedMultipleCallsNoCheck end");
+ }
+
+ public void testNestedMultipleCallsCheck() {
+ System.out.println("testNestedMultipleCallsCheck start");
+ callNestedJavaFromNative(2, true);
+ System.out.println("testNestedMultipleCallsCheck end");
+ }
+
+ public void callableMethod() {
+ callableMethodInvokeCount++;
+ }
+
+ public void callableNestedMethod(int nofCalls, boolean withExceptionChecks) {
+ callJavaFromNative(nofCalls, withExceptionChecks);
+ }
+
+ public native void callJavaFromNative(int nofCalls, boolean withExceptionChecks);
+
+ public native void callNestedJavaFromNative(int nofCalls, boolean withExceptionChecks);
+
+ private native void initMethodIds(String callableMethodName,
+ String callableMethodSig,
+ String callableNestedMethodName,
+ String callableNestedMethodSig);
+
+
+ // Check warnings appear where they should, with start/end statements in output...
+ static void checkOuputForCorrectWarnings(OutputAnalyzer oa) throws RuntimeException {
+ List<String> lines = oa.asLines();
+ int expectedWarnings = 0;
+ int warningCount = 0;
+ int lineNo = 0;
+ boolean testStartLine = false;
+ for (String line : lines) {
+ lineNo++;
+ if (!testStartLine) { // Skip any warning before the actual test itself
+ testStartLine = line.startsWith(TEST_START);
+ continue;
+ }
+ if (line.startsWith(JNI_CHECK_EXCEPTION)) {
+ if (expectedWarnings == 0) {
+ oa.reportDiagnosticSummary();
+ throw new RuntimeException("Unexpected warning at line " + lineNo);
+ }
+ warningCount++;
+ if (warningCount > expectedWarnings) {
+ oa.reportDiagnosticSummary();
+ throw new RuntimeException("Unexpected warning at line " + lineNo);
+ }
+ }
+ else if (line.startsWith(EXPECT_WARNING_START)) {
+ String countStr = line.substring(EXPECT_WARNING_START.length() + 1);
+ expectedWarnings = Integer.parseInt(countStr);
+ }
+ else if (line.startsWith(EXPECT_WARNING_END)) {
+ if (warningCount != expectedWarnings) {
+ oa.reportDiagnosticSummary();
+ throw new RuntimeException("Missing warning at line " + lineNo);
+ }
+ warningCount = 0;
+ expectedWarnings = 0;
+ }
+ }
+ /*
+ System.out.println("Output looks good...");
+ oa.reportDiagnosticSummary();
+ */
+ }
+
+ public static void main(String[] args) throws Throwable {
+ if (args == null || args.length == 0) {
+ new TestCheckedJniExceptionCheck().test();
+ return;
+ }
+
+ // launch and check output
+ checkOuputForCorrectWarnings(ProcessTools.executeTestJvm("-Xcheck:jni",
+ "TestCheckedJniExceptionCheck"));
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hotspot/test/runtime/jni/checked/libTestCheckedJniExceptionCheck.c Tue Sep 13 09:04:44 2016 +0200
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2016, 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 <jni.h>
+
+static jmethodID _callable_method_id;
+static jmethodID _callable_nested_method_id;
+
+static void check_exceptions(JNIEnv *env) {
+ if ((*env)->ExceptionCheck(env)) {
+ (*env)->ExceptionDescribe(env);
+ (*env)->FatalError(env, "Unexpected Exception");
+ }
+}
+
+static jmethodID get_method_id(JNIEnv *env, jclass clz, jstring jname, jstring jsig) {
+ jmethodID mid;
+ const char *name, *sig;
+
+ name = (*env)->GetStringUTFChars(env, jname, NULL);
+ check_exceptions(env);
+
+ sig = (*env)->GetStringUTFChars(env, jsig, NULL);
+ check_exceptions(env);
+
+ mid = (*env)->GetMethodID(env, clz, name, sig);
+ check_exceptions(env);
+
+ (*env)->ReleaseStringUTFChars(env, jname, name);
+ (*env)->ReleaseStringUTFChars(env, jsig, sig);
+ return mid;
+}
+
+JNIEXPORT void JNICALL
+Java_TestCheckedJniExceptionCheck_initMethodIds(JNIEnv *env,
+ jobject obj,
+ jstring callable_method_name,
+ jstring callable_method_sig,
+ jstring callable_nested_method_name,
+ jstring callable_nested_method_sig) {
+ jclass clz = (*env)->GetObjectClass(env, obj);
+
+ _callable_method_id = get_method_id(env, clz,
+ callable_method_name,
+ callable_method_sig);
+
+ _callable_nested_method_id = get_method_id(env, clz,
+ callable_nested_method_name,
+ callable_nested_method_sig);
+}
+
+JNIEXPORT void JNICALL
+Java_TestCheckedJniExceptionCheck_callJavaFromNative(JNIEnv *env,
+ jobject obj,
+ jint nofCalls,
+ jboolean checkExceptions) {
+ int i;
+ for (i = 0; i < nofCalls; i++) {
+ (*env)->CallVoidMethod(env, obj, _callable_method_id);
+ if (checkExceptions == JNI_TRUE) {
+ check_exceptions(env);
+ }
+ }
+}
+
+JNIEXPORT void JNICALL
+Java_TestCheckedJniExceptionCheck_callNestedJavaFromNative(JNIEnv *env,
+ jobject obj,
+ jint nofCalls,
+ jboolean checkExceptions) {
+ int i;
+ for (i = 0; i < nofCalls; i++) {
+ (*env)->CallVoidMethod(env, obj, _callable_nested_method_id, nofCalls, checkExceptions);
+ if (checkExceptions == JNI_TRUE) {
+ check_exceptions(env);
+ }
+ }
+}