Bug 958706 - Add ThrowException and HandleUncaughtException methods to AndroidBridge; r=blassey
authorJim Chen <nchen@mozilla.com>
Fri, 17 Jan 2014 23:32:24 -0600
changeset 164124 0ed7f1bad6e301e9bdd7fa968846eaaca5226eef
parent 164123 3f69ca51a4e84d05c6ef1550886a824eb7abf24f
child 164125 a7cf0019d9491d3cd4dae65db8b5ada3e229963e
push id26026
push userphilringnalda@gmail.com
push dateSat, 18 Jan 2014 23:17:27 +0000
treeherdermozilla-central@61fd0f987cf2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersblassey
bugs958706
milestone29.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 958706 - Add ThrowException and HandleUncaughtException methods to AndroidBridge; r=blassey ThrowException throws a new exception with the appropriate message, meant for native JNI methods that return to Java code (i.e. methods in AndroidJNI.cpp). HandleUncaughtException will be called by the generated JNI stubs that C++ code uses. HandleUncaughtException calls the new GeckoAppShell.handleUncaughtException method, which behaves exactly like the normal uncaught exception handler (annotates the crash report and crashes). GeckoAppShell.handleUncaughtException has the noThrow annotation that will be seen by the generated code; as a result, its generated stub will not call HandleUncaughtException and result in a loop.
mobile/android/base/GeckoAppShell.java
mobile/android/base/mozglue/generatorannotations/WrapElementForJNI.java
widget/android/AndroidBridge.h
widget/android/GeneratedJNIWrappers.cpp
widget/android/GeneratedJNIWrappers.h
--- a/mobile/android/base/GeckoAppShell.java
+++ b/mobile/android/base/GeckoAppShell.java
@@ -214,44 +214,17 @@ public class GeckoAppShell
     public static native Message getNextMessageFromQueue(MessageQueue queue);
     public static native void onSurfaceTextureFrameAvailable(Object surfaceTexture, int id);
     public static native void dispatchMemoryPressure();
 
     public static void registerGlobalExceptionHandler() {
         Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
             @Override
             public void uncaughtException(Thread thread, Throwable e) {
-                // If the uncaught exception was rethrown, walk the exception `cause` chain to find
-                // the original exception so Socorro can correctly collate related crash reports.
-                Throwable cause;
-                while ((cause = e.getCause()) != null) {
-                    e = cause;
-                }
-
-                try {
-                    Log.e(LOGTAG, ">>> REPORTING UNCAUGHT EXCEPTION FROM THREAD "
-                                  + thread.getId() + " (\"" + thread.getName() + "\")", e);
-
-                    Thread mainThread = ThreadUtils.getUiThread();
-                    if (mainThread != null && thread != mainThread) {
-                        Log.e(LOGTAG, "Main thread stack:");
-                        for (StackTraceElement ste : mainThread.getStackTrace()) {
-                            Log.e(LOGTAG, ste.toString());
-                        }
-                    }
-
-                    if (e instanceof OutOfMemoryError) {
-                        SharedPreferences prefs = getSharedPreferences();
-                        SharedPreferences.Editor editor = prefs.edit();
-                        editor.putBoolean(GeckoApp.PREFS_OOM_EXCEPTION, true);
-                        editor.commit();
-                    }
-                } finally {
-                    reportJavaCrash(getStackTraceString(e));
-                }
+                handleUncaughtException(thread, e);
             }
         });
     }
 
     private static String getStackTraceString(Throwable e) {
         StringWriter sw = new StringWriter();
         PrintWriter pw = new PrintWriter(sw);
         e.printStackTrace(pw);
@@ -413,16 +386,52 @@ public class GeckoAppShell
     }
 
     // Tell the Gecko event loop that an event is available.
     public static native void notifyGeckoOfEvent(GeckoEvent event);
 
     /*
      *  The Gecko-side API: API methods that Gecko calls
      */
