Improve RCTNSErrorFromJSError

Reviewed By: adamjernst

Differential Revision: D3871146

fbshipit-source-id: 44de849852d8c00ab4589938ef317e4c8d98d7e6
This commit is contained in:
Pieter De Baets 2016-09-16 06:14:14 -07:00 committed by Facebook Github Bot 9
parent 95cce07baf
commit 228f104dad
5 changed files with 95 additions and 90 deletions

View File

@ -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);

View File

@ -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);
}

View File

@ -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:&regexError];
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];
});
}

View File

@ -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];

View File

@ -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 */,