/* Copyright (C) 2000-2005 SKYRIX Software AG This file is part of SOPE. SOPE is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. SOPE is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with SOPE; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "NGObjCRuntime.h" #include "NGMemoryAllocation.h" #include #include #include #include "common.h" #if NeXT_RUNTIME || APPLE_RUNTIME # include # include typedef struct objc_method_list *MethodList_t; typedef struct objc_ivar_list *IvarList_t; typedef struct objc_method *Method_t; #else # include #endif #import #import #import #import #import #define GCC_VERSION (__GNUC__ * 10000 \ + __GNUC_MINOR__ * 100 \ + __GNUC_PATCHLEVEL__) #if GNUSTEP_BASE_LIBRARY /* this is a hack for the extensions 0.8.6 library */ void class_add_behavior(Class class, Class behavior) { extern void behavior_class_add_class (Class class, Class behavior); behavior_class_add_class(class, behavior); } #endif @interface _NGMethodNameEnumerator : NSEnumerator { Class clazz; BOOL includeSuperclassMethods; NSMutableSet *names; struct objc_method_list *methods; // current method list unsigned i; // current index in current method list #if APPLE_RUNTIME || NeXT_RUNTIME void *iter; // runtime iterator #endif } - (id)initWithClass:(Class)_clazz includeSuperclassMethods:(BOOL)_flag; - (id)nextObject; @end #if NeXT_RUNTIME || APPLE_RUNTIME static __inline__ unsigned NGGetSizeOfType(char *ivarType) { // TODO: add more types ... switch (*ivarType) { /* the Apple runtime has no func to calc a type size ?! */ case '@': return sizeof(id); case ':': return sizeof(SEL); case 'c': return sizeof(signed char); case 's': return sizeof(signed short); case 'i': return sizeof(signed int); case 'C': return sizeof(unsigned char); case 'S': return sizeof(unsigned short); case 'I': return sizeof(unsigned int); default: return 0xDEAFBEAF; } } #endif @implementation NSObject(NGObjCRuntime) static NSArray *emptyArray = nil; static unsigned countMethodSpecs(SEL _selector, va_list *va) __attribute__((unused)); static unsigned countMethodSpecs(SEL _selector, va_list *va) { SEL selector; NSString *signature; IMP imp; unsigned count; selector = _selector; signature = nil; imp = NULL; if (selector) signature = va_arg(*va, id); if (signature) imp = va_arg(*va, IMP); count = 0; while ((selector != NULL) && (signature != nil) && (imp != NULL)) { count++; if ((selector = va_arg(*va, SEL)) == NULL) break; if ((signature = (id)va_arg(*va, NSString *)) == nil) break; if ((imp = (IMP)va_arg(*va, IMP)) == NULL) break; } return count; } static void fillMethodListWithSpecs(MethodList_t methods, SEL _selector, va_list *va) { /* takes triple: SEL, signature, IMP */ SEL selector; NSString *signature; IMP imp; unsigned count; selector = _selector; signature = selector ? va_arg(*va, NSString *) : (NSString *)nil; imp = signature ? va_arg(*va, IMP) : NULL; count = 0; while ((selector != NULL) && (signature != nil) && (imp != NULL)) { unsigned len; char *types; #if GNU_RUNTIME const char *selname; #endif /* allocate signature buffer */ len = [signature cStringLength]; types = malloc(len + 4); [signature getCString:types]; types[len] = 0; #if APPLE_RUNTIME || NeXT_RUNTIME count = methods->method_count; methods->method_list[count].method_name = selector; methods->method_list[count].method_types = types; methods->method_list[count].method_imp = imp; methods->method_count++; #else /* determine selector name */ selname = sel_get_name(selector); /* fill structure */ methods->method_list[count].method_name = (SEL)selname; methods->method_list[count].method_types = types; methods->method_list[count].method_imp = imp; count++; #endif /* go to next method spec */ if ((selector = va_arg(*va, SEL)) == NULL) break; if ((signature = va_arg(*va, NSString *)) == nil) break; if ((imp = va_arg(*va, IMP)) == NULL) break; } #if GNU_RUNTIME methods->method_count = count; methods->method_next = NULL; #endif } + (unsigned)instanceSize { return ((Class)self)->instance_size; } /* adding methods */ + (void)addMethodList:(MethodList_t)_methods { if (_methods == NULL) return; if (_methods->method_count == 0) return; #if NeXT_RUNTIME class_addMethods(self, _methods); #else { extern void class_add_method_list (Class class, MethodList_t list); class_add_method_list(self, _methods); } #endif } + (void)addClassMethodList:(MethodList_t)_methods { if (_methods == NULL) return; if (_methods->method_count == 0) return; #if NeXT_RUNTIME class_addMethods(((Class)self)->isa, _methods); #else { extern void class_add_method_list (Class class, MethodList_t list); class_add_method_list(((Class)self)->class_pointer, _methods); } #endif } + (void)addMethods:(SEL)_selector, ... { /* takes triples (sel, type, imp) finished by nil */ MethodList_t methods; va_list va; unsigned count; va_start(va, _selector); count = countMethodSpecs(_selector, &va); va_end(va); if (count == 0) return; #if NeXT_RUNTIME || APPLE_RUNTIME methods = malloc(sizeof(struct objc_method_list) + ((count + 1) * sizeof(struct objc_method))); methods->method_count = 0; va_start(va, _selector); fillMethodListWithSpecs(methods, _selector, &va); va_end(va); [self addMethodList:methods]; #else methods = malloc(sizeof(MethodList) + (count + 2) * sizeof(Method)); NSAssert(methods, @"could not allocate methodlist"); va_start(va, _selector); fillMethodListWithSpecs(methods, _selector, &va); va_end(va); [self addMethodList:methods]; #endif } + (void)addClassMethods:(SEL)_selector, ... { /* takes triples finished by nil */ MethodList_t methods; va_list va; unsigned count; va_start(va, _selector); count = countMethodSpecs(_selector, &va); va_end(va); if (count == 0) return; #if NeXT_RUNTIME methods = malloc(sizeof(struct objc_method_list) + ((count + 1) * sizeof(struct objc_method))); methods->method_count = 0; va_start(va, _selector); fillMethodListWithSpecs(methods, _selector, &va); va_end(va); [self addClassMethodList:methods]; #else methods = malloc(sizeof(MethodList) + count * sizeof(Method)); NSAssert(methods, @"couldn't allocate methodlist"); va_start(va, _selector); fillMethodListWithSpecs(methods, _selector, &va); va_end(va); [self addClassMethodList:methods]; #endif } + (NSEnumerator *)methodNameEnumerator { return [[[_NGMethodNameEnumerator alloc] initWithClass:self includeSuperclassMethods:NO] autorelease]; } + (NSEnumerator *)hierachyMethodNameEnumerator { return [[[_NGMethodNameEnumerator alloc] initWithClass:self includeSuperclassMethods:YES] autorelease]; } /* subclassing */ + (Class)subclass:(NSString *)_className ivarsList:(IvarList_t)_ivars { #if NeXT_RUNTIME [(NSObject *)self doesNotRecognizeSelector:_cmd]; return Nil; #else // TODO: do we really need a symtab? PyObjC does not seem to require that Module_t module; Class newMetaClass, newClass; unsigned nameLen; char *name, *moduleName, *metaName; int instanceSize, i; /* define names */ nameLen = [_className cStringLength]; name = malloc(nameLen + 3); [_className getCString:name]; moduleName = name; metaName = name; /* calc instance size */ // printf("calc isize ..\n"); for (i = 0, instanceSize = ((Class)self)->instance_size; i < _ivars->ivar_count; i++) { unsigned typeAlign, typeLen; // printf("ivar %s\n", _ivars->ivar_list[i].ivar_name); // printf(" type %s\n", _ivars->ivar_list[i].ivar_type); typeAlign = objc_alignof_type(_ivars->ivar_list[i].ivar_type); typeLen = objc_sizeof_type(_ivars->ivar_list[i].ivar_type); /* check if offset is aligned */ if ((instanceSize % typeAlign) != 0) { /* add alignment size */ instanceSize += (typeAlign - (instanceSize % typeAlign)); } instanceSize += typeLen; } /* allocate structures */ newMetaClass = malloc(sizeof(struct objc_class)); newClass = malloc(sizeof(struct objc_class)); NSCAssert(newMetaClass, @"could not allocate new meta class structure"); NSCAssert(newClass, @"could not allocate new class structure"); // printf("setup meta ..\n"); /* init meta class */ newMetaClass->super_class = (Class)((Class)self)->class_pointer->name; newMetaClass->class_pointer = newMetaClass->super_class->class_pointer; newMetaClass->name = metaName; newMetaClass->version = 0; newMetaClass->info = _CLS_META; newMetaClass->instance_size = newMetaClass->super_class->instance_size; newMetaClass->methods = NULL; newMetaClass->dtable = NULL; newMetaClass->subclass_list = NULL; newMetaClass->sibling_class = NULL; newMetaClass->protocols = NULL; newMetaClass->gc_object_type = NULL; // printf("setup class ..\n"); /* init class */ newClass->super_class = (Class)((Class)self)->name; newClass->class_pointer = newMetaClass; newClass->name = name; newClass->version = 0; newClass->info = _CLS_CLASS; newClass->instance_size = instanceSize; newClass->methods = NULL; newClass->dtable = NULL; newClass->subclass_list = NULL; newClass->sibling_class = NULL; newClass->protocols = NULL; newClass->gc_object_type = NULL; newClass->ivars = _ivars; /* allocate module */ module = malloc(sizeof(Module)); NSCAssert(module, @"could not allocate module !"); memset(module, 0, sizeof(Module)); module->version = 8; module->size = sizeof(Module); module->name = moduleName; /* allocate symtab with one entry */ module->symtab = malloc(sizeof(Symtab) + (2 * sizeof(void *))); module->symtab->sel_ref_cnt = 0; module->symtab->refs = 0; // ptr to array of 'struct objc_selector' module->symtab->cls_def_cnt = 1; module->symtab->cat_def_cnt = 0; module->symtab->defs[0] = newClass; module->symtab->defs[1] = NULL; /* execute module */ { #if GCC_VERSION < 30400 extern void __objc_exec_class(Module_t module); // is thread-safe extern void __objc_resolve_class_links(); #else void __objc_exec_class(void* module); void __objc_resolve_class_links(); #endif /* This is the main entry function in the GNU runtime. It does a LOT of work, including the setup of global hashes if missing. Some things (like the global hashes) are not relevant for us, since the runtime itself is already up. This function uses the internal lock for runtime modifications. The method does with the runtime lock applied (2.95.3): - setup globals - registers typed selectors for symtab->refs - walks over all classes - adds the class to the hash - registers the selectors of the class - registers the selectors of the metaclass - install dtable's (just assigns NULL?) - registers instance methods as class methods for root classes - inits protocols - add superclasses to unresolved-classes - walks over all categories - register uninitialized statics - walk over unclaimed categories - walk over unclaimed protocols - send the +load message Actually this function just calls __objc_exec_module(), don't know why we call this one instead. */ // printf("execute class\n"); __objc_exec_class(module); //printf("resolve links\n"); __objc_resolve_class_links(); } return NSClassFromString(_className); #endif } + (Class)subclass:(NSString *)_className ivarNames:(NSString **)_ivarNames ivarTypes:(NSString **)_ivarTypes ivarCount:(unsigned)ivarCount { unsigned currentSize; currentSize = ((Class)self)->instance_size; #if NeXT_RUNTIME || APPLE_RUNTIME { /* some tricks for Apple inspired by PyObjC, long live OpenSource ;-) */ IvarList_t ivars; struct objc_class *clazz; struct objc_class *metaClazz; struct objc_class *rootClazz; /* build ivars */ ivars = NULL; if (ivarCount > 0) { unsigned i; ivars = calloc(sizeof(struct objc_ivar_list) + (ivarCount) * sizeof(struct objc_ivar), sizeof(char)); for (i = 0; i < ivarCount; i++) { NSString *n, *t; Ivar var; char *ivarName, *ivarType; int ivarOffset; unsigned len, typeAlign, typeLen; n = _ivarNames[i]; t = _ivarTypes[i]; len = [n cStringLength]; ivarName = malloc(len + 2); [n getCString:ivarName]; ivarName[len] = '\0'; len = [t cStringLength]; ivarType = malloc(len + 2); [t getCString:ivarType]; ivarType[len] = '\0'; /* calc ivarOffset */ typeAlign = 0; // TODO: alignment?! typeLen = NGGetSizeOfType(ivarType); NSAssert1(typeLen != 0xDEAFBEAF, @"does not support ivars of type '%s'", ivarType); ivarOffset = currentSize; var = ivars->ivar_list + ivars->ivar_count; ivars->ivar_count++; var->ivar_name = ivarName; var->ivar_offset = ivarOffset; var->ivar_type = ivarType; /* adjust current size */ currentSize = ivarOffset + typeLen; } } // TODO: move the following to a subclass method /* determine root class */ for (rootClazz = self; rootClazz->super_class != NULL; ) rootClazz = rootClazz->super_class; /* setup meta class */ metaClazz = calloc(1, sizeof(struct objc_class)); metaClazz->isa = rootClazz->isa; // root-meta is the metameta metaClazz->name = strdup([_className cString]); metaClazz->info = CLS_META; metaClazz->super_class = ((struct objc_class *)self)->isa; metaClazz->instance_size = ((struct objc_class *)self)->isa->instance_size; metaClazz->ivars = NULL; metaClazz->protocols = NULL; /* setup class */ clazz = calloc(1, sizeof(struct objc_class)); clazz->isa = metaClazz; /* hook up meta class */ clazz->name = strdup([_className cString]); clazz->info = CLS_CLASS; clazz->super_class = self; clazz->instance_size = currentSize; clazz->ivars = ivars; clazz->protocols = NULL; #if 0 NSLog(@"instance size: %d, ivar-count: %d", currentSize, ivars->ivar_count); #endif /* setup method lists */ metaClazz->methodLists = calloc(1, sizeof(struct objc_method_list *)); clazz->methodLists = calloc(1, sizeof(struct objc_method_list *)); /* Note: MacOSX specific, Radar #3317376, hint taken from PyObjC */ metaClazz->methodLists[0] = (struct objc_method_list *)-1; clazz->methodLists[0] = (struct objc_method_list *)-1; /* add to runtime (according to PyObjC not reversible?) */ objc_addClass(clazz); return NSClassFromString(_className); } #else { unsigned i; IvarList_t ivars; ivars = malloc(sizeof(IvarList) + (sizeof(struct objc_ivar) * ivarCount)); ivars->ivar_count = ivarCount; for (i = 0; i < ivarCount; i++) { NSString *n, *t; char *ivarName, *ivarType; int ivarOffset; unsigned len, typeAlign, typeLen; n = _ivarNames[i]; t = _ivarTypes[i]; len = [n cStringLength]; ivarName = malloc(len + 2); [n getCString:ivarName]; ivarName[len] = '\0'; len = [t cStringLength]; ivarType = malloc(len + 2); [t getCString:ivarType]; ivarType[len] = '\0'; /* calc ivarOffset */ typeAlign = objc_alignof_type(ivarType); typeLen = objc_sizeof_type(ivarType); ivarOffset = currentSize; /* check if offset is aligned */ if ((ivarOffset % typeAlign) != 0) { /* align offset */ len = (typeAlign - (ivarOffset % typeAlign)); ivarOffset += len; } /* adjust current size */ currentSize = ivarOffset + typeLen; ivars->ivar_list[ivarCount].ivar_name = ivarName; ivars->ivar_list[ivarCount].ivar_type = ivarType; ivars->ivar_list[ivarCount].ivar_offset = ivarOffset; } return [self subclass:_className ivarsList:ivars]; } #endif } + (Class)subclass:(NSString *)_className ivars:(NSString *)_name1,... { va_list va; /* contains: name1, type1, name2, type2, ... */ unsigned ivarCount; NSString *n, *t; NSString **ivarNames = NULL; NSString **ivarTypes = NULL; Class clazz; /* determine number of args */ va_start(va, _name1); for (n = _name1, t = va_arg(va, NSString *), ivarCount = 0; (n != nil && t != nil); n = va_arg(va, NSString *), t = va_arg(va, NSString *)) ivarCount++; va_end(va); /* collect args */ if (ivarCount > 0) { ivarNames = calloc(ivarCount, sizeof(NSString *)); ivarTypes = calloc(ivarCount, sizeof(NSString *)); va_start(va, _name1); for (n = _name1, t = va_arg(va, NSString *), ivarCount = 0; (n != nil && t != nil); n = va_arg(va, NSString *), t = va_arg(va, NSString *)) { ivarNames[ivarCount] = n; ivarTypes[ivarCount] = t; ivarCount++; } va_end(va); } /* call primary method */ clazz = [self subclass:_className ivarNames:ivarNames ivarTypes:ivarTypes ivarCount:ivarCount]; if (ivarNames != NULL) free(ivarNames); if (ivarTypes != NULL) free(ivarTypes); return clazz; } /* instance variables */ + (NSArray *)instanceVariableNames { NSArray *result; NSString **names; int i; if (((Class)self)->ivars == NULL || ((Class)self)->ivars->ivar_count == 0) { if (emptyArray == nil) emptyArray = [[NSArray alloc] init]; return emptyArray; } names = calloc(((Class)self)->ivars->ivar_count + 2, sizeof(NSString *)); for (i = 0; i < ((Class)self)->ivars->ivar_count; i++) { register unsigned char *ivarName; ivarName = (void *)(((Class)self)->ivars->ivar_list[i].ivar_name); if (ivarName == NULL) { NSLog(@"WARNING(%s): ivar without name! (idx=%d)", __PRETTY_FUNCTION__, i); continue; } #if !LIB_FOUNDATION_LIBRARY names[i] = [NSString stringWithCString:(char *)ivarName]; #else names[i] = [NSString stringWithCStringNoCopy:(char *)ivarName freeWhenDone:NO]; #endif } result = [NSArray arrayWithObjects:names count:((Class)self)->ivars->ivar_count]; if (names) free(names); return result; } + (NSArray *)allInstanceVariableNames { NSMutableArray *varNames; Class c; varNames = [NSMutableArray arrayWithCapacity:32]; for (c = self; c != Nil; c = [c superclass]) [varNames addObjectsFromArray:[c instanceVariableNames]]; return [[varNames copy] autorelease]; } + (BOOL)hasInstanceVariableWithName:(NSString *)_ivarName { Class c; unsigned len = [_ivarName cStringLength]; char *ivarName; if (len == 0) return NO; ivarName = malloc(len + 1); [_ivarName getCString:ivarName]; ivarName[len] = '\0'; for (c = self; c != Nil; c = [c superclass]) { int i; for (i = 0; i < c->ivars->ivar_count; i++) { if (strcmp(ivarName, c->ivars->ivar_list[i].ivar_name) == 0) { free(ivarName); return YES; } } } free(ivarName); return NO; } + (NSString *)signatureOfInstanceVariableWithName:(NSString *)_ivarName { Class c; unsigned len = [_ivarName cStringLength]; char *ivarName; if (len == 0) return nil; ivarName = malloc(len + 1); [_ivarName getCString:ivarName]; ivarName[len] = '\0'; for (c = self; c != Nil; c = [c superclass]) { int i; for (i = 0; i < c->ivars->ivar_count; i++) { if (strcmp(ivarName, c->ivars->ivar_list[i].ivar_name) == 0) { /* found matching ivar name */ if (ivarName) free(ivarName); #if !LIB_FOUNDATION_LIBRARY return [NSString stringWithCString: (char *)(c->ivars->ivar_list[i].ivar_type)]; #else return [NSString stringWithCStringNoCopy: (char *)(c->ivars->ivar_list[i].ivar_type) freeWhenDone:NO]; #endif } } } if (ivarName) free(ivarName); return nil; } + (unsigned)offsetOfInstanceVariableWithName:(NSString *)_ivarName { Class c; unsigned len = [_ivarName cStringLength]; char *ivarName; if (len == 0) return NSNotFound; ivarName = malloc(len + 3); [_ivarName getCString:ivarName]; ivarName[len] = '\0'; for (c = self; c != Nil; c = [c superclass]) { int i; for (i = 0; i < c->ivars->ivar_count; i++) { if (strcmp(ivarName, c->ivars->ivar_list[i].ivar_name) == 0) { /* found matching ivar name */ free(ivarName); return c->ivars->ivar_list[i].ivar_offset; } } } free(ivarName); return NSNotFound; } @end /* NSObject(NGObjCRuntime) */ @implementation _NGMethodNameEnumerator - (id)initWithClass:(Class)_clazz includeSuperclassMethods:(BOOL)_flag { if (_clazz == Nil) { [self release]; return nil; } self->names = [[NSMutableSet alloc] initWithCapacity:200]; self->clazz = _clazz; self->includeSuperclassMethods = _flag; #if NeXT_RUNTIME self->iter = 0; self->methods = class_nextMethodList(self->clazz, &(self->iter)); #else self->methods = _clazz->methods; self->i = 0; #endif return self; } - (void)dealloc { [self->names release]; [super dealloc]; } - (id)nextObject { if (self->clazz == nil) return nil; if (self->methods == NULL) { /* methods of current class are done .. */ if (!self->includeSuperclassMethods) return nil; /* loop, maybe there are classes without a method-list ? */ while (self->methods == NULL) { if ((self->clazz = [self->clazz superclass]) == Nil) /* no more superclasses */ return nil; #if NeXT_RUNTIME self->iter = 0; self->methods = class_nextMethodList(self->clazz, &(self->iter)); #else self->methods = self->clazz->methods; #endif } self->i = 0; } #if DEBUG NSAssert(self->methods, @"missing method-list !"); #endif while (self->i >= (unsigned)self->methods->method_count) { #if NeXT_RUNTIME || APPLE_RUNTIME self->methods = class_nextMethodList(self->clazz, &(self->iter)); #else self->methods = self->methods->method_next; #endif if (self->methods == NULL) break; self->i = 0; } if (self->methods == NULL) { /* recurse to next super class */ return self->includeSuperclassMethods ? [self nextObject] : nil; } /* get name .. */ { Method_t m; NSString *name; m = &(self->methods->method_list[self->i]); self->i++; NSAssert(m, @"missing method structure !"); name = NSStringFromSelector(m->method_name); NSAssert(name, @"couldn't get method name !"); if ([self->names containsObject:name]) { /* this name was already delivered from a subclass, take next */ return [self nextObject]; } [self->names addObject:name]; return name; } } @end /* _NGMethodNameEnumerator */ #if GNU_RUNTIME @interface NGObjCClassEnumerator : NSEnumerator { void *state; } @end @implementation NGObjCClassEnumerator - (id)nextObject { return objc_next_class(&(self->state)); } @end /* NGObjCClassEnumerator */ #endif /* GNU_RUNTIME */