Destroy React Native instance after catching a fatal js error

Summary:
As title, destroy React Native instance after catching a fatal js error to avoid incoming calls into JS.

Changelog:
[Android][Changed] - Rename NativeModuleCallExceptionHandler to JSExceptionHandler for broader usage

Reviewed By: fkgozali

Differential Revision: D37379340

fbshipit-source-id: 465a30bc824a264b45df3e8b0b24edd61c4b571d
This commit is contained in:
Lulu Wu
2022-06-28 15:24:13 -07:00
committed by Facebook GitHub Bot
parent ca8481bd7d
commit b6f7689d70
19 changed files with 76 additions and 85 deletions
@@ -54,12 +54,12 @@ import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.CatalystInstance;
import com.facebook.react.bridge.CatalystInstanceImpl;
import com.facebook.react.bridge.JSBundleLoader;
import com.facebook.react.bridge.JSExceptionHandler;
import com.facebook.react.bridge.JSIModulePackage;
import com.facebook.react.bridge.JSIModuleType;
import com.facebook.react.bridge.JavaJSExecutor;
import com.facebook.react.bridge.JavaScriptExecutor;
import com.facebook.react.bridge.JavaScriptExecutorFactory;
import com.facebook.react.bridge.NativeModuleCallExceptionHandler;
import com.facebook.react.bridge.NativeModuleRegistry;
import com.facebook.react.bridge.NotThreadSafeBridgeIdleDebugListener;
import com.facebook.react.bridge.ProxyJavaScriptExecutor;
@@ -182,7 +182,7 @@ public class ReactInstanceManager {
// while true any spawned create thread should wait for proper clean up before initializing
private volatile Boolean mHasStartedDestroying = false;
private final MemoryPressureRouter mMemoryPressureRouter;
private final @Nullable NativeModuleCallExceptionHandler mNativeModuleCallExceptionHandler;
private final @Nullable JSExceptionHandler mJSExceptionHandler;
private final @Nullable JSIModulePackage mJSIModulePackage;
private final @Nullable ReactPackageTurboModuleManagerDelegate.Builder mTMMDelegateBuilder;
private List<ViewManager> mViewManagers;
@@ -226,7 +226,7 @@ public class ReactInstanceManager {
@Nullable NotThreadSafeBridgeIdleDebugListener bridgeIdleDebugListener,
LifecycleState initialLifecycleState,
@Nullable UIImplementationProvider mUIImplementationProvider,
NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler,
JSExceptionHandler jSExceptionHandler,
@Nullable RedBoxHandler redBoxHandler,
boolean lazyViewManagersEnabled,
@Nullable DevBundleDownloadListener devBundleDownloadListener,
@@ -268,7 +268,7 @@ public class ReactInstanceManager {
mBridgeIdleDebugListener = bridgeIdleDebugListener;
mLifecycleState = initialLifecycleState;
mMemoryPressureRouter = new MemoryPressureRouter(applicationContext);
mNativeModuleCallExceptionHandler = nativeModuleCallExceptionHandler;
mJSExceptionHandler = jSExceptionHandler;
mTMMDelegateBuilder = tmmDelegateBuilder;
synchronized (mPackages) {
PrinterHolder.getPrinter()
@@ -1331,11 +1331,9 @@ public class ReactInstanceManager {
ReactMarker.logMarker(CREATE_REACT_CONTEXT_START, jsExecutor.getName());
final ReactApplicationContext reactContext = new ReactApplicationContext(mApplicationContext);
NativeModuleCallExceptionHandler exceptionHandler =
mNativeModuleCallExceptionHandler != null
? mNativeModuleCallExceptionHandler
: mDevSupportManager;
reactContext.setNativeModuleCallExceptionHandler(exceptionHandler);
JSExceptionHandler exceptionHandler =
mJSExceptionHandler != null ? mJSExceptionHandler : mDevSupportManager;
reactContext.setJSExceptionHandler(exceptionHandler);
NativeModuleRegistry nativeModuleRegistry = processPackages(reactContext, mPackages, false);
@@ -1345,7 +1343,7 @@ public class ReactInstanceManager {
.setJSExecutor(jsExecutor)
.setRegistry(nativeModuleRegistry)
.setJSBundleLoader(jsBundleLoader)
.setNativeModuleCallExceptionHandler(exceptionHandler);
.setJSExceptionHandler(exceptionHandler);
ReactMarker.logMarker(CREATE_CATALYST_INSTANCE_START);
// CREATE_CATALYST_INSTANCE_END is in JSCExecutor.cpp
@@ -18,9 +18,9 @@ import com.facebook.hermes.reactexecutor.HermesExecutor;
import com.facebook.hermes.reactexecutor.HermesExecutorFactory;
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.bridge.JSBundleLoader;
import com.facebook.react.bridge.JSExceptionHandler;
import com.facebook.react.bridge.JSIModulePackage;
import com.facebook.react.bridge.JavaScriptExecutorFactory;
import com.facebook.react.bridge.NativeModuleCallExceptionHandler;
import com.facebook.react.bridge.NotThreadSafeBridgeIdleDebugListener;
import com.facebook.react.common.LifecycleState;
import com.facebook.react.common.SurfaceDelegateFactory;
@@ -53,7 +53,7 @@ public class ReactInstanceManagerBuilder {
private boolean mRequireActivity;
private @Nullable LifecycleState mInitialLifecycleState;
private @Nullable UIImplementationProvider mUIImplementationProvider;
private @Nullable NativeModuleCallExceptionHandler mNativeModuleCallExceptionHandler;
private @Nullable JSExceptionHandler mJSExceptionHandler;
private @Nullable Activity mCurrentActivity;
private @Nullable DefaultHardwareBackBtnHandler mDefaultHardwareBackBtnHandler;
private @Nullable RedBoxHandler mRedBoxHandler;
@@ -253,9 +253,8 @@ public class ReactInstanceManagerBuilder {
* DevSupportManager} will be used, which shows a redbox in dev mode and rethrows (crashes the
* app) in prod mode.
*/
public ReactInstanceManagerBuilder setNativeModuleCallExceptionHandler(
NativeModuleCallExceptionHandler handler) {
mNativeModuleCallExceptionHandler = handler;
public ReactInstanceManagerBuilder setJSExceptionHandler(JSExceptionHandler handler) {
mJSExceptionHandler = handler;
return this;
}
@@ -357,7 +356,7 @@ public class ReactInstanceManagerBuilder {
mBridgeIdleDebugListener,
Assertions.assertNotNull(mInitialLifecycleState, "Initial lifecycle state was not set"),
mUIImplementationProvider,
mNativeModuleCallExceptionHandler,
mJSExceptionHandler,
mRedBoxHandler,
mLazyViewManagersEnabled,
mDevBundleDownloadListener,
@@ -9,8 +9,8 @@ package com.facebook.react.bridge;
/**
* Like {@link AssertionError} but extends RuntimeException so that it may be caught by a {@link
* NativeModuleCallExceptionHandler}. See that class for more details. Used in conjunction with
* {@link SoftAssertions}.
* JSExceptionHandler}. See that class for more details. Used in conjunction with {@link
* SoftAssertions}.
*/
public class AssertionException extends RuntimeException {
@@ -93,7 +93,7 @@ public class CatalystInstanceImpl implements CatalystInstance {
private final NativeModuleRegistry mNativeModuleRegistry;
private final JSIModuleRegistry mJSIModuleRegistry = new JSIModuleRegistry();
private final NativeModuleCallExceptionHandler mNativeModuleCallExceptionHandler;
private final JSExceptionHandler mJSExceptionHandler;
private final MessageQueueThread mNativeModulesQueueThread;
private boolean mInitialized = false;
private volatile boolean mAcceptCalls = false;
@@ -120,7 +120,7 @@ public class CatalystInstanceImpl implements CatalystInstance {
final JavaScriptExecutor jsExecutor,
final NativeModuleRegistry nativeModuleRegistry,
final JSBundleLoader jsBundleLoader,
NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler) {
JSExceptionHandler jSExceptionHandler) {
FLog.d(ReactConstants.TAG, "Initializing React Xplat Bridge.");
Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "createCatalystInstanceImpl");
@@ -141,7 +141,7 @@ public class CatalystInstanceImpl implements CatalystInstance {
mNativeModuleRegistry = nativeModuleRegistry;
mJSModuleRegistry = new JavaScriptModuleRegistry();
mJSBundleLoader = jsBundleLoader;
mNativeModuleCallExceptionHandler = nativeModuleCallExceptionHandler;
mJSExceptionHandler = jSExceptionHandler;
mNativeModulesQueueThread = mReactQueueConfiguration.getNativeModulesQueueThread();
mTraceListener = new JSProfilerTraceListener(this);
Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
@@ -626,7 +626,7 @@ public class CatalystInstanceImpl implements CatalystInstance {
}
private void onNativeException(Exception e) {
mNativeModuleCallExceptionHandler.handleException(e);
mJSExceptionHandler.handleException(e);
mReactQueueConfiguration
.getUIQueueThread()
.runOnQueue(
@@ -682,7 +682,7 @@ public class CatalystInstanceImpl implements CatalystInstance {
private @Nullable JSBundleLoader mJSBundleLoader;
private @Nullable NativeModuleRegistry mRegistry;
private @Nullable JavaScriptExecutor mJSExecutor;
private @Nullable NativeModuleCallExceptionHandler mNativeModuleCallExceptionHandler;
private @Nullable JSExceptionHandler mJSExceptionHandler;
public Builder setReactQueueConfigurationSpec(
ReactQueueConfigurationSpec ReactQueueConfigurationSpec) {
@@ -705,8 +705,8 @@ public class CatalystInstanceImpl implements CatalystInstance {
return this;
}
public Builder setNativeModuleCallExceptionHandler(NativeModuleCallExceptionHandler handler) {
mNativeModuleCallExceptionHandler = handler;
public Builder setJSExceptionHandler(JSExceptionHandler handler) {
mJSExceptionHandler = handler;
return this;
}
@@ -716,7 +716,7 @@ public class CatalystInstanceImpl implements CatalystInstance {
Assertions.assertNotNull(mJSExecutor),
Assertions.assertNotNull(mRegistry),
Assertions.assertNotNull(mJSBundleLoader),
Assertions.assertNotNull(mNativeModuleCallExceptionHandler));
Assertions.assertNotNull(mJSExceptionHandler));
}
}
}
@@ -8,7 +8,7 @@
package com.facebook.react.bridge;
/** Crashy crashy exception handler. */
public class DefaultNativeModuleCallExceptionHandler implements NativeModuleCallExceptionHandler {
public class DefaultJSExceptionHandler implements JSExceptionHandler {
@Override
public void handleException(Exception e) {
@@ -11,22 +11,21 @@ import android.os.AsyncTask;
/**
* Abstract base for a AsyncTask that should have any RuntimeExceptions it throws handled by the
* {@link com.facebook.react.bridge.NativeModuleCallExceptionHandler} registered if the app is in
* dev mode.
* {@link JSExceptionHandler} registered if the app is in dev mode.
*
* <p>This class doesn't allow doInBackground to return a results. If you need this use
* GuardedResultAsyncTask instead.
*/
public abstract class GuardedAsyncTask<Params, Progress> extends AsyncTask<Params, Progress, Void> {
private final NativeModuleCallExceptionHandler mExceptionHandler;
private final JSExceptionHandler mExceptionHandler;
@Deprecated
protected GuardedAsyncTask(ReactContext reactContext) {
this(reactContext.getExceptionHandler());
}
protected GuardedAsyncTask(NativeModuleCallExceptionHandler exceptionHandler) {
protected GuardedAsyncTask(JSExceptionHandler exceptionHandler) {
mExceptionHandler = exceptionHandler;
}
@@ -11,19 +11,18 @@ import android.os.AsyncTask;
/**
* Abstract base for a AsyncTask with result support that should have any RuntimeExceptions it
* throws handled by the {@link com.facebook.react.bridge.NativeModuleCallExceptionHandler}
* registered if the app is in dev mode.
* throws handled by the {@link JSExceptionHandler} registered if the app is in dev mode.
*/
public abstract class GuardedResultAsyncTask<Result> extends AsyncTask<Void, Void, Result> {
private final NativeModuleCallExceptionHandler mExceptionHandler;
private final JSExceptionHandler mExceptionHandler;
@Deprecated
protected GuardedResultAsyncTask(ReactContext reactContext) {
this(reactContext.getExceptionHandler());
}
protected GuardedResultAsyncTask(NativeModuleCallExceptionHandler exceptionHandler) {
protected GuardedResultAsyncTask(JSExceptionHandler exceptionHandler) {
mExceptionHandler = exceptionHandler;
}
@@ -9,19 +9,18 @@ package com.facebook.react.bridge;
/**
* Abstract base for a Runnable that should have any RuntimeExceptions it throws handled by the
* {@link com.facebook.react.bridge.NativeModuleCallExceptionHandler} registered if the app is in
* dev mode.
* {@link JSExceptionHandler} registered if the app is in dev mode.
*/
public abstract class GuardedRunnable implements Runnable {
private final NativeModuleCallExceptionHandler mExceptionHandler;
private final JSExceptionHandler mExceptionHandler;
@Deprecated
public GuardedRunnable(ReactContext reactContext) {
this(reactContext.getExceptionHandler());
}
public GuardedRunnable(NativeModuleCallExceptionHandler exceptionHandler) {
public GuardedRunnable(JSExceptionHandler exceptionHandler) {
mExceptionHandler = exceptionHandler;
}
@@ -8,15 +8,15 @@
package com.facebook.react.bridge;
/**
* Interface for a class that knows how to handle an Exception thrown by a native module invoked
* from JS. Since these Exceptions are triggered by JS calls (and can be fixed in JS), a common way
* to handle one is to show a error dialog and allow the developer to change and reload JS.
* Interface for a class that knows how to handle an Exception invoked from JS. Since these
* Exceptions are triggered by JS calls (and can be fixed in JS), a common way to handle one is to
* show a error dialog and allow the developer to change and reload JS.
*
* <p>We should also note that we have a unique stance on what 'caused' means: even if there's a bug
* in the framework/native code, it was triggered by JS and theoretically since we were able to set
* up the bridge, JS could change its logic, reload, and not trigger that crash.
* up the React Instance, JS could change its logic, reload, and not trigger that crash.
*/
public interface NativeModuleCallExceptionHandler {
public interface JSExceptionHandler {
/** Do something to display or log the exception. */
void handleException(Exception e);
@@ -60,8 +60,8 @@ public class ReactContext extends ContextWrapper {
private @Nullable MessageQueueThread mUiMessageQueueThread;
private @Nullable MessageQueueThread mNativeModulesMessageQueueThread;
private @Nullable MessageQueueThread mJSMessageQueueThread;
private @Nullable NativeModuleCallExceptionHandler mNativeModuleCallExceptionHandler;
private @Nullable NativeModuleCallExceptionHandler mExceptionHandlerWrapper;
private @Nullable JSExceptionHandler mJSExceptionHandler;
private @Nullable JSExceptionHandler mExceptionHandlerWrapper;
private @Nullable WeakReference<Activity> mCurrentActivity;
private boolean mIsInitialized = false;
@@ -123,9 +123,8 @@ public class ReactContext extends ContextWrapper {
}
}
public void setNativeModuleCallExceptionHandler(
@Nullable NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler) {
mNativeModuleCallExceptionHandler = nativeModuleCallExceptionHandler;
public void setJSExceptionHandler(@Nullable JSExceptionHandler jSExceptionHandler) {
mJSExceptionHandler = jSExceptionHandler;
}
private void raiseCatalystInstanceMissingException() {
@@ -406,18 +405,17 @@ public class ReactContext extends ContextWrapper {
}
/**
* Passes the given exception to the current {@link
* com.facebook.react.bridge.NativeModuleCallExceptionHandler} if one exists, rethrowing
* Passes the given exception to the current {@link JSExceptionHandler} if one exists, rethrowing
* otherwise.
*/
public void handleException(Exception e) {
boolean catalystInstanceVariableExists = mCatalystInstance != null;
boolean isCatalystInstanceAlive =
catalystInstanceVariableExists && !mCatalystInstance.isDestroyed();
boolean hasExceptionHandler = mNativeModuleCallExceptionHandler != null;
boolean hasExceptionHandler = mJSExceptionHandler != null;
if (isCatalystInstanceAlive && hasExceptionHandler) {
mNativeModuleCallExceptionHandler.handleException(e);
mJSExceptionHandler.handleException(e);
} else {
FLog.e(
ReactConstants.TAG,
@@ -432,14 +430,14 @@ public class ReactContext extends ContextWrapper {
}
}
public class ExceptionHandlerWrapper implements NativeModuleCallExceptionHandler {
public class ExceptionHandlerWrapper implements JSExceptionHandler {
@Override
public void handleException(Exception e) {
ReactContext.this.handleException(e);
}
}
public NativeModuleCallExceptionHandler getExceptionHandler() {
public JSExceptionHandler getExceptionHandler() {
if (mExceptionHandlerWrapper == null) {
mExceptionHandlerWrapper = new ExceptionHandlerWrapper();
}
@@ -11,9 +11,9 @@ import androidx.annotation.Nullable;
/**
* Utility class to make assertions that should not hard-crash the app but instead be handled by the
* Catalyst app {@link NativeModuleCallExceptionHandler}. See the javadoc on that class for more
* information about our opinion on when these assertions should be used as opposed to assertions
* that might throw AssertionError Throwables that will cause the app to hard crash.
* Catalyst app {@link JSExceptionHandler}. See the javadoc on that class for more information about
* our opinion on when these assertions should be used as opposed to assertions that might throw
* AssertionError Throwables that will cause the app to hard crash.
*/
public class SoftAssertions {
@@ -31,7 +31,7 @@ import androidx.annotation.UiThread;
import com.facebook.common.logging.FLog;
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.R;
import com.facebook.react.bridge.DefaultNativeModuleCallExceptionHandler;
import com.facebook.react.bridge.DefaultJSExceptionHandler;
import com.facebook.react.bridge.JSBundleLoader;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactMarker;
@@ -94,7 +94,7 @@ public abstract class DevSupportManagerBase implements DevSupportManager {
private final @Nullable String mJSAppBundleName;
private final File mJSBundleDownloadedFile;
private final File mJSSplitBundlesDir;
private final DefaultNativeModuleCallExceptionHandler mDefaultNativeModuleCallExceptionHandler;
private final DefaultJSExceptionHandler mDefaultJSExceptionHandler;
private final DevLoadingViewController mDevLoadingViewController;
private @Nullable SurfaceDelegate mRedBoxSurfaceDelegate;
@@ -201,7 +201,7 @@ public abstract class DevSupportManagerBase implements DevSupportManager {
final String splitBundlesDir = subclassTag.toLowerCase(Locale.ROOT) + "_dev_js_split_bundles";
mJSSplitBundlesDir = mApplicationContext.getDir(splitBundlesDir, Context.MODE_PRIVATE);
mDefaultNativeModuleCallExceptionHandler = new DefaultNativeModuleCallExceptionHandler();
mDefaultJSExceptionHandler = new DefaultJSExceptionHandler();
setDevSupportEnabled(enableOnCreate);
@@ -217,7 +217,7 @@ public abstract class DevSupportManagerBase implements DevSupportManager {
if (mIsDevSupportEnabled) {
logJSException(e);
} else {
mDefaultNativeModuleCallExceptionHandler.handleException(e);
mDefaultJSExceptionHandler.handleException(e);
}
}
@@ -11,7 +11,7 @@ import android.app.Activity;
import android.util.Pair;
import android.view.View;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.DefaultNativeModuleCallExceptionHandler;
import com.facebook.react.bridge.DefaultJSExceptionHandler;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.common.SurfaceDelegate;
@@ -33,10 +33,10 @@ import java.io.File;
*/
public class DisabledDevSupportManager implements DevSupportManager {
private final DefaultNativeModuleCallExceptionHandler mDefaultNativeModuleCallExceptionHandler;
private final DefaultJSExceptionHandler mDefaultJSExceptionHandler;
public DisabledDevSupportManager() {
mDefaultNativeModuleCallExceptionHandler = new DefaultNativeModuleCallExceptionHandler();
mDefaultJSExceptionHandler = new DefaultJSExceptionHandler();
}
@Override
@@ -192,7 +192,7 @@ public class DisabledDevSupportManager implements DevSupportManager {
@Override
public void handleException(Exception e) {
mDefaultNativeModuleCallExceptionHandler.handleException(e);
mDefaultJSExceptionHandler.handleException(e);
}
@Override
@@ -11,7 +11,7 @@ import android.app.Activity;
import android.util.Pair;
import android.view.View;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.NativeModuleCallExceptionHandler;
import com.facebook.react.bridge.JSExceptionHandler;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.common.SurfaceDelegate;
@@ -23,7 +23,7 @@ import java.io.File;
* implementation {@link BridgeDevSupportManager}. In production mode, use the dummy implementation
* {@link DisabledDevSupportManager}.
*/
public interface DevSupportManager extends NativeModuleCallExceptionHandler {
public interface DevSupportManager extends JSExceptionHandler {
void showNewJavaError(String message, Throwable e);
@@ -8,20 +8,20 @@
package com.facebook.react.fabric;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.NativeModuleCallExceptionHandler;
import com.facebook.react.bridge.JSExceptionHandler;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.modules.core.ChoreographerCompat;
public abstract class GuardedFrameCallback extends ChoreographerCompat.FrameCallback {
@NonNull private final NativeModuleCallExceptionHandler mExceptionHandler;
@NonNull private final JSExceptionHandler mExceptionHandler;
@Deprecated
protected GuardedFrameCallback(@NonNull ReactContext reactContext) {
this(reactContext.getExceptionHandler());
}
protected GuardedFrameCallback(@NonNull NativeModuleCallExceptionHandler exceptionHandler) {
protected GuardedFrameCallback(@NonNull JSExceptionHandler exceptionHandler) {
mExceptionHandler = exceptionHandler;
}
@@ -7,13 +7,13 @@
package com.facebook.react.uimanager;
import com.facebook.react.bridge.JSExceptionHandler;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.modules.core.ChoreographerCompat;
/**
* Abstract base for a Choreographer FrameCallback that should have any RuntimeExceptions it throws
* handled by the {@link com.facebook.react.bridge.NativeModuleCallExceptionHandler} registered if
* the app is in dev mode.
* handled by the {@link JSExceptionHandler} registered if the app is in dev mode.
*/
public abstract class GuardedFrameCallback extends ChoreographerCompat.FrameCallback {