+
+    @WrapElementForJNI(generateStatic = true, noThrow = true)
+    public static void handleUncaughtException(Thread thread, Throwable e) {
+        if (thread == null) {
+            thread = Thread.currentThread();
+        }
+        // If the uncaught exception was rethrown, walk the exception `cause` chain to find
+        // the original exception so Socorro can correctly collate related crash reports.
+        Throwable cause;
+        while ((cause = e.getCause()) != null) {
+            e = cause;
+        }
+
+        try {
+            Log.e(LOGTAG, ">>> REPORTING UNCAUGHT EXCEPTION FROM THREAD "
+                          + thread.getId() + " (\"" + thread.getName() + "\")", e);
+
+            Thread mainThread = ThreadUtils.getUiThread();
+            if (mainThread != null && thread != mainThread) {
+                Log.e(LOGTAG, "Main thread stack:");
+                for (StackTraceElement ste : mainThread.getStackTrace()) {
+                    Log.e(LOGTAG, ste.toString());
+                }
+            }
+
+            if (e instanceof OutOfMemoryError) {
+                SharedPreferences prefs = getSharedPreferences();
+                SharedPreferences.Editor editor = prefs.edit();
+                editor.putBoolean(GeckoApp.PREFS_OOM_EXCEPTION, true);
+                editor.commit();
+            }
+        } finally {
+            reportJavaCrash(getStackTraceString(e));
+        }
+    }
+
     @WrapElementForJNI(generateStatic = true)
     public static void notifyIME(int type) {
         if (mEditableListener != null) {
             mEditableListener.notifyIME(type);
         }
     }
 
     @WrapElementForJNI(generateStatic = true)
--- a/mobile/android/base/mozglue/generatorannotations/WrapElementForJNI.java
+++ b/mobile/android/base/mozglue/generatorannotations/WrapElementForJNI.java
@@ -33,9 +33,15 @@ public @interface WrapElementForJNI {
     /**
      * If set, the generated method stub will support being called from any thread via the use of
      * GetJNIForThread. This is rarely useful, at time of writing, as well as possibly risky.
      * See information in AndroidBridge.cpp regarding GetJNIForThread.
      *
      * Did I mention use of this function is discouraged?
      */
     boolean allowMultithread() default false;
+
+    /**
+     * If set, the generated stub will not handle uncaught exceptions.
+     * Any exception must be handled or cleared by the code calling the stub.
+     */
+    boolean noThrow() default false;
 }
--- a/widget/android/AndroidBridge.h
+++ b/widget/android/AndroidBridge.h
@@ -154,16 +154,43 @@ public:
         MOZ_ASSERT(sBridge->mJNIEnv);
         return sBridge->mJNIEnv;
     }
 
     static bool HasEnv() {
         return sBridge && sBridge->mJNIEnv;
     }
 
+    static bool ThrowException(JNIEnv *aEnv, const char *aClass,
+                               const char *aMessage) {
+        MOZ_ASSERT(aEnv, "Invalid thread JNI env");
+        jclass cls = aEnv->FindClass(aClass);
+        MOZ_ASSERT(cls, "Cannot find exception class");
+        bool ret = !aEnv->ThrowNew(cls, aMessage);
+        aEnv->DeleteLocalRef(cls);
+        return ret;
+    }
+
+    static bool ThrowException(JNIEnv *aEnv, const char *aMessage) {
+        return ThrowException(aEnv, "java/lang/Exception", aMessage);
+    }
+
+    static void HandleUncaughtException(JNIEnv *aEnv) {
+        MOZ_ASSERT(aEnv);
+        if (!aEnv->ExceptionCheck()) {
+            return;
+        }
+        jthrowable e = aEnv->ExceptionOccurred();
+        MOZ_ASSERT(e);
+        aEnv->ExceptionClear();
+        GeckoAppShell::HandleUncaughtException(nullptr, e);
+        // Should be dead by now...
+        MOZ_CRASH("Failed to handle uncaught exception");
+    }
+
     // The bridge needs to be constructed via ConstructBridge first,
     // and then once the Gecko main thread is spun up (Gecko side),
     // SetMainThread should be called which will create the JNIEnv for
     // us to use.  toolkit/xre/nsAndroidStartup.cpp calls
     // SetMainThread.
     bool SetMainThread(pthread_t thr);
 
     /* These are all implemented in Java */
