From 1b994f9dddd2f559b4838ae7081ef71d407d7dfb Mon Sep 17 00:00:00 2001 From: Valentin Shergin Date: Tue, 8 Sep 2020 13:21:34 -0700 Subject: [PATCH] Proxing NSException stack trace to NSError object Summary: When we catch an Objective-C exception and convert it to NSError we need to somehow represent the call stack from NSException instance in NSError instance. For now, we just attach the stack trace to `message` field. The next step would be to figure out how to pass the Objective-C stack trace to error reporting infra to help it to display the stack trace nicely in the web interface. Changelog: [Internal] Fabric-specific internal change. Reviewed By: sammy-SC Differential Revision: D23557600 fbshipit-source-id: a080c2e186e719e42dcfc01bb12f5811e3c5b2e6 --- React/Base/RCTAssert.h | 5 +++++ React/Base/RCTAssert.m | 1 + React/Base/RCTUtils.h | 3 +++ React/Base/RCTUtils.m | 10 ++++++++++ React/CxxModule/RCTCxxUtils.mm | 3 +-- 5 files changed, 20 insertions(+), 2 deletions(-) diff --git a/React/Base/RCTAssert.h b/React/Base/RCTAssert.h index 6b5109ed74bb81..9f0e9af2dec37c 100644 --- a/React/Base/RCTAssert.h +++ b/React/Base/RCTAssert.h @@ -64,6 +64,11 @@ RCT_EXTERN NSString *const RCTJSStackTraceKey; */ RCT_EXTERN NSString *const RCTJSRawStackTraceKey; +/** + * Objective-C stack trace string provided as part of an NSError's userInfo + */ +RCT_EXTERN NSString *const RCTObjCStackTraceKey; + /** * Name of fatal exceptions generated by RCTFatal */ diff --git a/React/Base/RCTAssert.m b/React/Base/RCTAssert.m index b454fae65b92f0..087900d1979565 100644 --- a/React/Base/RCTAssert.m +++ b/React/Base/RCTAssert.m @@ -11,6 +11,7 @@ NSString *const RCTErrorDomain = @"RCTErrorDomain"; NSString *const RCTJSStackTraceKey = @"RCTJSStackTraceKey"; NSString *const RCTJSRawStackTraceKey = @"RCTJSRawStackTraceKey"; +NSString *const RCTObjCStackTraceKey = @"RCTObjCStackTraceKey"; NSString *const RCTFatalExceptionName = @"RCTFatalException"; NSString *const RCTUntruncatedMessageKey = @"RCTUntruncatedMessageKey"; diff --git a/React/Base/RCTUtils.h b/React/Base/RCTUtils.h index 1270e471d28d57..5643035b55ea17 100644 --- a/React/Base/RCTUtils.h +++ b/React/Base/RCTUtils.h @@ -98,6 +98,9 @@ RCT_EXTERN BOOL RCTForceTouchAvailable(void); // Create an NSError in the RCTErrorDomain RCT_EXTERN NSError *RCTErrorWithMessage(NSString *message); +// Creates an NSError from given an NSException +RCT_EXTERN NSError *RCTErrorWithNSException(NSException *exception); + // Convert nil values to NSNull, and vice-versa #define RCTNullIfNil(value) ((value) ?: (id)kCFNull) #define RCTNilIfNull(value) \ diff --git a/React/Base/RCTUtils.m b/React/Base/RCTUtils.m index 13547d5852c697..1d26c256557a28 100644 --- a/React/Base/RCTUtils.m +++ b/React/Base/RCTUtils.m @@ -581,6 +581,16 @@ BOOL RCTForceTouchAvailable(void) return [[NSError alloc] initWithDomain:RCTErrorDomain code:0 userInfo:errorInfo]; } +NSError *RCTErrorWithNSException(NSException *exception) +{ + NSString *message = [NSString stringWithFormat:@"NSException: %@; trace: %@.", + exception, + [[exception callStackSymbols] componentsJoinedByString:@";"]]; + NSDictionary *errorInfo = + @{NSLocalizedDescriptionKey : message, RCTObjCStackTraceKey : [exception callStackSymbols]}; + return [[NSError alloc] initWithDomain:RCTErrorDomain code:0 userInfo:errorInfo]; +} + double RCTZeroIfNaN(double value) { return isnan(value) || isinf(value) ? 0 : value; diff --git a/React/CxxModule/RCTCxxUtils.mm b/React/CxxModule/RCTCxxUtils.mm index a25328fac75371..b81a8da54ae947 100644 --- a/React/CxxModule/RCTCxxUtils.mm +++ b/React/CxxModule/RCTCxxUtils.mm @@ -74,8 +74,7 @@ func(); return nil; } @catch (NSException *exception) { - NSString *message = [NSString stringWithFormat:@"Exception '%@' was thrown from JS thread", exception]; - return RCTErrorWithMessage(message); + return RCTErrorWithNSException(exception); } @catch (id exception) { // This will catch any other ObjC exception, but no C++ exceptions return RCTErrorWithMessage(@"non-std ObjC Exception");