summaryrefslogtreecommitdiffstats
path: root/third_party/ocmock/OCMock/OCMBoxedReturnValueProvider.mm
blob: cf59610344e39efd15c622124356391c2dc490d4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
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