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:
Ramanpreet Nara
2024-10-10 16:03:02 -07:00
committed by Facebook GitHub Bot
parent ff698f542e
commit e4645d033a
17 changed files with 258 additions and 81 deletions
@@ -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;
}
@@ -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;
}
@@ -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)
@@ -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));
}
}
@@ -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
@@ -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);
}
};
@@ -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();
@@ -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;
@@ -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
@@ -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;
@@ -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
};