fix(jni): fail cleanly when a callback method does not resolve

GetMethodID raises NoSuchMethodError when a lookup fails, and any further JNI
call made while an exception is pending aborts the process. runIperfLive
resolved three method IDs back to back without checking, so the first failure
made the second call fatal — turning a diagnosable configuration problem into
a SIGABRT with a stack that pointed at the JNI entry rather than the cause.

resolve_method() clears the pending exception and returns NULL, and the caller
now bails out with a log naming which lookups failed and pointing at the keep
rules. The R8 problem this exposed is fixed separately; this is about the
failure being reportable rather than fatal, whatever the cause.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-26 22:43:35 +09:00
parent 2b3f20d3a0
commit ff13d63333
+30 -3
View File
@@ -86,6 +86,21 @@ struct iperf_run_state {
* iperf_run_client() and cleared after it returns. */
static struct iperf_run_state *g_current_state = NULL;
/* Resolve a method ID, clearing the exception a failed lookup leaves behind.
*
* GetMethodID raises NoSuchMethodError on failure, and any further JNI call
* made while an exception is pending aborts the process. Returning NULL and
* clearing lets the caller fail on its own terms. */
static jmethodID resolve_method(JNIEnv *env, jclass cls,
const char *name, const char *sig) {
jmethodID mid = (*env)->GetMethodID(env, cls, name, sig);
if ((*env)->ExceptionCheck(env)) {
(*env)->ExceptionClear(env);
return NULL;
}
return mid;
}
/* Helper: invoke the output callback via CallVoidMethod */
static void signal_output(JNIEnv *env, struct iperf_run_state *state,
const char *msg) {
@@ -272,13 +287,25 @@ Java_com_novoyuuparosk_iperf3gui_IperfRunner_runIperfLive(
/* Resolve callback method IDs up front so failures can be reported. */
jclass callback_cls = (*env)->GetObjectClass(env, callback);
jmethodID mid_output = (*env)->GetMethodID(
jmethodID mid_output = resolve_method(
env, callback_cls, "onOutput", "(Ljava/lang/String;)V");
jmethodID mid_error = (*env)->GetMethodID(
jmethodID mid_error = resolve_method(
env, callback_cls, "onError", "(Ljava/lang/String;)V");
jmethodID mid_complete = (*env)->GetMethodID(
jmethodID mid_complete = resolve_method(
env, callback_cls, "onComplete", "()V");
/* If the names did not resolve there is no way to report anything: the
* reporting path is the thing that is missing. Bail out loudly instead.
*
* In practice this means R8 renamed the callback in a release build and
* android/app/proguard-rules.pro has stopped covering it. */
if (!mid_output || !mid_error || !mid_complete) {
LOGE("Callback methods did not resolve (onOutput=%p onError=%p "
"onComplete=%p) — check the -keep rules for IperfCallback",
mid_output, mid_error, mid_complete);
return;
}
/* Report a startup failure directly on the callback object and finish,
* so Dart's completer always resolves instead of waiting for a timeout. */
#define FAIL_EARLY(msg) do { \