Improve RCTNSErrorFromJSError
Reviewed By: adamjernst Differential Revision: D3871146 fbshipit-source-id: 44de849852d8c00ab4589938ef317e4c8d98d7e6
This commit is contained in:
parent
95cce07baf
commit
228f104dad
|
@ -0,0 +1,35 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import <JavaScriptCore/JavaScriptCore.h>
|
||||
|
||||
#import "RCTDefines.h"
|
||||
|
||||
typedef struct RCTJSCWrapper RCTJSCWrapper;
|
||||
|
||||
/**
|
||||
Translates a given exception into an NSError.
|
||||
|
||||
@param exception The JavaScript exception object to translate into an NSError. This must be
|
||||
a JavaScript Error object, otherwise no stack trace information will be available.
|
||||
|
||||
@return The translated NSError object
|
||||
|
||||
- The JS exception's name property is incorporated in the NSError's localized description
|
||||
- The JS exception's message property is the NSError's failure reason
|
||||
- The JS exception's unsymbolicated stack trace is available via the NSError userInfo's RCTJSExceptionUnsymbolicatedStackTraceKey
|
||||
*/
|
||||
RCT_EXTERN NSError *RCTNSErrorFromJSError(JSValue *exception);
|
||||
|
||||
/**
|
||||
Translates a given exception into an NSError.
|
||||
|
||||
@see RCTNSErrorFromJSError for details
|
||||
*/
|
||||
RCT_EXTERN NSError *RCTNSErrorFromJSErrorRef(JSValueRef exception, JSGlobalContextRef ctx, RCTJSCWrapper *jscWrapper);
|
|
@ -0,0 +1,39 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#include "RCTJSCErrorHandling.h"
|
||||
|
||||
#import "RCTAssert.h"
|
||||
#import "RCTJSStackFrame.h"
|
||||
#import "RCTJSCWrapper.h"
|
||||
|
||||
NSString *const RCTJSExceptionUnsymbolicatedStackTraceKey = @"RCTJSExceptionUnsymbolicatedStackTraceKey";
|
||||
|
||||
NSError *RCTNSErrorFromJSError(JSValue *exception)
|
||||
{
|
||||
NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
|
||||
userInfo[NSLocalizedDescriptionKey] = [NSString stringWithFormat:@"Unhandled JS Exception: %@", [exception[@"name"] toString] ?: @"Unknown"];
|
||||
NSString *const exceptionMessage = [exception[@"message"] toString];
|
||||
if ([exceptionMessage length]) {
|
||||
userInfo[NSLocalizedFailureReasonErrorKey] = exceptionMessage;
|
||||
}
|
||||
NSString *const stack = [exception[@"stack"] toString];
|
||||
if ([stack length]) {
|
||||
NSArray<RCTJSStackFrame *> *const unsymbolicatedFrames = [RCTJSStackFrame stackFramesWithLines:stack];
|
||||
userInfo[RCTJSStackTraceKey] = unsymbolicatedFrames;
|
||||
}
|
||||
return [NSError errorWithDomain:RCTErrorDomain code:1 userInfo:userInfo];
|
||||
}
|
||||
|
||||
NSError *RCTNSErrorFromJSErrorRef(JSValueRef exceptionRef, JSGlobalContextRef ctx, RCTJSCWrapper *jscWrapper)
|
||||
{
|
||||
JSContext *context = [jscWrapper->JSContext contextWithJSGlobalContextRef:ctx];
|
||||
JSValue *exception = [jscWrapper->JSValue valueWithJSValueRef:exceptionRef inContext:context];
|
||||
return RCTNSErrorFromJSError(exception);
|
||||
}
|
|
@ -30,9 +30,9 @@
|
|||
#import "RCTRedBox.h"
|
||||
#import "RCTSourceCode.h"
|
||||
#import "RCTJSCWrapper.h"
|
||||
#import "RCTJSCErrorHandling.h"
|
||||
|
||||
NSString *const RCTJSCThreadName = @"com.facebook.react.JavaScript";
|
||||
|
||||
NSString *const RCTJavaScriptContextCreatedNotification = @"RCTJavaScriptContextCreatedNotification";
|
||||
|
||||
static NSString *const RCTJSCProfilerEnabledDefaultsKey = @"RCTJSCProfilerEnabled";
|
||||
|
@ -163,81 +163,6 @@ RCT_NOT_IMPLEMENTED(-(instancetype)init)
|
|||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
static NSString *RCTJSValueToNSString(RCTJSCWrapper *jscWrapper, JSContextRef context, JSValueRef value, JSValueRef *exception)
|
||||
{
|
||||
JSStringRef JSString = jscWrapper->JSValueToStringCopy(context, value, exception);
|
||||
if (!JSString) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
CFStringRef string = jscWrapper->JSStringCopyCFString(kCFAllocatorDefault, JSString);
|
||||
jscWrapper->JSStringRelease(JSString);
|
||||
|
||||
return (__bridge_transfer NSString *)string;
|
||||
}
|
||||
|
||||
static NSString *RCTJSValueToJSONString(RCTJSCWrapper *jscWrapper, JSContextRef context, JSValueRef value, JSValueRef *exception, unsigned indent)
|
||||
{
|
||||
JSStringRef jsString = jscWrapper->JSValueCreateJSONString(context, value, indent, exception);
|
||||
CFStringRef string = jscWrapper->JSStringCopyCFString(kCFAllocatorDefault, jsString);
|
||||
jscWrapper->JSStringRelease(jsString);
|
||||
|
||||
return (__bridge_transfer NSString *)string;
|
||||
}
|
||||
|
||||
static NSError *RCTNSErrorFromJSError(RCTJSCWrapper *jscWrapper, JSContextRef context, JSValueRef jsError)
|
||||
{
|
||||
NSMutableDictionary *errorInfo = [NSMutableDictionary new];
|
||||
|
||||
NSString *description = jsError ? RCTJSValueToNSString(jscWrapper, context, jsError, NULL) : @"Unknown JS error";
|
||||
errorInfo[NSLocalizedDescriptionKey] = [@"Unhandled JS Exception: " stringByAppendingString:description];
|
||||
|
||||
NSString *details = jsError ? RCTJSValueToJSONString(jscWrapper, 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
|
||||
|
||||
static void RCTInstallJSCProfiler(RCTBridge *bridge, JSContextRef context)
|
||||
|
@ -699,7 +624,7 @@ static void installBasicSynchronousHooksOnContext(JSContext *context)
|
|||
id objcValue;
|
||||
if (errorJSRef || error) {
|
||||
if (!error) {
|
||||
error = RCTNSErrorFromJSError(jscWrapper, contextJSRef, errorJSRef);
|
||||
error = RCTNSErrorFromJSError([jscWrapper->JSValue valueWithJSValueRef:errorJSRef inContext:context]);
|
||||
}
|
||||
} else {
|
||||
// We often return `null` from JS when there is nothing for native side. [JSValue toValue]
|
||||
|
@ -799,11 +724,12 @@ static NSError *executeApplicationScript(NSData *script, NSURL *sourceURL, RCTJS
|
|||
JSValueRef jsError = NULL;
|
||||
JSStringRef execJSString = jscWrapper->JSStringCreateWithUTF8CString((const char *)script.bytes);
|
||||
JSStringRef bundleURL = jscWrapper->JSStringCreateWithUTF8CString(sourceURL.absoluteString.UTF8String);
|
||||
JSValueRef result = jscWrapper->JSEvaluateScript(ctx, execJSString, NULL, bundleURL, 0, &jsError);
|
||||
jscWrapper->JSEvaluateScript(ctx, execJSString, NULL, bundleURL, 0, &jsError);
|
||||
jscWrapper->JSStringRelease(bundleURL);
|
||||
jscWrapper->JSStringRelease(execJSString);
|
||||
[performanceLogger markStopForTag:RCTPLScriptExecution];
|
||||
NSError *error = result ? nil : RCTNSErrorFromJSError(jscWrapper, ctx, jsError);
|
||||
|
||||
NSError *error = jsError ? RCTNSErrorFromJSErrorRef(jsError, ctx, jscWrapper) : nil;
|
||||
RCT_PROFILE_END_EVENT(0, @"js_call");
|
||||
return error;
|
||||
}
|
||||
|
@ -864,7 +790,7 @@ static NSError *executeApplicationScript(NSData *script, NSURL *sourceURL, RCTJS
|
|||
jscWrapper->JSStringRelease(JSName);
|
||||
|
||||
if (jsError) {
|
||||
error = RCTNSErrorFromJSError(jscWrapper, ctx, jsError);
|
||||
error = RCTNSErrorFromJSErrorRef(jsError, ctx, jscWrapper);
|
||||
}
|
||||
}
|
||||
RCT_PROFILE_END_EVENT(0, @"js_call,json_call");
|
||||
|
@ -904,7 +830,7 @@ static void executeRandomAccessModule(RCTJSCExecutor *executor, uint32_t moduleI
|
|||
|
||||
if (!result) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
RCTFatal(RCTNSErrorFromJSError(jscWrapper, ctx, jsError));
|
||||
RCTFatal(RCTNSErrorFromJSErrorRef(jsError, ctx, jscWrapper));
|
||||
[executor invalidate];
|
||||
});
|
||||
}
|
||||
|
|
|
@ -378,32 +378,31 @@ RCT_EXPORT_MODULE()
|
|||
if (details) {
|
||||
combinedMessage = [NSString stringWithFormat:@"%@\n\n%@", message, details];
|
||||
}
|
||||
[self showErrorMessage:combinedMessage];
|
||||
[self showErrorMessage:combinedMessage withStack:nil isUpdate:NO];
|
||||
}
|
||||
|
||||
- (void)showErrorMessage:(NSString *)message withRawStack:(NSString *)rawStack
|
||||
{
|
||||
NSArray<RCTJSStackFrame *> *stack = [RCTJSStackFrame stackFramesWithLines:rawStack];
|
||||
[self _showErrorMessage:message withStack:stack isUpdate:NO];
|
||||
[self showErrorMessage:message withStack:stack isUpdate:NO];
|
||||
}
|
||||
|
||||
- (void)showErrorMessage:(NSString *)message withStack:(NSArray<NSDictionary *> *)stack
|
||||
- (void)showErrorMessage:(NSString *)message withStack:(NSArray *)stack
|
||||
{
|
||||
[self showErrorMessage:message withStack:stack isUpdate:NO];
|
||||
}
|
||||
|
||||
- (void)updateErrorMessage:(NSString *)message withStack:(NSArray<NSDictionary *> *)stack
|
||||
- (void)updateErrorMessage:(NSString *)message withStack:(NSArray *)stack
|
||||
{
|
||||
[self showErrorMessage:message withStack:stack isUpdate:YES];
|
||||
}
|
||||
|
||||
- (void)showErrorMessage:(NSString *)message withStack:(NSArray<NSDictionary *> *)stack isUpdate:(BOOL)isUpdate
|
||||
- (void)showErrorMessage:(NSString *)message withStack:(NSArray *)stack isUpdate:(BOOL)isUpdate
|
||||
{
|
||||
[self _showErrorMessage:message withStack:[RCTJSStackFrame stackFramesWithDictionaries:stack] isUpdate:isUpdate];
|
||||
}
|
||||
if (![[stack firstObject] isKindOfClass:[RCTJSStackFrame class]]) {
|
||||
stack = [RCTJSStackFrame stackFramesWithDictionaries:stack];
|
||||
}
|
||||
|
||||
- (void)_showErrorMessage:(NSString *)message withStack:(NSArray<RCTJSStackFrame *> *)stack isUpdate:(BOOL)isUpdate
|
||||
{
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (!self->_window) {
|
||||
self->_window = [[RCTRedBoxWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
|
||||
|
|
|
@ -82,6 +82,7 @@
|
|||
391E86A41C623EC800009732 /* RCTTouchEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 391E86A21C623EC800009732 /* RCTTouchEvent.m */; };
|
||||
3D1E68DB1CABD13900DD7465 /* RCTDisplayLink.m in Sources */ = {isa = PBXBuildFile; fileRef = 3D1E68D91CABD13900DD7465 /* RCTDisplayLink.m */; };
|
||||
3D37B5821D522B190042D5B5 /* RCTFont.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3D37B5811D522B190042D5B5 /* RCTFont.mm */; };
|
||||
3DC724321D8BF99A00808C32 /* RCTJSCErrorHandling.m in Sources */ = {isa = PBXBuildFile; fileRef = 3DC724311D8BF99A00808C32 /* RCTJSCErrorHandling.m */; };
|
||||
3EDCA8A51D3591E700450C31 /* RCTErrorInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 3EDCA8A41D3591E700450C31 /* RCTErrorInfo.m */; };
|
||||
58114A161AAE854800E7D092 /* RCTPicker.m in Sources */ = {isa = PBXBuildFile; fileRef = 58114A131AAE854800E7D092 /* RCTPicker.m */; };
|
||||
58114A171AAE854800E7D092 /* RCTPickerManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 58114A151AAE854800E7D092 /* RCTPickerManager.m */; };
|
||||
|
@ -282,6 +283,8 @@
|
|||
3D37B5811D522B190042D5B5 /* RCTFont.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RCTFont.mm; sourceTree = "<group>"; };
|
||||
3DB910701C74B21600838BBE /* RCTWebSocketProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTWebSocketProxy.h; sourceTree = "<group>"; };
|
||||
3DB910711C74B21600838BBE /* RCTWebSocketProxyDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTWebSocketProxyDelegate.h; sourceTree = "<group>"; };
|
||||
3DC724301D8BF99A00808C32 /* RCTJSCErrorHandling.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTJSCErrorHandling.h; sourceTree = "<group>"; };
|
||||
3DC724311D8BF99A00808C32 /* RCTJSCErrorHandling.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTJSCErrorHandling.m; sourceTree = "<group>"; };
|
||||
3EDCA8A21D3591E700450C31 /* RCTErrorCustomizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTErrorCustomizer.h; sourceTree = "<group>"; };
|
||||
3EDCA8A31D3591E700450C31 /* RCTErrorInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTErrorInfo.h; sourceTree = "<group>"; };
|
||||
3EDCA8A41D3591E700450C31 /* RCTErrorInfo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTErrorInfo.m; sourceTree = "<group>"; };
|
||||
|
@ -362,6 +365,8 @@
|
|||
134FCB381A6E7F0800051CC8 /* Executors */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3DC724301D8BF99A00808C32 /* RCTJSCErrorHandling.h */,
|
||||
3DC724311D8BF99A00808C32 /* RCTJSCErrorHandling.m */,
|
||||
134FCB391A6E7F0800051CC8 /* RCTJSCExecutor.h */,
|
||||
134FCB3A1A6E7F0800051CC8 /* RCTJSCExecutor.mm */,
|
||||
85C199EC1CD2407900DAD810 /* RCTJSCWrapper.h */,
|
||||
|
@ -806,6 +811,7 @@
|
|||
B95154321D1B34B200FE7B80 /* RCTActivityIndicatorView.m in Sources */,
|
||||
13B0801A1A69489C00A75B9A /* RCTNavigator.m in Sources */,
|
||||
137327E71AA5CF210034F82E /* RCTTabBar.m in Sources */,
|
||||
3DC724321D8BF99A00808C32 /* RCTJSCErrorHandling.m in Sources */,
|
||||
13F17A851B8493E5007D4C75 /* RCTRedBox.m in Sources */,
|
||||
83392EB31B6634E10013B15F /* RCTModalHostViewController.m in Sources */,
|
||||
14435CE51AAC4AE100FC20F4 /* RCTMap.m in Sources */,
|
||||
|
|
Loading…
Reference in New Issue