]> git.wincent.com - WOTest.git/blob - WOStub.m
Fix object-to-pointer comparisons on Leopard
[WOTest.git] / WOStub.m
1 //
2 //  WOStub.m
3 //  WOTest
4 //
5 //  Created by Wincent Colaiuta on 14 June 2005.
6 //
7 //  Copyright 2005-2007 Wincent Colaiuta.
8 //  This program is free software: you can redistribute it and/or modify
9 //  it under the terms of the GNU General Public License as published by
10 //  the Free Software Foundation, either version 3 of the License, or
11 //  (at your option) any later version.
12 //
13 //  This program is distributed in the hope that it will be useful,
14 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
15 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 //  GNU General Public License for more details.
17 //
18 //  You should have received a copy of the GNU General Public License
19 //  along with this program.  If not, see <http://www.gnu.org/licenses/>.
20 //
21
22 // class header
23 #import "WOStub.h"
24
25 // system headers
26 #import <objc/objc-class.h>
27
28 // framework headers
29 #import "NSInvocation+WOTest.h"
30 #import "NSMethodSignature+WOTest.h"
31 #import "NSObject+WOTest.h"
32 #import "NSProxy+WOTest.h"
33 #import "NSValue+WOTest.h"
34 #import "WOMock.h"
35
36 @implementation WOStub
37
38 - (id)init
39 {
40     return self; // super (NSProxy) has no init method    
41 }
42
43 - (void)dealloc
44 {
45     [self setInvocation:nil];
46     [self setReturnValue:nil];
47     [self setException:nil];
48     [super dealloc];
49 }
50
51 - (id)anyArguments
52 {
53     [self setAcceptsAnyArguments:YES];
54     return self;
55 }
56
57 #pragma mark -
58 #pragma mark Recording
59
60 - (id)returning:(NSValue *)aValue
61 {
62     NSAssert(([self returnValue] == nil), @"WOStub returning: invoked but return value already recorded");
63     [self setReturnValue:aValue];
64     return self;
65 }
66
67 - (id)raising:(id)anException
68 {
69     NSAssert(([self exception] == nil), @"WOStub raising: invoked but exception already recorded");
70     [self setException:anException];
71     return self;
72 }
73
74 #pragma mark -
75 #pragma mark Testing equality
76
77 - (BOOL)matchesInvocation:(NSInvocation *)anInvocation
78 {
79     NSParameterAssert(anInvocation != nil);
80     NSInvocation *recordedInvocation = [self invocation];
81     NSAssert((recordedInvocation != nil), @"WOStub sent matchesInvocation but no invocation yet recorded");
82     return ([anInvocation WOTest_isEqualToInvocation:recordedInvocation] ||
83             ([self acceptsAnyArguments] && [anInvocation WOTest_isEqualToInvocationIgnoringArguments:recordedInvocation]));
84 }
85
86 #pragma mark -
87 #pragma mark Proxy methods 
88
89 - (void)forwardInvocation:(NSInvocation *)anInvocation
90 {
91     NSAssert(([self invocation] == nil), @"WOStub sent message but message previously recorded");
92     [self setInvocation:anInvocation];
93     [anInvocation retainArguments];
94 }
95
96 /*
97  
98  http://lists.apple.com/archives/cocoa-dev/2004/Jun/msg00990.html
99  
100  "On PPC, the prearg area is used to store the 13 floating-point parameter registers, which may contain method parameters that need to be restored when the marg_list is used. The i386 function call ABI has no additional registers to be saved, so its prearg area is empty. The implementations of _objc_msgForward() and objc_msgSendv() in objc4's objc-msg-ppc.s contain more details that may be useful to you.
101  
102  In general, you probably want to avoid marg_list and objc_msgSendv(). Together they are primarily an implementation detail of forward:: ." 
103
104  (Greg Parker, Apple)
105  
106  */
107
108 - forward:(SEL)sel :(marg_list)args
109 {
110     NSAssert(([self invocation] == nil), @"WOStub sent message but message previously recorded");
111
112     // let standard event flow take place (but note that NSProxy implementation raises so subclasses must do the real work)
113     if ([self methodSignatureForSelector:sel])
114       return [super forward:sel :args];
115     
116     // fallback to internal lookup
117     NSMethodSignature *signature = [[delegate methodSignatures] objectForKey:NSStringFromSelector(sel)];
118     
119     // at this point it would be great to be able to improvise but it's not possible to figure out an accurate method signature
120     NSAssert((signature != nil), ([NSString stringWithFormat:@"no method signature for selector %@", NSStringFromSelector(sel)]));
121     
122     NSInvocation *forwardInvocation = [NSInvocation invocationWithMethodSignature:signature];
123     [forwardInvocation setSelector:sel];
124     
125     
126     // store arguments in invocation
127     int offset = 0;
128     for (unsigned i = 0, max = [signature numberOfArguments]; i < max; i++)
129     {
130         const char *type = [signature getArgumentTypeAtIndex:i];    // always id, SEL, ...
131                 
132 #if defined(__ppc__)
133
134         // TODO: finish this implementation and copy it (or otherwise make it available) to WOMock.m
135         // leave the compiler warnings about "unused variable 'type'" and "unused variable 'offset'" to remind me to do it
136         
137         // the PPC ABI has special conventions for floats, doubles, structs
138
139         // contents of floating point registers f1 through f13 stored at args + 0 through args + 96
140         // register based params go in r3 (receiver id), r4 (SEL), and r5 through r10
141         // these params are respectively at:
142         // 1st param: r3 (receiver id) args + (13 * 8) + 24 (24 bytes in the linkage area not sure what it's for)
143         // 2nd param: r4 (SEL) args + (13 * 8) + 28
144         // 3rd param: r5 args + (13 * 8) + 32
145         // 4th param: r6 args + (13 * 8) + 36
146         // 5th param: r7 args + (13 * 8) + 40
147         // 6th param: r8 args + (13 * 8) + 44
148         // 7th param: r9 args + (13 * 8) + 48
149         // 8th param: r10 args + (13 * 8) + 52
150         // the remaining parameters are on the stack (starting at args + (13 * 8) + 56) 
151         // note that marg_prearg_size is defined in the headers for ppc and equals 128 (13 * 8 + 24 bytes for linkage area)
152         
153         // from http://darwinsource.opendarwin.org/10.4.3/objc4-267/runtime/Messengers.subproj/objc-msg-ppc.s
154 //        typedef struct objc_sendv_margs {
155 //            double    floatingPointArgs[13];
156 //            int     linkageArea[6];
157 //            int               registerArgs[8];
158 //            int               stackArgs[variable];
159 //        };
160 //        
161 //        if (strcmp(type, @encode(float)) == 0)
162 //        {}
163 //        else if (strcmp(type, @encode(double)) == 0)
164 //        {}
165 //        else
166         
167 #elif defined(__i386__)
168
169         // on i386 the marg_getRef macro and its helper, marg_adjustedOffset, should work fine
170         if (strcmp(type, @encode(id)) == 0)
171         {
172             id *ref = marg_getRef(args, offset, id);
173             [forwardInvocation WOTest_setArgumentValue:[NSValue valueWithBytes:ref objCType:type] atIndex:i];
174         }
175         else if (strcmp(type, @encode(SEL)) == 0)
176         {
177             SEL *ref = marg_getRef(args, offset, SEL);
178             [forwardInvocation WOTest_setArgumentValue:[NSValue valueWithBytes:ref objCType:type] atIndex:i];
179         }
180         else
181             [NSException raise:NSGenericException format:@"type %s not supported", type];
182         
183         offset += [NSValue WOTest_sizeForType:[NSString stringWithUTF8String:type]];
184
185 #elif defined(__ppc64__)
186         // there is no objc-msg-ppc.s so for now just omit support rather than make assumptions
187 #error ppc64 not supported yet
188             
189 #else
190
191 #error Unsupported architecture
192
193 #endif
194         
195     }
196     [self forwardInvocation:forwardInvocation];
197     return nil; // nobody cares what a stub returns
198 }
199
200 #pragma mark -
201 #pragma mark Accessors
202
203 - (NSInvocation *)recordedInvocation
204 {
205     NSInvocation *recordedInvocation = [self invocation];
206     NSAssert((recordedInvocation != nil), @"WOStub sent recordedInvocation but no invocation yet recorded");    
207     return recordedInvocation;
208 }
209
210 - (NSInvocation *)invocation
211 {
212     return [[invocation retain] autorelease]; 
213 }
214
215 - (void)setInvocation:(NSInvocation *)anInvocation
216 {
217     if (invocation != anInvocation)
218     {
219         [anInvocation retain];
220         [invocation release];
221         invocation = anInvocation;
222     }
223 }
224
225 - (NSValue *)returnValue
226 {
227     return [[returnValue retain] autorelease]; 
228 }
229
230 - (void)setReturnValue:(NSValue *)aReturnValue
231 {
232     if (returnValue != aReturnValue)
233     {
234         [aReturnValue retain];
235         [returnValue release];
236         returnValue = aReturnValue;
237     }
238 }
239
240 - (BOOL)acceptsAnyArguments
241 {
242     return acceptsAnyArguments;
243 }
244
245 - (void)setAcceptsAnyArguments:(BOOL)flag
246 {
247     acceptsAnyArguments = flag;
248 }
249
250 - (id)exception
251 {
252     return [[exception retain] autorelease]; 
253 }
254
255 - (void)setException:(id)anException
256 {
257     if (exception != anException)
258     {
259         [anException retain];
260         [exception release];
261         exception = anException;
262     }
263 }
264
265 @end