Index: third_party/ocmock/OCMock/OCMBoxedReturnValueProvider.mm |
diff --git a/third_party/ocmock/OCMock/OCMBoxedReturnValueProvider.mm b/third_party/ocmock/OCMock/OCMBoxedReturnValueProvider.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..cf59610344e39efd15c622124356391c2dc490d4 |
--- /dev/null |
+++ b/third_party/ocmock/OCMock/OCMBoxedReturnValueProvider.mm |
@@ -0,0 +1,145 @@ |
+//--------------------------------------------------------------------------------------- |
+// $Id$ |
+// Copyright (c) 2009 by Mulle Kybernetik. See License file for details. |
+//--------------------------------------------------------------------------------------- |
+ |
+#import "OCMBoxedReturnValueProvider.h" |
+#import <objc/runtime.h> |
+ |
+#if defined(__clang__) |
+#include <vector> // for _LIBCPP_ABI_VERSION to detect if using libc++ |
+#endif |
+ |
+#if defined(__clang__) && defined(_LIBCPP_ABI_VERSION) |
+namespace { |
+// Default stack size to use when checking for matching opening and closing |
+// characters (<> and {}). This is used to reduce the number of allocations |
+// in AdvanceTypeDescriptionPointer function. |
+const size_t kDefaultStackSize = 32; |
+ |
+// Move to the next pertinent character in a type description. This skips |
+// all the field expansion that clang includes in the type description when |
+// compiling with libc++. |
+// |
+// See inner comment of -isValueTypeCompatibleWithInvocation: for more details. |
+// Returns true if the pointer was advanced, false if the type description was |
+// not correctly parsed. |
+bool AdvanceTypeDescriptionPointer(const char *&typeDescription) { |
+ if (!*typeDescription) |
+ return true; |
+ |
+ ++typeDescription; |
+ if (*typeDescription != '=') |
+ return true; |
+ |
+ ++typeDescription; |
+ std::vector<char> stack; |
+ stack.reserve(kDefaultStackSize); |
+ while (*typeDescription) { |
+ const char current = *typeDescription; |
+ if (current == '<' || current == '{') { |
+ stack.push_back(current); |
+ } else if (current == '>' || current == '}') { |
+ if (!stack.empty()) { |
+ const char opening = stack.back(); |
+ if ((opening == '<' && current != '>') || |
+ (opening == '{' && current != '}')) { |
+ return false; |
+ } |
+ stack.pop_back(); |
+ } else { |
+ return current == '}'; |
+ } |
+ } else if (current == ',' && stack.empty()) { |
+ return true; |
+ } |
+ ++typeDescription; |
+ } |
+ return true; |
+} |
+} |
+#endif // defined(__clang__) && defined(_LIBCPP_ABI_VERSION) |
+ |
+@interface OCMBoxedReturnValueProvider () |
+ |
+- (BOOL)isValueTypeCompatibleWithInvocation:(NSInvocation *)anInvocation; |
+ |
+@end |
+ |
+@implementation OCMBoxedReturnValueProvider |
+ |
+- (BOOL)isValueTypeCompatibleWithInvocation:(NSInvocation *)anInvocation { |
+ const char *returnType = [[anInvocation methodSignature] methodReturnType]; |
+ const char *valueType = [(NSValue *)returnValue objCType]; |
+ |
+#if defined(__aarch64__) || defined(__x86_64__) |
+ // ARM64 uses 'B' for BOOLs in method signature but 'c' in NSValue. That case |
+ // should match. |
+ if (strcmp(returnType, "B") == 0 && strcmp(valueType, "c") == 0) |
+ return YES; |
+#endif // defined(__aarch64__) || defined(__x86_64__) |
+ |
+#if defined(__clang__) && defined(_LIBCPP_ABI_VERSION) |
+ // The type representation of the return type of the invocation, and the |
+ // type representation passed to NSValue are not the same for C++ objects |
+ // when compiling with libc++ with clang. |
+ // |
+ // In that configuration, the C++ class are expanded to list the types of |
+ // the fields, but the depth of the expansion for templated types is larger |
+ // for the value stored in the NSValue. |
+ // |
+ // For example, when creating a OCMOCK_VALUE with a GURL object (from the |
+ // Chromium project), then the two types representations are: |
+ // |
+ // r^{GURL={basic_string<char, std::__1::char_traits<char>, std::__1::alloca |
+ // tor<char> >={__compressed_pair<std::__1::basic_string<char, std::__1::cha |
+ // r_traits<char>, std::__1::allocator<char> >::__rep, std::__1::allocator<c |
+ // har> >={__rep}}}B{Parsed={Component=ii}{Component=ii}{Component=ii}{Compo |
+ // nent=ii}{Component=ii}{Component=ii}{Component=ii}{Component=ii}^{Parsed} |
+ // }{scoped_ptr<GURL, base::DefaultDeleter<GURL> >={scoped_ptr_impl<GURL, ba |
+ // se::DefaultDeleter<GURL> >={Data=^{GURL}}}}} |
+ // |
+ // r^{GURL={basic_string<char, std::__1::char_traits<char>, std::__1::alloca |
+ // tor<char> >={__compressed_pair<std::__1::basic_string<char, std::__1::cha |
+ // r_traits<char>, std::__1::allocator<char> >::__rep, std::__1::allocator<c |
+ // har> >={__rep=(?={__long=II*}{__short=(?=Cc)[11c]}{__raw=[3L]})}}}B{Parse |
+ // d={Component=ii}{Component=ii}{Component=ii}{Component=ii}{Component=ii}{ |
+ // Component=ii}{Component=ii}{Component=ii}^{Parsed}}{scoped_ptr<GURL, base |
+ // ::DefaultDeleter<GURL> >={scoped_ptr_impl<GURL, base::DefaultDeleter<GURL |
+ // > >={Data=^{GURL}}}}} |
+ // |
+ // Since those types should be considered equals, we un-expand them during |
+ // the comparison. For that, we remove everything following an "=" until we |
+ // meet a non-matched "}" or a ",". |
+ |
+ while (*returnType && *valueType) { |
+ if (*returnType != *valueType) |
+ return NO; |
+ |
+ if (!AdvanceTypeDescriptionPointer(returnType)) |
+ return NO; |
+ |
+ if (!AdvanceTypeDescriptionPointer(valueType)) |
+ return NO; |
+ } |
+ |
+ return !*returnType && !*valueType; |
+#else |
+ return strcmp(returnType, valueType) == 0; |
+#endif // defined(__clang__) && defined(_LIBCPP_ABI_VERSION) |
+} |
+ |
+- (void)handleInvocation:(NSInvocation *)anInvocation |
+{ |
+ if (![self isValueTypeCompatibleWithInvocation:anInvocation]) { |
+ const char *returnType = [[anInvocation methodSignature] methodReturnType]; |
+ const char *valueType = [(NSValue *)returnValue objCType]; |
+ @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"Return value does not match method signature; signature declares '%s' but value is '%s'.", returnType, valueType] userInfo:nil]; |
+ } |
+ void *buffer = malloc([[anInvocation methodSignature] methodReturnLength]); |
+ [returnValue getValue:buffer]; |
+ [anInvocation setReturnValue:buffer]; |
+ free(buffer); |
+} |
+ |
+@end |