From 173587f5ec4418acd62bc28212034f670b7474c4 Mon Sep 17 00:00:00 2001 From: Alan Lee Date: Mon, 17 Jun 2024 10:39:22 -0700 Subject: [PATCH] extract ParsedError handling to helper (#44922) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/44922 extract ParsedError handling code into `StackTraceHelperTest`. Changelog: [Internal] Reviewed By: cortinico Differential Revision: D58512611 fbshipit-source-id: 959978d80907f2afba96ef249c2fddd351d099ff --- .../ReactAndroid/api/ReactAndroid.api | 7 +++ .../react/devsupport/StackTraceHelper.java | 37 ++++++++++- .../facebook/react/runtime/ReactInstance.java | 25 +------- .../react/devsupport/StackTraceHelperTest.kt | 63 +++++++++++++++++++ 4 files changed, 107 insertions(+), 25 deletions(-) diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index 8029d80ed20..d0336cdacb2 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -2323,12 +2323,19 @@ public class com/facebook/react/devsupport/ReleaseDevSupportManager : com/facebo public class com/facebook/react/devsupport/StackTraceHelper { public static final field COLUMN_KEY Ljava/lang/String; + public static final field FILE_KEY Ljava/lang/String; + public static final field ID_KEY Ljava/lang/String; + public static final field IS_FATAL_KEY Ljava/lang/String; public static final field LINE_NUMBER_KEY Ljava/lang/String; + public static final field MESSAGE_KEY Ljava/lang/String; + public static final field METHOD_NAME_KEY Ljava/lang/String; + public static final field STACK_KEY Ljava/lang/String; public fun ()V public static fun convertJavaStackTrace (Ljava/lang/Throwable;)[Lcom/facebook/react/devsupport/interfaces/StackFrame; public static fun convertJsStackTrace (Lcom/facebook/react/bridge/ReadableArray;)[Lcom/facebook/react/devsupport/interfaces/StackFrame; public static fun convertJsStackTrace (Ljava/lang/String;)[Lcom/facebook/react/devsupport/interfaces/StackFrame; public static fun convertJsStackTrace (Lorg/json/JSONArray;)[Lcom/facebook/react/devsupport/interfaces/StackFrame; + public static fun convertParsedError (Lcom/facebook/react/interfaces/exceptionmanager/ReactJsExceptionHandler$ParsedError;)Lcom/facebook/react/bridge/JavaOnlyMap; public static fun formatFrameSource (Lcom/facebook/react/devsupport/interfaces/StackFrame;)Ljava/lang/String; public static fun formatStackTrace (Ljava/lang/String;[Lcom/facebook/react/devsupport/interfaces/StackFrame;)Ljava/lang/String; } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/StackTraceHelper.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/StackTraceHelper.java index a40b0c12095..70b779a8dc8 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/StackTraceHelper.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/StackTraceHelper.java @@ -8,12 +8,17 @@ package com.facebook.react.devsupport; import androidx.annotation.Nullable; +import com.facebook.react.bridge.JavaOnlyArray; +import com.facebook.react.bridge.JavaOnlyMap; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.ReadableType; import com.facebook.react.common.MapBuilder; import com.facebook.react.devsupport.interfaces.StackFrame; +import com.facebook.react.interfaces.exceptionmanager.ReactJsExceptionHandler.ParsedError; import java.io.File; +import java.util.ArrayList; +import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.json.JSONArray; @@ -23,8 +28,15 @@ import org.json.JSONObject; /** Helper class converting JS and Java stack traces into arrays of {@link StackFrame} objects. */ public class StackTraceHelper { - public static final java.lang.String COLUMN_KEY = "column"; - public static final java.lang.String LINE_NUMBER_KEY = "lineNumber"; + public static final String COLUMN_KEY = "column"; + public static final String LINE_NUMBER_KEY = "lineNumber"; + public static final String FILE_KEY = "file"; + public static final String METHOD_NAME_KEY = "methodName"; + + public static final String MESSAGE_KEY = "message"; + public static final String STACK_KEY = "stack"; + public static final String ID_KEY = "id"; + public static final String IS_FATAL_KEY = "isFatal"; private static final Pattern STACK_FRAME_PATTERN1 = Pattern.compile("^(?:(.*?)@)?(.*?)\\:([0-9]+)\\:([0-9]+)$"); @@ -246,4 +258,25 @@ public class StackTraceHelper { return stackTrace.toString(); } + + public static JavaOnlyMap convertParsedError(ParsedError error) { + List frames = error.getFrames(); + List readableMapList = new ArrayList<>(); + for (ParsedError.StackFrame frame : frames) { + JavaOnlyMap map = new JavaOnlyMap(); + map.putDouble(COLUMN_KEY, frame.getColumnNumber()); + map.putDouble(LINE_NUMBER_KEY, frame.getLineNumber()); + map.putString(FILE_KEY, (String) frame.getFileName()); + map.putString(METHOD_NAME_KEY, (String) frame.getMethodName()); + readableMapList.add(map); + } + + JavaOnlyMap data = new JavaOnlyMap(); + data.putString(MESSAGE_KEY, error.getMessage()); + data.putArray(STACK_KEY, JavaOnlyArray.from(readableMapList)); + data.putInt(ID_KEY, error.getExceptionId()); + data.putBoolean(IS_FATAL_KEY, error.isFatal()); + + return data; + } } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactInstance.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactInstance.java index 723e8ed039d..beb6bbcdab5 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactInstance.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactInstance.java @@ -7,11 +7,6 @@ package com.facebook.react.runtime; -import static com.facebook.react.util.JSStackTrace.COLUMN_KEY; -import static com.facebook.react.util.JSStackTrace.FILE_KEY; -import static com.facebook.react.util.JSStackTrace.LINE_NUMBER_KEY; -import static com.facebook.react.util.JSStackTrace.METHOD_NAME_KEY; - import android.content.res.AssetManager; import android.view.View; import com.facebook.common.logging.FLog; @@ -28,7 +23,6 @@ import com.facebook.react.ViewManagerOnDemandReactPackage; import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.JSBundleLoader; import com.facebook.react.bridge.JSBundleLoaderDelegate; -import com.facebook.react.bridge.JavaOnlyArray; import com.facebook.react.bridge.JavaOnlyMap; import com.facebook.react.bridge.JavaScriptContextHolder; import com.facebook.react.bridge.NativeArray; @@ -36,7 +30,6 @@ import com.facebook.react.bridge.NativeMap; import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.ReactNoCrashSoftException; import com.facebook.react.bridge.ReactSoftExceptionLogger; -import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.RuntimeExecutor; import com.facebook.react.bridge.RuntimeScheduler; import com.facebook.react.bridge.queue.MessageQueueThread; @@ -45,6 +38,7 @@ import com.facebook.react.bridge.queue.QueueThreadExceptionHandler; import com.facebook.react.bridge.queue.ReactQueueConfiguration; import com.facebook.react.bridge.queue.ReactQueueConfigurationImpl; import com.facebook.react.bridge.queue.ReactQueueConfigurationSpec; +import com.facebook.react.devsupport.StackTraceHelper; import com.facebook.react.devsupport.interfaces.DevSupportManager; import com.facebook.react.fabric.Binding; import com.facebook.react.fabric.BindingImpl; @@ -332,22 +326,7 @@ final class ReactInstance { @Override public void reportJsException(ParsedError error) { - List frames = error.getFrames(); - List readableMapList = new ArrayList<>(); - for (ReactJsExceptionHandler.ParsedError.StackFrame frame : frames) { - JavaOnlyMap map = new JavaOnlyMap(); - map.putDouble(COLUMN_KEY, frame.getColumnNumber()); - map.putDouble(LINE_NUMBER_KEY, frame.getLineNumber()); - map.putString(FILE_KEY, (String) frame.getFileName()); - map.putString(METHOD_NAME_KEY, (String) frame.getMethodName()); - readableMapList.add(map); - } - - JavaOnlyMap data = new JavaOnlyMap(); - data.putString("message", error.getMessage()); - data.putArray("stack", JavaOnlyArray.from(readableMapList)); - data.putInt("id", error.getExceptionId()); - data.putBoolean("isFatal", error.isFatal()); + JavaOnlyMap data = StackTraceHelper.convertParsedError(error); // Simulate async native module method call mNativemodulesmessagequeuethread.runOnQueue( diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/devsupport/StackTraceHelperTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/devsupport/StackTraceHelperTest.kt index 8a0c3d8d869..5e19da6b688 100644 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/devsupport/StackTraceHelperTest.kt +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/devsupport/StackTraceHelperTest.kt @@ -7,11 +7,15 @@ package com.facebook.react.devsupport +import com.facebook.react.bridge.ReadableMap +import com.facebook.react.common.annotations.UnstableReactNativeAPI +import com.facebook.react.interfaces.exceptionmanager.ReactJsExceptionHandler.* import org.assertj.core.api.Assertions.assertThat import org.junit.Test import org.junit.runner.RunWith import org.robolectric.RobolectricTestRunner +@OptIn(UnstableReactNativeAPI::class) @RunWith(RobolectricTestRunner::class) class StackTraceHelperTest { @Test @@ -58,4 +62,63 @@ class StackTraceHelperTest { assertThat(frame.line).isEqualTo(-1) assertThat(frame.column).isEqualTo(-1) } + + @Test + fun testConvertParsedError() { + val error = getParsedErrorTestData() + + val data = StackTraceHelper.convertParsedError(error) + assertThat(data.getString("message")).isEqualTo("error message") + assertThat(data.getInt("id")).isEqualTo(123) + assertThat(data.getBoolean("isFatal")).isEqualTo(true) + + val stack = data.getArray("stack") + assertThat(stack).isNotNull() + stack?.let { + assertThat(stack.size()).isEqualTo(2) + assertStackFrameMap(stack.getMap(0), "file1", "method1", 1, 10) + assertStackFrameMap(stack.getMap(1), "file2", "method2", 2, 20) + } + } + + private fun assertStackFrameMap( + map: ReadableMap, + filename: String, + methodName: String, + lineNumber: Int, + columnNumber: Int + ) { + + assertThat(map.getString("file")).isEqualTo(filename) + assertThat(map.getString("methodName")).isEqualTo(methodName) + assertThat(map.getDouble("lineNumber").toInt()).isEqualTo(lineNumber) + assertThat(map.getDouble("column").toInt()).isEqualTo(columnNumber) + } + + private fun getParsedErrorTestData(): ParsedError { + val frame1 = + object : ParsedError.StackFrame { + override val fileName = "file1" + override val methodName = "method1" + override val lineNumber = 1 + override val columnNumber = 10 + } + + val frame2 = + object : ParsedError.StackFrame { + override val fileName = "file2" + override val methodName = "method2" + override val lineNumber = 2 + override val columnNumber = 20 + } + + val frames = listOf(frame1, frame2) + + return object : ParsedError { + override val frames = frames + override val message = "error message" + override val exceptionId = 123 + override val isFatal = true + } + } }