|
|
1.1 ! root 1: /* Method dispatcher and class-object creator for Objective C. ! 2: Copyright (C) 1992 Free Software Foundation, Inc. ! 3: ! 4: This file is part of GNU CC. ! 5: ! 6: GNU CC is free software; you can redistribute it and/or modify ! 7: it under the terms of the GNU General Public License as published by ! 8: the Free Software Foundation; either version 2, or (at your option) ! 9: any later version. ! 10: ! 11: GNU CC is distributed in the hope that it will be useful, ! 12: but WITHOUT ANY WARRANTY; without even the implied warranty of ! 13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ! 14: GNU General Public License for more details. ! 15: ! 16: You should have received a copy of the GNU General Public License ! 17: along with GNU CC; see the file COPYING. If not, write to ! 18: the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */ ! 19: ! 20: /* As a special exception, if you link this library with files ! 21: compiled with GCC to produce an executable, this does not cause ! 22: the resulting executable to be covered by the GNU General Public License. ! 23: This exception does not however invalidate any other reasons why ! 24: the executable file might be covered by the GNU General Public License. */ ! 25: ! 26: #include "tconfig.h" ! 27: #include "assert.h" ! 28: #include <ctype.h> ! 29: #include "gstdarg.h" ! 30: #include <stdio.h> ! 31: #include "gstddef.h" ! 32: ! 33: #include "hash.h" ! 34: #include "objc.h" ! 35: #include "objc-proto.h" ! 36: ! 37: ! 38: #define MODULE_HASH_SIZE 32 /* Initial module hash table size. ! 39: Value doesn't really matter. */ ! 40: ! 41: #define CLASS_HASH_SIZE 32 /* Initial number of buckets size of ! 42: class hash table. Value doesn't ! 43: really matter. */ ! 44: ! 45: ! 46: /* Forward declare some functions. */ ! 47: id objc_object_create (Class_t), ! 48: objc_object_dispose (id), ! 49: objc_object_realloc (id, unsigned int), ! 50: objc_object_copy (id); ! 51: void objc_error (id object, const char *fmt, va_list ap); ! 52: static id nil_method (id, SEL, ...); ! 53: static id return_error_static (id, SEL, ...); ! 54: static IMP handle_runtime_error (id, SEL); ! 55: static void initialize_dispatch_tables (void); ! 56: static SEL record_selector (const char*); ! 57: static void record_methods_from_class (Class_t); ! 58: static void record_methods_from_list (MethodList_t); ! 59: static void initialize_class (const char*); ! 60: /* ! 61: * This is a hash table of Class_t structures. ! 62: * ! 63: * At initialization the executable is searched for all Class_t structures. ! 64: * Since these structures are created by the compiler they are therefore ! 65: * located in the TEXT segment. ! 66: * ! 67: * A identical structure is allocated from the free store and initialized from ! 68: * its TEXT counterpart and placed in the hash table using the TEXT part as ! 69: * its key. ! 70: * ! 71: * Since the free store structure is now writable, additional initialization ! 72: * takes place such as its "info" variable, method cache allocation, and ! 73: * linking of all of its method and ivar lists from multiple implementations. ! 74: */ ! 75: cache_ptr class_hash_table = NULL; ! 76: ! 77: /* ! 78: * This variable is a flag used within the messaging routines. If a ! 79: * application sets it to !0 then the messager will print messages sent to ! 80: * objects. ! 81: */ ! 82: BOOL objc_trace = NO; ! 83: ! 84: /* This mutex provides a course lock for method dispatch. */ ! 85: MUTEX runtimeMutex; ! 86: ! 87: /* ! 88: * This hash table is used by the initialization routines. When the ! 89: * constructor function (__objc_execClass) is called it is passed a pointer ! 90: * to a module structure. That pointer is stored in this table and its ! 91: * contents are processed in __objcInit. ! 92: */ ! 93: cache_ptr module_hash_table = NULL; ! 94: ! 95: /* ! 96: * This hash table is used in the constructor subroutine to hold pointers ! 97: * to categories that have not been attached to a class. Constructors are ! 98: * received in a random order. Files may contain category implementation ! 99: * of objects whose constructor hasn't been executed yet. Therefore, ! 100: * there is no object to attach the categories. ! 101: * ! 102: * This hash table holds pointers to categories that haven't been ! 103: * attached to objects. As objects are processed the category hash ! 104: * table is searched for attachments. If a category is found for the ! 105: * object it is attached to the object and deleted from the hash table. ! 106: */ ! 107: cache_ptr unclaimed_category_hash_table = NULL; ! 108: ! 109: /* ! 110: * This flag is used by the messager routines to determine if the run-time ! 111: * has been initialized. If the run-time isn't initialized then a ! 112: * initialization clean up routine is called. ! 113: */ ! 114: static int initialized = 0; ! 115: ! 116: /* ! 117: * Records that hold pointers to arrays of records. The terminal records are ! 118: * method implementations. ! 119: * ! 120: * The size of the first record is the number of unique classes in the ! 121: * executable. The second is the number of selectors. ! 122: * ! 123: * The second record conatins methods that are visible to the class -- that is, ! 124: * methods that are not overriden from the classt to the root object. ! 125: * ! 126: * The cache pointers of class and meta class structures point to one of these ! 127: * records. ! 128: */ ! 129: static struct record *instance_method_record = NULL; ! 130: static struct record *factory_method_record = NULL; ! 131: ! 132: /* ! 133: * This structure is used to translate between selectors and their ASCII ! 134: * representation. A NULL terminated array of char*, ! 135: * OBJC_SELECTOR_REFERENCES, is passed to the constructor routine: ! 136: * __objc_execClass. That routine places entries from that array into this ! 137: * structure. The location within OBJC_SELECTOR_REFERENCES where the string ! 138: * was taken from is replaced with a small integer, the index into the array ! 139: * inside selectorTranslateTable. That integer then becomes the selector. ! 140: * ! 141: * Selectors begin at 1 to numEntries. A selector outside of that range is ! 142: * considered an error. The selector integers are used as the first index ! 143: * into the instance_method_record and factory_method_record arrays. ! 144: */ ! 145: static struct record *selector_record = NULL; ! 146: ! 147: /* ! 148: * This data structure is used in the special case where usual fatal error ! 149: * functions are called but have been overridden in a class. The value ! 150: * returned by that function is returned to the calling object. error_static ! 151: * holds the returned value until it is retrieved by return_error_static ! 152: * which returns it to the calling function. ! 153: */ ! 154: static id error_static; ! 155: ! 156: ! 157: /* Given a class and selector, return the selector's implementation. */ ! 158: static inline IMP ! 159: get_imp (Class_t class, SEL sel) ! 160: { ! 161: IMP imp = NULL; ! 162: imp = record_get (getClassNumber (class), ! 163: record_get ((unsigned int)sel, *class->cache)); ! 164: ! 165: return imp; ! 166: } ! 167: ! 168: ! 169: static void ! 170: error (msg, arg1, arg2) ! 171: char *msg, *arg1, *arg2; ! 172: { ! 173: #if 0 /* There is no portable way to get the program name. Too bad. */ ! 174: fprintf (stderr, "%s: ", programname); ! 175: #endif ! 176: fprintf (stderr, msg, arg1, arg2); ! 177: fprintf (stderr, "\n"); ! 178: } ! 179: ! 180: static void ! 181: fatal (msg, arg1, arg2) ! 182: char *msg, *arg1, *arg2; ! 183: { ! 184: error (msg, arg1, arg2); ! 185: exit (1); ! 186: } ! 187: ! 188: static void * ! 189: xmalloc (unsigned int size) ! 190: { ! 191: void *ptr = (void *) malloc (size); ! 192: if (ptr == 0) ! 193: fatal ("virtual memory exceeded"); ! 194: return ptr; ! 195: } ! 196: ! 197: static void * ! 198: xcalloc (unsigned int size, unsigned int units) ! 199: { ! 200: void *ptr = (void *) calloc (size, units); ! 201: if (ptr == 0) ! 202: fatal ("virtual memory exceeded"); ! 203: return ptr; ! 204: } ! 205: ! 206: void * ! 207: __objc_xmalloc (unsigned int size) ! 208: { ! 209: void *ptr = (void *) malloc (size); ! 210: if (ptr == 0) ! 211: fatal ("virtual memory exceeded"); ! 212: return ptr; ! 213: } ! 214: ! 215: void * ! 216: __objc_xrealloc (void *optr, unsigned int size) ! 217: { ! 218: void *ptr = (void *) realloc (optr, size); ! 219: if (ptr == 0) ! 220: fatal ("virtual memory exceeded"); ! 221: return ptr; ! 222: } ! 223: ! 224: void * ! 225: __objc_xcalloc (unsigned int size, unsigned int units) ! 226: { ! 227: void *ptr = (void *) calloc (size, units); ! 228: if (ptr == 0) ! 229: fatal ("virtual memory exceeded"); ! 230: return ptr; ! 231: } ! 232: ! 233: static inline char * ! 234: my_strdup (const char *str) ! 235: { ! 236: char *new = (char *) xmalloc (strlen (str) + 1); ! 237: strcpy (new, str); ! 238: ! 239: return new; ! 240: } ! 241: ! 242: ! 243: /* ! 244: * This function is called by constructor functions generated for each module ! 245: * compiled. ! 246: * ! 247: * The purpose of this function is to gather the module pointers so that they ! 248: * may be processed by the initialization clean up routine. ! 249: */ ! 250: void ! 251: __objc_execClass (Module_t module) ! 252: { ! 253: /* Has we processed any constructors previously? ! 254: Flag used to indicate that some global data structures ! 255: need to be built. */ ! 256: static BOOL previous_constructors = 0; ! 257: ! 258: Symtab_t symtab = module->symtab; ! 259: Class_t object_class; ! 260: node_ptr node; ! 261: SEL *(*selectors)[] = (SEL *(*)[])symtab->refs; ! 262: int i; ! 263: BOOL incomplete = 0; ! 264: ! 265: ! 266: assert (module->size == sizeof (Module)); ! 267: DEBUG_PRINTF ("received load module: %s\n",module->name); ! 268: ! 269: /* Header file data structure hack test. */ ! 270: assert (sizeof (Class) == sizeof (MetaClass)); ! 271: ! 272: /* On the first call of this routine, initialize ! 273: some data structures. */ ! 274: if (!previous_constructors) { ! 275: ! 276: /* Enable malloc debugging. This'll slow'er down! */ ! 277: #if defined (DEBUG) && defined (NeXT) ! 278: malloc_debug (62); ! 279: #endif ! 280: ! 281: /* Allocate and initialize the mutex. */ ! 282: MUTEX_ALLOC (&runtimeMutex); ! 283: MUTEX_INIT (runtimeMutex); ! 284: ! 285: /* Allocate the module hash table. */ ! 286: module_hash_table ! 287: = hash_new (MODULE_HASH_SIZE, (hash_func_type)hash_ptr, ! 288: (compare_func_type)compare_ptrs); ! 289: ! 290: /* Allocate a table for unclaimed categories. */ ! 291: unclaimed_category_hash_table ! 292: = hash_new (16, (hash_func_type)hash_ptr, ! 293: (compare_func_type)compare_ptrs); ! 294: ! 295: /* Allocate a master selector table if it doesn't exist. */ ! 296: selector_record = record_new (); ! 297: ! 298: previous_constructors = 1; ! 299: } ! 300: ! 301: /* Save the module pointer for later processing. */ ! 302: hash_add (&module_hash_table, module, module); ! 303: ! 304: /* Parse the classes in the load module and gather selector information. */ ! 305: DEBUG_PRINTF ("gathering selectors from module: %s\n",module->name); ! 306: for (i = 0; i < symtab->cls_def_cnt; ++i) { ! 307: Class_t class = (Class_t)symtab->defs[i]; ! 308: ! 309: /* Make sure we have what we think. */ ! 310: assert (class->info & CLS_CLASS); ! 311: assert (class->class_pointer->info & CLS_META); ! 312: DEBUG_PRINTF ("phase 1, processing class: %s\n", class->name); ! 313: ! 314: /* Store the class in the class table and assign class numbers. */ ! 315: addClassToHash (class); ! 316: ! 317: /* Store all of the selectors in the class and meta class. */ ! 318: record_methods_from_class (class); ! 319: record_methods_from_class ((Class_t)class->class_pointer); ! 320: ! 321: /* Initialize the cache pointers. */ ! 322: class->cache = &instance_method_record; ! 323: class->class_pointer->cache = &factory_method_record; ! 324: } ! 325: ! 326: /* Replace referenced selectors. */ ! 327: for (i=0; i < symtab->sel_ref_cnt; ++i) ! 328: (*selectors)[i] = record_selector ((const char*)(*selectors)[i]); ! 329: ! 330: /* Try to build the class hierarchy and initialize the data structures. */ ! 331: object_class = objc_getClass ("Object"); ! 332: if (object_class) { ! 333: ! 334: /* Make sure we have what we think we have. */ ! 335: assert (object_class->class_pointer->info & CLS_META); ! 336: assert (object_class->info & CLS_CLASS); ! 337: ! 338: /* Connect the classes together (at least as much as we can). */ ! 339: for (node = hash_next (class_hash_table, NULL); node; ! 340: node = hash_next (class_hash_table, node)) { ! 341: Class_t class1 = node->value; ! 342: ! 343: /* Make sure we have what we think we have. */ ! 344: assert (class1->info & CLS_CLASS); ! 345: assert (class1->class_pointer->info & CLS_META); ! 346: ! 347: /* The class_pointer of all meta classes point to Object's meta class. */ ! 348: class1->class_pointer->class_pointer = object_class->class_pointer; ! 349: ! 350: /* Assign super class pointers */ ! 351: if (class1->super_class) { ! 352: Class_t aSuperClass = objc_getClass ((char*)class1->super_class); ! 353: ! 354: if (aSuperClass) { ! 355: ! 356: DEBUG_PRINTF ("making class connections for: %s\n", ! 357: class1->name); ! 358: ! 359: class1->super_class = aSuperClass; ! 360: if (class1->class_pointer->super_class) ! 361: class1->class_pointer->super_class = class1->super_class->class_pointer; ! 362: ! 363: /* Mark the class as initialized. */ ! 364: class1->info |= CLS_RTI; ! 365: } else ! 366: /* Couldn't find the class's super class. */ ! 367: incomplete = 1; ! 368: } ! 369: } ! 370: } else ! 371: /* Couldn't find class Object. */ ! 372: incomplete = 1; ! 373: ! 374: /* Process category information from the module. */ ! 375: for (i = 0; i < symtab->cat_def_cnt; ++i) { ! 376: Category_t category = symtab->defs[i + symtab->cls_def_cnt]; ! 377: Class_t class = objc_getClass (category->class_name); ! 378: ! 379: /* If the class for the category exists then append its ! 380: methods. */ ! 381: if (class) { ! 382: ! 383: DEBUG_PRINTF ("processing categories from (module,object): %s, %s\n", ! 384: module->name, ! 385: class_getClassName (class)); ! 386: ! 387: /* Do instance methods. */ ! 388: if (category->instance_methods) ! 389: addMethodsToClass (class, category->instance_methods); ! 390: ! 391: /* Do class methods. */ ! 392: if (category->class_methods) ! 393: addMethodsToClass ((Class_t)class->class_pointer, category->class_methods); ! 394: } else { ! 395: /* The object to which the category methods belong can't ! 396: be found. Save the information. */ ! 397: hash_add (&unclaimed_category_hash_table, category, category); ! 398: ! 399: incomplete = 1; ! 400: } ! 401: } ! 402: ! 403: /* Scan the unclaimed category hash. ! 404: Attempt to attach any unclaimed categories to objects. */ ! 405: for (node = hash_next (unclaimed_category_hash_table, NULL); node; ! 406: node = hash_next (unclaimed_category_hash_table, node)) { ! 407: Category_t category = node->value; ! 408: Class_t class = objc_getClass (category->class_name); ! 409: ! 410: if (class) { ! 411: ! 412: DEBUG_PRINTF ("attaching stored categories to object: %s\n", ! 413: class_getClassName (class)); ! 414: ! 415: /* Delete this class from the hash table. */ ! 416: hash_remove (unclaimed_category_hash_table, category); ! 417: node = NULL; ! 418: ! 419: if (category->instance_methods) ! 420: addMethodsToClass (class, category->instance_methods); ! 421: ! 422: if (category->class_methods) ! 423: addMethodsToClass ((Class_t)class->class_pointer, ! 424: category->class_methods); ! 425: } else ! 426: incomplete = 1; ! 427: } ! 428: ! 429: /* Can we finish the run time initialization? */ ! 430: if (!incomplete) { ! 431: ! 432: initialize_dispatch_tables (); ! 433: ! 434: /* Debug run time test. ! 435: We're initialized! */ ! 436: initialized = 1; ! 437: ! 438: /* Print out class tables if debugging. */ ! 439: DEBUG_PRINTF ("dump of class tables from objcInit\n"); ! 440: debug_dump_classes (); ! 441: } ! 442: ! 443: } ! 444: ! 445: ! 446: IMP ! 447: objc_msgSend (id receiver, SEL sel) ! 448: { ! 449: /* ! 450: * A method is always called by the compiler. If a method wasn't ! 451: * found then supply a default. ! 452: */ ! 453: IMP imp = nil_method; ! 454: ! 455: ! 456: /* The run time must be initialized at this point. ! 457: Otherwise we get a message sent to a object with a bogus selector. */ ! 458: assert (initialized); ! 459: ! 460: /* Objective-C allows messages to be sent to a nil object. */ ! 461: if (receiver) { ! 462: ! 463: /* Check for common programmer error. */ ! 464: if (!receiver->class_pointer) { ! 465: fprintf (stderr, "method %s sent to deallocated object %#x\n", ! 466: sel_getName (sel), receiver); ! 467: abort (); ! 468: } ! 469: ! 470: /* Initialize the class if need be. */ ! 471: if (!(receiver->class_pointer->info & CLS_INITIALIZED)) ! 472: initialize_class (receiver->class_pointer->name); ! 473: ! 474: /* ! 475: * If we're passed a object then its class_pointer is a Class. If ! 476: * we're passed a Class then its class_pointer is a MetaClass. ! 477: * Therefore, searching for a instance or class method ! 478: * requires no special decision making here. ! 479: * ! 480: * Look for the method. ! 481: */ ! 482: imp = get_imp (receiver->class_pointer, sel); ! 483: ! 484: /* If the method cannot be found then perform error handling. */ ! 485: if (!imp) ! 486: imp = handle_runtime_error (receiver, sel); ! 487: } ! 488: ! 489: /* Nice debugging messages if enabled. */ ! 490: if (objc_trace) { ! 491: printf ("trace: objc_msgSend , obj=%#x, class=%s, method=%s\n", ! 492: receiver, ! 493: receiver->class_pointer->name, ! 494: sel_getName (sel)); ! 495: fflush (stdout); ! 496: } ! 497: ! 498: return imp; ! 499: } ! 500: ! 501: ! 502: IMP ! 503: objc_msgSendSuper (Super_t super, SEL sel) ! 504: { ! 505: IMP imp; ! 506: ! 507: ! 508: assert (initialized); ! 509: ! 510: if (!(super->class->info & CLS_INITIALIZED)) ! 511: initialize_class (super->class->name); ! 512: if (!(super->receiver->class_pointer->info & CLS_INITIALIZED)) ! 513: initialize_class (super->receiver->class_pointer->name); ! 514: ! 515: imp = get_imp (super->class, sel); ! 516: ! 517: if (!imp) ! 518: imp = handle_runtime_error (super->receiver, sel); ! 519: ! 520: if (objc_trace) { ! 521: printf ("trace: objc_msgSendSuper , obj=%#x, class=%s, method=%s\n", ! 522: super->receiver, ! 523: super->receiver->class_pointer->name, ! 524: sel_getName (sel)); ! 525: fflush (stdout); ! 526: } ! 527: ! 528: return imp; ! 529: } ! 530: ! 531: ! 532: /* ! 533: * This function is called by objc_msgSend or objc_msgSendSuper when a ! 534: * message is sent to a object which it does not recognize. ! 535: */ ! 536: static IMP ! 537: handle_runtime_error (id object, SEL sel) ! 538: { ! 539: IMP imp; ! 540: ! 541: ! 542: /* ! 543: * If the object recognizes the doesNotRecognize: method then we're ! 544: * going to send it. ! 545: */ ! 546: imp = get_imp (object->class_pointer, sel_getUid ("doesNotRecognize:")); ! 547: if (imp) ! 548: error_static = (*imp)(object, sel_getUid ("doesNotRecognize:"), sel); ! 549: else { ! 550: /* ! 551: * The object doesn't recognize the method. Check for ! 552: * responding to error:. If it does then sent it. ! 553: */ ! 554: char msg[256 + strlen (sel_getName (sel)) ! 555: + strlen (object->class_pointer->name)]; ! 556: ! 557: sprintf (msg, "%s does not recognize %s", ! 558: object->class_pointer->name, sel_getName (sel)); ! 559: ! 560: imp = get_imp (object->class_pointer, sel_getUid ("error:")); ! 561: if (imp) ! 562: error_static = (*imp)(object, sel_getUid ("error:"), msg); ! 563: else { ! 564: /* ! 565: * The object doesn't respond to doesNotRecognize: or ! 566: * error:; Therefore, a default action is taken. ! 567: */ ! 568: fprintf (stderr, "%s\n", msg); ! 569: abort (); ! 570: } ! 571: } ! 572: ! 573: /* ! 574: * Either doesNotRecognize: or error: has been overridden. We have ! 575: * to return that value as the default action. ! 576: */ ! 577: return return_error_static; ! 578: } ! 579: ! 580: ! 581: /* ! 582: * This function is used by the run-time to provide a method where nil ! 583: * objects can receive messages. ! 584: * ! 585: * This method simply returns self. ! 586: */ ! 587: static id ! 588: nil_method (id object, SEL sel, ...) ! 589: { ! 590: return object; ! 591: } ! 592: ! 593: ! 594: /* ! 595: * This function is used by the run-time to provide a method where nil ! 596: * objects can receive messages. ! 597: * ! 598: * This method simply returns self. ! 599: * ! 600: * Note: multiple thread problem area. ! 601: */ ! 602: static id ! 603: return_error_static (id object, SEL sel, ...) ! 604: { ! 605: return error_static; ! 606: } ! 607: ! 608: ! 609: /* ! 610: * These variables provide a way for the defalut methods of object ! 611: * allocation, destruction, and reallocation to be overridden. ! 612: */ ! 613: id (*_alloc)(Class_t) = objc_object_create; ! 614: id (*_dealloc)(id) = objc_object_dispose; ! 615: id (*_realloc)(id, unsigned int) = objc_object_realloc; ! 616: id (*_copy)(id) = objc_object_copy; ! 617: void (*_error)(id, const char*, va_list) = objc_error; ! 618: ! 619: ! 620: id ! 621: objc_object_create (Class_t class) ! 622: { ! 623: id object; ! 624: ! 625: ! 626: assert (class); ! 627: ! 628: /* ! 629: * Allocate memory for the object, initialize the memory to 0, and ! 630: * set the object's class_pointer. ! 631: * ! 632: * The object's class_pointer is the class's TEXT image. It is used by ! 633: * the messager as the key to the class hash for methods. ! 634: * ! 635: * No need to initialize the class. That was done in objcInit. ! 636: */ ! 637: object = (id) xcalloc (1, class->instance_size); ! 638: object->class_pointer = class; ! 639: ! 640: return object; ! 641: } ! 642: ! 643: ! 644: id ! 645: objc_object_dispose (id object) ! 646: { ! 647: object->class_pointer = NULL; ! 648: free (object); ! 649: ! 650: return nil; ! 651: } ! 652: ! 653: ! 654: id ! 655: objc_object_realloc (id object, unsigned int length) ! 656: { ! 657: id obj; ! 658: /* Can't resize a object smaller than its instance size. */ ! 659: /* Don't use assert here; ! 660: checks for user errors shouldn't depend on NDEBUG. */ ! 661: if (length < object->class_pointer->instance_size) ! 662: abort (); ! 663: ! 664: obj = (id) realloc (object, length); ! 665: bzero ((char*)obj + object->class_pointer->instance_size, ! 666: length - object->class_pointer->instance_size); ! 667: ! 668: return obj; ! 669: } ! 670: ! 671: ! 672: id ! 673: objc_object_copy (id object) ! 674: { ! 675: id obj; ! 676: obj = class_createInstance (object->class_pointer); ! 677: bcopy (object, obj, objc_classSize (object)); ! 678: ! 679: return obj; ! 680: } ! 681: ! 682: ! 683: void ! 684: objc_error (id object, const char *fmt, va_list ap) ! 685: { ! 686: vfprintf (stderr, fmt, ap); ! 687: abort (); ! 688: } ! 689: ! 690: ! 691: /* Silly function to skip past a sequence of digits in a string. */ ! 692: static inline const char * ! 693: skip_digits (const char *str) ! 694: { ! 695: while (isdigit (*str)) ! 696: ++str; ! 697: ! 698: return str; ! 699: } ! 700: ! 701: ! 702: unsigned int ! 703: method_getNumberOfArguments (Method_t method) ! 704: { ! 705: unsigned int num = 0; ! 706: const char *args = &method->method_types[1]; ! 707: ! 708: ! 709: while (*args) { ! 710: ! 711: /* Skip past size info. */ ! 712: args = skip_digits (args); ! 713: ! 714: /* Argument type next. */ ! 715: assert (*args); ! 716: ++num; ! 717: ! 718: /* Step to next arg. */ ! 719: ++args; ! 720: } ! 721: ! 722: assert (num >= 2); ! 723: return num; ! 724: } ! 725: ! 726: ! 727: unsigned int ! 728: method_getArgumentInfo (Method_t method, int indx, const char **type, ! 729: int *offset) ! 730: { ! 731: const char *args = skip_digits (&method->method_types[1]); ! 732: int i; ! 733: ! 734: ! 735: assert (method_getNumberOfArguments (method) >= indx); ! 736: ! 737: /* Step to arg. */ ! 738: for (i = 0; i < indx; ++i) { ! 739: ++args; ! 740: args = skip_digits (args); ! 741: } ! 742: ! 743: /* Return arg data. */ ! 744: *type = args++; ! 745: *offset = atoi (args); ! 746: ! 747: return indx; ! 748: } ! 749: ! 750: ! 751: /* This function is not thread safe. */ ! 752: Ivar_t ! 753: object_getIvarAddress (id object, const char *name) ! 754: { ! 755: Class_t class = object->class_pointer; /* Here is the thread safe problem. */ ! 756: Ivar_t ivar = NULL; ! 757: ! 758: ! 759: do { ! 760: IvarList_t ivars = class->ivars; ! 761: int i; ! 762: ! 763: /* Look at all of the ivar names. */ ! 764: for (i = 0; i < ivars->ivar_count; ++i) ! 765: if (!strcmp (name, ivars->ivar_list[i].ivar_name)) ! 766: ivar = &ivars->ivar_list[i]; ! 767: ! 768: /* ! 769: * If the ivar wasn't found then lets look to the ! 770: * super class. ! 771: * ! 772: * If the class is Object then the super class is NULL ! 773: * and we're done. ! 774: */ ! 775: class = class->super_class; ! 776: ! 777: } while (!ivar && class); ! 778: ! 779: return ivar; ! 780: } ! 781: ! 782: ! 783: /* ! 784: * Search for a method starting from the current class up its hierarchy. ! 785: * ! 786: * Return a pointer to the method's method structure if found. NULL otherwise. ! 787: */ ! 788: Method_t ! 789: searchForMethodInHierarchy (Class_t class, SEL sel) ! 790: { ! 791: Method_t method = NULL; ! 792: const char* name; ! 793: ! 794: if (sel == 0) ! 795: return NULL; ! 796: ! 797: name = sel_getName (sel); ! 798: ! 799: if (name == 0) ! 800: return NULL; ! 801: ! 802: /* ! 803: * Scan the method list of the class. If the method isn't found in ! 804: * the list then step to its super class. ! 805: */ ! 806: do { ! 807: ! 808: method = searchForMethodInList (class->methods, name); ! 809: class = class->super_class; ! 810: ! 811: } while (!method && class); ! 812: ! 813: return method; ! 814: } ! 815: ! 816: ! 817: /* ! 818: * Given a linked list of method and a method's name. Search for the named ! 819: * method's method structure. ! 820: * ! 821: * Return a pointer to the method's method structure if found. NULL otherwise. ! 822: */ ! 823: Method_t ! 824: searchForMethodInList (MethodList_t list, const char *name) ! 825: { ! 826: MethodList_t method_list = list; ! 827: ! 828: ! 829: /* Check for bumbling. */ ! 830: /* ??? Who generates the name? Is it the user, or part of this file? ! 831: If we crash here, whose fault is it? */ ! 832: assert (name); ! 833: ! 834: /* If not found then we'll search the list. */ ! 835: while (method_list) { ! 836: int i; ! 837: ! 838: /* Search the method list. */ ! 839: for (i = 0; i < method_list->method_count; ++i) { ! 840: Method_t method = &method_list->method_list[i]; ! 841: ! 842: if (method->method_name) ! 843: if (!strcmp (method->method_name, name)) ! 844: return method; ! 845: } ! 846: ! 847: /* The method wasn't found. Follow the link to the next list of ! 848: methods. */ ! 849: method_list = method_list->method_next; ! 850: } ! 851: ! 852: return NULL; ! 853: } ! 854: ! 855: ! 856: /* ! 857: * This function adds a method list to a class. ! 858: * ! 859: * This function is typically called by another function specific to the ! 860: * run-time. As such this function does not worry about thread safe issued. ! 861: */ ! 862: void ! 863: addMethodsToClass (Class_t class, MethodList_t list) ! 864: { ! 865: int i; ! 866: ! 867: ! 868: /* Passing of a linked list is not allowed. Do multiple calls. */ ! 869: assert (!list->method_next); ! 870: ! 871: /* Check for duplicates. */ ! 872: for (i = 0; i < list->method_count; ++i) { ! 873: Method_t method = &list->method_list[i]; ! 874: ! 875: if (method->method_name) /* Sometimes these are NULL */ ! 876: if (searchForMethodInList (class->methods, method->method_name)) { ! 877: /* ! 878: * Duplication. Print a error message an change the ! 879: * method name to NULL. ! 880: */ ! 881: fprintf (stderr, "attempt to add a existing method: %s\n", ! 882: method->method_name); ! 883: method->method_name = NULL; ! 884: } ! 885: } ! 886: ! 887: /* Add the methods to the class's method list. */ ! 888: list->method_next = class->methods; ! 889: class->methods = list; ! 890: } ! 891: ! 892: ! 893: /* ! 894: * This function removes the instance and factory methods in the passed list ! 895: * from a class. ! 896: * ! 897: * Methods are removed from a class by replacing the method's name with NULL. ! 898: * ! 899: * ! 900: * This function is typically called by another function specific to the ! 901: * run-time. As such this function does not worry about thread safe issued. ! 902: */ ! 903: void ! 904: class_removeMethods (Class_t class, MethodList_t method_list) ! 905: { ! 906: int i; ! 907: ! 908: ! 909: /* Passing of a linked list is not allowed. Do multiple calls. */ ! 910: assert (!method_list->method_next); ! 911: ! 912: /* ! 913: * For each method in the list search the method lists erasing any ! 914: * entries found. ! 915: */ ! 916: for (i = 0; i < method_list->method_count; ++i) { ! 917: Method_t kill_method = &method_list->method_list[i]; ! 918: Method_t method; ! 919: ! 920: /* Remove any instance method found. */ ! 921: method = searchForMethodInList (class->methods, ! 922: kill_method->method_name); ! 923: if (method) ! 924: method->method_name = NULL; ! 925: ! 926: /* Remove any factory method found. */ ! 927: method = searchForMethodInList (class->class_pointer->methods, ! 928: kill_method->method_name); ! 929: if (method) ! 930: method->method_name = NULL; ! 931: } ! 932: } ! 933: ! 934: ! 935: /* ! 936: * This is a incomplete implementation of posing. This function does the ! 937: * bulk of the work but does not initialize the class method caches. That is ! 938: * a run-time specific operation. ! 939: * ! 940: * I implement posing by hiding SUPER_CLASS, creating new class and meta ! 941: * class structures, initializing it with IMPOSTOR, and changing it such ! 942: * that it is identified as SUPER_CLASS. SUPER_CLASS remains in the ! 943: * hierarchy but is inaccessible by the means. The class hierarchy is then re ! 944: * arranged such that all of the subclasses of SUPER_CLASS now inherit from ! 945: * the new class structures -- except the impostor itself. The only dramatic ! 946: * effect on the application is that subclasses of SUPER_CLASS cannot do a ! 947: * [ .... superClass ] and expect their real super class. ! 948: */ ! 949: Class_t ! 950: class_poseAs (Class_t impostor, Class_t super_class) ! 951: { ! 952: Class_t new_class = (Class_t) calloc (1, sizeof (Class)); ! 953: MetaClass_t new_meta_class = (MetaClass_t) calloc (1, sizeof (MetaClass)); ! 954: node_ptr node; ! 955: char *new_name = (char *) malloc (strlen (super_class->name) + 12); ! 956: ! 957: ! 958: assert (new_class); ! 959: assert (new_meta_class); ! 960: assert (new_name); ! 961: ! 962: /* No dispatching while the the posing class is being built. ! 963: The dispatch tables will be hacked on. */ ! 964: MUTEX_LOCK (runtimeMutex); ! 965: ! 966: assert (impostor->info & CLS_CLASS); ! 967: assert (super_class->info & CLS_CLASS); ! 968: ! 969: assert (impostor->instance_size == super_class->instance_size); ! 970: ! 971: /* Create the impostor class. */ ! 972: new_class->class_pointer = new_meta_class; ! 973: new_class->super_class = super_class; ! 974: new_class->name = super_class->name; ! 975: new_class->version = super_class->version; ! 976: new_class->info = super_class->info; ! 977: new_class->instance_size = super_class->instance_size; ! 978: new_class->ivars = super_class->ivars; ! 979: new_class->methods = impostor->methods; ! 980: new_class->cache = &instance_method_record; ! 981: ! 982: /* Create the impostor meta class. */ ! 983: new_meta_class->class_pointer = super_class->class_pointer->class_pointer; ! 984: new_meta_class->super_class = super_class->class_pointer->super_class; ! 985: new_meta_class->name = super_class->class_pointer->name; ! 986: new_meta_class->version = super_class->class_pointer->version; ! 987: new_meta_class->info = super_class->class_pointer->info; ! 988: new_meta_class->instance_size = super_class->class_pointer->instance_size; ! 989: new_meta_class->ivars = super_class->class_pointer->ivars; ! 990: new_meta_class->methods = impostor->class_pointer->methods; ! 991: new_meta_class->cache = &factory_method_record; ! 992: ! 993: /* ! 994: * Delete the class from the hash table, change its name so that it ! 995: * can no longer be found, then place it back into the hash table ! 996: * using its new name. ! 997: * ! 998: * Don't worry about the class number. It is already assigned. ! 999: * ! 1000: * Don't worry about dangling pointers. Life's a bitch. (A little bit ! 1001: * of memory is lost with the hash key.) ! 1002: */ ! 1003: hash_remove (class_hash_table, super_class->name); ! 1004: sprintf (new_name, "%s*", super_class->name); ! 1005: super_class->name = new_name; ! 1006: super_class->class_pointer->name = new_name; ! 1007: hash_add (&class_hash_table, super_class->name, super_class); ! 1008: ! 1009: /* ! 1010: * Now change all of the classes derived from super_class to be ! 1011: * derived from a impostor (except the impostor's impostor. ! 1012: */ ! 1013: for (node = hash_next (class_hash_table, NULL); node; ! 1014: node = hash_next (class_hash_table, node)) { ! 1015: ! 1016: Class_t class1 = node->value; ! 1017: ! 1018: if (class1->super_class == super_class) ! 1019: if (class1 != impostor) ! 1020: class1->super_class = new_class; ! 1021: } ! 1022: ! 1023: /* Place the impostor class in class hash table ! 1024: and assign it a class number. */ ! 1025: addClassToHash (new_class); ! 1026: ! 1027: /* Reinitialize the dispatch tables. */ ! 1028: initialize_dispatch_tables (); ! 1029: ! 1030: MUTEX_UNLOCK (runtimeMutex); ! 1031: ! 1032: /* Print out class tables if debugging. */ ! 1033: DEBUG_PRINTF ("dump of class tables class_poseAs\n"); ! 1034: debug_dump_classes (); ! 1035: ! 1036: return new_class; ! 1037: } ! 1038: ! 1039: ! 1040: /* ! 1041: * This routine is given a class and records all of the methods in its class ! 1042: * structure in the record table. ! 1043: */ ! 1044: static void ! 1045: record_methods_from_class (Class_t class) ! 1046: { ! 1047: MethodList_t method_list; ! 1048: ! 1049: ! 1050: method_list = class->methods; ! 1051: while (method_list) { ! 1052: record_methods_from_list (method_list); ! 1053: method_list = method_list->method_next; ! 1054: } ! 1055: } ! 1056: ! 1057: ! 1058: /* ! 1059: * This routine is given a list of methods and records each of the methods in ! 1060: * the record table. This is the routine that does the actual recording ! 1061: * work. ! 1062: */ ! 1063: static void ! 1064: record_methods_from_list (MethodList_t method_list) ! 1065: { ! 1066: int i; ! 1067: ! 1068: ! 1069: for (i = 0; i < method_list->method_count; ++i) { ! 1070: Method_t method = &method_list->method_list[i]; ! 1071: ! 1072: record_selector (method->method_name); ! 1073: } ! 1074: } ! 1075: ! 1076: ! 1077: SEL ! 1078: sel_getUid (const STR name) ! 1079: { ! 1080: int i; ! 1081: ! 1082: ! 1083: for (i = 1; i <= record_entries (selector_record); ++i) ! 1084: if (!strcmp (name, record_get (i, selector_record))) ! 1085: return (SEL)i; ! 1086: ! 1087: /* Unable to locate selector. Return error value. */ ! 1088: return (SEL)0; ! 1089: } ! 1090: ! 1091: ! 1092: STR ! 1093: sel_getName (SEL selector) ! 1094: { ! 1095: return record_get ((unsigned int)selector, selector_record); ! 1096: } ! 1097: ! 1098: ! 1099: /* ! 1100: * Store the passed selector name in the selector record and return its ! 1101: * selector value (value returned by sel_getUid). ! 1102: */ ! 1103: static SEL ! 1104: record_selector (const char *sel) ! 1105: { ! 1106: int j; ! 1107: ! 1108: ! 1109: /* Find either the selector in the table or an empty slot. */ ! 1110: for (j = 1; j <= record_entries (selector_record); ++j) ! 1111: if (!strcmp (sel, record_get (j, selector_record))) ! 1112: return (SEL)j; ! 1113: ! 1114: /* Save the selector name. */ ! 1115: record_store (my_strdup (sel), selector_record); ! 1116: DEBUG_PRINTF ("Record: %s as: %#x\n", sel, j); ! 1117: ! 1118: return (SEL)j; ! 1119: } ! 1120: ! 1121: ! 1122: /* ! 1123: * Initialize the dispatch tables. This requires the initialization of the ! 1124: * instance_method_record and factory_method_record arrays and the arrays they ! 1125: * point to. ! 1126: * ! 1127: * The first array is indexed by a class number. Therefore its size is the ! 1128: * number of classes in the executable. The second array is indexed by a ! 1129: * selector id. Therefore its size is the number of unique selectors in the ! 1130: * application. ! 1131: * ! 1132: * When a method is sent to a object its class number is extracted from the ! 1133: * class structure and used in the first array. The selector id is used in ! 1134: * the second. The result value is a method implementation. ! 1135: */ ! 1136: static void ! 1137: initialize_dispatch_tables (void) ! 1138: { ! 1139: int i; ! 1140: ! 1141: ! 1142: /* Check to make sure things are in place. */ ! 1143: assert (selector_record); ! 1144: ! 1145: /* Blow away the instance and factory method records. */ ! 1146: if (factory_method_record) { ! 1147: for (i = 1; i <= record_entries (factory_method_record); ++i) ! 1148: record_delete (record_get (i, factory_method_record)); ! 1149: record_delete (factory_method_record); ! 1150: } ! 1151: if (instance_method_record) { ! 1152: for (i = 1; i <= record_entries (instance_method_record); ++i) ! 1153: record_delete (record_get (i, instance_method_record)); ! 1154: record_delete (instance_method_record); ! 1155: } ! 1156: ! 1157: /* Reallocate the instance and factory method records. */ ! 1158: factory_method_record = record_new (); ! 1159: instance_method_record = record_new (); ! 1160: for (i = 1; i <= record_entries (selector_record); ++i) { ! 1161: record_store (record_new (), factory_method_record); ! 1162: record_store (record_new (), instance_method_record); ! 1163: } ! 1164: ! 1165: /* Fool all of the secondary records into thinking they have data. */ ! 1166: for (i = 1; i <= record_entries (selector_record); ++i) { ! 1167: struct record *record; ! 1168: node_ptr node; ! 1169: ! 1170: record = record_get (i, factory_method_record); ! 1171: for (node = hash_next (module_hash_table, NULL); node; ! 1172: node = hash_next (module_hash_table, node)) ! 1173: record_store (NULL, record); ! 1174: ! 1175: record = record_get (i, instance_method_record); ! 1176: for (node = hash_next (module_hash_table, NULL); node; ! 1177: node = hash_next (module_hash_table, node)) ! 1178: record_store (NULL, record); ! 1179: } ! 1180: ! 1181: /* For all classes fill in the methods implemented by the class and visiable ! 1182: from the class in the hierarchy. Those methods are assigned to the ! 1183: class. */ ! 1184: for (i = 1; i <= record_entries (selector_record); ++i) { /* i is a sel */ ! 1185: node_ptr node; ! 1186: ! 1187: for (node = hash_next (class_hash_table, NULL); node; ! 1188: node = hash_next (class_hash_table, node)) { ! 1189: Class_t class = node->value; ! 1190: MetaClass_t meta_class = class->class_pointer; ! 1191: int class_number = getClassNumber (class); ! 1192: Method_t method; ! 1193: ! 1194: /* DEBUG_PRINTF ("Assignment of sel=%s, class=%s (%#x, %#x)\n", ! 1195: sel_getName ((SEL)i), class->name, ! 1196: searchForMethodInHierarchy (class, (SEL)i), ! 1197: searchForMethodInHierarchy ((Class_t)meta_class, (SEL)i)); */ ! 1198: ! 1199: method = searchForMethodInHierarchy (class, (SEL)i); ! 1200: if (method) ! 1201: record_store_at (class_number, method->method_imp, ! 1202: record_get (i, instance_method_record)); ! 1203: ! 1204: assert (class_number == getClassNumber ((Class_t)class->class_pointer)); ! 1205: method = searchForMethodInHierarchy ((Class_t)meta_class, (SEL)i); ! 1206: if (method) ! 1207: record_store_at (class_number, method->method_imp, ! 1208: record_get (i, factory_method_record)); ! 1209: } ! 1210: } ! 1211: } ! 1212: ! 1213: ! 1214: /* ! 1215: * This method is called by the dispatch routines when a class has not been ! 1216: * initialized. This method is responsible for initializing the class. This ! 1217: * is accomplished by first testing the class itself for responding to the ! 1218: * +initialize method. If such a method is implemented then it is called. ! 1219: * Before exit, irregardless if the class implements +initialize, the class ! 1220: * is marked as initialized. ! 1221: */ ! 1222: static void ! 1223: initialize_class (const char *name) ! 1224: { ! 1225: Method_t method = NULL; ! 1226: Class_t class = objc_getClass (name); ! 1227: SEL sel = sel_getUid ("initialize"); ! 1228: ! 1229: ! 1230: /* The class should not be initialized at this point. */ ! 1231: assert (!(class->info & CLS_INITIALIZED)); ! 1232: assert (!(class->class_pointer->info & CLS_INITIALIZED)); ! 1233: ! 1234: /* Search for the +initialize method. ! 1235: Call it if it exists. */ ! 1236: if (sel) ! 1237: method = searchForMethodInList (class->class_pointer->methods, ! 1238: sel_getName (sel)); ! 1239: if (method) { ! 1240: IMP imp; ! 1241: ! 1242: DEBUG_PRINTF ("Class: %s sending +%s\n", ! 1243: name, sel_getName (sel)); ! 1244: imp = get_imp ((Class_t)class->class_pointer, sel); ! 1245: assert (imp); ! 1246: (*imp)((id)class, sel); ! 1247: } ! 1248: ! 1249: /* Mark the class as initialized. */ ! 1250: class->info |= CLS_INITIALIZED; ! 1251: class->class_pointer->info |= CLS_INITIALIZED; ! 1252: } ! 1253: ! 1254: ! 1255: /* ! 1256: * Silly little function that checks to make sure the class hash table is ! 1257: * initialized. If it isn't initialized then do it. ! 1258: */ ! 1259: static inline void ! 1260: class_hash_init (void) ! 1261: { ! 1262: static unsigned int init = 0; ! 1263: ! 1264: ! 1265: if (!init) ! 1266: class_hash_table = hash_new (CLASS_HASH_SIZE, ! 1267: (hash_func_type)hash_string, ! 1268: (compare_func_type)compare_strings); ! 1269: init = 1; ! 1270: } ! 1271: ! 1272: ! 1273: Class_t ! 1274: objc_getClass (const char *name) ! 1275: { ! 1276: Class_t class; ! 1277: ! 1278: ! 1279: /* Make sure the class hash table exists. */ ! 1280: class_hash_init (); ! 1281: ! 1282: class = hash_value_for_key (class_hash_table, name); ! 1283: ! 1284: return class; ! 1285: } ! 1286: ! 1287: ! 1288: MetaClass_t ! 1289: objc_getMetaClass (const char *name) ! 1290: { ! 1291: /* Meta classes are pointed to by the class's class_pointer. ! 1292: Just get the class and return its class_pointer. */ ! 1293: return (objc_getClass (name))->class_pointer; ! 1294: } ! 1295: ! 1296: ! 1297: void ! 1298: addClassToHash (Class_t class) ! 1299: { ! 1300: Class_t hClass; ! 1301: ! 1302: ! 1303: class_hash_init (); ! 1304: ! 1305: /* Check to see if the class is already in the hash table. */ ! 1306: hClass = hash_value_for_key (class_hash_table, class->name); ! 1307: if (!hClass) { ! 1308: ! 1309: /* The class isn't in the hash table. Add the class and ! 1310: assign a class number. */ ! 1311: static unsigned int class_number = 1; ! 1312: ! 1313: setClassNumber (class, class_number); ! 1314: setClassNumber ((Class_t)class->class_pointer, class_number); ! 1315: ++class_number; ! 1316: ! 1317: hash_add (&class_hash_table, class->name, class); ! 1318: } ! 1319: } ! 1320: ! 1321: ! 1322: void ! 1323: debug_dump_classes (void) ! 1324: { ! 1325: node_ptr node; ! 1326: int i; ! 1327: ! 1328: ! 1329: DEBUG_PRINTF ("class tables\n"); ! 1330: i = 0; ! 1331: for (node = hash_next (class_hash_table, NULL); node; ! 1332: node = hash_next (class_hash_table, node)) { ! 1333: ! 1334: Class_t class = node->value; ! 1335: ! 1336: DEBUG_PRINTF ("Class { /*%#x*/\n", class); ! 1337: DEBUG_PRINTF (" MetaClass_t class_pointer = %#x\n", class->class_pointer); ! 1338: DEBUG_PRINTF (" Class_t super_class = %#x\n", class->super_class); ! 1339: DEBUG_PRINTF (" char *name = %s\n", class->name); ! 1340: DEBUG_PRINTF (" long version = %ld\n", class->version); ! 1341: DEBUG_PRINTF (" long info = %#x\n", class->info); ! 1342: DEBUG_PRINTF (" long instance_size = %ld\n", class->instance_size); ! 1343: DEBUG_PRINTF (" IvarList_t ivars = %#x\n", class->ivars); ! 1344: DEBUG_PRINTF (" MethodList_t methods = %#x\n", class->methods); ! 1345: DEBUG_PRINTF (" cache_ptr cache = %#x\n", class->cache); ! 1346: DEBUG_PRINTF ("}[%d];\n", i++); ! 1347: } ! 1348: ! 1349: i = 0; ! 1350: for (node = hash_next (class_hash_table, NULL); node; ! 1351: node = hash_next (class_hash_table, node)) { ! 1352: ! 1353: Class_t class = (Class_t)((Class_t)(node->value))->class_pointer; ! 1354: ! 1355: DEBUG_PRINTF ("MetaClass { /*%#x*/\n", class); ! 1356: DEBUG_PRINTF (" MetaClass_t class_pointer = %#x\n", class->class_pointer); ! 1357: DEBUG_PRINTF (" MetaClass_t super_class = %#x\n", class->super_class); ! 1358: DEBUG_PRINTF (" char *name = %s\n", class->name); ! 1359: DEBUG_PRINTF (" long version = %ld\n", class->version); ! 1360: DEBUG_PRINTF (" long info = %#x\n", class->info); ! 1361: DEBUG_PRINTF (" long instance_size = %ld\n", class->instance_size); ! 1362: DEBUG_PRINTF (" IvarList_t ivars = %#x\n", class->ivars); ! 1363: DEBUG_PRINTF (" MethodList_t methods = %#x\n", class->methods); ! 1364: DEBUG_PRINTF (" cache_ptr cache = %#x\n", class->cache); ! 1365: DEBUG_PRINTF ("}[%d];\n", i++); ! 1366: } ! 1367: } ! 1368:
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.