Bug 1235475 - Crash at the exception source when an exception is in native code; r=snorp
authorJim Chen <nchen@mozilla.com>
Wed, 30 Dec 2015 18:36:41 -0500
changeset 313184 957b7601f2f678c4314118b2ebe3c67867cc789f
parent 313183 ac70678c5f5eb8d7b4477001600b13d5770960ac
child 313185 079b6dfd8aecc0d0bba87469302842dbfc9e12e7
push id5703
push userraliiev@mozilla.com
push dateMon, 07 Mar 2016 14:18:41 +0000
treeherdermozilla-beta@31e373ad5b5f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssnorp
bugs1235475
milestone46.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1235475 - Crash at the exception source when an exception is in native code; r=snorp When we have a Java exception in native code, the Java stack in the exception will not be very useful because the top frame is the native entry point. In this case, the native stack is more useful. However, currently we don't get a good native stack in this situation because we go through Java when handling the exception, and the native stack we get will have a lot of unknown frames inside libdvm or libart. This patch makes us stay in native code when handling an uncaught exception from native code, so that we get a good native stack.
mobile/android/base/java/org/mozilla/gecko/CrashHandler.java
mobile/android/base/java/org/mozilla/gecko/GeckoAppShell.java
widget/android/AndroidBridge.cpp
widget/android/AndroidBridge.h
widget/android/GeneratedJNIWrappers.cpp
widget/android/GeneratedJNIWrappers.h
widget/android/jni/Accessors.h
widget/android/jni/Natives.h
widget/android/jni/Refs.h
widget/android/jni/Utils.cpp
widget/android/jni/Utils.h
widget/android/nsWindow.cpp
--- a/mobile/android/base/java/org/mozilla/gecko/CrashHandler.java
+++ b/mobile/android/base/java/org/mozilla/gecko/CrashHandler.java
@@ -140,17 +140,17 @@ public class CrashHandler implements Thr
     }
 
     /**
      * Record an exception stack in logs.
      *
      * @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);
 
             if (MAIN_THREAD != thread) {
                 Log.e(LOGTAG, "Main thread (" + MAIN_THREAD.getId() + ") stack:");
                 for (StackTraceElement ste : MAIN_THREAD.getStackTrace()) {
                     Log.e(LOGTAG, "    " + ste.toString());
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoAppShell.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoAppShell.java
@@ -391,18 +391,30 @@ public class GeckoAppShell
     // Synchronously notify a Gecko observer; must be called from Gecko thread.
     public static native void notifyGeckoObservers(String subject, String data);
 
     /*
      *  The Gecko-side API: API methods that Gecko calls
      */
 
     @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();
     private static boolean sWaitingForEventAck;
 
     // Block the current thread until the Gecko event loop is caught up
     public static void sendEventToGeckoSync(GeckoEvent e) {
         e.setAckNeeded(true);
--- a/widget/android/AndroidBridge.cpp
+++ b/widget/android/AndroidBridge.cpp
@@ -2143,30 +2143,30 @@ AndroidBridge::SetPresentationSurface(EG
 {
     mPresentationSurface = aPresentationSurface;
 }
 
 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;
 }
 
 nsresult AndroidBridge::InputStreamRead(Object::Param obj, char *aBuf, uint32_t aCount, uint32_t *aRead) {
     JNIEnv* const env = GetEnvForThread();
     auto arr = Object::LocalRef::Adopt(env, env->NewDirectByteBuffer(aBuf, aCount));
     jint read = env->CallIntMethod(obj.Get(), sBridge->jByteBufferRead, arr.Get());
 
--- a/widget/android/AndroidBridge.h
+++ b/widget/android/AndroidBridge.h
@@ -556,17 +556,17 @@ public:
     }
 
     JNIEnv* GetEnv() {
         return mJNIEnv;
     }
 
     bool CheckForException() {
         if (mJNIEnv->ExceptionCheck()) {
-            jni::HandleUncaughtException(mJNIEnv);
+            MOZ_CATCH_JNI_EXCEPTION(mJNIEnv);
             return true;
         }
         return false;
     }
 
     // Note! Calling Purge makes all previous local refs created in
     // the AutoLocalJNIFrame's scope INVALID; be sure that you locked down
     // any local refs that you need to keep around in global refs!
--- a/widget/android/GeneratedJNIWrappers.cpp
+++ b/widget/android/GeneratedJNIWrappers.cpp
@@ -444,19 +444,19 @@ constexpr char GeckoAppShell::HandleGeck
 auto GeckoAppShell::HandleGeckoMessageWrapper(mozilla::jni::Object::Param a0) -> void
 {
     return mozilla::jni::Method<HandleGeckoMessageWrapper_t>::Call(nullptr, nullptr, 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<HandleUncaughtException_t>::Call(nullptr, nullptr, a0, a1);
+    return mozilla::jni::Method<HandleUncaughtException_t>::Call(nullptr, nullptr, a0);
 }
 
 constexpr char GeckoAppShell::HideProgressDialog_t::name[];
 constexpr char GeckoAppShell::HideProgressDialog_t::signature[];
 
 auto GeckoAppShell::HideProgressDialog() -> void
 {
     return mozilla::jni::Method<HideProgressDialog_t>::Call(nullptr, nullptr);
--- a/widget/android/GeneratedJNIWrappers.h
+++ b/widget/android/GeneratedJNIWrappers.h
@@ -1079,31 +1079,30 @@ public:
                 mozilla::jni::ExceptionMode::ABORT;
     };
 
     static auto HandleGeckoMessageWrapper(mozilla::jni::Object::Param) -> void;
 
 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 {
         typedef GeckoAppShell Owner;
         typedef void ReturnType;
         typedef void SetterType;
         typedef mozilla::jni::Args<> Args;
         static constexpr char name[] = "hideProgressDialog";
--- a/widget/android/jni/Accessors.h
+++ b/widget/android/jni/Accessors.h
@@ -67,20 +67,20 @@ protected:
         return env;
     }
 
     // Called after making a JNIEnv call.
     template<class Traits>
     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);
         }
     }
 };
 
 
 // Member<> is used to call a JNI method given a traits class.
 template<class Traits, typename ReturnType = typename Traits::ReturnType>
 class Method : public Accessor
