From 3cef3010e6f90f03bcce8f0744dd25399b254ae0 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Fri, 7 Aug 2015 06:06:17 -0700 Subject: [PATCH] Fix RCTAssert logic --- React/Base/RCTAssert.h | 27 +++++++++++----- React/Base/RCTAssert.m | 71 +++++++++++++++++++++++++++++------------- 2 files changed, 70 insertions(+), 28 deletions(-) diff --git a/React/Base/RCTAssert.h b/React/Base/RCTAssert.h index c7a331b43..05cf7bb58 100644 --- a/React/Base/RCTAssert.h +++ b/React/Base/RCTAssert.h @@ -14,13 +14,13 @@ /** * The default error domain to be used for React errors. */ -extern NSString *const RCTErrorDomain; +RCT_EXTERN NSString *const RCTErrorDomain; /** * A block signature to be used for custom assertion handling. */ typedef void (^RCTAssertFunction)( - BOOL condition, + NSString *condition, NSString *fileName, NSNumber *lineNumber, NSString *function, @@ -30,12 +30,18 @@ typedef void (^RCTAssertFunction)( /** * This is the main assert macro that you should use. */ -#define RCTAssert(condition, ...) do { BOOL pass = ((condition) != 0); \ -if (RCT_NSASSERT && !pass) { [[NSAssertionHandler currentHandler] handleFailureInFunction:@(__func__) \ -file:@(__FILE__) lineNumber:__LINE__ description:__VA_ARGS__]; } \ -_RCTAssertFormat(pass, __FILE__, __LINE__, __func__, __VA_ARGS__); \ +#define RCTAssert(condition, ...) do { \ + if ((condition) == 0) { \ + _RCTAssertFormat(#condition, __FILE__, __LINE__, __func__, __VA_ARGS__); \ + if (RCT_NSASSERT) { \ + [[NSAssertionHandler currentHandler] handleFailureInFunction:@(__func__) \ + file:@(__FILE__) lineNumber:__LINE__ description:__VA_ARGS__]; \ + } \ + } \ } while (false) -RCT_EXTERN void _RCTAssertFormat(BOOL, const char *, int, const char *, NSString *, ...) NS_FORMAT_FUNCTION(5,6); +RCT_EXTERN void _RCTAssertFormat( + const char *, const char *, int, const char *, NSString *, ... +) NS_FORMAT_FUNCTION(5,6); /** * Convenience macro for asserting that a parameter is non-nil/non-zero. @@ -64,6 +70,13 @@ RCT_EXTERN RCTAssertFunction RCTGetAssertFunction(void); */ RCT_EXTERN void RCTAddAssertFunction(RCTAssertFunction assertFunction); +/** + * This method temporarily overrides the assert function while performing the + * specified block. This is useful for testing purposes (to detect if a given + * function asserts something) or to suppress or override assertions temporarily. + */ +RCT_EXTERN void RCTPerformBlockWithAssertFunction(void (^block)(void), RCTAssertFunction assertFunction); + /** * Get the current thread's name (or the current queue, if in debug mode) */ diff --git a/React/Base/RCTAssert.m b/React/Base/RCTAssert.m index 78506f7ce..a990f7c1b 100644 --- a/React/Base/RCTAssert.m +++ b/React/Base/RCTAssert.m @@ -11,6 +11,8 @@ NSString *const RCTErrorDomain = @"RCTErrorDomain"; +static NSString *const RCTAssertFunctionStack = @"RCTAssertFunctionStack"; + RCTAssertFunction RCTCurrentAssertFunction = nil; NSException *_RCTNotImplementedException(SEL, Class); @@ -22,26 +24,6 @@ NSException *_RCTNotImplementedException(SEL cmd, Class cls) reason:msg userInfo:nil]; } -void _RCTAssertFormat( - BOOL condition, - const char *fileName, - int lineNumber, - const char *function, - NSString *format, ...) -{ - if (RCTCurrentAssertFunction) { - - va_list args; - va_start(args, format); - NSString *message = [[NSString alloc] initWithFormat:format arguments:args]; - va_end(args); - - RCTCurrentAssertFunction( - condition, @(fileName), @(lineNumber), @(function), message - ); - } -} - void RCTSetAssertFunction(RCTAssertFunction assertFunction) { RCTCurrentAssertFunction = assertFunction; @@ -56,7 +38,7 @@ void RCTAddAssertFunction(RCTAssertFunction assertFunction) { RCTAssertFunction existing = RCTCurrentAssertFunction; if (existing) { - RCTCurrentAssertFunction = ^(BOOL condition, + RCTCurrentAssertFunction = ^(NSString *condition, NSString *fileName, NSNumber *lineNumber, NSString *function, @@ -70,6 +52,34 @@ void RCTAddAssertFunction(RCTAssertFunction assertFunction) } } +/** + * 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 *functionStack = threadDictionary[RCTAssertFunctionStack]; + RCTAssertFunction assertFunction = [functionStack lastObject]; + if (assertFunction) { + return assertFunction; + } + return RCTCurrentAssertFunction; +} + +void RCTPerformBlockWithAssertFunction(void (^block)(void), RCTAssertFunction assertFunction) +{ + NSMutableDictionary *threadDictionary = [NSThread currentThread].threadDictionary; + NSMutableArray *functionStack = threadDictionary[RCTAssertFunctionStack]; + if (!functionStack) { + functionStack = [[NSMutableArray alloc] init]; + threadDictionary[RCTAssertFunctionStack] = functionStack; + } + [functionStack addObject:assertFunction]; + block(); + [functionStack removeLastObject]; +} + NSString *RCTCurrentThreadName(void) { NSThread *thread = [NSThread currentThread]; @@ -84,3 +94,22 @@ NSString *RCTCurrentThreadName(void) } return threadName; } + +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); + } +}