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 |