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.
This commit is contained in:
Jim Chen 2014-01-17 23:32:24 -06:00
parent d5b9a4492a
commit 29cc16d495
5 changed files with 101 additions and 28 deletions

View File

@ -219,34 +219,7 @@ public class GeckoAppShell
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);
}
});
}
@ -418,6 +391,42 @@ public class GeckoAppShell
/*
* 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) {

View File

@ -38,4 +38,10 @@ public @interface WrapElementForJNI {
* 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;
}

View File

@ -159,6 +159,33 @@ public:
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

View File

@ -51,6 +51,7 @@ 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;
@ -132,6 +133,7 @@ void GeckoAppShell::InitStubs(JNIEnv *jEnv) {
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");
@ -1307,6 +1309,33 @@ jstring GeckoAppShell::HandleGeckoMessageWrapper(const nsAString& a0) {
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) {

View File

@ -58,6 +58,7 @@ public:
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();
@ -138,6 +139,7 @@ protected:
static jmethodID jGetShowPasswordSetting;
static jmethodID jGetSystemColoursWrapper;
static jmethodID jHandleGeckoMessageWrapper;
static jmethodID jHandleUncaughtException;
static jmethodID jHideProgressDialog;
static jmethodID jInitCameraWrapper;
static jmethodID jIsNetworkLinkKnown;