| OLD | NEW |
| (Empty) |
| 1 //------------------------------------------------------------------------------
--------- | |
| 2 // $Id$ | |
| 3 // Copyright (c) 2009 by Mulle Kybernetik. See License file for details. | |
| 4 //------------------------------------------------------------------------------
--------- | |
| 5 | |
| 6 #import "OCMBoxedReturnValueProvider.h" | |
| 7 #import <objc/runtime.h> | |
| 8 | |
| 9 #if defined(__clang__) | |
| 10 #include <vector> // for _LIBCPP_ABI_VERSION to detect if using libc++ | |
| 11 #endif | |
| 12 | |
| 13 #if defined(__clang__) && defined(_LIBCPP_ABI_VERSION) | |
| 14 namespace { | |
| 15 // Default stack size to use when checking for matching opening and closing | |
| 16 // characters (<> and {}). This is used to reduce the number of allocations | |
| 17 // in AdvanceTypeDescriptionPointer function. | |
| 18 const size_t kDefaultStackSize = 32; | |
| 19 | |
| 20 // Move to the next pertinent character in a type description. This skips | |
| 21 // all the field expansion that clang includes in the type description when | |
| 22 // compiling with libc++. | |
| 23 // | |
| 24 // See inner comment of -isValueTypeCompatibleWithInvocation: for more details. | |
| 25 // Returns true if the pointer was advanced, false if the type description was | |
| 26 // not correctly parsed. | |
| 27 bool AdvanceTypeDescriptionPointer(const char *&typeDescription) { | |
| 28 if (!*typeDescription) | |
| 29 return true; | |
| 30 | |
| 31 ++typeDescription; | |
| 32 if (*typeDescription != '=') | |
| 33 return true; | |
| 34 | |
| 35 ++typeDescription; | |
| 36 std::vector<char> stack; | |
| 37 stack.reserve(kDefaultStackSize); | |
| 38 while (*typeDescription) { | |
| 39 const char current = *typeDescription; | |
| 40 if (current == '<' || current == '{') { | |
| 41 stack.push_back(current); | |
| 42 } else if (current == '>' || current == '}') { | |
| 43 if (!stack.empty()) { | |
| 44 const char opening = stack.back(); | |
| 45 if ((opening == '<' && current != '>') || | |
| 46 (opening == '{' && current != '}
')) { | |
| 47 return false; | |
| 48 } | |
| 49 stack.pop_back(); | |
| 50 } else { | |
| 51 return current == '}'; | |
| 52 } | |
| 53 } else if (current == ',' && stack.empty()) { | |
| 54 return true; | |
| 55 } | |
| 56 ++typeDescription; | |
| 57 } | |
| 58 return true; | |
| 59 } | |
| 60 } | |
| 61 #endif // defined(__clang__) && defined(_LIBCPP_ABI_VERSION) | |
| 62 | |
| 63 @interface OCMBoxedReturnValueProvider () | |
| 64 | |
| 65 - (BOOL)isValueTypeCompatibleWithInvocation:(NSInvocation *)anInvocation; | |
| 66 | |
| 67 @end | |
| 68 | |
| 69 @implementation OCMBoxedReturnValueProvider | |
| 70 | |
| 71 - (BOOL)isValueTypeCompatibleWithInvocation:(NSInvocation *)anInvocation { | |
| 72 const char *returnType = [[anInvocation methodSignature] methodReturnTyp
e]; | |
| 73 const char *valueType = [(NSValue *)returnValue objCType]; | |
| 74 | |
| 75 #if defined(__aarch64__) || defined(__x86_64__) | |
| 76 // ARM64 uses 'B' for BOOLs in method signature but 'c' in NSValue. That
case | |
| 77 // should match. | |
| 78 if (strcmp(returnType, "B") == 0 && strcmp(valueType, "c") == 0) | |
| 79 return YES; | |
| 80 #endif // defined(__aarch64__) || defined(__x86_64__) | |
| 81 | |
| 82 #if defined(__clang__) && defined(_LIBCPP_ABI_VERSION) | |
| 83 // The type representation of the return type of the invocation, and the | |
| 84 // type representation passed to NSValue are not the same for C++ object
s | |
| 85 // when compiling with libc++ with clang. | |
| 86 // | |
| 87 // In that configuration, the C++ class are expanded to list the types o
f | |
| 88 // the fields, but the depth of the expansion for templated types is lar
ger | |
| 89 // for the value stored in the NSValue. | |
| 90 // | |
| 91 // For example, when creating a OCMOCK_VALUE with a GURL object (from th
e | |
| 92 // Chromium project), then the two types representations are: | |
| 93 // | |
| 94 // r^{GURL={basic_string<char, std::__1::char_traits<char>, std::__1::al
loca | |
| 95 // tor<char> >={__compressed_pair<std::__1::basic_string<char, std::__1:
:cha | |
| 96 // r_traits<char>, std::__1::allocator<char> >::__rep, std::__1::allocat
or<c | |
| 97 // har> >={__rep}}}B{Parsed={Component=ii}{Component=ii}{Component=ii}{C
ompo | |
| 98 // nent=ii}{Component=ii}{Component=ii}{Component=ii}{Component=ii}^{Par
sed} | |
| 99 // }{scoped_ptr<GURL, base::DefaultDeleter<GURL> >={scoped_ptr_impl<GURL
, ba | |
| 100 // se::DefaultDeleter<GURL> >={Data=^{GURL}}}}} | |
| 101 // | |
| 102 // r^{GURL={basic_string<char, std::__1::char_traits<char>, std::__1::al
loca | |
| 103 // tor<char> >={__compressed_pair<std::__1::basic_string<char, std::__1:
:cha | |
| 104 // r_traits<char>, std::__1::allocator<char> >::__rep, std::__1::allocat
or<c | |
| 105 // har> >={__rep=(?={__long=II*}{__short=(?=Cc)[11c]}{__raw=[3L]})}}}B{P
arse | |
| 106 // d={Component=ii}{Component=ii}{Component=ii}{Component=ii}{Component=
ii}{ | |
| 107 // Component=ii}{Component=ii}{Component=ii}^{Parsed}}{scoped_ptr<GURL,
base | |
| 108 // ::DefaultDeleter<GURL> >={scoped_ptr_impl<GURL, base::DefaultDeleter<
GURL | |
| 109 // > >={Data=^{GURL}}}}} | |
| 110 // | |
| 111 // Since those types should be considered equals, we un-expand them duri
ng | |
| 112 // the comparison. For that, we remove everything following an "=" until
we | |
| 113 // meet a non-matched "}" or a ",". | |
| 114 | |
| 115 while (*returnType && *valueType) { | |
| 116 if (*returnType != *valueType) | |
| 117 return NO; | |
| 118 | |
| 119 if (!AdvanceTypeDescriptionPointer(returnType)) | |
| 120 return NO; | |
| 121 | |
| 122 if (!AdvanceTypeDescriptionPointer(valueType)) | |
| 123 return NO; | |
| 124 } | |
| 125 | |
| 126 return !*returnType && !*valueType; | |
| 127 #else | |
| 128 return strcmp(returnType, valueType) == 0; | |
| 129 #endif // defined(__clang__) && defined(_LIBCPP_ABI_VERSION) | |
| 130 } | |
| 131 | |
| 132 - (void)handleInvocation:(NSInvocation *)anInvocation | |
| 133 { | |
| 134 if (![self isValueTypeCompatibleWithInvocation:anInvocation]) { | |
| 135 const char *returnType = [[anInvocation methodSignature] methodR
eturnType]; | |
| 136 const char *valueType = [(NSValue *)returnValue objCType]; | |
| 137 @throw [NSException exceptionWithName:NSInvalidArgumentException
reason:[NSString stringWithFormat:@"Return value does not match method signatur
e; signature declares '%s' but value is '%s'.", returnType, valueType] userInfo:
nil]; | |
| 138 } | |
| 139 void *buffer = malloc([[anInvocation methodSignature] methodReturnLength
]); | |
| 140 [returnValue getValue:buffer]; | |
| 141 [anInvocation setReturnValue:buffer]; | |
| 142 free(buffer); | |
| 143 } | |
| 144 | |
| 145 @end | |
| OLD | NEW |