From a022236d87eba0544e347927f22d55b81af33971 Mon Sep 17 00:00:00 2001 From: Greg Pomerantz Date: Thu, 20 Aug 2026 22:16:13 -0400 Subject: [PATCH] Guard against Gio's null-view detach event (recents-wipe SIGABRT) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Gio's GioView.onDestroyView sends ViewEvent{View: 0} as its detach signal when the view is destroyed — i.e. the activity going away on a recents-wipe. handleEvent passed that zero straight through to InitPgp/registerFragment, whose GetObjectClass(null) aborts the process (JNI DETECTED ERROR: java_object == null in call to GetObjectClass). Guard on both sides: handleEvent ignores the View == 0 detach signal (a re-attach arrives as a fresh event with a live view), InitPgp returns early on a zero view so unfiltered callers are covered, and registerFragment null-checks as defense in depth. Same fix as pad (fdbffc9); verified there on a Pixel 9 Pro / Android 17 where the recents swipe used to crash the app. --- cmd/passgo-gui/impl_android.go | 9 +++++++++ impl_android.go | 6 ++++++ jni_android.c | 8 ++++++++ 3 files changed, 23 insertions(+) diff --git a/cmd/passgo-gui/impl_android.go b/cmd/passgo-gui/impl_android.go index 43f7ac3..f5ee7b0 100644 --- a/cmd/passgo-gui/impl_android.go +++ b/cmd/passgo-gui/impl_android.go @@ -23,6 +23,15 @@ func init() { func handleEvent(e event.Event) { switch e := e.(type) { case app.ViewEvent: + // View == 0 is Gio's detach signal: GioView.onDestroyView sends + // ViewEvent{View: 0} when the view is destroyed (e.g. the activity + // going away on a recents-wipe). There is nothing to register for a + // detached view; passing the null ref on would abort in JNI + // (GetObjectClass on null). A re-attach arrives as a fresh event + // with a live view. + if e.View == 0 { + return + } initPgp(e.View) } } diff --git a/impl_android.go b/impl_android.go index f33fcd5..3d6f642 100644 --- a/impl_android.go +++ b/impl_android.go @@ -49,6 +49,12 @@ func Java_st_wow_git_passgo_PgpConnect_installComplete(env *C.JNIEnv, class C.jc func InitPgp(view uintptr) { log.Printf("InitPgp()") + // view == 0 is Gio's detach signal (GioView.onDestroyView); nothing to + // register. Guard here as well so callers that forward the event + // unfiltered cannot drive registerFragment with a null ref (JNI abort). + if view == 0 { + return + } jvm = app.JavaVM() SetJVM(jvm) // why? RunInJVM(func(env *JNIEnv) { diff --git a/jni_android.c b/jni_android.c index c1c63d3..54ae212 100644 --- a/jni_android.c +++ b/jni_android.c @@ -7,6 +7,14 @@ void registerFragment(JNIEnv *env, jobject view) { + if (view == NULL) { + // Detach signal (Gio sends ViewEvent{View: 0} when the view is + // destroyed, e.g. the activity going away on a recents-wipe). + // The Go side already filters these; this guard keeps the JNI + // calls safe if one ever slips through (GetObjectClass on null + // aborts the process). + return; + } jclass cls = (*env)->GetObjectClass(env, view); jmethodID mid = (*env)->GetMethodID(env, cls, "getContext", "()Landroid/content/Context;"); jobject ctx = (*env)->CallObjectMethod(env, view, mid);