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