Capture StackOverflowExceptions triggered when drawing a ReactViewGroup or ReactRootView

Reviewed By: achen1

Differential Revision: D6653395

fbshipit-source-id: 849b1a2ed6ab9bc057414d451e97a673178c30dd
This commit is contained in:
David Vacca 2018-01-18 18:45:54 -08:00 committed by Facebook Github Bot
parent 877f1cde2e
commit 1aac962378
6 changed files with 105 additions and 48 deletions

View File

@ -12,6 +12,7 @@ package com.facebook.react;
import static com.facebook.systrace.Systrace.TRACE_TAG_REACT_JAVA_BRIDGE;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.os.Build;
import android.os.Bundle;
@ -40,6 +41,7 @@ import com.facebook.react.modules.appregistry.AppRegistry;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.facebook.react.modules.deviceinfo.DeviceInfoModule;
import com.facebook.react.uimanager.DisplayMetricsHolder;
import com.facebook.react.uimanager.IllegalViewOperationException;
import com.facebook.react.uimanager.JSTouchDispatcher;
import com.facebook.react.uimanager.MeasureSpecProvider;
import com.facebook.react.uimanager.PixelUtil;
@ -201,6 +203,17 @@ public class ReactRootView extends SizeMonitoringFrameLayout
return true;
}
@Override
protected void dispatchDraw(Canvas canvas) {
try {
super.dispatchDraw(canvas);
} catch (StackOverflowError e) {
// Adding special exception management for StackOverflowError for logging purposes.
// This will be removed in the future.
handleException(new IllegalViewOperationException("StackOverflowError", e));
}
}
private void dispatchJSTouchEvent(MotionEvent event) {
if (mReactInstanceManager == null || !mIsAttachedToInstance ||
mReactInstanceManager.getCurrentReactContext() == null) {
@ -496,6 +509,15 @@ public class ReactRootView extends SizeMonitoringFrameLayout
mRootViewTag = rootViewTag;
}
@Override
public void handleException(Exception e) {
if (mReactInstanceManager != null && mReactInstanceManager.getCurrentReactContext() != null) {
mReactInstanceManager.getCurrentReactContext().handleException(e);
} else {
throw new RuntimeException(e);
}
}
@Nullable
public ReactInstanceManager getReactInstanceManager() {
return mReactInstanceManager;

View File

@ -304,13 +304,13 @@ public class ReactContext extends ContextWrapper {
* {@link com.facebook.react.bridge.NativeModuleCallExceptionHandler} if one exists, rethrowing
* otherwise.
*/
public void handleException(RuntimeException e) {
public void handleException(Exception e) {
if (mCatalystInstance != null &&
!mCatalystInstance.isDestroyed() &&
mNativeModuleCallExceptionHandler != null) {
mNativeModuleCallExceptionHandler.handleException(e);
} else {
throw e;
throw new RuntimeException(e);
}
}

View File

@ -19,4 +19,8 @@ public class IllegalViewOperationException extends JSApplicationCausedNativeExce
public IllegalViewOperationException(String msg) {
super(msg);
}
public IllegalViewOperationException(String msg, Throwable cause) {
super(msg, cause);
}
}

View File

@ -21,4 +21,6 @@ public interface RootView {
* from the child's onTouchIntercepted implementation.
*/
void onChildStartedNativeGesture(MotionEvent androidEvent);
void handleException(Exception e);
}

View File

@ -315,18 +315,27 @@ public class ReactModalHostView extends ViewGroup implements LifecycleEventListe
super.onSizeChanged(w, h, oldw, oldh);
if (getChildCount() > 0) {
final int viewTag = getChildAt(0).getId();
ReactContext reactContext = (ReactContext) getContext();
ReactContext reactContext = getReactContext();
reactContext.runOnNativeModulesQueueThread(
new GuardedRunnable(reactContext) {
@Override
public void runGuarded() {
((ReactContext) getContext()).getNativeModule(UIManagerModule.class)
(getReactContext()).getNativeModule(UIManagerModule.class)
.updateNodeSize(viewTag, w, h);
}
});
}
}
@Override
public void handleException(Exception e) {
getReactContext().handleException(e);
}
private ReactContext getReactContext() {
return (ReactContext) getContext();
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
mJSTouchDispatcher.handleTouchEvent(event, getEventDispatcher());
@ -354,7 +363,7 @@ public class ReactModalHostView extends ViewGroup implements LifecycleEventListe
}
private EventDispatcher getEventDispatcher() {
ReactContext reactContext = (ReactContext) getContext();
ReactContext reactContext = getReactContext();
return reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher();
}
}

View File

@ -28,12 +28,15 @@ import com.facebook.react.modules.i18nmanager.I18nUtil;
import com.facebook.react.touch.OnInterceptTouchEventListener;
import com.facebook.react.touch.ReactHitSlopView;
import com.facebook.react.touch.ReactInterceptingViewGroup;
import com.facebook.react.uimanager.IllegalViewOperationException;
import com.facebook.react.uimanager.MeasureSpecAssertions;
import com.facebook.react.uimanager.PointerEvents;
import com.facebook.react.uimanager.ReactClippingViewGroup;
import com.facebook.react.uimanager.ReactClippingViewGroupHelper;
import com.facebook.react.uimanager.ReactPointerEventsView;
import com.facebook.react.uimanager.ReactZIndexedViewGroup;
import com.facebook.react.uimanager.RootView;
import com.facebook.react.uimanager.RootViewUtil;
import com.facebook.react.uimanager.ViewGroupDrawingOrderHelper;
import com.facebook.yoga.YogaConstants;
import javax.annotation.Nullable;
@ -657,6 +660,24 @@ public class ReactViewGroup extends ViewGroup implements
@Override
protected void dispatchDraw(Canvas canvas) {
try {
dispatchOverflowDraw(canvas);
super.dispatchDraw(canvas);
} catch (StackOverflowError e) {
// Adding special exception management for StackOverflowError for logging purposes.
// This will be removed in the future.
RootView rootView = RootViewUtil.getRootView(ReactViewGroup.this);
IllegalViewOperationException wrappedException =
new IllegalViewOperationException("StackOverflowError", e);
if (rootView != null) {
rootView.handleException(wrappedException);
} else {
throw wrappedException;
}
}
}
private void dispatchOverflowDraw(Canvas canvas) {
if (mOverflow != null) {
switch (mOverflow) {
case "visible":
@ -674,9 +695,9 @@ public class ReactViewGroup extends ViewGroup implements
final RectF borderWidth = mReactBackgroundDrawable.getDirectionAwareBorderInsets();
if (borderWidth.top > 0
|| borderWidth.left > 0
|| borderWidth.bottom > 0
|| borderWidth.right > 0) {
|| borderWidth.left > 0
|| borderWidth.bottom > 0
|| borderWidth.right > 0) {
left += borderWidth.left;
top += borderWidth.top;
right -= borderWidth.right;
@ -685,32 +706,32 @@ public class ReactViewGroup extends ViewGroup implements
final float borderRadius = mReactBackgroundDrawable.getFullBorderRadius();
float topLeftBorderRadius =
mReactBackgroundDrawable.getBorderRadiusOrDefaultTo(
borderRadius, ReactViewBackgroundDrawable.BorderRadiusLocation.TOP_LEFT);
mReactBackgroundDrawable.getBorderRadiusOrDefaultTo(
borderRadius, ReactViewBackgroundDrawable.BorderRadiusLocation.TOP_LEFT);
float topRightBorderRadius =
mReactBackgroundDrawable.getBorderRadiusOrDefaultTo(
borderRadius, ReactViewBackgroundDrawable.BorderRadiusLocation.TOP_RIGHT);
mReactBackgroundDrawable.getBorderRadiusOrDefaultTo(
borderRadius, ReactViewBackgroundDrawable.BorderRadiusLocation.TOP_RIGHT);
float bottomLeftBorderRadius =
mReactBackgroundDrawable.getBorderRadiusOrDefaultTo(
borderRadius, ReactViewBackgroundDrawable.BorderRadiusLocation.BOTTOM_LEFT);
mReactBackgroundDrawable.getBorderRadiusOrDefaultTo(
borderRadius, ReactViewBackgroundDrawable.BorderRadiusLocation.BOTTOM_LEFT);
float bottomRightBorderRadius =
mReactBackgroundDrawable.getBorderRadiusOrDefaultTo(
borderRadius, ReactViewBackgroundDrawable.BorderRadiusLocation.BOTTOM_RIGHT);
mReactBackgroundDrawable.getBorderRadiusOrDefaultTo(
borderRadius, ReactViewBackgroundDrawable.BorderRadiusLocation.BOTTOM_RIGHT);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
final boolean isRTL = mLayoutDirection == View.LAYOUT_DIRECTION_RTL;
float topStartBorderRadius =
mReactBackgroundDrawable.getBorderRadius(
ReactViewBackgroundDrawable.BorderRadiusLocation.TOP_START);
mReactBackgroundDrawable.getBorderRadius(
ReactViewBackgroundDrawable.BorderRadiusLocation.TOP_START);
float topEndBorderRadius =
mReactBackgroundDrawable.getBorderRadius(
ReactViewBackgroundDrawable.BorderRadiusLocation.TOP_END);
mReactBackgroundDrawable.getBorderRadius(
ReactViewBackgroundDrawable.BorderRadiusLocation.TOP_END);
float bottomStartBorderRadius =
mReactBackgroundDrawable.getBorderRadius(
ReactViewBackgroundDrawable.BorderRadiusLocation.BOTTOM_START);
mReactBackgroundDrawable.getBorderRadius(
ReactViewBackgroundDrawable.BorderRadiusLocation.BOTTOM_START);
float bottomEndBorderRadius =
mReactBackgroundDrawable.getBorderRadius(
ReactViewBackgroundDrawable.BorderRadiusLocation.BOTTOM_END);
mReactBackgroundDrawable.getBorderRadius(
ReactViewBackgroundDrawable.BorderRadiusLocation.BOTTOM_END);
if (I18nUtil.getInstance().doLeftAndRightSwapInRTL(getContext())) {
if (YogaConstants.isUndefined(topStartBorderRadius)) {
@ -730,13 +751,13 @@ public class ReactViewGroup extends ViewGroup implements
}
final float directionAwareTopLeftRadius =
isRTL ? topEndBorderRadius : topStartBorderRadius;
isRTL ? topEndBorderRadius : topStartBorderRadius;
final float directionAwareTopRightRadius =
isRTL ? topStartBorderRadius : topEndBorderRadius;
isRTL ? topStartBorderRadius : topEndBorderRadius;
final float directionAwareBottomLeftRadius =
isRTL ? bottomEndBorderRadius : bottomStartBorderRadius;
isRTL ? bottomEndBorderRadius : bottomStartBorderRadius;
final float directionAwareBottomRightRadius =
isRTL ? bottomStartBorderRadius : bottomEndBorderRadius;
isRTL ? bottomStartBorderRadius : bottomEndBorderRadius;
topLeftBorderRadius = directionAwareTopLeftRadius;
topRightBorderRadius = directionAwareTopRightRadius;
@ -744,13 +765,13 @@ public class ReactViewGroup extends ViewGroup implements
bottomRightBorderRadius = directionAwareBottomRightRadius;
} else {
final float directionAwareTopLeftRadius =
isRTL ? topEndBorderRadius : topStartBorderRadius;
isRTL ? topEndBorderRadius : topStartBorderRadius;
final float directionAwareTopRightRadius =
isRTL ? topStartBorderRadius : topEndBorderRadius;
isRTL ? topStartBorderRadius : topEndBorderRadius;
final float directionAwareBottomLeftRadius =
isRTL ? bottomEndBorderRadius : bottomStartBorderRadius;
isRTL ? bottomEndBorderRadius : bottomStartBorderRadius;
final float directionAwareBottomRightRadius =
isRTL ? bottomStartBorderRadius : bottomEndBorderRadius;
isRTL ? bottomStartBorderRadius : bottomEndBorderRadius;
if (!YogaConstants.isUndefined(directionAwareTopLeftRadius)) {
topLeftBorderRadius = directionAwareTopLeftRadius;
@ -771,27 +792,27 @@ public class ReactViewGroup extends ViewGroup implements
}
if (topLeftBorderRadius > 0
|| topRightBorderRadius > 0
|| bottomRightBorderRadius > 0
|| bottomLeftBorderRadius > 0) {
|| topRightBorderRadius > 0
|| bottomRightBorderRadius > 0
|| bottomLeftBorderRadius > 0) {
if (mPath == null) {
mPath = new Path();
}
mPath.rewind();
mPath.addRoundRect(
new RectF(left, top, right, bottom),
new float[] {
Math.max(topLeftBorderRadius - borderWidth.left, 0),
Math.max(topLeftBorderRadius - borderWidth.top, 0),
Math.max(topRightBorderRadius - borderWidth.right, 0),
Math.max(topRightBorderRadius - borderWidth.top, 0),
Math.max(bottomRightBorderRadius - borderWidth.right, 0),
Math.max(bottomRightBorderRadius - borderWidth.bottom, 0),
Math.max(bottomLeftBorderRadius - borderWidth.left, 0),
Math.max(bottomLeftBorderRadius - borderWidth.bottom, 0),
},
Path.Direction.CW);
new RectF(left, top, right, bottom),
new float[]{
Math.max(topLeftBorderRadius - borderWidth.left, 0),
Math.max(topLeftBorderRadius - borderWidth.top, 0),
Math.max(topRightBorderRadius - borderWidth.right, 0),
Math.max(topRightBorderRadius - borderWidth.top, 0),
Math.max(bottomRightBorderRadius - borderWidth.right, 0),
Math.max(bottomRightBorderRadius - borderWidth.bottom, 0),
Math.max(bottomLeftBorderRadius - borderWidth.left, 0),
Math.max(bottomLeftBorderRadius - borderWidth.bottom, 0),
},
Path.Direction.CW);
canvas.clipPath(mPath);
} else {
canvas.clipRect(new RectF(left, top, right, bottom));
@ -802,6 +823,5 @@ public class ReactViewGroup extends ViewGroup implements
break;
}
}
super.dispatchDraw(canvas);
}
}