react-native/React/Base/RCTAssert.m

189 lines
6.6 KiB
Mathematica
Raw Normal View History

/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTAssert.h"
#import "RCTLog.h"
2015-04-11 15:08:00 -07:00
NSString *const RCTErrorDomain = @"RCTErrorDomain";
NSString *const RCTJSStackTraceKey = @"RCTJSStackTraceKey";
NSString *const RCTJSRawStackTraceKey = @"RCTJSRawStackTraceKey";
NSString *const RCTFatalExceptionName = @"RCTFatalException";
Extend reason message for `RCTFatalException` (#22532) Summary: Fixes #22530 As described in the issue, the previous behavior for the `RCTFatal` macro was to truncate the `reason` on the resulting `NSException` to 75 characters. This would ensure the reason would fit on a single line, but resulted in issues debugging errors that occurred in the wild, as many crash logging tools (like Sentry) discard the `name` value of the exception and use the `reason` as their primary identifier. At 75 characters, useful information like the location of the error would usually be truncated. - [x] This extends the truncation threshold to 175 characters, which should be short enough to prevent full-screen-takeover length errors, but long enough to provide useful context to the error. - [x] This adds a `userInfo` value to the resulting `NSException`. It copies over the `userInfo` from the `NSError` passed to the macro, and adds an "untruncated message" value that contains the untruncated version of the `NSException`'s reason. [iOS] [Changed] - RCTFatalExceptions now include more information in their reason and a userInfo. <!-- CATEGORY may be: - [General] - [iOS] - [Android] TYPE may be: - [Added] for new features. - [Changed] for changes in existing functionality. - [Deprecated] for soon-to-be removed features. - [Removed] for now removed features. - [Fixed] for any bug fixes. - [Security] in case of vulnerabilities. For more detail, see https://keepachangelog.com/en/1.0.0/#how MESSAGE may answer "what and why" on a feature level. Use this to briefly tell React Native users about notable changes. EXAMPLES: [General] [Added] - Add snapToOffsets prop to ScrollView component [General] [Fixed] - Fix various issues in snapToInterval on ScrollView component [iOS] [Fixed] - Fix crash in RCTImagePicker --> Pull Request resolved: https://github.com/facebook/react-native/pull/22532 Differential Revision: D13373469 Pulled By: cpojer fbshipit-source-id: ac140d14ce76e1664869437c2c178bdd65ab6e0e
2018-12-06 20:19:06 -08:00
NSString *const RCTUntruncatedMessageKey = @"RCTUntruncatedMessageKey";
2015-08-07 06:06:17 -07:00
static NSString *const RCTAssertFunctionStack = @"RCTAssertFunctionStack";
2015-04-11 15:08:00 -07:00
RCTAssertFunction RCTCurrentAssertFunction = nil;
RCTFatalHandler RCTCurrentFatalHandler = nil;
2015-04-11 15:08:00 -07:00
NSException *_RCTNotImplementedException(SEL, Class);
NSException *_RCTNotImplementedException(SEL cmd, Class cls)
{
NSString *msg = [NSString stringWithFormat:@"%s is not implemented "
"for the class %@", sel_getName(cmd), cls];
return [NSException exceptionWithName:@"RCTNotDesignatedInitializerException"
reason:msg userInfo:nil];
}
2015-04-11 15:08:00 -07:00
void RCTSetAssertFunction(RCTAssertFunction assertFunction)
{
RCTCurrentAssertFunction = assertFunction;
}
RCTAssertFunction RCTGetAssertFunction(void)
{
return RCTCurrentAssertFunction;
}
void RCTAddAssertFunction(RCTAssertFunction assertFunction)
{
RCTAssertFunction existing = RCTCurrentAssertFunction;
if (existing) {
2015-08-07 06:06:17 -07:00
RCTCurrentAssertFunction = ^(NSString *condition,
2015-04-11 15:08:00 -07:00
NSString *fileName,
NSNumber *lineNumber,
NSString *function,
NSString *message) {
existing(condition, fileName, lineNumber, function, message);
assertFunction(condition, fileName, lineNumber, function, message);
};
} else {
RCTCurrentAssertFunction = assertFunction;
}
}
2015-08-07 06:06:17 -07:00
/**
* returns the topmost stacked assert function for the current thread, which
* may not be the same as the current value of RCTCurrentAssertFunction.
*/
static RCTAssertFunction RCTGetLocalAssertFunction()
{
NSMutableDictionary *threadDictionary = [NSThread currentThread].threadDictionary;
NSArray<RCTAssertFunction> *functionStack = threadDictionary[RCTAssertFunctionStack];
RCTAssertFunction assertFunction = functionStack.lastObject;
2015-08-07 06:06:17 -07:00
if (assertFunction) {
return assertFunction;
}
return RCTCurrentAssertFunction;
}
void RCTPerformBlockWithAssertFunction(void (^block)(void), RCTAssertFunction assertFunction)
{
NSMutableDictionary *threadDictionary = [NSThread currentThread].threadDictionary;
NSMutableArray<RCTAssertFunction> *functionStack = threadDictionary[RCTAssertFunctionStack];
2015-08-07 06:06:17 -07:00
if (!functionStack) {
functionStack = [NSMutableArray new];
2015-08-07 06:06:17 -07:00
threadDictionary[RCTAssertFunctionStack] = functionStack;
}
[functionStack addObject:assertFunction];
block();
[functionStack removeLastObject];
}
NSString *RCTCurrentThreadName(void)
{
NSThread *thread = [NSThread currentThread];
NSString *threadName = RCTIsMainQueue() || thread.isMainThread ? @"main" : thread.name;
if (threadName.length == 0) {
const char *label = dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL);
if (label && strlen(label) > 0) {
threadName = @(label);
} else {
threadName = [NSString stringWithFormat:@"%p", thread];
}
}
return threadName;
}
2015-08-07 06:06:17 -07:00
void _RCTAssertFormat(
const char *condition,
const char *fileName,
int lineNumber,
const char *function,
NSString *format, ...)
{
RCTAssertFunction assertFunction = RCTGetLocalAssertFunction();
if (assertFunction) {
va_list args;
va_start(args, format);
NSString *message = [[NSString alloc] initWithFormat:format arguments:args];
va_end(args);
assertFunction(@(condition), @(fileName), @(lineNumber), @(function), message);
}
}
void RCTFatal(NSError *error)
{
_RCTLogNativeInternal(RCTLogLevelFatal, NULL, 0, @"%@", error.localizedDescription);
RCTFatalHandler fatalHandler = RCTGetFatalHandler();
if (fatalHandler) {
fatalHandler(error);
} else {
#if DEBUG
@try {
#endif
NSString *name = [NSString stringWithFormat:@"%@: %@", RCTFatalExceptionName, error.localizedDescription];
Extend reason message for `RCTFatalException` (#22532) Summary: Fixes #22530 As described in the issue, the previous behavior for the `RCTFatal` macro was to truncate the `reason` on the resulting `NSException` to 75 characters. This would ensure the reason would fit on a single line, but resulted in issues debugging errors that occurred in the wild, as many crash logging tools (like Sentry) discard the `name` value of the exception and use the `reason` as their primary identifier. At 75 characters, useful information like the location of the error would usually be truncated. - [x] This extends the truncation threshold to 175 characters, which should be short enough to prevent full-screen-takeover length errors, but long enough to provide useful context to the error. - [x] This adds a `userInfo` value to the resulting `NSException`. It copies over the `userInfo` from the `NSError` passed to the macro, and adds an "untruncated message" value that contains the untruncated version of the `NSException`'s reason. [iOS] [Changed] - RCTFatalExceptions now include more information in their reason and a userInfo. <!-- CATEGORY may be: - [General] - [iOS] - [Android] TYPE may be: - [Added] for new features. - [Changed] for changes in existing functionality. - [Deprecated] for soon-to-be removed features. - [Removed] for now removed features. - [Fixed] for any bug fixes. - [Security] in case of vulnerabilities. For more detail, see https://keepachangelog.com/en/1.0.0/#how MESSAGE may answer "what and why" on a feature level. Use this to briefly tell React Native users about notable changes. EXAMPLES: [General] [Added] - Add snapToOffsets prop to ScrollView component [General] [Fixed] - Fix various issues in snapToInterval on ScrollView component [iOS] [Fixed] - Fix crash in RCTImagePicker --> Pull Request resolved: https://github.com/facebook/react-native/pull/22532 Differential Revision: D13373469 Pulled By: cpojer fbshipit-source-id: ac140d14ce76e1664869437c2c178bdd65ab6e0e
2018-12-06 20:19:06 -08:00
// Truncate the localized description to 175 characters to avoid wild screen overflows
NSString *message = RCTFormatError(error.localizedDescription, error.userInfo[RCTJSStackTraceKey], 175);
// Attach an untruncated copy of the description to the userInfo, in case it is needed
NSMutableDictionary *userInfo = [error.userInfo mutableCopy];
[userInfo setObject:RCTFormatError(error.localizedDescription, error.userInfo[RCTJSStackTraceKey], -1)
forKey:RCTUntruncatedMessageKey];
// Expected resulting exception information:
// name: RCTFatalException: <underlying error description>
// reason: <underlying error description plus JS stack trace, truncated to 175 characters>
// userInfo: <underlying error userinfo, plus untruncated description plus JS stack trace>
@throw [[NSException alloc] initWithName:name reason:message userInfo:userInfo];
#if DEBUG
} @catch (NSException *e) {}
#endif
}
}
void RCTSetFatalHandler(RCTFatalHandler fatalhandler)
{
RCTCurrentFatalHandler = fatalhandler;
}
RCTFatalHandler RCTGetFatalHandler(void)
{
return RCTCurrentFatalHandler;
}
NSString *RCTFormatError(NSString *message, NSArray<NSDictionary<NSString *, id> *> *stackTrace, NSUInteger maxMessageLength)
{
if (maxMessageLength > 0 && message.length > maxMessageLength) {
message = [[message substringToIndex:maxMessageLength] stringByAppendingString:@"..."];
}
NSMutableString *prettyStack = [NSMutableString string];
if (stackTrace) {
[prettyStack appendString:@", stack:\n"];
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"^(\\d+\\.js)$"
options:NSRegularExpressionCaseInsensitive
error:NULL];
for (NSDictionary<NSString *, id> *frame in stackTrace) {
NSString *fileName = [frame[@"file"] lastPathComponent];
if (fileName && [regex numberOfMatchesInString:fileName options:0 range:NSMakeRange(0, [fileName length])]) {
fileName = [fileName stringByAppendingString:@":"];
} else {
fileName = @"";
}
[prettyStack appendFormat:@"%@@%@%@:%@\n", frame[@"methodName"], fileName, frame[@"lineNumber"], frame[@"column"]];
}
}
return [NSString stringWithFormat:@"%@%@", message, prettyStack];
}