RCTAssert.m 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. /*
  2. * Copyright (c) Facebook, Inc. and its affiliates.
  3. *
  4. * This source code is licensed under the MIT license found in the
  5. * LICENSE file in the root directory of this source tree.
  6. */
  7. #import "RCTAssert.h"
  8. #import "RCTLog.h"
  9. NSString *const RCTErrorDomain = @"RCTErrorDomain";
  10. NSString *const RCTJSStackTraceKey = @"RCTJSStackTraceKey";
  11. NSString *const RCTJSRawStackTraceKey = @"RCTJSRawStackTraceKey";
  12. NSString *const RCTFatalExceptionName = @"RCTFatalException";
  13. NSString *const RCTUntruncatedMessageKey = @"RCTUntruncatedMessageKey";
  14. static NSString *const RCTAssertFunctionStack = @"RCTAssertFunctionStack";
  15. RCTAssertFunction RCTCurrentAssertFunction = nil;
  16. RCTFatalHandler RCTCurrentFatalHandler = nil;
  17. RCTFatalExceptionHandler RCTCurrentFatalExceptionHandler = nil;
  18. NSException *_RCTNotImplementedException(SEL, Class);
  19. NSException *_RCTNotImplementedException(SEL cmd, Class cls)
  20. {
  21. NSString *msg = [NSString stringWithFormat:
  22. @"%s is not implemented "
  23. "for the class %@",
  24. sel_getName(cmd),
  25. cls];
  26. return [NSException exceptionWithName:@"RCTNotDesignatedInitializerException" reason:msg userInfo:nil];
  27. }
  28. void RCTSetAssertFunction(RCTAssertFunction assertFunction)
  29. {
  30. RCTCurrentAssertFunction = assertFunction;
  31. }
  32. RCTAssertFunction RCTGetAssertFunction(void)
  33. {
  34. return RCTCurrentAssertFunction;
  35. }
  36. void RCTAddAssertFunction(RCTAssertFunction assertFunction)
  37. {
  38. RCTAssertFunction existing = RCTCurrentAssertFunction;
  39. if (existing) {
  40. RCTCurrentAssertFunction =
  41. ^(NSString *condition, NSString *fileName, NSNumber *lineNumber, NSString *function, NSString *message) {
  42. existing(condition, fileName, lineNumber, function, message);
  43. assertFunction(condition, fileName, lineNumber, function, message);
  44. };
  45. } else {
  46. RCTCurrentAssertFunction = assertFunction;
  47. }
  48. }
  49. /**
  50. * returns the topmost stacked assert function for the current thread, which
  51. * may not be the same as the current value of RCTCurrentAssertFunction.
  52. */
  53. static RCTAssertFunction RCTGetLocalAssertFunction()
  54. {
  55. NSMutableDictionary *threadDictionary = [NSThread currentThread].threadDictionary;
  56. NSArray<RCTAssertFunction> *functionStack = threadDictionary[RCTAssertFunctionStack];
  57. RCTAssertFunction assertFunction = functionStack.lastObject;
  58. if (assertFunction) {
  59. return assertFunction;
  60. }
  61. return RCTCurrentAssertFunction;
  62. }
  63. void RCTPerformBlockWithAssertFunction(void (^block)(void), RCTAssertFunction assertFunction)
  64. {
  65. NSMutableDictionary *threadDictionary = [NSThread currentThread].threadDictionary;
  66. NSMutableArray<RCTAssertFunction> *functionStack = threadDictionary[RCTAssertFunctionStack];
  67. if (!functionStack) {
  68. functionStack = [NSMutableArray new];
  69. threadDictionary[RCTAssertFunctionStack] = functionStack;
  70. }
  71. [functionStack addObject:assertFunction];
  72. block();
  73. [functionStack removeLastObject];
  74. }
  75. NSString *RCTCurrentThreadName(void)
  76. {
  77. NSThread *thread = [NSThread currentThread];
  78. NSString *threadName = RCTIsMainQueue() || thread.isMainThread ? @"main" : thread.name;
  79. if (threadName.length == 0) {
  80. const char *label = dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL);
  81. if (label && strlen(label) > 0) {
  82. threadName = @(label);
  83. } else {
  84. threadName = [NSString stringWithFormat:@"%p", thread];
  85. }
  86. }
  87. return threadName;
  88. }
  89. void _RCTAssertFormat(
  90. const char *condition,
  91. const char *fileName,
  92. int lineNumber,
  93. const char *function,
  94. NSString *format,
  95. ...)
  96. {
  97. RCTAssertFunction assertFunction = RCTGetLocalAssertFunction();
  98. if (assertFunction) {
  99. va_list args;
  100. va_start(args, format);
  101. NSString *message = [[NSString alloc] initWithFormat:format arguments:args];
  102. va_end(args);
  103. assertFunction(@(condition), @(fileName), @(lineNumber), @(function), message);
  104. }
  105. }
  106. void RCTFatal(NSError *error)
  107. {
  108. _RCTLogNativeInternal(RCTLogLevelFatal, NULL, 0, @"%@", error.localizedDescription);
  109. RCTFatalHandler fatalHandler = RCTGetFatalHandler();
  110. if (fatalHandler) {
  111. fatalHandler(error);
  112. } else {
  113. #if DEBUG
  114. @try {
  115. #endif
  116. NSString *name = [NSString stringWithFormat:@"%@: %@", RCTFatalExceptionName, error.localizedDescription];
  117. // Truncate the localized description to 175 characters to avoid wild screen overflows
  118. NSString *message = RCTFormatError(error.localizedDescription, error.userInfo[RCTJSStackTraceKey], 175);
  119. // Attach an untruncated copy of the description to the userInfo, in case it is needed
  120. NSMutableDictionary *userInfo = [error.userInfo mutableCopy];
  121. [userInfo setObject:RCTFormatError(error.localizedDescription, error.userInfo[RCTJSStackTraceKey], -1)
  122. forKey:RCTUntruncatedMessageKey];
  123. // Expected resulting exception information:
  124. // name: RCTFatalException: <underlying error description>
  125. // reason: <underlying error description plus JS stack trace, truncated to 175 characters>
  126. // userInfo: <underlying error userinfo, plus untruncated description plus JS stack trace>
  127. @throw [[NSException alloc] initWithName:name reason:message userInfo:userInfo];
  128. #if DEBUG
  129. } @catch (NSException *e) {
  130. }
  131. #endif
  132. }
  133. }
  134. void RCTSetFatalHandler(RCTFatalHandler fatalHandler)
  135. {
  136. RCTCurrentFatalHandler = fatalHandler;
  137. }
  138. RCTFatalHandler RCTGetFatalHandler(void)
  139. {
  140. return RCTCurrentFatalHandler;
  141. }
  142. NSString *
  143. RCTFormatError(NSString *message, NSArray<NSDictionary<NSString *, id> *> *stackTrace, NSUInteger maxMessageLength)
  144. {
  145. if (maxMessageLength > 0 && message.length > maxMessageLength) {
  146. message = [[message substringToIndex:maxMessageLength] stringByAppendingString:@"..."];
  147. }
  148. NSString *prettyStack = RCTFormatStackTrace(stackTrace);
  149. return [NSString
  150. stringWithFormat:@"%@%@%@", message, prettyStack ? @", stack:\n" : @"", prettyStack ? prettyStack : @""];
  151. }
  152. NSString *RCTFormatStackTrace(NSArray<NSDictionary<NSString *, id> *> *stackTrace)
  153. {
  154. if (stackTrace) {
  155. NSMutableString *prettyStack = [NSMutableString string];
  156. NSRegularExpression *regex =
  157. [NSRegularExpression regularExpressionWithPattern:@"\\b((?:seg-\\d+(?:_\\d+)?|\\d+)\\.js)"
  158. options:NSRegularExpressionCaseInsensitive
  159. error:NULL];
  160. for (NSDictionary<NSString *, id> *frame in stackTrace) {
  161. NSString *fileName = [frame[@"file"] lastPathComponent];
  162. NSTextCheckingResult *match =
  163. fileName != nil ? [regex firstMatchInString:fileName options:0 range:NSMakeRange(0, fileName.length)] : nil;
  164. if (match) {
  165. fileName = [NSString stringWithFormat:@"%@:", [fileName substringWithRange:match.range]];
  166. } else {
  167. fileName = @"";
  168. }
  169. [prettyStack
  170. appendFormat:@"%@@%@%@:%@\n", frame[@"methodName"], fileName, frame[@"lineNumber"], frame[@"column"]];
  171. }
  172. return prettyStack;
  173. }
  174. return nil;
  175. }
  176. void RCTFatalException(NSException *exception)
  177. {
  178. _RCTLogNativeInternal(RCTLogLevelFatal, NULL, 0, @"%@: %@", exception.name, exception.reason);
  179. RCTFatalExceptionHandler fatalExceptionHandler = RCTGetFatalExceptionHandler();
  180. if (fatalExceptionHandler) {
  181. fatalExceptionHandler(exception);
  182. } else {
  183. #if DEBUG
  184. @try {
  185. #endif
  186. @throw exception;
  187. #if DEBUG
  188. } @catch (NSException *e) {
  189. }
  190. #endif
  191. }
  192. }
  193. void RCTSetFatalExceptionHandler(RCTFatalExceptionHandler fatalExceptionHandler)
  194. {
  195. RCTCurrentFatalExceptionHandler = fatalExceptionHandler;
  196. }
  197. RCTFatalExceptionHandler RCTGetFatalExceptionHandler(void)
  198. {
  199. return RCTCurrentFatalExceptionHandler;
  200. }