From 603b44589682db3ff33ade172facb0c5e309f1be Mon Sep 17 00:00:00 2001 From: Jeff Brown Date: Fri, 6 Apr 2012 17:39:41 -0700 Subject: [PATCH] Ensure that apps crash if they throw exceptions. Previously, if an app threw an uncaught exception in an input, vsync or native activity callback, it would log the exception then continue limping merrily along. In the case of input, it could result in an ANR occurring because we had not drained all of the pending input events and marked them as finished (we only marked the most recent one finished). Bug: 6304124 Change-Id: I87d76f7fd605e1a8af1237c66d8d62973080277e --- core/jni/android_app_NativeActivity.cpp | 40 ++++------ core/jni/android_os_MessageQueue.cpp | 73 +++++++++++++++---- core/jni/android_os_MessageQueue.h | 45 +++++++++++- .../jni/android_view_DisplayEventReceiver.cpp | 25 +++---- core/jni/android_view_InputEventReceiver.cpp | 40 +++++----- ...droid_server_input_InputManagerService.cpp | 5 +- 6 files changed, 146 insertions(+), 82 deletions(-) diff --git a/core/jni/android_app_NativeActivity.cpp b/core/jni/android_app_NativeActivity.cpp index 655a8342e08af..19bc154aee1a8 100644 --- a/core/jni/android_app_NativeActivity.cpp +++ b/core/jni/android_app_NativeActivity.cpp @@ -416,8 +416,8 @@ struct NativeCode : public ANativeActivity { if (env != NULL && clazz != NULL) { env->DeleteGlobalRef(clazz); } - if (looper != NULL && mainWorkRead >= 0) { - looper->removeFd(mainWorkRead); + if (messageQueue != NULL && mainWorkRead >= 0) { + messageQueue->getLooper()->removeFd(mainWorkRead); } if (nativeInputQueue != NULL) { nativeInputQueue->mWorkWrite = -1; @@ -481,7 +481,7 @@ struct NativeCode : public ANativeActivity { // These are used to wake up the main thread to process work. int mainWorkRead; int mainWorkWrite; - sp looper; + sp messageQueue; }; void android_NativeActivity_finish(ANativeActivity* activity) { @@ -515,16 +515,6 @@ void android_NativeActivity_hideSoftInput( // ------------------------------------------------------------------------ -static bool checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName) { - if (env->ExceptionCheck()) { - ALOGE("An exception was thrown by callback '%s'.", methodName); - LOGE_EX(env); - env->ExceptionClear(); - return true; - } - return false; -} - /* * Callback for handling native events on the application's main thread. */ @@ -551,7 +541,8 @@ static int mainWorkCallback(int fd, int events, void* data) { if (inputEventObj) { handled = code->env->CallBooleanMethod(code->clazz, gNativeActivityClassInfo.dispatchUnhandledKeyEvent, inputEventObj); - checkAndClearExceptionFromCallback(code->env, "dispatchUnhandledKeyEvent"); + code->messageQueue->raiseAndClearException( + code->env, "dispatchUnhandledKeyEvent"); code->env->DeleteLocalRef(inputEventObj); } else { ALOGE("Failed to obtain key event for dispatchUnhandledKeyEvent."); @@ -566,7 +557,7 @@ static int mainWorkCallback(int fd, int events, void* data) { if (inputEventObj) { code->env->CallVoidMethod(code->clazz, gNativeActivityClassInfo.preDispatchKeyEvent, inputEventObj, seq); - checkAndClearExceptionFromCallback(code->env, "preDispatchKeyEvent"); + code->messageQueue->raiseAndClearException(code->env, "preDispatchKeyEvent"); code->env->DeleteLocalRef(inputEventObj); } else { ALOGE("Failed to obtain key event for preDispatchKeyEvent."); @@ -575,27 +566,27 @@ static int mainWorkCallback(int fd, int events, void* data) { } break; case CMD_FINISH: { code->env->CallVoidMethod(code->clazz, gNativeActivityClassInfo.finish); - checkAndClearExceptionFromCallback(code->env, "finish"); + code->messageQueue->raiseAndClearException(code->env, "finish"); } break; case CMD_SET_WINDOW_FORMAT: { code->env->CallVoidMethod(code->clazz, gNativeActivityClassInfo.setWindowFormat, work.arg1); - checkAndClearExceptionFromCallback(code->env, "setWindowFormat"); + code->messageQueue->raiseAndClearException(code->env, "setWindowFormat"); } break; case CMD_SET_WINDOW_FLAGS: { code->env->CallVoidMethod(code->clazz, gNativeActivityClassInfo.setWindowFlags, work.arg1, work.arg2); - checkAndClearExceptionFromCallback(code->env, "setWindowFlags"); + code->messageQueue->raiseAndClearException(code->env, "setWindowFlags"); } break; case CMD_SHOW_SOFT_INPUT: { code->env->CallVoidMethod(code->clazz, gNativeActivityClassInfo.showIme, work.arg1); - checkAndClearExceptionFromCallback(code->env, "showIme"); + code->messageQueue->raiseAndClearException(code->env, "showIme"); } break; case CMD_HIDE_SOFT_INPUT: { code->env->CallVoidMethod(code->clazz, gNativeActivityClassInfo.hideIme, work.arg1); - checkAndClearExceptionFromCallback(code->env, "hideIme"); + code->messageQueue->raiseAndClearException(code->env, "hideIme"); } break; default: ALOGW("Unknown work command: %d", work.cmd); @@ -634,9 +625,9 @@ loadNativeCode_native(JNIEnv* env, jobject clazz, jstring path, jstring funcName return 0; } - code->looper = android_os_MessageQueue_getLooper(env, messageQueue); - if (code->looper == NULL) { - ALOGW("Unable to retrieve MessageQueue's Looper"); + code->messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueue); + if (code->messageQueue == NULL) { + ALOGW("Unable to retrieve native MessageQueue"); delete code; return 0; } @@ -655,7 +646,8 @@ loadNativeCode_native(JNIEnv* env, jobject clazz, jstring path, jstring funcName result = fcntl(code->mainWorkWrite, F_SETFL, O_NONBLOCK); SLOGW_IF(result != 0, "Could not make main work write pipe " "non-blocking: %s", strerror(errno)); - code->looper->addFd(code->mainWorkRead, 0, ALOOPER_EVENT_INPUT, mainWorkCallback, code); + code->messageQueue->getLooper()->addFd( + code->mainWorkRead, 0, ALOOPER_EVENT_INPUT, mainWorkCallback, code); code->ANativeActivity::callbacks = &code->callbacks; if (env->GetJavaVM(&code->vm) < 0) { diff --git a/core/jni/android_os_MessageQueue.cpp b/core/jni/android_os_MessageQueue.cpp index 12a77d53db25b..a4dcac6d886a1 100644 --- a/core/jni/android_os_MessageQueue.cpp +++ b/core/jni/android_os_MessageQueue.cpp @@ -17,6 +17,7 @@ #define LOG_TAG "MessageQueue-JNI" #include "JNIHelp.h" +#include #include #include @@ -24,31 +25,46 @@ namespace android { -// ---------------------------------------------------------------------------- - static struct { jfieldID mPtr; // native object attached to the DVM MessageQueue } gMessageQueueClassInfo; -// ---------------------------------------------------------------------------- -class NativeMessageQueue { +class NativeMessageQueue : public MessageQueue { public: NativeMessageQueue(); - ~NativeMessageQueue(); + virtual ~NativeMessageQueue(); - inline sp getLooper() { return mLooper; } + virtual void raiseException(JNIEnv* env, const char* msg, jthrowable exceptionObj); + + void pollOnce(JNIEnv* env, int timeoutMillis); - void pollOnce(int timeoutMillis); void wake(); private: - sp mLooper; + bool mInCallback; + jthrowable mExceptionObj; }; -// ---------------------------------------------------------------------------- -NativeMessageQueue::NativeMessageQueue() { +MessageQueue::MessageQueue() { +} + +MessageQueue::~MessageQueue() { +} + +bool MessageQueue::raiseAndClearException(JNIEnv* env, const char* msg) { + jthrowable exceptionObj = env->ExceptionOccurred(); + if (exceptionObj) { + env->ExceptionClear(); + raiseException(env, msg, exceptionObj); + env->DeleteLocalRef(exceptionObj); + return true; + } + return false; +} + +NativeMessageQueue::NativeMessageQueue() : mInCallback(false), mExceptionObj(NULL) { mLooper = Looper::getForThread(); if (mLooper == NULL) { mLooper = new Looper(false); @@ -59,8 +75,32 @@ NativeMessageQueue::NativeMessageQueue() { NativeMessageQueue::~NativeMessageQueue() { } -void NativeMessageQueue::pollOnce(int timeoutMillis) { +void NativeMessageQueue::raiseException(JNIEnv* env, const char* msg, jthrowable exceptionObj) { + if (exceptionObj) { + if (mInCallback) { + if (mExceptionObj) { + env->DeleteLocalRef(mExceptionObj); + } + mExceptionObj = jthrowable(env->NewLocalRef(exceptionObj)); + ALOGE("Exception in MessageQueue callback: %s", msg); + jniLogException(env, ANDROID_LOG_ERROR, LOG_TAG, exceptionObj); + } else { + ALOGE("Exception: %s", msg); + jniLogException(env, ANDROID_LOG_ERROR, LOG_TAG, exceptionObj); + LOG_ALWAYS_FATAL("raiseException() was called when not in a callback, exiting."); + } + } +} + +void NativeMessageQueue::pollOnce(JNIEnv* env, int timeoutMillis) { + mInCallback = true; mLooper->pollOnce(timeoutMillis); + mInCallback = false; + if (mExceptionObj) { + env->Throw(mExceptionObj); + env->DeleteLocalRef(mExceptionObj); + mExceptionObj = NULL; + } } void NativeMessageQueue::wake() { @@ -81,19 +121,20 @@ static void android_os_MessageQueue_setNativeMessageQueue(JNIEnv* env, jobject m reinterpret_cast(nativeMessageQueue)); } -sp android_os_MessageQueue_getLooper(JNIEnv* env, jobject messageQueueObj) { +sp android_os_MessageQueue_getMessageQueue(JNIEnv* env, jobject messageQueueObj) { NativeMessageQueue* nativeMessageQueue = android_os_MessageQueue_getNativeMessageQueue(env, messageQueueObj); - return nativeMessageQueue != NULL ? nativeMessageQueue->getLooper() : NULL; + return nativeMessageQueue; } static void android_os_MessageQueue_nativeInit(JNIEnv* env, jobject obj) { NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue(); - if (! nativeMessageQueue) { + if (!nativeMessageQueue) { jniThrowRuntimeException(env, "Unable to allocate native queue"); return; } + nativeMessageQueue->incStrong(env); android_os_MessageQueue_setNativeMessageQueue(env, obj, nativeMessageQueue); } @@ -102,7 +143,7 @@ static void android_os_MessageQueue_nativeDestroy(JNIEnv* env, jobject obj) { android_os_MessageQueue_getNativeMessageQueue(env, obj); if (nativeMessageQueue) { android_os_MessageQueue_setNativeMessageQueue(env, obj, NULL); - delete nativeMessageQueue; + nativeMessageQueue->decStrong(env); } } @@ -113,7 +154,7 @@ static void throwQueueNotInitialized(JNIEnv* env) { static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj, jint ptr, jint timeoutMillis) { NativeMessageQueue* nativeMessageQueue = reinterpret_cast(ptr); - nativeMessageQueue->pollOnce(timeoutMillis); + nativeMessageQueue->pollOnce(env, timeoutMillis); } static void android_os_MessageQueue_nativeWake(JNIEnv* env, jobject obj, jint ptr) { diff --git a/core/jni/android_os_MessageQueue.h b/core/jni/android_os_MessageQueue.h index f961d8f2f5da8..49d2aa0c283d7 100644 --- a/core/jni/android_os_MessageQueue.h +++ b/core/jni/android_os_MessageQueue.h @@ -18,12 +18,53 @@ #define _ANDROID_OS_MESSAGEQUEUE_H #include "jni.h" +#include namespace android { -class Looper; +class MessageQueue : public RefBase { +public: + /* Gets the message queue's looper. */ + inline sp getLooper() const { + return mLooper; + } -extern sp android_os_MessageQueue_getLooper(JNIEnv* env, jobject messageQueueObj); + /* Checks whether the JNI environment has a pending exception. + * + * If an exception occurred, logs it together with the specified message, + * and calls raiseException() to ensure the exception will be raised when + * the callback returns, clears the pending exception from the environment, + * then returns true. + * + * If no exception occurred, returns false. + */ + bool raiseAndClearException(JNIEnv* env, const char* msg); + + /* Raises an exception from within a callback function. + * The exception will be rethrown when control returns to the message queue which + * will typically cause the application to crash. + * + * This message can only be called from within a callback function. If it is called + * at any other time, the process will simply be killed. + * + * Does nothing if exception is NULL. + * + * (This method does not take ownership of the exception object reference. + * The caller is responsible for releasing its reference when it is done.) + */ + virtual void raiseException(JNIEnv* env, const char* msg, jthrowable exceptionObj) = 0; + +protected: + MessageQueue(); + virtual ~MessageQueue(); + +protected: + sp mLooper; +}; + +/* Gets the native object associated with a MessageQueue. */ +extern sp android_os_MessageQueue_getMessageQueue( + JNIEnv* env, jobject messageQueueObj); } // namespace android diff --git a/core/jni/android_view_DisplayEventReceiver.cpp b/core/jni/android_view_DisplayEventReceiver.cpp index 72c171cf229f3..d80bfb339040b 100644 --- a/core/jni/android_view_DisplayEventReceiver.cpp +++ b/core/jni/android_view_DisplayEventReceiver.cpp @@ -45,7 +45,7 @@ static struct { class NativeDisplayEventReceiver : public RefBase { public: NativeDisplayEventReceiver(JNIEnv* env, - jobject receiverObj, const sp& looper); + jobject receiverObj, const sp& messageQueue); status_t initialize(); status_t scheduleVsync(); @@ -55,7 +55,7 @@ protected: private: jobject mReceiverObjGlobal; - sp mLooper; + sp mMessageQueue; DisplayEventReceiver mReceiver; bool mWaitingForVsync; @@ -65,9 +65,9 @@ private: NativeDisplayEventReceiver::NativeDisplayEventReceiver(JNIEnv* env, - jobject receiverObj, const sp& looper) : + jobject receiverObj, const sp& messageQueue) : mReceiverObjGlobal(env->NewGlobalRef(receiverObj)), - mLooper(looper), mWaitingForVsync(false) { + mMessageQueue(messageQueue), mWaitingForVsync(false) { ALOGV("receiver %p ~ Initializing input event receiver.", this); } @@ -75,7 +75,7 @@ NativeDisplayEventReceiver::~NativeDisplayEventReceiver() { ALOGV("receiver %p ~ Disposing display event receiver.", this); if (!mReceiver.initCheck()) { - mLooper->removeFd(mReceiver.getFd()); + mMessageQueue->getLooper()->removeFd(mReceiver.getFd()); } JNIEnv* env = AndroidRuntime::getJNIEnv(); @@ -89,7 +89,7 @@ status_t NativeDisplayEventReceiver::initialize() { return result; } - int rc = mLooper->addFd(mReceiver.getFd(), 0, ALOOPER_EVENT_INPUT, + int rc = mMessageQueue->getLooper()->addFd(mReceiver.getFd(), 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this); if (rc < 0) { return UNKNOWN_ERROR; @@ -151,12 +151,7 @@ int NativeDisplayEventReceiver::handleReceiveCallback(int receiveFd, int events, gDisplayEventReceiverClassInfo.dispatchVsync, vsyncTimestamp, vsyncCount); ALOGV("receiver %p ~ Returned from vsync handler.", data); - if (env->ExceptionCheck()) { - ALOGE("An exception occurred while dispatching a vsync event."); - LOGE_EX(env); - env->ExceptionClear(); - } - + r->mMessageQueue->raiseAndClearException(env, "dispatchVsync"); return 1; // keep the callback } @@ -183,14 +178,14 @@ bool NativeDisplayEventReceiver::readLastVsyncMessage( static jint nativeInit(JNIEnv* env, jclass clazz, jobject receiverObj, jobject messageQueueObj) { - sp looper = android_os_MessageQueue_getLooper(env, messageQueueObj); - if (looper == NULL) { + sp messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj); + if (messageQueue == NULL) { jniThrowRuntimeException(env, "MessageQueue is not initialized."); return 0; } sp receiver = new NativeDisplayEventReceiver(env, - receiverObj, looper); + receiverObj, messageQueue); status_t status = receiver->initialize(); if (status) { String8 message; diff --git a/core/jni/android_view_InputEventReceiver.cpp b/core/jni/android_view_InputEventReceiver.cpp index e7d4244728312..348437d0f34ab 100644 --- a/core/jni/android_view_InputEventReceiver.cpp +++ b/core/jni/android_view_InputEventReceiver.cpp @@ -48,7 +48,7 @@ class NativeInputEventReceiver : public RefBase { public: NativeInputEventReceiver(JNIEnv* env, jobject receiverObj, const sp& inputChannel, - const sp& looper); + const sp& messageQueue); status_t initialize(); status_t finishInputEvent(uint32_t seq, bool handled); @@ -61,7 +61,7 @@ protected: private: jobject mReceiverObjGlobal; InputConsumer mInputConsumer; - sp mLooper; + sp mMessageQueue; PreallocatedInputEventFactory mInputEventFactory; bool mBatchedInputEventPending; @@ -72,9 +72,10 @@ private: NativeInputEventReceiver::NativeInputEventReceiver(JNIEnv* env, - jobject receiverObj, const sp& inputChannel, const sp& looper) : + jobject receiverObj, const sp& inputChannel, + const sp& messageQueue) : mReceiverObjGlobal(env->NewGlobalRef(receiverObj)), - mInputConsumer(inputChannel), mLooper(looper), + mInputConsumer(inputChannel), mMessageQueue(messageQueue), mBatchedInputEventPending(false) { #if DEBUG_DISPATCH_CYCLE ALOGD("channel '%s' ~ Initializing input event receiver.", getInputChannelName()); @@ -86,7 +87,7 @@ NativeInputEventReceiver::~NativeInputEventReceiver() { ALOGD("channel '%s' ~ Disposing input event receiver.", getInputChannelName()); #endif - mLooper->removeFd(mInputConsumer.getChannel()->getFd()); + mMessageQueue->getLooper()->removeFd(mInputConsumer.getChannel()->getFd()); JNIEnv* env = AndroidRuntime::getJNIEnv(); env->DeleteGlobalRef(mReceiverObjGlobal); @@ -94,7 +95,8 @@ NativeInputEventReceiver::~NativeInputEventReceiver() { status_t NativeInputEventReceiver::initialize() { int receiveFd = mInputConsumer.getChannel()->getFd(); - mLooper->addFd(receiveFd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this); + mMessageQueue->getLooper()->addFd( + receiveFd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this); return OK; } @@ -157,12 +159,8 @@ status_t NativeInputEventReceiver::consumeEvents(bool consumeBatches) { #endif env->CallVoidMethod(mReceiverObjGlobal, gInputEventReceiverClassInfo.dispatchBatchedInputEventPending); - - if (env->ExceptionCheck()) { - ALOGE("channel '%s' ~ An exception occurred while dispatching that " - "batched input events are pending.", getInputChannelName()); - LOGE_EX(env); - env->ExceptionClear(); + if (mMessageQueue->raiseAndClearException( + env, "dispatchBatchedInputEventPending")) { mBatchedInputEventPending = false; // try again later } } @@ -182,6 +180,7 @@ status_t NativeInputEventReceiver::consumeEvents(bool consumeBatches) { #endif inputEventObj = android_view_KeyEvent_fromNative(env, static_cast(inputEvent)); + mMessageQueue->raiseAndClearException(env, "new KeyEvent"); break; case AINPUT_EVENT_TYPE_MOTION: @@ -190,6 +189,7 @@ status_t NativeInputEventReceiver::consumeEvents(bool consumeBatches) { #endif inputEventObj = android_view_MotionEvent_obtainAsCopy(env, static_cast(inputEvent)); + mMessageQueue->raiseAndClearException(env, "new MotionEvent"); break; default: @@ -200,7 +200,7 @@ status_t NativeInputEventReceiver::consumeEvents(bool consumeBatches) { if (!inputEventObj) { ALOGW("channel '%s' ~ Failed to obtain event object.", getInputChannelName()); mInputConsumer.sendFinishedSignal(seq, false); - return NO_MEMORY; + continue; } #if DEBUG_DISPATCH_CYCLE @@ -211,14 +211,8 @@ status_t NativeInputEventReceiver::consumeEvents(bool consumeBatches) { env->DeleteLocalRef(inputEventObj); - if (env->ExceptionCheck()) { - ALOGE("channel '%s' ~ An exception occurred while dispatching an event.", - getInputChannelName()); - LOGE_EX(env); - env->ExceptionClear(); - + if (mMessageQueue->raiseAndClearException(env, "dispatchInputEvent")) { mInputConsumer.sendFinishedSignal(seq, false); - return OK; } } } @@ -233,14 +227,14 @@ static jint nativeInit(JNIEnv* env, jclass clazz, jobject receiverObj, return 0; } - sp looper = android_os_MessageQueue_getLooper(env, messageQueueObj); - if (looper == NULL) { + sp messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj); + if (messageQueue == NULL) { jniThrowRuntimeException(env, "MessageQueue is not initialized."); return 0; } sp receiver = new NativeInputEventReceiver(env, - receiverObj, inputChannel, looper); + receiverObj, inputChannel, messageQueue); status_t status = receiver->initialize(); if (status) { String8 message; diff --git a/services/jni/com_android_server_input_InputManagerService.cpp b/services/jni/com_android_server_input_InputManagerService.cpp index c57402f0932c1..75c20f3db5ab8 100644 --- a/services/jni/com_android_server_input_InputManagerService.cpp +++ b/services/jni/com_android_server_input_InputManagerService.cpp @@ -950,8 +950,9 @@ void NativeInputManager::loadPointerResources(PointerResources* outResources) { static jint nativeInit(JNIEnv* env, jclass clazz, jobject serviceObj, jobject contextObj, jobject messageQueueObj) { - sp looper = android_os_MessageQueue_getLooper(env, messageQueueObj); - NativeInputManager* im = new NativeInputManager(contextObj, serviceObj, looper); + sp messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj); + NativeInputManager* im = new NativeInputManager(contextObj, serviceObj, + messageQueue->getLooper()); im->incStrong(serviceObj); return reinterpret_cast(im); }