1 //---------------------------------------------------------------------------------------
3 // Copyright (c) 2009 by Mulle Kybernetik. See License file for details.
4 //---------------------------------------------------------------------------------------
6 #import "OCMBoxedReturnValueProvider.h"
7 #import <objc/runtime.h>
10 #include <vector> // for _LIBCPP_ABI_VERSION to detect if using libc++
13 #if defined(__clang__) && defined(_LIBCPP_ABI_VERSION)
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;
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++.
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)
32 if (*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 == '}') {
44 const char opening = stack.back();
45 if ((opening == '<' && current != '>') ||
46 (opening == '{' && current != '}')) {
51 return current == '}';
53 } else if (current == ',' && stack.empty()) {
61 #endif // defined(__clang__) && defined(_LIBCPP_ABI_VERSION)
63 @interface OCMBoxedReturnValueProvider ()
65 - (BOOL)isValueTypeCompatibleWithInvocation:(NSInvocation *)anInvocation;
69 @implementation OCMBoxedReturnValueProvider
71 - (BOOL)isValueTypeCompatibleWithInvocation:(NSInvocation *)anInvocation {
72 const char *returnType = [[anInvocation methodSignature] methodReturnType];
73 const char *valueType = [(NSValue *)returnValue objCType];
75 #if defined(__aarch64__) || defined(__x86_64__)
76 // ARM64 uses 'B' for BOOLs in method signature but 'c' in NSValue. That case
78 if (strcmp(returnType, "B") == 0 && strcmp(valueType, "c") == 0)
80 #endif // defined(__aarch64__) || defined(__x86_64__)
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++ objects
85 // when compiling with libc++ with clang.
87 // In that configuration, the C++ class are expanded to list the types of
88 // the fields, but the depth of the expansion for templated types is larger
89 // for the value stored in the NSValue.
91 // For example, when creating a OCMOCK_VALUE with a GURL object (from the
92 // Chromium project), then the two types representations are:
94 // r^{GURL={basic_string<char, std::__1::char_traits<char>, std::__1::alloca
95 // tor<char> >={__compressed_pair<std::__1::basic_string<char, std::__1::cha
96 // r_traits<char>, std::__1::allocator<char> >::__rep, std::__1::allocator<c
97 // har> >={__rep}}}B{Parsed={Component=ii}{Component=ii}{Component=ii}{Compo
98 // nent=ii}{Component=ii}{Component=ii}{Component=ii}{Component=ii}^{Parsed}
99 // }{scoped_ptr<GURL, base::DefaultDeleter<GURL> >={scoped_ptr_impl<GURL, ba
100 // se::DefaultDeleter<GURL> >={Data=^{GURL}}}}}
102 // r^{GURL={basic_string<char, std::__1::char_traits<char>, std::__1::alloca
103 // tor<char> >={__compressed_pair<std::__1::basic_string<char, std::__1::cha
104 // r_traits<char>, std::__1::allocator<char> >::__rep, std::__1::allocator<c
105 // har> >={__rep=(?={__long=II*}{__short=(?=Cc)[11c]}{__raw=[3L]})}}}B{Parse
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}}}}}
111 // Since those types should be considered equals, we un-expand them during
112 // the comparison. For that, we remove everything following an "=" until we
113 // meet a non-matched "}" or a ",".
115 while (*returnType && *valueType) {
116 if (*returnType != *valueType)
119 if (!AdvanceTypeDescriptionPointer(returnType))
122 if (!AdvanceTypeDescriptionPointer(valueType))
126 return !*returnType && !*valueType;
128 return strcmp(returnType, valueType) == 0;
129 #endif // defined(__clang__) && defined(_LIBCPP_ABI_VERSION)
132 - (void)handleInvocation:(NSInvocation *)anInvocation
134 if (![self isValueTypeCompatibleWithInvocation:anInvocation]) {
135 const char *returnType = [[anInvocation methodSignature] methodReturnType];
136 const char *valueType = [(NSValue *)returnValue objCType];
137 @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];
139 void *buffer = malloc([[anInvocation methodSignature] methodReturnLength]);
140 [returnValue getValue:buffer];
141 [anInvocation setReturnValue:buffer];