--- a/widget/android/jni/Natives.h
+++ b/widget/android/jni/Natives.h
@@ -102,29 +102,29 @@ struct NativePtr
     }
 
     template<class LocalRef>
     static void Set(const LocalRef& instance, UniquePtr<Impl>&& ptr)
     {
         Clear(instance);
         SetNativeHandle(instance.Env(), instance.Get(),
                           reinterpret_cast<uintptr_t>(ptr.release()));
-        HandleUncaughtException(instance.Env());
+        MOZ_CATCH_JNI_EXCEPTION(instance.Env());
     }
 
     template<class LocalRef>
     static void Clear(const LocalRef& instance)
     {
         UniquePtr<Impl> ptr(reinterpret_cast<Impl*>(
                 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());
         }
     }
 };
 
 template<class Impl>
 struct NativePtr<Impl, /* UseWeakPtr = */ true>
 {
     static Impl* Get(JNIEnv* env, jobject instance)
@@ -150,29 +150,29 @@ struct NativePtr<Impl, /* UseWeakPtr = *
     }
 
     template<class LocalRef>
     static void Set(const LocalRef& instance, Impl* ptr)
     {
         Clear(instance);
         SetNativeHandle(instance.Env(), instance.Get(),
                           reinterpret_cast<uintptr_t>(new WeakPtr<Impl>(ptr)));
-        HandleUncaughtException(instance.Env());
+        MOZ_CATCH_JNI_EXCEPTION(instance.Env());
     }
 
     template<class LocalRef>
     static void Clear(const LocalRef& instance)
     {
         const auto ptr = reinterpret_cast<WeakPtr<Impl>*>(
                 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;
         }
     }
 };
 
 } // namespace
 
 /**
@@ -323,27 +323,27 @@ class ProxyNativeCall
     }
 
     template<bool Static, bool ThisArg, size_t... Indices>
     typename mozilla::EnableIf<!Static && ThisArg, void>::Type
     Call(const typename Owner::LocalRef& inst,
          mozilla::IndexSequence<Indices...>) const
     {
         Impl* const impl = NativePtr<Impl>::Get(inst);
-        HandleUncaughtException(inst.Env());
+        MOZ_CATCH_JNI_EXCEPTION(inst.Env());
         (impl->*mNativeCall)(inst, mozilla::Get<Indices>(mArgs)...);
     }
 
     template<bool Static, bool ThisArg, size_t... Indices>
     typename mozilla::EnableIf<!Static && !ThisArg, void>::Type
     Call(const typename Owner::LocalRef& inst,
          mozilla::IndexSequence<Indices...>) const
     {
         Impl* const impl = NativePtr<Impl>::Get(inst);
-        HandleUncaughtException(inst.Env());
+        MOZ_CATCH_JNI_EXCEPTION(inst.Env());
         (impl->*mNativeCall)(mozilla::Get<Indices>(mArgs)...);
     }
 
     template<size_t... Indices>
     void Clear(JNIEnv* env, mozilla::IndexSequence<Indices...>)
     {
         int dummy[] = {
             (ProxyArg<Args>::Clear(env, Get<Indices>(mArgs)), 0)...
--- a/widget/android/jni/Refs.h
+++ b/widget/android/jni/Refs.h
@@ -566,59 +566,59 @@ public:
     MOZ_IMPLICIT Ref(decltype(nullptr)) : Base(nullptr) {}
 
     // Get the length of the object array.
     size_t Length() const
     {
         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;
     }
 
     Object::LocalRef GetElement(size_t index) const
     {
         MOZ_ASSERT(ObjectArray::mInstance);
         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;
     }
 
     nsTArray<Object::LocalRef> GetElements() const
     {
         MOZ_ASSERT(ObjectArray::mInstance);
         JNIEnv* const env = GetEnvForThread();
         const jsize len = size_t(env->GetArrayLength(
                 jarray(ObjectArray::mInstance)));
 
         nsTArray<Object::LocalRef> array((size_t(len)));
         for (jsize i = 0; i < len; i++) {
             array.AppendElement(Object::LocalRef::Adopt(
                     env, env->GetObjectArrayElement(
                             jobjectArray(ObjectArray::mInstance), i)));
-            HandleUncaughtException(env);
+            MOZ_CATCH_JNI_EXCEPTION(env);
         }
         return array;
     }
 
     operator nsTArray<Object::LocalRef>() const
     {
         return GetElements();
     }
 
     void SetElement(size_t index, Object::Param element) const
     {
         MOZ_ASSERT(ObjectArray::mInstance);
         JNIEnv* const env = GetEnvForThread();
         env->SetObjectArrayElement(jobjectArray(ObjectArray::mInstance),
                                    jsize(index), element.Get());
-        HandleUncaughtException(env);
+        MOZ_CATCH_JNI_EXCEPTION(env);
     }
 };
 
 
 // Ref specialization for jstring.
 template<>
 class Ref<String> : public RefBase<String, jstring>
 {
@@ -672,17 +672,17 @@ private:
     // Not null if we should delete ref on destruction.
     JNIEnv* const mEnv;
 
     static jstring GetString(JNIEnv* env, const nsAString& str)
     {
         const jstring result = env->NewString(
                 reinterpret_cast<const jchar*>(str.BeginReading()),
                 str.Length());
-        HandleUncaughtException(env);
+        MOZ_CATCH_JNI_EXCEPTION(env);
         return result;
     }
 
 public:
     MOZ_IMPLICIT Type(const String::Ref& ref)
         : Ref<String>(ref.Get())
         , mEnv(nullptr)
     {}
--- a/widget/android/jni/Utils.cpp
+++ b/widget/android/jni/Utils.cpp
@@ -3,16 +3,20 @@
 
 #include <pthread.h>
 
 #include "mozilla/Assertions.h"
 
 #include "AndroidBridge.h"
 #include "GeneratedJNIWrappers.h"
 
+#ifdef MOZ_CRASHREPORTER
+#include "nsExceptionHandler.h"
+#endif
+
 namespace mozilla {
 namespace jni {
 
 namespace detail {
 
 #define DEFINE_PRIMITIVE_TYPE_ADAPTER(NativeType, JNIType, JNIName, ABIName)	\
     \
     constexpr JNIType (JNIEnv::*TypeAdapter<NativeType>::Call) \
@@ -124,33 +128,44 @@ bool ThrowException(JNIEnv *aEnv, const 
 
     ClassObject::LocalRef cls =
             ClassObject::LocalRef::Adopt(aEnv->FindClass(aClass));
     MOZ_ASSERT(cls, "Cannot find exception class");
 
     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 {
 
 jclass sJNIObjectClass;
 jfieldID sJNIObjectHandleField;
 
 bool EnsureJNIObject(JNIEnv* env, jobject instance) {
--- a/widget/android/jni/Utils.h
+++ b/widget/android/jni/Utils.h
@@ -48,17 +48,24 @@ inline bool ThrowException(const char *a
     return ThrowException(GetEnvForThread(), aClass, aMessage);
 }
 
 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);
 
 void SetNativeHandle(JNIEnv* env, jobject instance, uintptr_t handle);
 
 } // jni
 } // mozilla
 
--- a/widget/android/nsWindow.cpp
+++ b/widget/android/nsWindow.cpp
@@ -178,17 +178,17 @@ public:
         typename mozilla::EnableIf<!Static, bool>::Type IsStaleCall()
         {
             JNIEnv* const env = mozilla::jni::GetEnvForThread();
             const auto& thisArg = Base::lambda.GetThisArg();
 
             const auto natives = reinterpret_cast<
                     mozilla::WeakPtr<typename T::TargetClass>*>(
                     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
             // a weak pointer. Stale calls should be discarded. Note that it's
             // an error if natives is nullptr here; we return false but the
             // native call will throw an error.
             return natives && !natives->get();
         }