From 1aac9623789e3d2a428b51ae699d4c340b3afb99 Mon Sep 17 00:00:00 2001 From: David Vacca Date: Thu, 18 Jan 2018 18:45:54 -0800 Subject: [PATCH] Capture StackOverflowExceptions triggered when drawing a ReactViewGroup or ReactRootView Reviewed By: achen1 Differential Revision: D6653395 fbshipit-source-id: 849b1a2ed6ab9bc057414d451e97a673178c30dd --- .../com/facebook/react/ReactRootView.java | 22 ++++ .../facebook/react/bridge/ReactContext.java | 4 +- .../IllegalViewOperationException.java | 4 + .../facebook/react/uimanager/RootView.java | 2 + .../react/views/modal/ReactModalHostView.java | 15 ++- .../react/views/view/ReactViewGroup.java | 106 +++++++++++------- 6 files changed, 105 insertions(+), 48 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java b/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java index e5fafe5987e174..d96dfc93e7d893 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java @@ -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; @@ -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; @@ -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) { @@ -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; diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactContext.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactContext.java index bac4fdf8a885bb..37bb9b64cf34f4 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactContext.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactContext.java @@ -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); } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/IllegalViewOperationException.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/IllegalViewOperationException.java index d515ef10d07590..d23735264c57d9 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/IllegalViewOperationException.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/IllegalViewOperationException.java @@ -19,4 +19,8 @@ public class IllegalViewOperationException extends JSApplicationCausedNativeExce public IllegalViewOperationException(String msg) { super(msg); } + + public IllegalViewOperationException(String msg, Throwable cause) { + super(msg, cause); + } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/RootView.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/RootView.java index 05a11ee95535de..1e57ae1ff7fec4 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/RootView.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/RootView.java @@ -21,4 +21,6 @@ public interface RootView { * from the child's onTouchIntercepted implementation. */ void onChildStartedNativeGesture(MotionEvent androidEvent); + + void handleException(Exception e); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.java b/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.java index 8dfa9dd40209a5..afd3d3f3f57faf 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.java @@ -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()); @@ -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(); } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java b/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java index e0984f86e09cac..c31eb66bd628d9 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java @@ -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; @@ -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": @@ -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; @@ -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)) { @@ -730,13 +751,13 @@ 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; @@ -744,13 +765,13 @@ protected void dispatchDraw(Canvas canvas) { 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 @@ 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)); @@ -802,6 +823,5 @@ protected void dispatchDraw(Canvas canvas) { break; } } - super.dispatchDraw(canvas); } }