--- a/widget/android/GeneratedJNIWrappers.cpp
+++ b/widget/android/GeneratedJNIWrappers.cpp
@@ -46,16 +46,17 @@ jmethodID GeckoAppShell::jGetMessageWrap
 jmethodID GeckoAppShell::jGetMimeTypeFromExtensionsWrapper = 0;
 jmethodID GeckoAppShell::jGetNextMessageInListWrapper = 0;
 jmethodID GeckoAppShell::jGetProxyForURIWrapper = 0;
 jmethodID GeckoAppShell::jGetScreenDepthWrapper = 0;
 jmethodID GeckoAppShell::jGetScreenOrientationWrapper = 0;
 jmethodID GeckoAppShell::jGetShowPasswordSetting = 0;
 jmethodID GeckoAppShell::jGetSystemColoursWrapper = 0;
 jmethodID GeckoAppShell::jHandleGeckoMessageWrapper = 0;
+jmethodID GeckoAppShell::jHandleUncaughtException = 0;
 jmethodID GeckoAppShell::jHideProgressDialog = 0;
 jmethodID GeckoAppShell::jInitCameraWrapper = 0;
 jmethodID GeckoAppShell::jIsNetworkLinkKnown = 0;
 jmethodID GeckoAppShell::jIsNetworkLinkUp = 0;
 jmethodID GeckoAppShell::jIsTablet = 0;
 jmethodID GeckoAppShell::jKillAnyZombies = 0;
 jmethodID GeckoAppShell::jLoadPluginClass = 0;
 jmethodID GeckoAppShell::jLockScreenOrientation = 0;
@@ -127,16 +128,17 @@ void GeckoAppShell::InitStubs(JNIEnv *jE
     jGetMimeTypeFromExtensionsWrapper = getStaticMethod("getMimeTypeFromExtensions", "(Ljava/lang/String;)Ljava/lang/String;");
     jGetNextMessageInListWrapper = getStaticMethod("getNextMessageInList", "(II)V");
     jGetProxyForURIWrapper = getStaticMethod("getProxyForURI", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;I)Ljava/lang/String;");
     jGetScreenDepthWrapper = getStaticMethod("getScreenDepth", "()I");
     jGetScreenOrientationWrapper = getStaticMethod("getScreenOrientation", "()S");
     jGetShowPasswordSetting = getStaticMethod("getShowPasswordSetting", "()Z");
     jGetSystemColoursWrapper = getStaticMethod("getSystemColors", "()[I");
     jHandleGeckoMessageWrapper = getStaticMethod("handleGeckoMessage", "(Ljava/lang/String;)Ljava/lang/String;");
+    jHandleUncaughtException = getStaticMethod("handleUncaughtException", "(Ljava/lang/Thread;Ljava/lang/Throwable;)V");
     jHideProgressDialog = getStaticMethod("hideProgressDialog", "()V");
     jInitCameraWrapper = getStaticMethod("initCamera", "(Ljava/lang/String;III)[I");
     jIsNetworkLinkKnown = getStaticMethod("isNetworkLinkKnown", "()Z");
     jIsNetworkLinkUp = getStaticMethod("isNetworkLinkUp", "()Z");
     jIsTablet = getStaticMethod("isTablet", "()Z");
     jKillAnyZombies = getStaticMethod("killAnyZombies", "()V");
     jLoadPluginClass = getStaticMethod("loadPluginClass", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Class;");
     jLockScreenOrientation = getStaticMethod("lockScreenOrientation", "(I)V");
@@ -1302,16 +1304,43 @@ jstring GeckoAppShell::HandleGeckoMessag
         env->PopLocalFrame(nullptr);
         return nullptr;
     }
 
     jstring ret = static_cast<jstring>(env->PopLocalFrame(temp));
     return ret;
 }
 
