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 |