Report JSC errors as JS exceptions
Summary:When JSC throws an error on startup (e.g. a SyntaxError) or when invoking a method that is not caught by RCTExceptionsManager, we previously just reported is a native error, with a (useless) native stack trace in the redbox. This changes that behaviour to report a JS stacktrace. The same issue was previously reported here: https://github.com/facebook/react-native/pull/5677 Reviewed By: majak Differential Revision: D3037387 fb-gh-sync-id: 06f8333e0eb50dcef0b26284754262301b8a5f08 fbshipit-source-id: 06f8333e0eb50dcef0b26284754262301b8a5f08
This commit is contained in:
parent
ed930b4710
commit
5cdfe0f4b1
|
@ -23,6 +23,7 @@
|
|||
#import "RCTProfile.h"
|
||||
#import "RCTSourceCode.h"
|
||||
#import "RCTUtils.h"
|
||||
#import "RCTRedBox.h"
|
||||
|
||||
#define RCTAssertJSThread() \
|
||||
RCTAssert(![NSStringFromClass([_javaScriptExecutor class]) isEqualToString:@"RCTJSCExecutor"] || \
|
||||
|
@ -506,6 +507,10 @@ RCT_EXTERN NSArray<Class> *RCTGetModuleClasses(void);
|
|||
postNotificationName:RCTJavaScriptDidFailToLoadNotification
|
||||
object:_parentBridge userInfo:@{@"bridge": self, @"error": error}];
|
||||
|
||||
if ([error userInfo][RCTJSStackTraceKey]) {
|
||||
[self.redBox showErrorMessage:[error localizedDescription]
|
||||
withStack:[error userInfo][RCTJSStackTraceKey]];
|
||||
}
|
||||
RCTFatal(error);
|
||||
}
|
||||
|
||||
|
@ -776,21 +781,13 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithBundleURL:(__unused NSURL *)bundleUR
|
|||
{
|
||||
RCTAssertJSThread();
|
||||
|
||||
RCTJavaScriptCallback processResponse = ^(id json, NSError *error) {
|
||||
if (error) {
|
||||
RCTFatal(error);
|
||||
}
|
||||
|
||||
if (!_valid) {
|
||||
return;
|
||||
}
|
||||
[self handleBuffer:json batchEnded:YES];
|
||||
};
|
||||
|
||||
__weak typeof(self) weakSelf = self;
|
||||
[_javaScriptExecutor callFunctionOnModule:module
|
||||
method:method
|
||||
arguments:args
|
||||
callback:processResponse];
|
||||
callback:^(id json, NSError *error) {
|
||||
[weakSelf _processResponse:json error:error];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)_actuallyInvokeCallback:(NSNumber *)cbID
|
||||
|
@ -798,20 +795,28 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithBundleURL:(__unused NSURL *)bundleUR
|
|||
{
|
||||
RCTAssertJSThread();
|
||||
|
||||
RCTJavaScriptCallback processResponse = ^(id json, NSError *error) {
|
||||
if (error) {
|
||||
RCTFatal(error);
|
||||
}
|
||||
|
||||
if (!_valid) {
|
||||
return;
|
||||
}
|
||||
[self handleBuffer:json batchEnded:YES];
|
||||
};
|
||||
|
||||
__weak typeof(self) weakSelf = self;
|
||||
[_javaScriptExecutor invokeCallbackID:cbID
|
||||
arguments:args
|
||||
callback:processResponse];
|
||||
callback:^(id json, NSError *error) {
|
||||
[weakSelf _processResponse:json error:error];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)_processResponse:(id)json error:(NSError *)error
|
||||
{
|
||||
if (error) {
|
||||
if ([error userInfo][RCTJSStackTraceKey]) {
|
||||
[self.redBox showErrorMessage:[error localizedDescription]
|
||||
withStack:[error userInfo][RCTJSStackTraceKey]];
|
||||
}
|
||||
RCTFatal(error);
|
||||
}
|
||||
|
||||
if (!_valid) {
|
||||
return;
|
||||
}
|
||||
[self handleBuffer:json batchEnded:YES];
|
||||
}
|
||||
|
||||
#pragma mark - Payload Processing
|
||||
|
|
|
@ -140,9 +140,55 @@ static NSString *RCTJSValueToJSONString(JSContextRef context, JSValueRef value,
|
|||
|
||||
static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError)
|
||||
{
|
||||
NSString *errorMessage = jsError ? RCTJSValueToNSString(context, jsError, NULL) : @"Unknown JS error";
|
||||
NSString *details = jsError ? RCTJSValueToJSONString(context, jsError, NULL, 2) : @"No details";
|
||||
return [NSError errorWithDomain:@"JS" code:1 userInfo:@{NSLocalizedDescriptionKey: errorMessage, NSLocalizedFailureReasonErrorKey: details}];
|
||||
NSMutableDictionary *errorInfo = [NSMutableDictionary new];
|
||||
|
||||
NSString *description = jsError ? RCTJSValueToNSString(context, jsError, NULL) : @"Unknown JS error";
|
||||
errorInfo[NSLocalizedDescriptionKey] = [@"Unhandled JS Exception: " stringByAppendingString:description];
|
||||
|
||||
NSString *details = jsError ? RCTJSValueToJSONString(context, jsError, NULL, 0) : nil;
|
||||
if (details) {
|
||||
errorInfo[NSLocalizedFailureReasonErrorKey] = details;
|
||||
|
||||
// Format stack as used in RCTFormatError
|
||||
id json = RCTJSONParse(details, NULL);
|
||||
if ([json isKindOfClass:[NSDictionary class]]) {
|
||||
if (json[@"stack"]) {
|
||||
NSError *regexError;
|
||||
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"^([^@]+)@(.*):(\\d+):(\\d+)$" options:0 error:®exError];
|
||||
if (regexError) {
|
||||
RCTLogError(@"Failed to build regex: %@", [regexError localizedDescription]);
|
||||
}
|
||||
|
||||
NSMutableArray *stackTrace = [NSMutableArray array];
|
||||
for (NSString *stackLine in [json[@"stack"] componentsSeparatedByString:@"\n"]) {
|
||||
NSTextCheckingResult *result = [regex firstMatchInString:stackLine options:0 range:NSMakeRange(0, stackLine.length)];
|
||||
if (result) {
|
||||
[stackTrace addObject:@{
|
||||
@"methodName": [stackLine substringWithRange:[result rangeAtIndex:1]],
|
||||
@"file": [stackLine substringWithRange:[result rangeAtIndex:2]],
|
||||
@"lineNumber": [stackLine substringWithRange:[result rangeAtIndex:3]],
|
||||
@"column": [stackLine substringWithRange:[result rangeAtIndex:4]]
|
||||
}];
|
||||
}
|
||||
}
|
||||
if ([stackTrace count]) {
|
||||
errorInfo[RCTJSStackTraceKey] = stackTrace;
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to just logging the line number
|
||||
if (!errorInfo[RCTJSStackTraceKey] && json[@"line"]) {
|
||||
errorInfo[RCTJSStackTraceKey] = @[@{
|
||||
@"methodName": @"",
|
||||
@"file": RCTNullIfNil(json[@"sourceURL"]),
|
||||
@"lineNumber": RCTNullIfNil(json[@"line"]),
|
||||
@"column": @0,
|
||||
}];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [NSError errorWithDomain:RCTErrorDomain code:1 userInfo:errorInfo];
|
||||
}
|
||||
|
||||
#if RCT_DEV
|
||||
|
@ -630,7 +676,7 @@ static void RCTInstallJSCProfiler(RCTBridge *bridge, JSContextRef context)
|
|||
RCTLogError(@"%@", errorDesc);
|
||||
|
||||
if (onComplete) {
|
||||
NSError *error = [NSError errorWithDomain:@"JS" code:2 userInfo:@{NSLocalizedDescriptionKey: errorDesc}];
|
||||
NSError *error = [NSError errorWithDomain:RCTErrorDomain code:2 userInfo:@{NSLocalizedDescriptionKey: errorDesc}];
|
||||
onComplete(error);
|
||||
}
|
||||
return;
|
||||
|
|
|
@ -103,9 +103,10 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
|
|||
[[[NSURLSession sharedSession] dataTaskWithRequest:request] resume];
|
||||
}
|
||||
|
||||
- (void)showErrorMessage:(NSString *)message withStack:(NSArray<NSDictionary *> *)stack showIfHidden:(BOOL)shouldShow
|
||||
- (void)showErrorMessage:(NSString *)message withStack:(NSArray<NSDictionary *> *)stack isUpdate:(BOOL)isUpdate
|
||||
{
|
||||
if ((self.hidden && shouldShow) || (!self.hidden && [_lastErrorMessage isEqualToString:message])) {
|
||||
// Show if this is a new message, or if we're updating the previous message
|
||||
if ((self.hidden && !isUpdate) || (!self.hidden && isUpdate && [_lastErrorMessage isEqualToString:message])) {
|
||||
_lastStackTrace = stack;
|
||||
// message is displayed using UILabel, which is unable to render text of
|
||||
// unlimited length, so we truncate it
|
||||
|
@ -277,7 +278,7 @@ RCT_EXPORT_MODULE()
|
|||
|
||||
- (void)showErrorMessage:(NSString *)message
|
||||
{
|
||||
[self showErrorMessage:message withStack:nil showIfHidden:YES];
|
||||
[self showErrorMessage:message withStack:nil isUpdate:NO];
|
||||
}
|
||||
|
||||
- (void)showErrorMessage:(NSString *)message withDetails:(NSString *)details
|
||||
|
@ -291,21 +292,21 @@ RCT_EXPORT_MODULE()
|
|||
|
||||
- (void)showErrorMessage:(NSString *)message withStack:(NSArray<NSDictionary *> *)stack
|
||||
{
|
||||
[self showErrorMessage:message withStack:stack showIfHidden:YES];
|
||||
[self showErrorMessage:message withStack:stack isUpdate:NO];
|
||||
}
|
||||
|
||||
- (void)updateErrorMessage:(NSString *)message withStack:(NSArray<NSDictionary *> *)stack
|
||||
{
|
||||
[self showErrorMessage:message withStack:stack showIfHidden:NO];
|
||||
[self showErrorMessage:message withStack:stack isUpdate:YES];
|
||||
}
|
||||
|
||||
- (void)showErrorMessage:(NSString *)message withStack:(NSArray<NSDictionary *> *)stack showIfHidden:(BOOL)shouldShow
|
||||
- (void)showErrorMessage:(NSString *)message withStack:(NSArray<NSDictionary *> *)stack isUpdate:(BOOL)isUpdate
|
||||
{
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (!_window) {
|
||||
_window = [[RCTRedBoxWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
|
||||
}
|
||||
[_window showErrorMessage:message withStack:stack showIfHidden:shouldShow];
|
||||
[_window showErrorMessage:message withStack:stack isUpdate:isUpdate];
|
||||
});
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue