diff --git a/android/app/src/main/cpp/iperf_jni.c b/android/app/src/main/cpp/iperf_jni.c index ec6dca4..caae63c 100644 --- a/android/app/src/main/cpp/iperf_jni.c +++ b/android/app/src/main/cpp/iperf_jni.c @@ -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 { \