+void GeckoAppShell::HandleUncaughtException(jobject a0, jthrowable a1) {
+    JNIEnv *env = AndroidBridge::GetJNIEnv();
+    if (!env) {
+        ALOG_BRIDGE("Aborted: No env - %s", __PRETTY_FUNCTION__);
+        return;
+    }
+
+    if (env->PushLocalFrame(2) != 0) {
+        ALOG_BRIDGE("Exceptional exit of: %s", __PRETTY_FUNCTION__);
+        env->ExceptionDescribe();
+        env->ExceptionClear();
+        return;
+    }
+
+    env->CallStaticVoidMethod(mGeckoAppShellClass, jHandleUncaughtException, a0, a1);
+
+    if (env->ExceptionCheck()) {
+        ALOG_BRIDGE("Exceptional exit of: %s", __PRETTY_FUNCTION__);
+        env->ExceptionDescribe();
+        env->ExceptionClear();
+        env->PopLocalFrame(nullptr);
+        return;
+    }
+
+    env->PopLocalFrame(nullptr);
+}
+
 void GeckoAppShell::HideProgressDialog() {
     JNIEnv *env = AndroidBridge::GetJNIEnv();
     if (!env) {
         ALOG_BRIDGE("Aborted: No env - %s", __PRETTY_FUNCTION__);
         return;
     }
 
     if (env->PushLocalFrame(0) != 0) {
--- a/widget/android/GeneratedJNIWrappers.h
+++ b/widget/android/GeneratedJNIWrappers.h
@@ -53,16 +53,17 @@ public:
     static jstring GetMimeTypeFromExtensionsWrapper(const nsAString& a0);
     static void GetNextMessageInListWrapper(int32_t a0, int32_t a1);
     static jstring GetProxyForURIWrapper(const nsAString& a0, const nsAString& a1, const nsAString& a2, int32_t a3);
     static int32_t GetScreenDepthWrapper();
     static int16_t GetScreenOrientationWrapper();
     static bool GetShowPasswordSetting();
     static jintArray GetSystemColoursWrapper();
     static jstring HandleGeckoMessageWrapper(const nsAString& a0);
+    static void HandleUncaughtException(jobject a0, jthrowable a1);
     static void HideProgressDialog();
     static jintArray InitCameraWrapper(const nsAString& a0, int32_t a1, int32_t a2, int32_t a3);
     static bool IsNetworkLinkKnown();
     static bool IsNetworkLinkUp();
     static bool IsTablet();
     static void KillAnyZombies();
     static jclass LoadPluginClass(const nsAString& a0, const nsAString& a1);
     static void LockScreenOrientation(int32_t a0);
@@ -133,16 +134,17 @@ protected:
     static jmethodID jGetMimeTypeFromExtensionsWrapper;
     static jmethodID jGetNextMessageInListWrapper;
     static jmethodID jGetProxyForURIWrapper;
     static jmethodID jGetScreenDepthWrapper;
     static jmethodID jGetScreenOrientationWrapper;
     static jmethodID jGetShowPasswordSetting;
     static jmethodID jGetSystemColoursWrapper;
     static jmethodID jHandleGeckoMessageWrapper;
+    static jmethodID jHandleUncaughtException;
     static jmethodID jHideProgressDialog;
     static jmethodID jInitCameraWrapper;
     static jmethodID jIsNetworkLinkKnown;
     static jmethodID jIsNetworkLinkUp;
     static jmethodID jIsTablet;
     static jmethodID jKillAnyZombies;
     static jmethodID jLoadPluginClass;
     static jmethodID jLockScreenOrientation;