Skip to content

Commit

Permalink
Capture StackOverflowExceptions triggered when drawing a ReactViewGro…
Browse files Browse the repository at this point in the history
…up or ReactRootView

Reviewed By: achen1

Differential Revision: D6653395

fbshipit-source-id: 849b1a2ed6ab9bc057414d451e97a673178c30dd
  • Loading branch information
mdvacca authored and facebook-github-bot committed Jan 19, 2018
1 parent 877f1cd commit 1aac962
Show file tree
Hide file tree
Showing 6 changed files with 105 additions and 48 deletions.
22 changes: 22 additions & 0 deletions ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java
Expand Up @@ -12,6 +12,7 @@
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;
Expand Down Expand Up @@ -40,6 +41,7 @@
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;
Expand Down Expand Up @@ -201,6 +203,17 @@ public boolean onTouchEvent(MotionEvent ev) {
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) {
Expand Down Expand Up @@ -496,6 +509,15 @@ public void setRootViewTag(int rootViewTag) {
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;
Expand Down
Expand Up @@ -304,13 +304,13 @@ public void runOnJSQueueThread(Runnable runnable) {
* {@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);
}
}

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

public IllegalViewOperationException(String msg, Throwable cause) {
super(msg, cause);
}
}
Expand Up @@ -21,4 +21,6 @@ public interface RootView {
* from the child's onTouchIntercepted implementation.
*/
void onChildStartedNativeGesture(MotionEvent androidEvent);

void handleException(Exception e);
}
Expand Up @@ -315,18 +315,27 @@ protected void onSizeChanged(final int w, final int h, int oldw, int oldh) {
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());
Expand Down Expand Up @@ -354,7 +363,7 @@ public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
}

private EventDispatcher getEventDispatcher() {
ReactContext reactContext = (ReactContext) getContext();
ReactContext reactContext = getReactContext();
return reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher();
}
}
Expand Down
Expand Up @@ -28,12 +28,15 @@
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;
Expand Down Expand Up @@ -657,6 +660,24 @@ private void updateBackgroundDrawable(Drawable drawable) {

@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":
Expand All @@ -674,9 +695,9 @@ protected void dispatchDraw(Canvas canvas) {
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;
Expand All @@ -685,32 +706,32 @@ protected void dispatchDraw(Canvas canvas) {

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)) {
Expand All @@ -730,27 +751,27 @@ protected void dispatchDraw(Canvas canvas) {
}

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;
bottomLeftBorderRadius = directionAwareBottomLeftRadius;
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;
Expand All @@ -771,27 +792,27 @@ protected void dispatchDraw(Canvas canvas) {
}

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));
Expand All @@ -802,6 +823,5 @@ protected void dispatchDraw(Canvas canvas) {
break;
}
}
super.dispatchDraw(canvas);
}
}

0 comments on commit 1aac962

Please sign in to comment.