diff --git a/mobile/android/base/java/org/mozilla/gecko/CrashHandler.java b/mobile/android/base/java/org/mozilla/gecko/CrashHandler.java index 84ff6e833ac0..c1183b6ce471 100644 --- a/mobile/android/base/java/org/mozilla/gecko/CrashHandler.java +++ b/mobile/android/base/java/org/mozilla/gecko/CrashHandler.java @@ -145,7 +145,7 @@ public class CrashHandler implements Thread.UncaughtExceptionHandler { * @param thread The exception thread * @param exc An exception */ - protected void logException(final Thread thread, final Throwable exc) { + public static void logException(final Thread thread, final Throwable exc) { try { Log.e(LOGTAG, ">>> REPORTING UNCAUGHT EXCEPTION FROM THREAD " + thread.getId() + " (\"" + thread.getName() + "\")", exc); diff --git a/mobile/android/base/java/org/mozilla/gecko/GeckoAppShell.java b/mobile/android/base/java/org/mozilla/gecko/GeckoAppShell.java index cda80b6f1e3d..0a115d3c1b16 100644 --- a/mobile/android/base/java/org/mozilla/gecko/GeckoAppShell.java +++ b/mobile/android/base/java/org/mozilla/gecko/GeckoAppShell.java @@ -396,8 +396,20 @@ public class GeckoAppShell */ @WrapForJNI(allowMultithread = true, noThrow = true) - public static void handleUncaughtException(Thread thread, Throwable e) { - CRASH_HANDLER.uncaughtException(thread, e); + public static String handleUncaughtException(Throwable e) { + if (AppConstants.MOZ_CRASHREPORTER) { + final Throwable exc = CrashHandler.getRootException(e); + final StackTraceElement[] stack = exc.getStackTrace(); + if (stack.length >= 1 && stack[0].isNativeMethod()) { + // The exception occurred when running native code. Return an exception + // string and trigger the crash reporter inside the caller so that we get + // a better native stack in Socorro. + CrashHandler.logException(Thread.currentThread(), exc); + return CrashHandler.getExceptionStackTrace(exc); + } + } + CRASH_HANDLER.uncaughtException(null, e); + return null; } private static final Object sEventAckLock = new Object(); diff --git a/widget/android/AndroidBridge.cpp b/widget/android/AndroidBridge.cpp index 0c7740c7ceb5..983c5e330909 100644 --- a/widget/android/AndroidBridge.cpp +++ b/widget/android/AndroidBridge.cpp @@ -2148,20 +2148,20 @@ Object::LocalRef AndroidBridge::ChannelCreate(Object::Param stream) { JNIEnv* const env = GetEnvForThread(); auto rv = Object::LocalRef::Adopt(env, env->CallStaticObjectMethod( sBridge->jChannels, sBridge->jChannelCreate, stream.Get())); - HandleUncaughtException(env); + MOZ_CATCH_JNI_EXCEPTION(env); return rv; } void AndroidBridge::InputStreamClose(Object::Param obj) { JNIEnv* const env = GetEnvForThread(); env->CallVoidMethod(obj.Get(), sBridge->jClose); - HandleUncaughtException(env); + MOZ_CATCH_JNI_EXCEPTION(env); } uint32_t AndroidBridge::InputStreamAvailable(Object::Param obj) { JNIEnv* const env = GetEnvForThread(); auto rv = env->CallIntMethod(obj.Get(), sBridge->jAvailable); - HandleUncaughtException(env); + MOZ_CATCH_JNI_EXCEPTION(env); return rv; } diff --git a/widget/android/AndroidBridge.h b/widget/android/AndroidBridge.h index 410e6af75345..66073b9f8bb0 100644 --- a/widget/android/AndroidBridge.h +++ b/widget/android/AndroidBridge.h @@ -561,7 +561,7 @@ public: bool CheckForException() { if (mJNIEnv->ExceptionCheck()) { - jni::HandleUncaughtException(mJNIEnv); + MOZ_CATCH_JNI_EXCEPTION(mJNIEnv); return true; } return false; diff --git a/widget/android/GeneratedJNIWrappers.cpp b/widget/android/GeneratedJNIWrappers.cpp index 77cffdbda4ea..f5e88c785303 100644 --- a/widget/android/GeneratedJNIWrappers.cpp +++ b/widget/android/GeneratedJNIWrappers.cpp @@ -449,9 +449,9 @@ auto GeckoAppShell::HandleGeckoMessageWrapper(mozilla::jni::Object::Param a0) -> constexpr char GeckoAppShell::HandleUncaughtException_t::name[]; constexpr char GeckoAppShell::HandleUncaughtException_t::signature[]; -auto GeckoAppShell::HandleUncaughtException(mozilla::jni::Object::Param a0, mozilla::jni::Throwable::Param a1) -> void +auto GeckoAppShell::HandleUncaughtException(mozilla::jni::Throwable::Param a0) -> mozilla::jni::String::LocalRef { - return mozilla::jni::Method::Call(nullptr, nullptr, a0, a1); + return mozilla::jni::Method::Call(nullptr, nullptr, a0); } constexpr char GeckoAppShell::HideProgressDialog_t::name[]; diff --git a/widget/android/GeneratedJNIWrappers.h b/widget/android/GeneratedJNIWrappers.h index 677d01f4d016..c95566dfae13 100644 --- a/widget/android/GeneratedJNIWrappers.h +++ b/widget/android/GeneratedJNIWrappers.h @@ -1084,21 +1084,20 @@ public: public: struct HandleUncaughtException_t { typedef GeckoAppShell Owner; - typedef void ReturnType; - typedef void SetterType; + typedef mozilla::jni::String::LocalRef ReturnType; + typedef mozilla::jni::String::Param SetterType; typedef mozilla::jni::Args< - mozilla::jni::Object::Param, mozilla::jni::Throwable::Param> Args; static constexpr char name[] = "handleUncaughtException"; static constexpr char signature[] = - "(Ljava/lang/Thread;Ljava/lang/Throwable;)V"; + "(Ljava/lang/Throwable;)Ljava/lang/String;"; static const bool isStatic = true; static const bool isMultithreaded = true; static const mozilla::jni::ExceptionMode exceptionMode = mozilla::jni::ExceptionMode::IGNORE; }; - static auto HandleUncaughtException(mozilla::jni::Object::Param, mozilla::jni::Throwable::Param) -> void; + static auto HandleUncaughtException(mozilla::jni::Throwable::Param) -> mozilla::jni::String::LocalRef; public: struct HideProgressDialog_t { diff --git a/widget/android/jni/Accessors.h b/widget/android/jni/Accessors.h index e3a73f449890..21129c7c0b29 100644 --- a/widget/android/jni/Accessors.h +++ b/widget/android/jni/Accessors.h @@ -72,10 +72,10 @@ protected: static void EndAccess(JNIEnv* env, nsresult* rv) { if (Traits::exceptionMode == ExceptionMode::ABORT) { - return HandleUncaughtException(env); + MOZ_CATCH_JNI_EXCEPTION(env); } else if (Traits::exceptionMode == ExceptionMode::NSRESULT) { - return GetNsresult(env, rv); + GetNsresult(env, rv); } } }; diff --git a/widget/android/jni/Natives.h b/widget/android/jni/Natives.h index d65d7f5a88c2..459b353475b7 100644 --- a/widget/android/jni/Natives.h +++ b/widget/android/jni/Natives.h @@ -107,7 +107,7 @@ struct NativePtr Clear(instance); SetNativeHandle(instance.Env(), instance.Get(), reinterpret_cast(ptr.release())); - HandleUncaughtException(instance.Env()); + MOZ_CATCH_JNI_EXCEPTION(instance.Env()); } template @@ -115,11 +115,11 @@ struct NativePtr { UniquePtr ptr(reinterpret_cast( GetNativeHandle(instance.Env(), instance.Get()))); - HandleUncaughtException(instance.Env()); + MOZ_CATCH_JNI_EXCEPTION(instance.Env()); if (ptr) { SetNativeHandle(instance.Env(), instance.Get(), 0); - HandleUncaughtException(instance.Env()); + MOZ_CATCH_JNI_EXCEPTION(instance.Env()); } } }; @@ -155,7 +155,7 @@ struct NativePtr Clear(instance); SetNativeHandle(instance.Env(), instance.Get(), reinterpret_cast(new WeakPtr(ptr))); - HandleUncaughtException(instance.Env()); + MOZ_CATCH_JNI_EXCEPTION(instance.Env()); } template @@ -163,11 +163,11 @@ struct NativePtr { const auto ptr = reinterpret_cast*>( GetNativeHandle(instance.Env(), instance.Get())); - HandleUncaughtException(instance.Env()); + MOZ_CATCH_JNI_EXCEPTION(instance.Env()); if (ptr) { SetNativeHandle(instance.Env(), instance.Get(), 0); - HandleUncaughtException(instance.Env()); + MOZ_CATCH_JNI_EXCEPTION(instance.Env()); delete ptr; } } @@ -328,7 +328,7 @@ class ProxyNativeCall mozilla::IndexSequence) const { Impl* const impl = NativePtr::Get(inst); - HandleUncaughtException(inst.Env()); + MOZ_CATCH_JNI_EXCEPTION(inst.Env()); (impl->*mNativeCall)(inst, mozilla::Get(mArgs)...); } @@ -338,7 +338,7 @@ class ProxyNativeCall mozilla::IndexSequence) const { Impl* const impl = NativePtr::Get(inst); - HandleUncaughtException(inst.Env()); + MOZ_CATCH_JNI_EXCEPTION(inst.Env()); (impl->*mNativeCall)(mozilla::Get(mArgs)...); } diff --git a/widget/android/jni/Refs.h b/widget/android/jni/Refs.h index 867add4c4ea2..c713b8b6657a 100644 --- a/widget/android/jni/Refs.h +++ b/widget/android/jni/Refs.h @@ -571,7 +571,7 @@ public: MOZ_ASSERT(ObjectArray::mInstance); JNIEnv* const env = GetEnvForThread(); const size_t ret = env->GetArrayLength(jarray(ObjectArray::mInstance)); - HandleUncaughtException(env); + MOZ_CATCH_JNI_EXCEPTION(env); return ret; } @@ -581,7 +581,7 @@ public: JNIEnv* const env = GetEnvForThread(); auto ret = Object::LocalRef::Adopt(env, env->GetObjectArrayElement( jobjectArray(ObjectArray::mInstance), jsize(index))); - HandleUncaughtException(env); + MOZ_CATCH_JNI_EXCEPTION(env); return ret; } @@ -597,7 +597,7 @@ public: array.AppendElement(Object::LocalRef::Adopt( env, env->GetObjectArrayElement( jobjectArray(ObjectArray::mInstance), i))); - HandleUncaughtException(env); + MOZ_CATCH_JNI_EXCEPTION(env); } return array; } @@ -613,7 +613,7 @@ public: JNIEnv* const env = GetEnvForThread(); env->SetObjectArrayElement(jobjectArray(ObjectArray::mInstance), jsize(index), element.Get()); - HandleUncaughtException(env); + MOZ_CATCH_JNI_EXCEPTION(env); } }; @@ -677,7 +677,7 @@ private: const jstring result = env->NewString( reinterpret_cast(str.BeginReading()), str.Length()); - HandleUncaughtException(env); + MOZ_CATCH_JNI_EXCEPTION(env); return result; } diff --git a/widget/android/jni/Utils.cpp b/widget/android/jni/Utils.cpp index 0feeb7a74b55..0b0f57eb4ae9 100644 --- a/widget/android/jni/Utils.cpp +++ b/widget/android/jni/Utils.cpp @@ -8,6 +8,10 @@ #include "AndroidBridge.h" #include "GeneratedJNIWrappers.h" +#ifdef MOZ_CRASHREPORTER +#include "nsExceptionHandler.h" +#endif + namespace mozilla { namespace jni { @@ -129,23 +133,34 @@ bool ThrowException(JNIEnv *aEnv, const char *aClass, return !aEnv->ThrowNew(cls.Get(), aMessage); } -void HandleUncaughtException(JNIEnv *aEnv) +bool HandleUncaughtException(JNIEnv* aEnv) { MOZ_ASSERT(aEnv, "Invalid thread JNI env"); if (!aEnv->ExceptionCheck()) { - return; + return false; } +#ifdef DEBUG + aEnv->ExceptionDescribe(); +#endif + Throwable::LocalRef e = Throwable::LocalRef::Adopt(aEnv->ExceptionOccurred()); MOZ_ASSERT(e); aEnv->ExceptionClear(); - widget::GeckoAppShell::HandleUncaughtException(nullptr, e); + String::LocalRef stack = widget::GeckoAppShell::HandleUncaughtException(e); - // Should be dead by now... - MOZ_CRASH("Failed to handle uncaught exception"); +#ifdef MOZ_CRASHREPORTER + if (stack) { + // GeckoAppShell wants us to annotate and trigger the crash reporter. + CrashReporter::AnnotateCrashReport( + NS_LITERAL_CSTRING("AuxiliaryJavaStack"), nsCString(stack)); + } +#endif // MOZ_CRASHREPORTER + + return true; } namespace { diff --git a/widget/android/jni/Utils.h b/widget/android/jni/Utils.h index fb69f1bef826..1e8c0d44b785 100644 --- a/widget/android/jni/Utils.h +++ b/widget/android/jni/Utils.h @@ -53,7 +53,14 @@ inline bool ThrowException(const char *aMessage) return ThrowException(GetEnvForThread(), aMessage); } -void HandleUncaughtException(JNIEnv *aEnv); +bool HandleUncaughtException(JNIEnv* aEnv); + +#define MOZ_CATCH_JNI_EXCEPTION(env) \ + do { \ + if (mozilla::jni::HandleUncaughtException((env))) { \ + MOZ_CRASH("JNI exception"); \ + } \ + } while (0) uintptr_t GetNativeHandle(JNIEnv* env, jobject instance); diff --git a/widget/android/nsWindow.cpp b/widget/android/nsWindow.cpp index 08363755b0f2..fc1c96730800 100644 --- a/widget/android/nsWindow.cpp +++ b/widget/android/nsWindow.cpp @@ -183,7 +183,7 @@ public: const auto natives = reinterpret_cast< mozilla::WeakPtr*>( jni::GetNativeHandle(env, thisArg.Get())); - jni::HandleUncaughtException(env); + MOZ_CATCH_JNI_EXCEPTION(env); // The call is stale if the nsWindow has been destroyed on the // Gecko side, but the Java object is still attached to it through