From a65ae8eff6ec6f9ad283ac8e96f00802421a14da Mon Sep 17 00:00:00 2001 From: Gijs Weterings Date: Mon, 25 Apr 2022 03:26:24 -0700 Subject: [PATCH] Attach js_extra_data to JS errors on iOS Summary: Changelog: [iOS][Fixed] - Expose the extraData dict attached to JavaScript errors to the native ExceptionManager on iOS, similar to Android Attaching the `extraData` dict to JavaScript crash reports is something that was done for Android only in 2019 (D16133080 (https://github.com/facebook/react-native/commit/3a825c036065783aae6fb734028f986dbead80d7)), and somehow we never really got around to adding it in iOS. This diff finally adds the capability to iOS as well. `extraData` can be used to attach various bits of data to a crash report for better debugging and categorization. As with the Android implementation, `extraData` is not attached if the `reportException` API is not used. Reviewed By: dmitryrykun Differential Revision: D35743658 fbshipit-source-id: de4060cb6e514db1d85907441a8962f98e9b8392 --- React/Base/RCTAssert.h | 5 ++++ React/Base/RCTAssert.m | 1 + React/CoreModules/RCTExceptionsManager.h | 6 +++-- React/CoreModules/RCTExceptionsManager.mm | 30 ++++++++++++++++------- 4 files changed, 31 insertions(+), 11 deletions(-) diff --git a/React/Base/RCTAssert.h b/React/Base/RCTAssert.h index 7c7fc2e6fac..99b0af24996 100644 --- a/React/Base/RCTAssert.h +++ b/React/Base/RCTAssert.h @@ -74,6 +74,11 @@ RCT_EXTERN NSString *const RCTObjCStackTraceKey; */ RCT_EXTERN NSString *const RCTFatalExceptionName; +/** + * Stringified JSON object containing extra data to attach to the error from JavaScript. + */ +RCT_EXTERN NSString *const RCTJSExtraDataKey; + /** * A block signature to be used for custom assertion handling. */ diff --git a/React/Base/RCTAssert.m b/React/Base/RCTAssert.m index 4044456646e..56bf6ebe1b5 100644 --- a/React/Base/RCTAssert.m +++ b/React/Base/RCTAssert.m @@ -14,6 +14,7 @@ NSString *const RCTJSRawStackTraceKey = @"RCTJSRawStackTraceKey"; NSString *const RCTObjCStackTraceKey = @"RCTObjCStackTraceKey"; NSString *const RCTFatalExceptionName = @"RCTFatalException"; NSString *const RCTUntruncatedMessageKey = @"RCTUntruncatedMessageKey"; +NSString *const RCTJSExtraDataKey = @"RCTJSExtraDataKey"; static NSString *const RCTAssertFunctionStack = @"RCTAssertFunctionStack"; diff --git a/React/CoreModules/RCTExceptionsManager.h b/React/CoreModules/RCTExceptionsManager.h index 389715193b4..456a487fafb 100644 --- a/React/CoreModules/RCTExceptionsManager.h +++ b/React/CoreModules/RCTExceptionsManager.h @@ -15,10 +15,12 @@ NS_ASSUME_NONNULL_BEGIN - (void)handleSoftJSExceptionWithMessage:(nullable NSString *)message stack:(nullable NSArray *)stack - exceptionId:(NSNumber *)exceptionId; + exceptionId:(NSNumber *)exceptionId + extraDataAsJSON:(nullable NSString *)extraDataAsJSON; - (void)handleFatalJSExceptionWithMessage:(nullable NSString *)message stack:(nullable NSArray *)stack - exceptionId:(NSNumber *)exceptionId; + exceptionId:(NSNumber *)exceptionId + extraDataAsJSON:(nullable NSString *)extraDataAsJSON; @optional - (void)updateJSExceptionWithMessage:(nullable NSString *)message diff --git a/React/CoreModules/RCTExceptionsManager.mm b/React/CoreModules/RCTExceptionsManager.mm index 9ee53d97f20..04b71272aaa 100644 --- a/React/CoreModules/RCTExceptionsManager.mm +++ b/React/CoreModules/RCTExceptionsManager.mm @@ -35,7 +35,10 @@ RCT_EXPORT_MODULE() return self; } -- (void)reportSoft:(NSString *)message stack:(NSArray *)stack exceptionId:(double)exceptionId +- (void)reportSoft:(NSString *)message + stack:(NSArray *)stack + exceptionId:(double)exceptionId + extraDataAsJSON:(nullable NSString *)extraDataAsJSON { RCTRedBox *redbox = [_moduleRegistry moduleForName:"RedBox"]; [redbox showErrorMessage:message withStack:stack errorCookie:(int)exceptionId]; @@ -43,11 +46,15 @@ RCT_EXPORT_MODULE() if (_delegate) { [_delegate handleSoftJSExceptionWithMessage:message stack:stack - exceptionId:[NSNumber numberWithDouble:exceptionId]]; + exceptionId:[NSNumber numberWithDouble:exceptionId] + extraDataAsJSON:extraDataAsJSON]; } } -- (void)reportFatal:(NSString *)message stack:(NSArray *)stack exceptionId:(double)exceptionId +- (void)reportFatal:(NSString *)message + stack:(NSArray *)stack + exceptionId:(double)exceptionId + extraDataAsJSON:(nullable NSString *)extraDataAsJSON { RCTRedBox *redbox = [_moduleRegistry moduleForName:"RedBox"]; [redbox showErrorMessage:message withStack:stack errorCookie:(int)exceptionId]; @@ -55,7 +62,8 @@ RCT_EXPORT_MODULE() if (_delegate) { [_delegate handleFatalJSExceptionWithMessage:message stack:stack - exceptionId:[NSNumber numberWithDouble:exceptionId]]; + exceptionId:[NSNumber numberWithDouble:exceptionId] + extraDataAsJSON:extraDataAsJSON]; } static NSUInteger reloadRetries = 0; @@ -64,7 +72,8 @@ RCT_EXPORT_MODULE() RCTTriggerReloadCommandListeners(@"JS Crash Reload"); } else if (!RCT_DEV) { NSString *description = [@"Unhandled JS Exception: " stringByAppendingString:message]; - NSDictionary *errorInfo = @{NSLocalizedDescriptionKey : description, RCTJSStackTraceKey : stack}; + NSDictionary *errorInfo = + @{NSLocalizedDescriptionKey : description, RCTJSStackTraceKey : stack, RCTJSExtraDataKey : extraDataAsJSON}; RCTFatal([NSError errorWithDomain:RCTErrorDomain code:0 userInfo:errorInfo]); } } @@ -74,7 +83,7 @@ RCT_EXPORT_METHOD(reportSoftException : (NSArray *)stack exceptionId : (double)exceptionId) { - [self reportSoft:message stack:stack exceptionId:exceptionId]; + [self reportSoft:message stack:stack exceptionId:exceptionId extraDataAsJSON:nil]; } RCT_EXPORT_METHOD(reportFatalException @@ -82,7 +91,7 @@ RCT_EXPORT_METHOD(reportFatalException : (NSArray *)stack exceptionId : (double)exceptionId) { - [self reportFatal:message stack:stack exceptionId:exceptionId]; + [self reportFatal:message stack:stack exceptionId:exceptionId extraDataAsJSON:nil]; } RCT_EXPORT_METHOD(updateExceptionMessage @@ -131,10 +140,13 @@ RCT_EXPORT_METHOD(reportException : (JS::NativeExceptionsManager::ExceptionData [stackArray addObject:frameDict]; } + NSDictionary *extraData = (NSDictionary *)data.extraData(); + NSString *extraDataAsJSON = RCTJSONStringify(extraData, NULL); + if (data.isFatal()) { - [self reportFatal:message stack:stackArray exceptionId:exceptionId]; + [self reportFatal:message stack:stackArray exceptionId:exceptionId extraDataAsJSON:extraDataAsJSON]; } else { - [self reportSoft:message stack:stackArray exceptionId:exceptionId]; + [self reportSoft:message stack:stackArray exceptionId:exceptionId extraDataAsJSON:extraDataAsJSON]; } }