Update mojo sdk to rev 1dc8a9a5db73d3718d99917fadf31f5fb2ebad4f
[chromium-blink-merge.git] / third_party / ocmock / OCMock / OCMBoxedReturnValueProvider.m
blobc316b4cea1c1887d63bca113a31c01448de09a9a
1 //---------------------------------------------------------------------------------------
2 //  $Id$
3 //  Copyright (c) 2009 by Mulle Kybernetik. See License file for details.
4 //---------------------------------------------------------------------------------------
6 #import "OCMBoxedReturnValueProvider.h"
8 static const char* OCMTypeWithoutQualifiers(const char* objCType)
10     while(strchr("rnNoORV", objCType[0]) != NULL)
11         objCType += 1;
12     return objCType;
16  * Sometimes an external type is an opaque struct (which will have an @encode of "{structName}"
17  * or "{structName=}") but the actual method return type, or property type, will know the contents
18  * of the struct (so will have an objcType of say "{structName=iiSS}".  This function will determine
19  * those are equal provided they have the same structure name, otherwise everything else will be
20  * compared textually.  This can happen particularly for pointers to such structures, which still
21  * encode what is being pointed to.
22  */
23 static BOOL OCMTypesEqualAllowOpaqueStructs(const char *type1, const char *type2)
25     type1 = OCMTypeWithoutQualifiers(type1);
26     type2 = OCMTypeWithoutQualifiers(type2);
28     switch (type1[0])
29     {
30         case '{':
31         case '(':
32         {
33             if (type2[0] != type1[0])
34                 return NO;
35             char endChar = type1[0] == '{'? '}' : ')';
36             
37             const char *type1End = strchr(type1, endChar);
38             const char *type2End = strchr(type2, endChar);
39             const char *type1Equals = strchr(type1, '=');
40             const char *type2Equals = strchr(type2, '=');
41             
42             /* Opaque types either don't have an equals sign (just the name and the end brace), or
43              * empty content after the equals sign.
44              * We want that to compare the same as a type of the same name but with the content.
45              */
46             BOOL type1Opaque = (type1Equals == NULL || (type1End < type1Equals) || type1Equals[1] == endChar);
47             BOOL type2Opaque = (type2Equals == NULL || (type2End < type2Equals) || type2Equals[1] == endChar);
48             const char *type1NameEnd = (type1Equals == NULL || (type1End < type1Equals)) ? type1End : type1Equals;
49             const char *type2NameEnd = (type2Equals == NULL || (type2End < type2Equals)) ? type2End : type2Equals;
50             intptr_t type1NameLen = type1NameEnd - type1;
51             intptr_t type2NameLen = type2NameEnd - type2;
52             
53             /* If the names are not equal, return NO */
54             if (type1NameLen != type2NameLen || strncmp(type1, type2, type1NameLen))
55                 return NO;
56             
57             /* If the same name, and at least one is opaque, that is close enough. */
58             if (type1Opaque || type2Opaque)
59                 return YES;
60             
61             /* Otherwise, compare all the elements.  Use NSGetSizeAndAlignment to walk through the struct elements. */
62             type1 = type1Equals + 1;
63             type2 = type2Equals + 1;
64             while (type1[0] != endChar && type1[0] != '\0')
65             {
66                 if (!OCMTypesEqualAllowOpaqueStructs(type1, type2))
67                     return NO;
68                 type1 = NSGetSizeAndAlignment(type1, NULL, NULL);
69                 type2 = NSGetSizeAndAlignment(type2, NULL, NULL);
70             }
71             return YES;
72         }
73         case '^':
74             /* for a pointer, make sure the other is a pointer, then recursively compare the rest */
75             if (type2[0] != type1[0])
76                 return NO;
77             return OCMTypesEqualAllowOpaqueStructs(type1+1, type2+1);
78         
79         case '\0':
80             return type2[0] == '\0';
82         default:
83         {
84             // Move the type pointers past the current types, then compare that region
85             const char *afterType1 =  NSGetSizeAndAlignment(type1, NULL, NULL);
86             const char *afterType2 =  NSGetSizeAndAlignment(type2, NULL, NULL);
87             intptr_t type1Len = afterType1 - type1;
88             intptr_t type2Len = afterType2 - type2;
89             
90             return (type1Len == type2Len && (strncmp(type1, type2, type1Len) == 0));
91         }
92     }
95 static CFNumberType OCMNumberTypeForObjCType(const char *objcType)
97     switch (objcType[0])
98     {
99         case 'c': return kCFNumberCharType;
100         case 'C': return kCFNumberCharType;
101         case 'B': return kCFNumberCharType;
102         case 's': return kCFNumberShortType;
103         case 'S': return kCFNumberShortType;
104         case 'i': return kCFNumberIntType;
105         case 'I': return kCFNumberIntType;
106         case 'l': return kCFNumberLongType;
107         case 'L': return kCFNumberLongType;
108         case 'q': return kCFNumberLongLongType;
109         case 'Q': return kCFNumberLongLongType;
110         case 'f': return kCFNumberFloatType;
111         case 'd': return kCFNumberDoubleType;
112     }
113     
114     return 0;
117 static NSNumber *OCMNumberForValue(NSValue *value)
119 #define CREATE_NUM(_type, _meth) ({ _type _v; [value getValue:&_v]; [NSNumber _meth _v]; })
120     switch ([value objCType][0])
121     {
122         case 'c': return CREATE_NUM(char,               numberWithChar:);
123         case 'C': return CREATE_NUM(unsigned char,      numberWithUnsignedChar:);
124         case 'B': return CREATE_NUM(bool,               numberWithBool:);
125         case 's': return CREATE_NUM(short,              numberWithShort:);
126         case 'S': return CREATE_NUM(unsigned short,     numberWithUnsignedShort:);
127         case 'i': return CREATE_NUM(int,                numberWithInt:);
128         case 'I': return CREATE_NUM(unsigned int,       numberWithUnsignedInt:);
129         case 'l': return CREATE_NUM(long,               numberWithLong:);
130         case 'L': return CREATE_NUM(unsigned long,      numberWithUnsignedLong:);
131         case 'q': return CREATE_NUM(long long,          numberWithLongLong:);
132         case 'Q': return CREATE_NUM(unsigned long long, numberWithUnsignedLongLong:);
133         case 'f': return CREATE_NUM(float,              numberWithFloat:);
134         case 'd': return CREATE_NUM(double,             numberWithDouble:);
135     }
136     
137     return nil;
140 @implementation OCMBoxedReturnValueProvider
142 - (void)handleInvocation:(NSInvocation *)anInvocation
144     const char *returnType = [[anInvocation methodSignature] methodReturnType];
145     NSUInteger returnTypeSize = [[anInvocation methodSignature] methodReturnLength];
146     char valueBuffer[returnTypeSize];
147     NSValue *value = (NSValue *)returnValue;
148     
149     if ([self getBytes:valueBuffer forValue:value compatibleWithType:returnType])
150     {
151         [anInvocation setReturnValue:valueBuffer];
152     }
153     else
154     {
155         [NSException raise:NSInvalidArgumentException
156                     format:@"Return value does not match method signature; signature declares '%s' but value is '%s'.", returnType, [value objCType]];
157     }
160 - (BOOL)isMethodReturnType:(const char *)returnType compatibleWithValueType:(const char *)valueType
162     /* Allow void* for methods that return id, mainly to be able to handle nil */
163     if(strcmp(returnType, @encode(id)) == 0 && strcmp(valueType, @encode(void *)) == 0)
164         return YES;
165     
166     /* Same types are obviously compatible */
167     if(strcmp(returnType, valueType) == 0)
168         return YES;
169     
170     @try {
171         if(OCMTypesEqualAllowOpaqueStructs(returnType, valueType))
172             return YES;
173     }
174     @catch (NSException *e) {
175         /* Probably a bitfield or something that NSGetSizeAndAlignment chokes on, oh well */
176         return NO;
177     }
179     return NO;
182 - (BOOL)getBytes:(void *)outputBuf forValue:(NSValue *)inputValue compatibleWithType:(const char *)targetType
184     /* If the types are directly compatible, use it */
185     if ([self isMethodReturnType:targetType compatibleWithValueType:[inputValue objCType]])
186     {
187         [inputValue getValue:outputBuf];
188         return YES;
189     }
191     /*
192      * See if they are similar number types, and if we can convert losslessly between them.
193      * For the most part, we set things up to use CFNumberGetValue, which returns false if
194      * conversion will be lossy.
195      */
196     CFNumberType inputType = OCMNumberTypeForObjCType([inputValue objCType]);
197     CFNumberType outputType = OCMNumberTypeForObjCType(targetType);
198     
199     if (inputType == 0 || outputType == 0) // one or both are non-number types
200         return NO;
201     
203     NSNumber *inputNumber = [inputValue isKindOfClass:[NSNumber class]]? (id)inputValue : OCMNumberForValue(inputValue);
204     
205     /*
206      * Due to some legacy, back-compatible requirements in CFNumber.c, CFNumberGetValue can return true for
207      * some conversions which should not be allowed (by reading source, conversions from integer types to
208      * 8-bit or 16-bit integer types).  So, check ourselves.
209      */
210     long long min = LLONG_MIN;
211     long long max = LLONG_MAX;
212     long long val = [inputNumber longLongValue];
213     switch (targetType[0])
214     {
215         case 'B':
216         case 'c': min = CHAR_MIN; max =  CHAR_MAX; break;
217         case 'C': min =        0; max = UCHAR_MAX; break;
218         case 's': min = SHRT_MIN; max =  SHRT_MAX; break;
219         case 'S': min =        0; max = USHRT_MAX; break;
220     }
221     if (val < min || val > max)
222         return NO;
224     /* Get the number, and return NO if the value was out of range or conversion was lossy */
225     return CFNumberGetValue((CFNumberRef)inputNumber, outputType, outputBuf);
228 @end