mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
easy: Align earlyjs c++ native data structures with js (#46895)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/46895 For the js error handling pipeline, the javascript data structure looks like [this](https://www.internalfb.com/code/fbsource/[6181b57f4ba3619f58056bcec65382650d6ff59a]/xplat/js/react-native-github/packages/react-native/src/private/specs/modules/NativeExceptionsManager.js?lines=17-35): ``` export type StackFrame = {| column: ?number, file: ?string, lineNumber: ?number, methodName: string, collapse?: boolean, |}; export type ExceptionData = { message: string, originalMessage: ?string, name: ?string, componentStack: ?string, stack: Array<StackFrame>, id: number, isFatal: boolean, // flowlint-next-line unclear-type:off extraData?: Object, ... }; ``` So, I made the c++ data structure look similar ``` struct ParsedError { struct StackFrame { std::optional<std::string> file; std::string methodName; std::optional<int> lineNumber; std::optional<int> column; }; std::string message; std::optional<std::string> originalMessage; std::optional<std::string> name; std::optional<std::string> componentStack; std::vector<StackFrame> stack; int id; bool isFatal; jsi::Object extraData; }; ``` Notes: * [parseErrorStack](https://fburl.com/code/e27q9gkc) doesn't actually generate a collapse property on the error object. So, I omitted it from the c++. * ExceptionsManager [always provides an extraData field](https://fburl.com/code/2bvcsxac). So, I made it required. * In C++, I just stored extraData as a jsi::Object. I wanted the freedom to store arbitrary key/value pairs. But, I also didn't want to use folly::dynamic. Changelog: [Internal] Reviewed By: alanleedev Differential Revision: D63929580 fbshipit-source-id: 51d8fcc79c9383789d456cfe4527cdd3f579395f
This commit is contained in:
committed by
Facebook GitHub Bot
parent
ff698f542e
commit
e4645d033a
@@ -181,8 +181,12 @@
|
||||
- (void)host:(RCTHost *)host
|
||||
didReceiveJSErrorStack:(NSArray<NSDictionary<NSString *, id> *> *)stack
|
||||
message:(NSString *)message
|
||||
originalMessage:(NSString *_Nullable)originalMessage
|
||||
name:(NSString *_Nullable)name
|
||||
componentStack:(NSString *_Nullable)componentStack
|
||||
exceptionId:(NSUInteger)exceptionId
|
||||
isFatal:(BOOL)isFatal
|
||||
extraData:(NSDictionary<NSString *, id> *)extraData
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -2395,12 +2395,16 @@ 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 COMPONENT_STACK_KEY Ljava/lang/String;
|
||||
public static final field EXTRA_DATA_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 NAME_KEY Ljava/lang/String;
|
||||
public static final field ORIGINAL_MESSAGE_KEY Ljava/lang/String;
|
||||
public static final field STACK_KEY Ljava/lang/String;
|
||||
public fun <init> ()V
|
||||
public static fun convertJavaStackTrace (Ljava/lang/Throwable;)[Lcom/facebook/react/devsupport/interfaces/StackFrame;
|
||||
@@ -2892,16 +2896,20 @@ public abstract interface class com/facebook/react/interfaces/TaskInterface {
|
||||
}
|
||||
|
||||
public abstract interface class com/facebook/react/interfaces/exceptionmanager/ReactJsExceptionHandler$ParsedError {
|
||||
public abstract fun getExceptionId ()I
|
||||
public abstract fun getFrames ()Ljava/util/List;
|
||||
public abstract fun getComponentStack ()Ljava/lang/String;
|
||||
public abstract fun getExtraData ()Lcom/facebook/react/bridge/ReadableMap;
|
||||
public abstract fun getId ()I
|
||||
public abstract fun getMessage ()Ljava/lang/String;
|
||||
public abstract fun getName ()Ljava/lang/String;
|
||||
public abstract fun getOriginalMessage ()Ljava/lang/String;
|
||||
public abstract fun getStack ()Ljava/util/List;
|
||||
public abstract fun isFatal ()Z
|
||||
}
|
||||
|
||||
public abstract interface class com/facebook/react/interfaces/exceptionmanager/ReactJsExceptionHandler$ParsedError$StackFrame {
|
||||
public abstract fun getColumnNumber ()I
|
||||
public abstract fun getFileName ()Ljava/lang/String;
|
||||
public abstract fun getLineNumber ()I
|
||||
public abstract fun getColumn ()Ljava/lang/Integer;
|
||||
public abstract fun getFile ()Ljava/lang/String;
|
||||
public abstract fun getLineNumber ()Ljava/lang/Integer;
|
||||
public abstract fun getMethodName ()Ljava/lang/String;
|
||||
}
|
||||
|
||||
|
||||
+18
-4
@@ -34,9 +34,13 @@ public class StackTraceHelper {
|
||||
public static final String METHOD_NAME_KEY = "methodName";
|
||||
|
||||
public static final String MESSAGE_KEY = "message";
|
||||
public static final String ORIGINAL_MESSAGE_KEY = "originalMessage";
|
||||
public static final String NAME_KEY = "name";
|
||||
public static final String COMPONENT_STACK_KEY = "componentStack";
|
||||
public static final String STACK_KEY = "stack";
|
||||
public static final String ID_KEY = "id";
|
||||
public static final String IS_FATAL_KEY = "isFatal";
|
||||
public static final String EXTRA_DATA_KEY = "extraData";
|
||||
|
||||
private static final Pattern STACK_FRAME_PATTERN1 =
|
||||
Pattern.compile("^(?:(.*?)@)?(.*?)\\:([0-9]+)\\:([0-9]+)$");
|
||||
@@ -260,22 +264,32 @@ public class StackTraceHelper {
|
||||
}
|
||||
|
||||
public static JavaOnlyMap convertParsedError(ParsedError error) {
|
||||
List<ParsedError.StackFrame> frames = error.getFrames();
|
||||
List<ParsedError.StackFrame> frames = error.getStack();
|
||||
List<ReadableMap> readableMapList = new ArrayList<>();
|
||||
for (ParsedError.StackFrame frame : frames) {
|
||||
JavaOnlyMap map = new JavaOnlyMap();
|
||||
map.putDouble(COLUMN_KEY, frame.getColumnNumber());
|
||||
map.putDouble(COLUMN_KEY, frame.getColumn());
|
||||
map.putDouble(LINE_NUMBER_KEY, frame.getLineNumber());
|
||||
map.putString(FILE_KEY, (String) frame.getFileName());
|
||||
map.putString(FILE_KEY, (String) frame.getFile());
|
||||
map.putString(METHOD_NAME_KEY, (String) frame.getMethodName());
|
||||
readableMapList.add(map);
|
||||
}
|
||||
|
||||
JavaOnlyMap data = new JavaOnlyMap();
|
||||
data.putString(MESSAGE_KEY, error.getMessage());
|
||||
if (error.getOriginalMessage() != null) {
|
||||
data.putString(ORIGINAL_MESSAGE_KEY, error.getOriginalMessage());
|
||||
}
|
||||
if (error.getName() != null) {
|
||||
data.putString(NAME_KEY, error.getName());
|
||||
}
|
||||
if (error.getComponentStack() != null) {
|
||||
data.putString(COMPONENT_STACK_KEY, error.getComponentStack());
|
||||
}
|
||||
data.putArray(STACK_KEY, JavaOnlyArray.from(readableMapList));
|
||||
data.putInt(ID_KEY, error.getExceptionId());
|
||||
data.putInt(ID_KEY, error.getId());
|
||||
data.putBoolean(IS_FATAL_KEY, error.isFatal());
|
||||
data.putMap(EXTRA_DATA_KEY, error.getExtraData());
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
+20
-10
@@ -8,6 +8,8 @@
|
||||
package com.facebook.react.interfaces.exceptionmanager
|
||||
|
||||
import com.facebook.proguard.annotations.DoNotStripAny
|
||||
import com.facebook.react.bridge.ReadableMap
|
||||
import com.facebook.react.bridge.ReadableNativeMap
|
||||
import com.facebook.react.common.annotations.UnstableReactNativeAPI
|
||||
import java.util.ArrayList
|
||||
|
||||
@@ -18,32 +20,40 @@ public fun interface ReactJsExceptionHandler {
|
||||
public interface ParsedError {
|
||||
@DoNotStripAny
|
||||
public interface StackFrame {
|
||||
public val fileName: String
|
||||
public val file: String?
|
||||
public val methodName: String
|
||||
public val lineNumber: Int
|
||||
public val columnNumber: Int
|
||||
public val lineNumber: Int?
|
||||
public val column: Int?
|
||||
}
|
||||
|
||||
public val frames: List<StackFrame>
|
||||
public val message: String
|
||||
public val exceptionId: Int
|
||||
public val originalMessage: String?
|
||||
public val name: String?
|
||||
public val componentStack: String?
|
||||
public val stack: List<StackFrame>
|
||||
public val id: Int
|
||||
public val isFatal: Boolean
|
||||
public val extraData: ReadableMap
|
||||
}
|
||||
|
||||
@DoNotStripAny
|
||||
private data class ParsedStackFrameImpl(
|
||||
override val fileName: String,
|
||||
override val file: String?,
|
||||
override val methodName: String,
|
||||
override val lineNumber: Int,
|
||||
override val columnNumber: Int,
|
||||
override val lineNumber: Int?,
|
||||
override val column: Int?,
|
||||
) : ParsedError.StackFrame
|
||||
|
||||
@DoNotStripAny
|
||||
private data class ParsedErrorImpl(
|
||||
override val frames: ArrayList<ParsedStackFrameImpl>,
|
||||
override val message: String,
|
||||
override val exceptionId: Int,
|
||||
override val originalMessage: String?,
|
||||
override val name: String?,
|
||||
override val componentStack: String?,
|
||||
override val stack: ArrayList<ParsedStackFrameImpl>,
|
||||
override val id: Int,
|
||||
override val isFatal: Boolean,
|
||||
override val extraData: ReadableNativeMap,
|
||||
) : ParsedError
|
||||
|
||||
public fun reportJsException(errorMap: ParsedError)
|
||||
|
||||
+29
-8
@@ -9,6 +9,9 @@
|
||||
#include <fbjni/fbjni.h>
|
||||
#include <glog/logging.h>
|
||||
#include <jni.h>
|
||||
#include <jsi/JSIDynamic.h>
|
||||
#include <jsi/jsi.h>
|
||||
#include <react/jni/ReadableNativeMap.h>
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
@@ -28,7 +31,10 @@ class ParsedStackFrameImpl
|
||||
static facebook::jni::local_ref<ParsedStackFrameImpl> create(
|
||||
const JsErrorHandler::ParsedError::StackFrame& frame) {
|
||||
return newInstance(
|
||||
frame.fileName, frame.methodName, frame.lineNumber, frame.columnNumber);
|
||||
frame.file ? jni::make_jstring(*frame.file) : nullptr,
|
||||
frame.methodName,
|
||||
frame.lineNumber ? jni::JInteger::valueOf(*frame.lineNumber) : nullptr,
|
||||
frame.column ? jni::JInteger::valueOf(*frame.column) : nullptr);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -39,27 +45,42 @@ class ParsedErrorImpl
|
||||
"Lcom/facebook/react/interfaces/exceptionmanager/ReactJsExceptionHandler$ParsedErrorImpl;";
|
||||
|
||||
static facebook::jni::local_ref<ParsedErrorImpl> create(
|
||||
jsi::Runtime& runtime,
|
||||
const JsErrorHandler::ParsedError& error) {
|
||||
auto stackFrames =
|
||||
facebook::jni::JArrayList<ParsedStackFrameImpl>::create();
|
||||
for (const auto& frame : error.frames) {
|
||||
stackFrames->add(ParsedStackFrameImpl::create(frame));
|
||||
auto stack = facebook::jni::JArrayList<ParsedStackFrameImpl>::create();
|
||||
for (const auto& frame : error.stack) {
|
||||
stack->add(ParsedStackFrameImpl::create(frame));
|
||||
}
|
||||
|
||||
auto extraDataDynamic =
|
||||
jsi::dynamicFromValue(runtime, jsi::Value(runtime, error.extraData));
|
||||
|
||||
auto extraData =
|
||||
ReadableNativeMap::createWithContents(std::move(extraDataDynamic));
|
||||
|
||||
return newInstance(
|
||||
stackFrames, error.message, error.exceptionId, error.isFatal);
|
||||
error.message,
|
||||
error.originalMessage ? jni::make_jstring(*error.originalMessage)
|
||||
: nullptr,
|
||||
error.name ? jni::make_jstring(*error.name) : nullptr,
|
||||
error.componentStack ? jni::make_jstring(*error.componentStack)
|
||||
: nullptr,
|
||||
stack,
|
||||
error.id,
|
||||
error.isFatal,
|
||||
extraData);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
void JReactExceptionManager::reportJsException(
|
||||
jsi::Runtime& runtime,
|
||||
const JsErrorHandler::ParsedError& error) {
|
||||
static const auto method =
|
||||
javaClassStatic()->getMethod<void(jni::alias_ref<ParsedError>)>(
|
||||
"reportJsException");
|
||||
if (self() != nullptr) {
|
||||
method(self(), ParsedErrorImpl::create(error));
|
||||
method(self(), ParsedErrorImpl::create(runtime, error));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-1
@@ -19,7 +19,9 @@ class JReactExceptionManager
|
||||
static auto constexpr kJavaDescriptor =
|
||||
"Lcom/facebook/react/interfaces/exceptionmanager/ReactJsExceptionHandler;";
|
||||
|
||||
void reportJsException(const JsErrorHandler::ParsedError& error);
|
||||
void reportJsException(
|
||||
jsi::Runtime& runtime,
|
||||
const JsErrorHandler::ParsedError& error);
|
||||
};
|
||||
|
||||
} // namespace facebook::react
|
||||
|
||||
+2
-1
@@ -52,10 +52,11 @@ JReactInstance::JReactInstance(
|
||||
jReactExceptionManager_ = jni::make_global(jReactExceptionManager);
|
||||
auto onJsError =
|
||||
[weakJReactExceptionManager = jni::make_weak(jReactExceptionManager)](
|
||||
jsi::Runtime& runtime,
|
||||
const JsErrorHandler::ParsedError& error) mutable noexcept {
|
||||
if (auto jReactExceptionManager =
|
||||
weakJReactExceptionManager.lockLocal()) {
|
||||
jReactExceptionManager->reportJsException(error);
|
||||
jReactExceptionManager->reportJsException(runtime, error);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+11
-6
@@ -7,6 +7,7 @@
|
||||
|
||||
package com.facebook.react.devsupport
|
||||
|
||||
import com.facebook.react.bridge.JavaOnlyMap
|
||||
import com.facebook.react.bridge.ReadableMap
|
||||
import com.facebook.react.common.annotations.UnstableReactNativeAPI
|
||||
import com.facebook.react.interfaces.exceptionmanager.ReactJsExceptionHandler.*
|
||||
@@ -98,27 +99,31 @@ class StackTraceHelperTest {
|
||||
private fun getParsedErrorTestData(): ParsedError {
|
||||
val frame1 =
|
||||
object : ParsedError.StackFrame {
|
||||
override val fileName = "file1"
|
||||
override val file = "file1"
|
||||
override val methodName = "method1"
|
||||
override val lineNumber = 1
|
||||
override val columnNumber = 10
|
||||
override val column = 10
|
||||
}
|
||||
|
||||
val frame2 =
|
||||
object : ParsedError.StackFrame {
|
||||
override val fileName = "file2"
|
||||
override val file = "file2"
|
||||
override val methodName = "method2"
|
||||
override val lineNumber = 2
|
||||
override val columnNumber = 20
|
||||
override val column = 20
|
||||
}
|
||||
|
||||
val frames = listOf(frame1, frame2)
|
||||
|
||||
return object : ParsedError {
|
||||
override val frames = frames
|
||||
override val message = "error message"
|
||||
override val exceptionId = 123
|
||||
override val originalMessage = null
|
||||
override val name = null
|
||||
override val componentStack = null
|
||||
override val stack = frames
|
||||
override val id = 123
|
||||
override val isFatal = true
|
||||
override val extraData = JavaOnlyMap()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,11 +13,64 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
std::string quote(const std::string& view) {
|
||||
return "\"" + view + "\"";
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
std::ostream& operator<<(
|
||||
std::ostream& os,
|
||||
const JsErrorHandler::ParsedError::StackFrame& frame) {
|
||||
auto file = frame.file ? quote(*frame.file) : "nil";
|
||||
auto methodName = quote(frame.methodName);
|
||||
auto lineNumber =
|
||||
frame.lineNumber ? std::to_string(*frame.lineNumber) : "nil";
|
||||
auto column = frame.column ? std::to_string(*frame.column) : "nil";
|
||||
|
||||
os << "StackFrame { .file = " << file << ", .methodName = " << methodName
|
||||
<< ", .lineNumber = " << lineNumber << ", .column = " << column << " }";
|
||||
return os;
|
||||
}
|
||||
std::ostream& operator<<(
|
||||
std::ostream& os,
|
||||
const JsErrorHandler::ParsedError& error) {
|
||||
auto message = quote(error.message);
|
||||
auto originalMessage =
|
||||
error.originalMessage ? quote(*error.originalMessage) : "nil";
|
||||
auto name = error.name ? quote(*error.name) : "nil";
|
||||
auto componentStack =
|
||||
error.componentStack ? quote(*error.componentStack) : "nil";
|
||||
auto id = std::to_string(error.id);
|
||||
auto isFatal = std::to_string(static_cast<int>(error.isFatal));
|
||||
auto extraData = "jsi::Object{ <omitted> } ";
|
||||
|
||||
os << "ParsedError {\n"
|
||||
<< " .message = " << message << "\n"
|
||||
<< " .originalMessage = " << originalMessage << "\n"
|
||||
<< " .name = " << name << "\n"
|
||||
<< " .componentStack = " << componentStack << "\n"
|
||||
<< " .stack = [\n";
|
||||
|
||||
for (const auto& frame : error.stack) {
|
||||
os << " " << frame << ", \n";
|
||||
}
|
||||
os << " ]\n"
|
||||
<< " .id = " << id << "\n"
|
||||
<< " .isFatal " << isFatal << "\n"
|
||||
<< " .extraData = " << extraData << "\n"
|
||||
<< "}";
|
||||
return os;
|
||||
}
|
||||
|
||||
// TODO(T198763073): Migrate away from std::regex in this function
|
||||
static JsErrorHandler::ParsedError
|
||||
parseErrorStack(const jsi::JSError& error, bool isFatal, bool isHermes) {
|
||||
static JsErrorHandler::ParsedError parseErrorStack(
|
||||
jsi::Runtime& runtime,
|
||||
const jsi::JSError& error,
|
||||
bool isFatal,
|
||||
bool isHermes) {
|
||||
/**
|
||||
* This parses the different stack traces and puts them into one format
|
||||
* This borrows heavily from TraceKit (https://github.com/occ/TraceKit)
|
||||
@@ -58,10 +111,10 @@ parseErrorStack(const jsi::JSError& error, bool isFatal, bool isHermes) {
|
||||
std::string str2 = std::string(searchResults[2]);
|
||||
if (str2.compare("native")) {
|
||||
frames.push_back({
|
||||
.fileName = std::string(searchResults[4]),
|
||||
.file = std::string(searchResults[4]),
|
||||
.methodName = std::string(searchResults[1]),
|
||||
.lineNumber = std::stoi(searchResults[5]),
|
||||
.columnNumber = std::stoi(searchResults[6]),
|
||||
.column = std::stoi(searchResults[6]),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -69,10 +122,10 @@ parseErrorStack(const jsi::JSError& error, bool isFatal, bool isHermes) {
|
||||
// @lint-ignore CLANGTIDY facebook-hte-StdRegexIsAwful
|
||||
if (std::regex_search(line, searchResults, REGEX_GECKO)) {
|
||||
frames.push_back({
|
||||
.fileName = std::string(searchResults[3]),
|
||||
.file = std::string(searchResults[3]),
|
||||
.methodName = std::string(searchResults[1]),
|
||||
.lineNumber = std::stoi(searchResults[4]),
|
||||
.columnNumber = std::stoi(searchResults[5]),
|
||||
.column = std::stoi(searchResults[5]),
|
||||
});
|
||||
} else if (
|
||||
// @lint-ignore CLANGTIDY facebook-hte-StdRegexIsAwful
|
||||
@@ -80,10 +133,10 @@ parseErrorStack(const jsi::JSError& error, bool isFatal, bool isHermes) {
|
||||
// @lint-ignore CLANGTIDY facebook-hte-StdRegexIsAwful
|
||||
std::regex_search(line, searchResults, REGEX_NODE)) {
|
||||
frames.push_back({
|
||||
.fileName = std::string(searchResults[2]),
|
||||
.file = std::string(searchResults[2]),
|
||||
.methodName = std::string(searchResults[1]),
|
||||
.lineNumber = std::stoi(searchResults[3]),
|
||||
.columnNumber = std::stoi(searchResults[4]),
|
||||
.column = std::stoi(searchResults[4]),
|
||||
});
|
||||
} else {
|
||||
continue;
|
||||
@@ -92,10 +145,14 @@ parseErrorStack(const jsi::JSError& error, bool isFatal, bool isHermes) {
|
||||
}
|
||||
|
||||
return {
|
||||
.frames = std::move(frames),
|
||||
.message = "EarlyJsError: " + error.getMessage(),
|
||||
.exceptionId = 0,
|
||||
.originalMessage = std::nullopt,
|
||||
.name = std::nullopt,
|
||||
.componentStack = std::nullopt,
|
||||
.stack = std::move(frames),
|
||||
.id = 0,
|
||||
.isFatal = isFatal,
|
||||
.extraData = jsi::Object(runtime),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -127,8 +184,8 @@ void JsErrorHandler::handleFatalError(
|
||||
}
|
||||
}
|
||||
// This is a hacky way to get Hermes stack trace.
|
||||
ParsedError parsedError = parseErrorStack(error, true, false);
|
||||
_onJsError(parsedError);
|
||||
ParsedError parsedError = parseErrorStack(runtime, error, true, false);
|
||||
_onJsError(runtime, parsedError);
|
||||
}
|
||||
|
||||
bool JsErrorHandler::hasHandledFatalError() {
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <jsi/jsi.h>
|
||||
#include <iostream>
|
||||
#include <optional>
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
@@ -15,19 +17,28 @@ class JsErrorHandler {
|
||||
public:
|
||||
struct ParsedError {
|
||||
struct StackFrame {
|
||||
std::string fileName;
|
||||
std::optional<std::string> file;
|
||||
std::string methodName;
|
||||
int lineNumber;
|
||||
int columnNumber;
|
||||
std::optional<int> lineNumber;
|
||||
std::optional<int> column;
|
||||
friend std::ostream& operator<<(
|
||||
std::ostream& os,
|
||||
const StackFrame& frame);
|
||||
};
|
||||
|
||||
std::vector<StackFrame> frames;
|
||||
std::string message;
|
||||
int exceptionId;
|
||||
std::optional<std::string> originalMessage;
|
||||
std::optional<std::string> name;
|
||||
std::optional<std::string> componentStack;
|
||||
std::vector<StackFrame> stack;
|
||||
int id;
|
||||
bool isFatal;
|
||||
jsi::Object extraData;
|
||||
friend std::ostream& operator<<(std::ostream& os, const ParsedError& error);
|
||||
};
|
||||
|
||||
using OnJsError = std::function<void(const ParsedError& error)>;
|
||||
using OnJsError =
|
||||
std::function<void(jsi::Runtime& runtime, const ParsedError& error)>;
|
||||
|
||||
explicit JsErrorHandler(OnJsError onJsError);
|
||||
~JsErrorHandler();
|
||||
|
||||
+3
-12
@@ -34,19 +34,10 @@ void ReactInstanceIntegrationTest::SetUp() {
|
||||
auto timerManager =
|
||||
std::make_shared<react::TimerManager>(std::move(mockRegistry));
|
||||
|
||||
auto onJsError = [](const JsErrorHandler::ParsedError& errorMap) noexcept {
|
||||
auto onJsError = [](jsi::Runtime& /*runtime*/,
|
||||
const JsErrorHandler::ParsedError& error) noexcept {
|
||||
LOG(INFO) << "[jsErrorHandlingFunc called]";
|
||||
LOG(INFO) << "message: " << errorMap.message;
|
||||
LOG(INFO) << "exceptionId: " << std::to_string(errorMap.exceptionId);
|
||||
LOG(INFO) << "isFatal: "
|
||||
<< std::to_string(static_cast<int>(errorMap.isFatal));
|
||||
auto frames = errorMap.frames;
|
||||
for (const auto& mapBuffer : frames) {
|
||||
LOG(INFO) << "[Frame]" << std::endl << "\tfile: " << mapBuffer.fileName;
|
||||
LOG(INFO) << "\tmethodName: " << mapBuffer.methodName;
|
||||
LOG(INFO) << "\tlineNumber: " << std::to_string(mapBuffer.lineNumber);
|
||||
LOG(INFO) << "\tcolumn: " << std::to_string(mapBuffer.columnNumber);
|
||||
}
|
||||
LOG(INFO) << error << std::endl;
|
||||
};
|
||||
|
||||
auto jsRuntimeFactory = std::make_unique<react::HermesInstance>();
|
||||
|
||||
@@ -135,11 +135,29 @@ static ShimRCTInstance *shimmedRCTInstance;
|
||||
stackFrame0[@"file"] = @"file2.js";
|
||||
[stack addObject:stackFrame1];
|
||||
|
||||
[instanceDelegate instance:[OCMArg any] didReceiveJSErrorStack:stack message:@"message" exceptionId:5 isFatal:YES];
|
||||
id extraData = [NSDictionary dictionary];
|
||||
|
||||
[instanceDelegate instance:[OCMArg any]
|
||||
didReceiveJSErrorStack:stack
|
||||
message:@"message"
|
||||
originalMessage:nil
|
||||
name:nil
|
||||
componentStack:nil
|
||||
exceptionId:5
|
||||
isFatal:YES
|
||||
extraData:extraData];
|
||||
|
||||
OCMVerify(
|
||||
OCMTimes(1),
|
||||
[_mockHostDelegate host:_subject didReceiveJSErrorStack:stack message:@"message" exceptionId:5 isFatal:YES]);
|
||||
[_mockHostDelegate host:_subject
|
||||
didReceiveJSErrorStack:stack
|
||||
message:@"message"
|
||||
originalMessage:nil
|
||||
name:nil
|
||||
componentStack:nil
|
||||
exceptionId:5
|
||||
isFatal:YES
|
||||
extraData:extraData]);
|
||||
}
|
||||
|
||||
- (void)testDidInitializeRuntime
|
||||
|
||||
@@ -30,8 +30,12 @@ typedef NSURL *_Nullable (^RCTHostBundleURLProvider)(void);
|
||||
- (void)host:(RCTHost *)host
|
||||
didReceiveJSErrorStack:(NSArray<NSDictionary<NSString *, id> *> *)stack
|
||||
message:(NSString *)message
|
||||
originalMessage:(NSString *_Nullable)originalMessage
|
||||
name:(NSString *_Nullable)name
|
||||
componentStack:(NSString *_Nullable)componentStack
|
||||
exceptionId:(NSUInteger)exceptionId
|
||||
isFatal:(BOOL)isFatal;
|
||||
isFatal:(BOOL)isFatal
|
||||
extraData:(NSDictionary<NSString *, id> *)extraData;
|
||||
|
||||
- (void)hostDidStart:(RCTHost *)host;
|
||||
|
||||
|
||||
+13
-1
@@ -318,10 +318,22 @@ class RCTHostHostTargetDelegate : public facebook::react::jsinspector_modern::Ho
|
||||
- (void)instance:(RCTInstance *)instance
|
||||
didReceiveJSErrorStack:(NSArray<NSDictionary<NSString *, id> *> *)stack
|
||||
message:(NSString *)message
|
||||
originalMessage:(NSString *_Nullable)originalMessage
|
||||
name:(NSString *_Nullable)name
|
||||
componentStack:(NSString *_Nullable)componentStack
|
||||
exceptionId:(NSUInteger)exceptionId
|
||||
isFatal:(BOOL)isFatal
|
||||
extraData:(NSDictionary<NSString *, id> *)extraData
|
||||
{
|
||||
[_hostDelegate host:self didReceiveJSErrorStack:stack message:message exceptionId:exceptionId isFatal:isFatal];
|
||||
[_hostDelegate host:self
|
||||
didReceiveJSErrorStack:stack
|
||||
message:message
|
||||
originalMessage:originalMessage
|
||||
name:name
|
||||
componentStack:componentStack
|
||||
exceptionId:exceptionId
|
||||
isFatal:isFatal
|
||||
extraData:extraData];
|
||||
}
|
||||
|
||||
- (void)instance:(RCTInstance *)instance didInitializeRuntime:(facebook::jsi::Runtime &)runtime
|
||||
|
||||
+5
-1
@@ -40,8 +40,12 @@ RCT_EXTERN void RCTInstanceSetRuntimeDiagnosticFlags(NSString *_Nullable flags);
|
||||
- (void)instance:(RCTInstance *)instance
|
||||
didReceiveJSErrorStack:(NSArray<NSDictionary<NSString *, id> *> *)stack
|
||||
message:(NSString *)message
|
||||
originalMessage:(NSString *_Nullable)originalMessage
|
||||
name:(NSString *_Nullable)name
|
||||
componentStack:(NSString *_Nullable)componentStack
|
||||
exceptionId:(NSUInteger)exceptionId
|
||||
isFatal:(BOOL)isFatal;
|
||||
isFatal:(BOOL)isFatal
|
||||
extraData:(NSDictionary<NSString *, id> *)extraData;
|
||||
|
||||
- (void)instance:(RCTInstance *)instance didInitializeRuntime:(facebook::jsi::Runtime &)runtime;
|
||||
|
||||
|
||||
+24
-10
@@ -31,6 +31,7 @@
|
||||
#import <React/RCTPerformanceLogger.h>
|
||||
#import <React/RCTRedBox.h>
|
||||
#import <React/RCTSurfacePresenter.h>
|
||||
#import <ReactCommon/RCTTurboModule.h>
|
||||
#import <ReactCommon/RCTTurboModuleManager.h>
|
||||
#import <ReactCommon/RuntimeExecutor.h>
|
||||
#import <cxxreact/ReactMarker.h>
|
||||
@@ -220,7 +221,9 @@ void RCTInstanceSetRuntimeDiagnosticFlags(NSString *flags)
|
||||
objCTimerRegistryRawPtr->setTimerManager(timerManager);
|
||||
|
||||
__weak __typeof(self) weakSelf = self;
|
||||
auto onJsError = [=](const JsErrorHandler::ParsedError &error) { [weakSelf _handleJSError:error]; };
|
||||
auto onJsError = [=](jsi::Runtime &runtime, const JsErrorHandler::ParsedError &error) {
|
||||
[weakSelf _handleJSError:error withRuntime:runtime];
|
||||
};
|
||||
|
||||
// Create the React Instance
|
||||
_reactInstance = std::make_unique<ReactInstance>(
|
||||
@@ -462,23 +465,34 @@ void RCTInstanceSetRuntimeDiagnosticFlags(NSString *flags)
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:@"RCTInstanceDidLoadBundle" object:nil];
|
||||
}
|
||||
|
||||
- (void)_handleJSError:(const JsErrorHandler::ParsedError &)error
|
||||
- (void)_handleJSError:(const JsErrorHandler::ParsedError &)error withRuntime:(jsi::Runtime &)runtime
|
||||
{
|
||||
NSString *message = [NSString stringWithCString:error.message.c_str() encoding:[NSString defaultCStringEncoding]];
|
||||
NSString *message = @(error.message.c_str());
|
||||
NSMutableArray<NSDictionary<NSString *, id> *> *stack = [NSMutableArray new];
|
||||
for (const JsErrorHandler::ParsedError::StackFrame &frame : error.frames) {
|
||||
for (const JsErrorHandler::ParsedError::StackFrame &frame : error.stack) {
|
||||
[stack addObject:@{
|
||||
@"file" : [NSString stringWithCString:frame.fileName.c_str() encoding:[NSString defaultCStringEncoding]],
|
||||
@"methodName" : [NSString stringWithCString:frame.methodName.c_str() encoding:[NSString defaultCStringEncoding]],
|
||||
@"lineNumber" : [NSNumber numberWithInt:frame.lineNumber],
|
||||
@"column" : [NSNumber numberWithInt:frame.columnNumber],
|
||||
@"file" : frame.file ? @((*frame.file).c_str()) : [NSNull null],
|
||||
@"methodName" : @(frame.methodName.c_str()),
|
||||
@"lineNumber" : frame.lineNumber ? @(*frame.lineNumber) : [NSNull null],
|
||||
@"column" : frame.column ? @(*frame.column) : [NSNull null],
|
||||
}];
|
||||
}
|
||||
|
||||
NSString *originalMessage = error.originalMessage ? @(error.originalMessage->c_str()) : nil;
|
||||
NSString *name = error.name ? @(error.name->c_str()) : nil;
|
||||
NSString *componentStack = error.componentStack ? @(error.componentStack->c_str()) : nil;
|
||||
id extraData =
|
||||
TurboModuleConvertUtils::convertJSIValueToObjCObject(runtime, jsi::Value(runtime, error.extraData), nullptr);
|
||||
|
||||
[_delegate instance:self
|
||||
didReceiveJSErrorStack:stack
|
||||
message:message
|
||||
exceptionId:error.exceptionId
|
||||
isFatal:error.isFatal];
|
||||
originalMessage:originalMessage
|
||||
name:name
|
||||
componentStack:componentStack
|
||||
exceptionId:error.id
|
||||
isFatal:error.isFatal
|
||||
extraData:extraData];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -124,7 +124,8 @@ class ReactInstanceTest : public ::testing::Test {
|
||||
auto mockRegistry = std::make_unique<MockTimerRegistry>();
|
||||
mockRegistry_ = mockRegistry.get();
|
||||
timerManager_ = std::make_shared<TimerManager>(std::move(mockRegistry));
|
||||
auto onJsError = [](const JsErrorHandler::ParsedError& errorMap) noexcept {
|
||||
auto onJsError = [](jsi::Runtime& /*runtime*/,
|
||||
const JsErrorHandler::ParsedError& /*error*/) noexcept {
|
||||
// Do nothing
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user