diff --git a/Source/OCMock/OCMStubRecorder.h b/Source/OCMock/OCMStubRecorder.h index 383d9b45..7ba2040e 100644 --- a/Source/OCMock/OCMStubRecorder.h +++ b/Source/OCMock/OCMStubRecorder.h @@ -18,6 +18,7 @@ #import #import +#import #if !TARGET_OS_WATCH @class XCTestExpectation; @@ -42,15 +43,32 @@ @interface OCMStubRecorder (Properties) -#define andReturn(aValue) _andReturn(({ \ - __typeof__(aValue) _val = (aValue); \ - NSValue *_nsval = [NSValue value:&_val withObjCType:@encode(__typeof__(_val))]; \ - if (OCMIsObjectType(@encode(__typeof(_val)))) { \ - objc_setAssociatedObject(_nsval, "OCMAssociatedBoxedValue", *(__unsafe_unretained id *) (void *) &_val, OBJC_ASSOCIATION_RETAIN); \ - } \ - _nsval; \ +// Used to autoselect `return` vs `returnValue` based on type. +// Gets complicated because NSValue cannot be NSCoded if it contains a type +// of void*, and typeof(nil) is void* in ObjC (but not ObjC++) code. +// Also we want to avoid undefined behaviours by casting _val into an id +// if it isn't a pointer type. +#define andReturn(aValue) _andReturn(({ \ + __typeof__(aValue) _val = (aValue); \ + const char *_encoding = @encode(__typeof(aValue)); \ + const void *_nilPtr = nil; \ + BOOL _objectOrNil = OCMIsObjectType(_encoding) || \ + (strcmp(_encoding, @encode(void *)) == 0 && \ + memcmp((void*)&_val, &_nilPtr, sizeof(void*)) == 0); \ + id _retVal; \ + if(_objectOrNil) \ + { \ + __unsafe_unretained id _unsafeId; \ + memcpy(&_unsafeId, (void*)&_val, sizeof(id)); \ + _retVal = _unsafeId; \ + } \ + else \ + { \ + _retVal = [NSValue valueWithBytes:&_val objCType:_encoding]; \ + } \ + [NSArray arrayWithObjects:@(_objectOrNil), _retVal, nil]; \ })) -@property (nonatomic, readonly) OCMStubRecorder *(^ _andReturn)(NSValue *); +@property (nonatomic, readonly) OCMStubRecorder *(^ _andReturn)(NSArray *); #define andThrow(anException) _andThrow(anException) @property (nonatomic, readonly) OCMStubRecorder *(^ _andThrow)(NSException *); diff --git a/Source/OCMock/OCMStubRecorder.m b/Source/OCMock/OCMStubRecorder.m index 2c2448b1..09dbeed0 100644 --- a/Source/OCMock/OCMStubRecorder.m +++ b/Source/OCMock/OCMStubRecorder.m @@ -126,21 +126,14 @@ @implementation OCMStubRecorder (Properties) @dynamic _andReturn; -- (OCMStubRecorder * (^)(NSValue *))_andReturn -{ - id (^theBlock)(id) = ^(NSValue *aValue) { - if(OCMIsObjectType([aValue objCType])) - { - id objValue = nil; - [aValue getValue:&objValue]; // TODO: deprecated but replacement available in 10.13 only - return [self andReturn:objValue]; - } - else - { - return [self andReturnValue:aValue]; - } +- (OCMStubRecorder *(^)(NSArray *))_andReturn +{ + id (^theBlock)(NSArray *) = ^ (NSArray *aTuple) + { + id value = (aTuple.count == 1) ? nil : aTuple[1]; + return [aTuple[0] boolValue] ? [self andReturn:value] : [self andReturnValue:value]; }; - return (id)[[theBlock copy] autorelease]; + return [[theBlock copy] autorelease]; } diff --git a/Source/OCMock/OCMockMacros.h b/Source/OCMock/OCMockMacros.h index 22535572..f0155849 100644 --- a/Source/OCMock/OCMockMacros.h +++ b/Source/OCMock/OCMockMacros.h @@ -34,68 +34,76 @@ #define OCMObserverMock() [OCMockObject observerMock] -#define OCMStub(invocation) \ +#define OCMStubWithStateClass(MacroStateClass, invocation) \ ({ \ _OCMSilenceWarnings( \ - [OCMMacroState beginStubMacro]; \ + [MacroStateClass beginStubMacro]; \ OCMStubRecorder *recorder = nil; \ @try{ \ invocation; \ }@catch(...){ \ - [[OCMMacroState globalState] setInvocationDidThrow:YES]; \ + [[MacroStateClass globalState] setInvocationDidThrow:YES]; \ /* NOLINTNEXTLINE(google-objc-avoid-throwing-exception) */ \ @throw; \ }@finally{ \ - recorder = [OCMMacroState endStubMacro]; \ + recorder = [MacroStateClass endStubMacro]; \ } \ recorder; \ ); \ }) -#define OCMExpect(invocation) \ +#define OCMStub(invocation) OCMStubWithStateClass(OCMMacroState, invocation) + +#define OCMExpectWithStateClass(MacroStateClass, invocation) \ ({ \ _OCMSilenceWarnings( \ - [OCMMacroState beginExpectMacro]; \ + [MacroStateClass beginExpectMacro]; \ OCMStubRecorder *recorder = nil; \ @try{ \ invocation; \ }@catch(...){ \ - [[OCMMacroState globalState] setInvocationDidThrow:YES]; \ + [[MacroStateClass globalState] setInvocationDidThrow:YES]; \ /* NOLINTNEXTLINE(google-objc-avoid-throwing-exception) */ \ @throw; \ }@finally{ \ - recorder = [OCMMacroState endExpectMacro]; \ + recorder = [MacroStateClass endExpectMacro]; \ } \ recorder; \ ); \ }) -#define OCMReject(invocation) \ +#define OCMExpect(invocation) OCMExpectWithStateClass(OCMMacroState, invocation) + +#define OCMRejectWithStateClass(MacroStateClass, invocation) \ ({ \ _OCMSilenceWarnings( \ - [OCMMacroState beginRejectMacro]; \ + [MacroStateClass beginRejectMacro]; \ OCMStubRecorder *recorder = nil; \ @try{ \ invocation; \ }@catch(...){ \ - [[OCMMacroState globalState] setInvocationDidThrow:YES]; \ + [[MacroStateClass globalState] setInvocationDidThrow:YES]; \ /* NOLINTNEXTLINE(google-objc-avoid-throwing-exception) */ \ @throw; \ }@finally{ \ - recorder = [OCMMacroState endRejectMacro]; \ + recorder = [MacroStateClass endRejectMacro]; \ } \ recorder; \ ); \ }) +#define OCMReject(invocation) OCMRejectWithStateClass(OCMMacroState, invocation) -#define OCMClassMethod(invocation) \ + +#define OCMClassMethodWithStateClass(MacroStateClass, invocation) \ _OCMSilenceWarnings( \ - [[OCMMacroState globalState] switchToClassMethod]; \ + [[MacroStateClass globalState] switchToClassMethod]; \ invocation; \ ); +#define OCMClassMethod(invocation) OCMClassMethodWithStateClass(OCMMacroState, invocation) + #ifndef OCM_DISABLE_SHORT_SYNTAX #define ClassMethod(invocation) OCMClassMethod(invocation) @@ -106,38 +114,43 @@ #define OCMVerifyAllWithDelay(mock, delay) [(OCMockObject *)mock verifyWithDelay:delay atLocation:OCMMakeLocation(self, __FILE__, __LINE__)] -#define _OCMVerify(invocation) \ +#define _OCMVerifyWithStateClass(MacroStateClass, invocation) \ ({ \ _OCMSilenceWarnings( \ - [OCMMacroState beginVerifyMacroAtLocation:OCMMakeLocation(self, __FILE__, __LINE__)]; \ + [MacroStateClass beginVerifyMacroAtLocation:OCMMakeLocation(self, __FILE__, __LINE__)]; \ @try{ \ invocation; \ }@catch(...){ \ - [[OCMMacroState globalState] setInvocationDidThrow:YES]; \ + [[MacroStateClass globalState] setInvocationDidThrow:YES]; \ /* NOLINTNEXTLINE(google-objc-avoid-throwing-exception) */ \ @throw; \ }@finally{ \ - [OCMMacroState endVerifyMacro]; \ + [MacroStateClass endVerifyMacro]; \ } \ ); \ }) -#define _OCMVerifyWithQuantifier(quantifier, invocation) \ +#define _OCMVerify(invocation) _OCMVerifyWithStateClass(OCMMacroState, invocation) + + +#define _OCMVerifyWithQuantifierAndStateClass(MacroStateClass, quantifier, invocation) \ ({ \ _OCMSilenceWarnings( \ - [OCMMacroState beginVerifyMacroAtLocation:OCMMakeLocation(self, __FILE__, __LINE__) withQuantifier:quantifier]; \ + [MacroStateClass beginVerifyMacroAtLocation:OCMMakeLocation(self, __FILE__, __LINE__) withQuantifier:quantifier]; \ @try{ \ invocation; \ }@catch(...){ \ - [[OCMMacroState globalState] setInvocationDidThrow:YES]; \ + [[MacroStateClass globalState] setInvocationDidThrow:YES]; \ /* NOLINTNEXTLINE(google-objc-avoid-throwing-exception) */ \ @throw; \ }@finally{ \ - [OCMMacroState endVerifyMacro]; \ + [MacroStateClass endVerifyMacro]; \ } \ ); \ }) +#define _OCMVerifyWithQuantifier(quantifier, invocation) _OCMVerifyWithQuantifierAndStateClass(OCMMacroState, quantifier, invocation) + // explanation for macros below here: https://stackoverflow.com/questions/3046889/optional-parameters-with-c-macros #define _OCMVerify_1(A) _OCMVerify(A)