1/* Test the Modern GNU Objective-C Runtime API.
2
3   This is test 'resolve-method', covering +resolveClassMethod: and
4   +resolveInstanceMethod:.  */
5
6/* { dg-do run } */
7/* { dg-skip-if "" { *-*-* } { "-fnext-runtime" } { "" } } */
8
9/* To get the modern GNU Objective-C Runtime API, you include
10   objc/runtime.h.  */
11#include <objc/runtime.h>
12
13/* For __objc_msg_forward2.  */
14#include <objc/message.h>
15
16#include <stdlib.h>
17#include <iostream>
18#include <cstring>
19
20@interface MyRootClass
21{ Class isa; }
22+ alloc;
23- init;
24@end
25
26@implementation MyRootClass
27+ alloc { return class_createInstance (self, 0); }
28- init  { return self; }
29@end
30
31
32/* A number of tests will try invoking methods that don't exist.  We
33   want to record the fact, but not abort the program, so we supply
34   our own fowarding implementation which will invoke the following
35   function for any method that is not found.  */
36
37/* Keep track of how many times a non-existing method was executed.  */
38static int nonExistingMethodCount = 0;
39
40/* Inspired by nil_method in libobjc.  */
41id nonExisting_method (id receiver __attribute__ ((__unused__)),
42		       SEL sel __attribute__ ((__unused__)))
43{
44  nonExistingMethodCount++;
45  return nil;
46}
47
48/* Keep track of how many times the forwarding lookup was invoked.  */
49static int forwardingCount = 0;
50
51/* We install this forwarding hook to cause all failed method lookups
52   to call our 'nonExisting_method' function.  */
53IMP forward_everything_to_non_existing_method (id receiver __attribute__ ((__unused__)),
54					       SEL sel __attribute__ ((__unused__)))
55{
56  forwardingCount++;
57  return (IMP)nonExisting_method;
58}
59
60
61/* 'CountClass' is used to test that +resolveClassMethod: and
62   +resolveInstanceMethod: are called when expected.  They do nothing
63   other than recording that they are called.  */
64@interface CountClass : MyRootClass
65+ (BOOL) resolveClassMethod: (SEL)selector;
66+ (BOOL) resolveInstanceMethod: (SEL)selector;
67+ (void) existingClassMethod;
68- (void) existingInstanceMethod;
69@end
70
71/* Count how many times the methods are called for class
72   'CountClass'.  */
73static int resolveClassMethodCount = 0;
74static int resolveInstanceMethodCount = 0;
75
76@implementation CountClass : MyRootClass
77+ (BOOL) resolveClassMethod: (SEL)selector
78{
79  resolveClassMethodCount++;
80  return NO;
81}
82+ (BOOL) resolveInstanceMethod: (SEL)selector
83{
84  resolveInstanceMethodCount++;
85  return NO;
86}
87+ (void) existingClassMethod
88{
89  return;
90}
91- (void) existingInstanceMethod
92{
93  return;
94}
95@end
96
97@protocol NonExistingStuff
98+ (void) nonExistingClassMethod;
99- (void) nonExistingInstanceMethod;
100@end
101
102/* Declare a category with some non existing methods, but don't
103   actually implement them.  */
104@interface CountClass (NonExistingStuff) <NonExistingStuff>
105@end
106
107
108/* 'SelfExtendingClass' is used to test that +resolveClassMethod: and
109   +resolveInstanceMethod: can extend the class.  Any time they are
110   called, they install the requested method, mapping it to the same
111   implementation as 'countHits'.  */
112@interface SelfExtendingClass : MyRootClass
113+ (int) countHits;
114+ (BOOL) resolveClassMethod: (SEL)selector;
115+ (BOOL) resolveInstanceMethod: (SEL)selector;
116@end
117
118/* How many times the countHits method (or a clone) was called.  */
119static int hitCount = 0;
120
121@implementation SelfExtendingClass : MyRootClass
122+ (int) countHits
123{
124  hitCount++;
125  return hitCount;
126}
127+ (BOOL) resolveClassMethod: (SEL)selector
128{
129  /* Duplicate the 'countHits' method into the new method.  */
130  Method method = class_getClassMethod (self, @selector (countHits));
131  class_addMethod (object_getClass (self), selector,
132		   method_getImplementation (method),
133		   method_getTypeEncoding (method));
134  resolveClassMethodCount++;
135  return YES;
136}
137+ (BOOL) resolveInstanceMethod: (SEL)selector
138{
139  /* Duplicate the 'countHits' method into the new method.  */
140  Method method = class_getClassMethod (self, @selector (countHits));
141  class_addMethod (self, selector,
142		   method_getImplementation (method),
143		   method_getTypeEncoding (method));
144  resolveInstanceMethodCount++;
145  return YES;
146
147}
148@end
149
150@protocol NonExistingStuff2
151+ (int) nonExistingCountHitsMethod;
152- (int) nonExistingCountHitsMethod;
153
154+ (int) nonExistingCountHitsMethod2;
155- (int) nonExistingCountHitsMethod2;
156
157+ (int) nonExistingCountHitsMethod3;
158- (int) nonExistingCountHitsMethod3;
159@end
160
161/* Declare a category with some non existing methods, but don't
162   actually implement them.  */
163@interface SelfExtendingClass (NonExistingStuff) <NonExistingStuff2>
164@end
165
166
167int main ()
168{
169  /* Functions are tested in alphabetical order.  */
170
171  /* Install our test forwarding hook.  */
172  __objc_msg_forward2 = forward_everything_to_non_existing_method;
173
174  std::cout << "Testing [+resolveClassMethod:] ...\n";
175  {
176    Method m;
177    IMP i;
178
179    /** CountClass tests.  **/
180
181    /* Call an existing method.  No +resolveClassMethod and no
182       forwarding should be triggered.  */
183    [CountClass existingClassMethod];
184
185    if (resolveClassMethodCount != 0)
186      abort ();
187
188    if (forwardingCount != 0)
189      abort ();
190
191    if (nonExistingMethodCount != 0)
192      abort ();
193
194    /* Call a non-existing method.  Both +resolveClassMethod and the
195       forwarding should be triggered.  */
196    [CountClass nonExistingClassMethod];
197
198    if (resolveClassMethodCount != 1)
199      abort ();
200
201    if (forwardingCount != 1)
202      abort ();
203
204    if (nonExistingMethodCount != 1)
205      abort ();
206
207    /* Now try the same tests with class_getClassMethod(), which
208       should trigger the resolve methods too, but not the
209       forwarding.  */
210    m = class_getClassMethod (objc_getClass ("CountClass"),
211			      @selector (existingClassMethod));
212    if (resolveClassMethodCount != 1)
213      abort ();
214
215    if (forwardingCount != 1)
216      abort ();
217
218    if (nonExistingMethodCount != 1)
219      abort ();
220
221    m = class_getClassMethod (objc_getClass ("CountClass"),
222			      @selector (nonExistingClassMethod));
223    if (resolveClassMethodCount != 2)
224      abort ();
225
226    if (forwardingCount != 1)
227      abort ();
228
229    if (nonExistingMethodCount != 1)
230      abort ();
231
232    /* Now try the same tests with class_getMethodImplementation(),
233       which should trigger the resolve methods and the forwarding
234       (but not execute the forwarding, obviously).  */
235    i = class_getMethodImplementation (object_getClass (objc_getClass ("CountClass")),
236				       @selector (existingClassMethod));
237    if (resolveClassMethodCount != 2)
238      abort ();
239
240    if (forwardingCount != 1)
241      abort ();
242
243    if (nonExistingMethodCount != 1)
244      abort ();
245
246    i = class_getMethodImplementation (object_getClass (objc_getClass ("CountClass")),
247				       @selector (nonExistingClassMethod));
248    if (resolveClassMethodCount != 3)
249      abort ();
250
251    if (forwardingCount != 2)
252      abort ();
253
254    if (nonExistingMethodCount != 1)
255      abort ();
256
257
258    /* Reset the counters for the next test.  */
259    resolveClassMethodCount = 0;
260    forwardingCount = 0;
261    nonExistingMethodCount = 0;
262
263
264    /** SelfExtendingClass tests.  **/
265
266    /* Try the direct countHits method first.  No resolving or
267       forwarding should be triggered.  */
268    if ([SelfExtendingClass countHits] != 1)
269      abort ();
270
271    if (resolveClassMethodCount != 0)
272      abort ();
273
274    if (forwardingCount != 0)
275      abort ();
276
277    if (nonExistingMethodCount != 0)
278      abort ();
279
280    /* Now, try calling a non-existing count method; it should be
281       installed and invoked.  */
282    if ([SelfExtendingClass nonExistingCountHitsMethod] != 2)
283      abort ();
284
285    if (resolveClassMethodCount != 1)
286      abort ();
287
288    if (forwardingCount != 0)
289      abort ();
290
291    if (nonExistingMethodCount != 0)
292      abort ();
293
294    /* Try it again.  The method has now been installed, so it should
295       be used and work, but with no additional resolving
296       involved.  */
297    if ([SelfExtendingClass nonExistingCountHitsMethod] != 3)
298      abort ();
299
300    if (resolveClassMethodCount != 1)
301      abort ();
302
303    if (forwardingCount != 0)
304      abort ();
305
306    if (nonExistingMethodCount != 0)
307      abort ();
308
309
310    /* Now try the same tests with class_getClassMethod().  */
311    m = class_getClassMethod (objc_getClass ("SelfExtendingClass"),
312			      @selector (nonExistingCountHitsMethod2));
313    if (resolveClassMethodCount != 2)
314      abort ();
315
316    if (forwardingCount != 0)
317      abort ();
318
319    if (nonExistingMethodCount != 0)
320      abort ();
321
322    /* Try it again.  The method has now been installed, so it should
323       be used and work, but with no additional resolving
324       involved.  */
325    if ([SelfExtendingClass nonExistingCountHitsMethod2] != 4)
326      abort ();
327
328    if (resolveClassMethodCount != 2)
329      abort ();
330
331    if (forwardingCount != 0)
332      abort ();
333
334    if (nonExistingMethodCount != 0)
335      abort ();
336
337
338    /* Now try the same tests with class_getMethodImplementation().  */
339    i = class_getMethodImplementation (object_getClass (objc_getClass ("SelfExtendingClass")),
340				       @selector (nonExistingCountHitsMethod3));
341    if (resolveClassMethodCount != 3)
342      abort ();
343
344    if (forwardingCount != 0)
345      abort ();
346
347    if (nonExistingMethodCount != 0)
348      abort ();
349
350    /* Try it again.  The method has now been installed, so it should
351       be used and work, but with no additional resolving
352       involved.  */
353    if ([SelfExtendingClass nonExistingCountHitsMethod3] != 5)
354      abort ();
355
356    if (resolveClassMethodCount != 3)
357      abort ();
358
359    if (forwardingCount != 0)
360      abort ();
361
362    if (nonExistingMethodCount != 0)
363      abort ();
364  }
365
366  /* Reset the counters for the next test.  */
367  nonExistingMethodCount = 0;
368  forwardingCount = 0;
369  hitCount = 0;
370
371  std::cout << "Testing [+resolveInstanceMethod:] ...\n";
372  {
373    Method m;
374    IMP i;
375    CountClass *object = [[CountClass alloc] init];
376    SelfExtendingClass *object2 = [[SelfExtendingClass alloc] init];
377
378    /** CountClass tests.  **/
379
380    /* Call an existing method.  No +resolveInstanceMethod and no
381       forwarding should be triggered.  */
382    [object existingInstanceMethod];
383
384    if (resolveInstanceMethodCount != 0)
385      abort ();
386
387    if (forwardingCount != 0)
388      abort ();
389
390    if (nonExistingMethodCount != 0)
391      abort ();
392
393    /* Call a non-existing method.  Both +resolveInstanceMethod and the
394       forwarding should be triggered.  */
395    [object nonExistingInstanceMethod];
396
397    if (resolveInstanceMethodCount != 1)
398      abort ();
399
400    if (forwardingCount != 1)
401      abort ();
402
403    if (nonExistingMethodCount != 1)
404      abort ();
405
406    /* Now try the same tests with class_getInstanceMethod(), which
407       should trigger the resolve methods too, but not the
408       forwarding.  */
409    m = class_getInstanceMethod (objc_getClass ("CountClass"),
410				 @selector (existingInstanceMethod));
411
412    if (resolveInstanceMethodCount != 1)
413      abort ();
414
415    if (forwardingCount != 1)
416      abort ();
417
418    if (nonExistingMethodCount != 1)
419      abort ();
420
421    m = class_getInstanceMethod (objc_getClass ("CountClass"),
422				 @selector (nonExistingInstanceMethod));
423
424    if (resolveInstanceMethodCount != 2)
425      abort ();
426
427    if (forwardingCount != 1)
428      abort ();
429
430    if (nonExistingMethodCount != 1)
431      abort ();
432
433    /* Now try the same tests with class_getMethodImplementation(),
434       which should trigger the resolve methods and the
435       forwarding.  */
436    i = class_getMethodImplementation (objc_getClass ("CountClass"),
437				       @selector (existingInstanceMethod));
438    if (resolveInstanceMethodCount != 2)
439      abort ();
440
441    if (forwardingCount != 1)
442      abort ();
443
444    if (nonExistingMethodCount != 1)
445      abort ();
446
447    i = class_getMethodImplementation (objc_getClass ("CountClass"),
448				       @selector (nonExistingInstanceMethod));
449    if (resolveInstanceMethodCount != 3)
450      abort ();
451
452    if (forwardingCount != 2)
453      abort ();
454
455    if (nonExistingMethodCount != 1)
456      abort ();
457
458    /* Reset the counters for the next test.  */
459    resolveInstanceMethodCount = 0;
460    forwardingCount = 0;
461    nonExistingMethodCount = 0;
462
463
464    /** SelfExtendingClass tests.  **/
465
466    /* Try the direct countHits method first.  No resolving or
467       forwarding should be triggered.  */
468    if ([SelfExtendingClass countHits] != 1)
469      abort ();
470
471    if (resolveInstanceMethodCount != 0)
472      abort ();
473
474    if (forwardingCount != 0)
475      abort ();
476
477    if (nonExistingMethodCount != 0)
478      abort ();
479
480    /* Now, try calling a non-existing count method; it should be
481       installed and invoked.  */
482    if ([object2 nonExistingCountHitsMethod] != 2)
483      abort ();
484
485    if (resolveInstanceMethodCount != 1)
486      abort ();
487
488    if (forwardingCount != 0)
489      abort ();
490
491    if (nonExistingMethodCount != 0)
492      abort ();
493
494    /* Try it again.  The method has now been installed, so it should
495       be used and work, but with no additional resolving
496       involved.  */
497    if ([object2 nonExistingCountHitsMethod] != 3)
498      abort ();
499
500    if (resolveInstanceMethodCount != 1)
501      abort ();
502
503    if (forwardingCount != 0)
504      abort ();
505
506    if (nonExistingMethodCount != 0)
507      abort ();
508
509    /* Now try the same tests with class_getInstanceMethod().  */
510    m = class_getInstanceMethod (objc_getClass ("SelfExtendingClass"),
511				 @selector (nonExistingCountHitsMethod2));
512    if (resolveInstanceMethodCount != 2)
513      abort ();
514
515    if (forwardingCount != 0)
516      abort ();
517
518    if (nonExistingMethodCount != 0)
519      abort ();
520
521    /* Try it again.  The method has now been installed, so it should
522       be used and work, but with no additional resolving
523       involved.  */
524    if ([object2 nonExistingCountHitsMethod2] != 4)
525      abort ();
526
527    if (resolveInstanceMethodCount != 2)
528      abort ();
529
530    if (forwardingCount != 0)
531      abort ();
532
533    if (nonExistingMethodCount != 0)
534      abort ();
535
536
537    /* Now try the same tests with class_getMethodImplementation().  */
538    i = class_getMethodImplementation (objc_getClass ("SelfExtendingClass"),
539				       @selector (nonExistingCountHitsMethod3));
540    if (resolveInstanceMethodCount != 3)
541      abort ();
542
543    if (forwardingCount != 0)
544      abort ();
545
546    if (nonExistingMethodCount != 0)
547      abort ();
548
549    /* Try it again.  The method has now been installed, so it should
550       be used and work, but with no additional resolving
551       involved.  */
552    if ([object2 nonExistingCountHitsMethod3] != 5)
553      abort ();
554
555    if (resolveInstanceMethodCount != 3)
556      abort ();
557
558    if (forwardingCount != 0)
559      abort ();
560
561    if (nonExistingMethodCount != 0)
562      abort ();
563  }
564
565
566  return (0);
567}
568