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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,6 @@ public interface RootView {
* from the child's onTouchIntercepted implementation.
*/
void onChildStartedNativeGesture(MotionEvent androidEvent);

void handleException(Exception e);
}
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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);
}
}

2 comments on commit 1aac962

@yongjiliu
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why StackOverflowExceptions? Could you provide related scenes?

@fabOnReact
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

He wants to log StackOverflowErrror in Facebook Production App.

Please sign in to comment.