From a7d032b7071d7477a624836ec36cf23603774c3f Mon Sep 17 00:00:00 2001 From: Marc Horowitz Date: Wed, 6 Jul 2016 16:01:17 -0700 Subject: [PATCH] Display JS exceptions and stacks in a red box Reviewed By: astreet Differential Revision: D3510875 fbshipit-source-id: a7042434b68cb849f5b0c4ef782befff6a27ef5c --- .../devsupport/DevSupportManagerImpl.java | 9 ++++- .../react/devsupport/JSException.java | 37 +++++++++++++++++++ .../jni/xreact/jni/JMessageQueueThread.cpp | 30 ++++++++++++++- 3 files changed, 73 insertions(+), 3 deletions(-) create mode 100644 ReactAndroid/src/main/java/com/facebook/react/devsupport/JSException.java diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerImpl.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerImpl.java index dd823f84a..8bb5423e9 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerImpl.java +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerImpl.java @@ -79,6 +79,7 @@ import com.facebook.react.modules.debug.DeveloperSettings; public class DevSupportManagerImpl implements DevSupportManager { private static final int JAVA_ERROR_COOKIE = -1; + private static final int JSEXCEPTION_ERROR_COOKIE = -1; private static final String JS_BUNDLE_FILE_NAME = "ReactNativeDevBundle.js"; private static enum ErrorType { JS, @@ -194,7 +195,13 @@ public class DevSupportManagerImpl implements DevSupportManager { public void handleException(Exception e) { if (mIsDevSupportEnabled) { FLog.e(ReactConstants.TAG, "Exception in native call from JS", e); - showNewJavaError(e.getMessage(), e); + if (e instanceof JSException) { + // TODO #11638796: convert the stack into something useful + showNewError(e.getMessage() + "\n\n" + ((JSException) e).getStack(), new StackFrame[] {}, + JSEXCEPTION_ERROR_COOKIE, ErrorType.JS); + } else { + showNewJavaError(e.getMessage(), e); + } } else { mDefaultNativeModuleCallExceptionHandler.handleException(e); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/JSException.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/JSException.java new file mode 100644 index 000000000..ca83f57bb --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/JSException.java @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.react.devsupport; + +import com.facebook.proguard.annotations.DoNotStrip; + +/** + * This represents an error evaluating JavaScript. It includes the usual + * message, and the raw JS stack where the error occurred (which may be empty). + */ + +@DoNotStrip +public class JSException extends Exception { + private final String mStack; + + @DoNotStrip + public JSException(String message, String stack, Throwable cause) { + super(message, cause); + mStack = stack; + } + + public JSException(String message, String stack) { + super(message); + mStack = stack; + } + + public String getStack() { + return mStack; + } +} diff --git a/ReactAndroid/src/main/jni/xreact/jni/JMessageQueueThread.cpp b/ReactAndroid/src/main/jni/xreact/jni/JMessageQueueThread.cpp index 6cba3235c..2510c2841 100644 --- a/ReactAndroid/src/main/jni/xreact/jni/JMessageQueueThread.cpp +++ b/ReactAndroid/src/main/jni/xreact/jni/JMessageQueueThread.cpp @@ -9,11 +9,37 @@ #include #include +#include + #include "JNativeRunnable.h" namespace facebook { namespace react { +namespace { + +struct JavaJSException : jni::JavaClass { + static constexpr auto kJavaDescriptor = "Lcom/facebook/react/devsupport/JSException;"; + + static local_ref create(const char* message, const char* stack, + const std::exception& ex) { + local_ref cause = jni::JCppException::create(ex); + return newInstance(make_jstring(message), make_jstring(stack), cause.get()); + } +}; + +std::function wrapRunnable(std::function&& runnable) { + return [runnable=std::move(runnable)] { + try { + runnable(); + } catch (const JSException& ex) { + throwNewJavaException(JavaJSException::create(ex.what(), ex.getStack().c_str(), ex).get()); + } + }; +} + +} + JMessageQueueThread::JMessageQueueThread(alias_ref jobj) : m_jobj(make_global(jobj)) { } @@ -21,7 +47,7 @@ JMessageQueueThread::JMessageQueueThread(alias_ref&& runnable) { static auto method = JavaMessageQueueThread::javaClassStatic()-> getMethod("runOnQueue"); - method(m_jobj, JNativeRunnable::newObjectCxxArgs(runnable).get()); + method(m_jobj, JNativeRunnable::newObjectCxxArgs(wrapRunnable(std::move(runnable))).get()); } void JMessageQueueThread::runOnQueueSync(std::function&& runnable) { @@ -29,7 +55,7 @@ void JMessageQueueThread::runOnQueueSync(std::function&& runnable) { getMethod("isOnThread"); if (jIsOnThread(m_jobj)) { - runnable(); + wrapRunnable(std::move(runnable))(); } else { std::mutex signalMutex; std::condition_variable signalCv;