_objc_msgSend
__class_lookupMethodAndLoadCache
__class_getFreedObjectClass
-__class_getNonexistentObjectClass
__class_isInitialized
__class_initialize
__class_isMetaClass
}
// Deallocate the thread list
- vm_deallocate (mach_task_self (), (vm_address_t) threads, sizeof(threads) * number);
+ vm_deallocate (mach_task_self (), (vm_address_t) threads, sizeof(threads[0]) * number);
// Return our finding
return result;
NULL // protocols
};
-static const struct old_class nonexistentObjectClass =
-{
- Nil, // isa
- Nil, // super_class
- "NONEXISTENT(id)", // name
- 0, // version
- 0, // info
- 0, // instance_size
- NULL, // ivars
- NULL, // methodLists
- (Cache) &_objc_empty_cache, // cache
- NULL // protocols
-};
-
/***********************************************************************
* _class_getFreedObjectClass. Return a pointer to the dummy freed
}
-/***********************************************************************
-* _class_getNonexistentClass. Return a pointer to the dummy nonexistent
-* object class. This is used when, for example, mapping the class
-* refs for an image, and the class can not be found, so that we can
-* catch later uses of the non-existent class.
-**********************************************************************/
-__private_extern__ Class _class_getNonexistentObjectClass(void)
-{
- return (Class)&nonexistentObjectClass;
-}
-
-
/***********************************************************************
* _objc_getFreedObjectClass. Return a pointer to the dummy freed
* object class. Freed objects get their isa pointers replaced with
sel_getName(sel), obj);
}
-/***********************************************************************
-* _nonexistentHandler.
-**********************************************************************/
-static void _nonexistentHandler(id obj, SEL sel)
-{
- __objc_error (obj, "message %s sent to non-existent object=%p",
- sel_getName(sel), obj);
-}
-
/***********************************************************************
* ABI-specific lookUpMethod helpers.
if (cls == _class_getFreedObjectClass())
return (IMP) _freedHandler;
- // Check for nonexistent class
- if (cls == _class_getNonexistentObjectClass())
- return (IMP) _nonexistentHandler;
-
if (init && !_class_isInitialized(cls)) {
_class_initialize (cls);
// If sel == initialize, _class_initialize will send +initialize and
static inline struct old_category *_category_asOld(Category cat) { return (struct old_category *)cat; }
extern void unload_class(struct old_class *cls);
-Class _class_getNonexistentObjectClass(void);
#endif
extern BOOL object_cxxConstruct(id obj);
/***********************************************************************
* remappedClasses
* Returns the oldClass => newClass map for realized future classes.
+* Returns the oldClass => NULL map for ignored weak-linked classes.
* Locking: runtimeLock must be read- or write-locked by the caller
**********************************************************************/
static NXMapTable *remappedClasses(BOOL create)
/***********************************************************************
* addRemappedClass
* newcls is a realized future class, replacing oldcls.
+* OR newcls is NULL, replacing ignored weak-linked class oldcls.
* Locking: runtimeLock must be write-locked by the caller
**********************************************************************/
static void addRemappedClass(class_t *oldcls, class_t *newcls)
* remapClass
* Returns the live class pointer for cls, which may be pointing to
* a class struct that has been reallocated.
+* Returns NULL if cls is ignored because of weak linking.
* Locking: runtimeLock must be read- or write-locked by the caller
**********************************************************************/
static class_t *remapClass(class_t *cls)
{
rwlock_assert_locked(&runtimeLock);
- class_t *newcls = NXMapGet(remappedClasses(YES), cls);
- return newcls ? newcls : cls;
+ class_t *c2;
+
+ if (!cls) return NULL;
+
+ if (NXMapMember(remappedClasses(YES), cls, (void**)&c2) == NX_MAPNOTAKEY) {
+ return cls;
+ } else {
+ return c2;
+ }
}
/***********************************************************************
* remapClassRef
-* Fix up a class ref, in case the class referenced has been reallocated.
+* Fix up a class ref, in case the class referenced has been reallocated
+* or is an ignored weak-linked class.
* Locking: runtimeLock must be read- or write-locked by the caller
**********************************************************************/
static void remapClassRef(class_t **clsref)
}
+/***********************************************************************
+* missingWeakSuperclass
+* Return YES if some superclass of cls was weak-linked and is missing.
+**********************************************************************/
+static BOOL
+missingWeakSuperclass(class_t *cls)
+{
+ assert(!isRealized(cls));
+
+ if (!cls->superclass) {
+ // superclass NULL. This is normal for root classes only.
+ return (!(cls->data->flags & RO_ROOT));
+ } else {
+ // superclass not NULL. Check if a higher superclass is missing.
+ class_t *supercls = remapClass(cls->superclass);
+ if (!supercls) return YES;
+ if (isRealized(supercls)) return NO;
+ return missingWeakSuperclass(supercls);
+ }
+}
+
+
/***********************************************************************
* realizeAllClassesInImage
* Non-lazily realizes all unrealized classes in the given image.
class_t **classlist = _getObjc2ClassList(hi, &count);
for (i = 0; i < count; i++) {
const char *name = getName(classlist[i]);
+
+ if (missingWeakSuperclass(classlist[i])) {
+ // No superclass (probably weak-linked).
+ // Disavow any knowledge of this subclass.
+ if (PrintConnecting) {
+ _objc_inform("CLASS: IGNORING class '%s' with "
+ "missing weak-linked superclass", name);
+ }
+ addRemappedClass(classlist[i], NULL);
+ classlist[i]->superclass = NULL;
+ classlist[i] = NULL;
+ continue;
+ }
+
if (NXCountMapTable(future_class_map) > 0) {
class_t *newCls = NXMapGet(future_class_map, name);
if (newCls) {
// Do NOT use cat->cls! It may have been remapped.
class_t *cls = remapClass(cat->cls);
+ if (!cls) {
+ // Category's target class is missing (probably weak-linked).
+ // Disavow any knowledge of this category.
+ catlist[i] = NULL;
+ if (PrintConnecting) {
+ _objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
+ "missing weak-linked target class",
+ cat->name, cat);
+ }
+ continue;
+ }
+
// Process this category.
// First, register the category with its target class.
// Then, rebuild the class's method lists (etc) if
// cls must already be connected.
static void schedule_class_load(class_t *cls)
{
+ if (!cls) return;
assert(isRealized(cls)); // _read_images should realize
if (cls->data->flags & RW_LOADED) return;
- class_t *supercls = getSuperclass(cls);
- if (supercls) schedule_class_load(supercls);
+ // Ensure superclass-first ordering
+ schedule_class_load(getSuperclass(cls));
add_class_to_loadable_list((Class)cls);
changeInfo(cls, RW_LOADED, 0);
class_t **classlist =
_getObjc2NonlazyClassList(hi, &count);
for (i = 0; i < count; i++) {
- class_t *cls = remapClass(classlist[i]);
- schedule_class_load(cls);
+ schedule_class_load(remapClass(classlist[i]));
}
category_t **categorylist = _getObjc2NonlazyCategoryList(hi, &count);
category_t *cat = categorylist[i];
// Do NOT use cat->cls! It may have been remapped.
class_t *cls = remapClass(cat->cls);
+ if (!cls) continue; // category for ignored weak-linked class
realizeClass(cls);
assert(isRealized(cls->isa));
add_category_to_loadable_list((Category)cat);
category_t **catlist = _getObjc2CategoryList(hi, &count);
for (i = 0; i < count; i++) {
category_t *cat = catlist[i];
+ if (!cat) continue; // category for ignored weak-linked class
class_t *cls = remapClass(cat->cls);
+ assert(cls); // shouldn't have live category for dead class
+
// fixme for MH_DYLIB cat's class may have been unloaded already
// unattached list
for (i = 0; i < count; i++) {
class_t *cls = classlist[i];
// fixme remapped classes?
- remove_class_from_loadable_list((Class)cls);
- unload_class(cls->isa, YES);
- unload_class(cls, NO);
+ // fixme ignored weak-linked classes
+ if (cls) {
+ remove_class_from_loadable_list((Class)cls);
+ unload_class(cls->isa, YES);
+ unload_class(cls, NO);
+ }
}
// Clean up protocols.
__private_extern__ const char **
_objc_copyClassNamesForImage(header_info *hi, unsigned int *outCount)
{
- size_t count, i;
+ size_t count, i, shift;
class_t **classlist;
const char **names;
classlist = _getObjc2ClassList(hi, &count);
names = malloc((count+1) * sizeof(const char *));
+ shift = 0;
for (i = 0; i < count; i++) {
- names[i] = getName(classlist[i]);
+ class_t *cls = remapClass(classlist[i]);
+ if (cls) {
+ names[i-shift] = getName(classlist[i]);
+ } else {
+ shift++; // ignored weak-linked class
+ }
}
+ count -= shift;
names[count] = NULL;
rwlock_unlock_read(&runtimeLock);
* (5) is not yet eligible for +load.
*
* Image mapping is NOT CURRENTLY THREAD-SAFE with respect to just about
- * * * anything. Image mapping IS RE-ENTRANT in several places: superclass
+ * anything. Image mapping IS RE-ENTRANT in several places: superclass
* lookup may cause ZeroLink to load another image, and +load calls may
* cause dyld to load another image.
*
* Read all classes in all new images.
* Add them all to unconnected_class_hash.
* Note any +load implementations before categories are attached.
- * Fix up any pended classrefs referring to them.
* Attach any pending categories.
* Read all categories in all new images.
* Attach categories whose parent class exists (connected or not),
* If the superclass is connected:
* connect the class
* mark the class eligible for +load, if implemented
+ * fix up any pended classrefs referring to the class
* connect any pended subclasses of the class
* Resolve selector refs and class refs in all new images.
* Class refs whose classes still do not exist are pended.
* are attached. Otherwise, if a category implements +load and its class
* has no class methods, the class's +load scan would find the category's
* +load method, which would then be called twice.
+ *
+ * Correctness: pended class refs are not fixed up until the class is
+ * connected. Classes with missing weak superclasses remain unconnected.
+ * Class refs to classes with missing weak superclasses must be NULL.
+ * Therefore class refs to unconnected classes must remain un-fixed.
*
**********************************************************************/
mutex_unlock(&classLock);
+ // Fix up pended class refs to this class, if any
+ resolve_references_to_class(cls);
+
// Connect newly-connectable subclasses
resolve_subclasses_of_class(cls);
mutex_unlock(&classLock);
if (!rejected) {
- // Fix up pended class refs to this class, if any
- resolve_references_to_class(newCls);
-
// Attach pended categories for this class, if any
resolve_categories_for_class(newCls);
}
struct old_class *cls;
// Get pointer to class of this name
- // YES unconnected, YES class loader
- cls = _class_asOld((Class)look_up_class(name, YES, YES));
+ // NO unconnected, YES class loader
+ // (real class with weak-missing superclass is unconnected now)
+ cls = _class_asOld((Class)look_up_class(name, NO, YES));
if (cls) {
// Referenced class exists. Fix up the reference.
*ref = isMeta ? cls->isa : cls;
} else {
- // Referenced class does not exist yet. Insert a placeholder
- // class and fix up the reference later.
+ // Referenced class does not exist yet. Insert NULL for now
+ // (weak-linking) and fix up the reference if the class arrives later.
pendClassReference (ref, name, isMeta);
- *ref = (struct old_class *)_class_getNonexistentObjectClass();
+ *ref = NULL;
}
}
if (!pendingClassRefsMap) return;
// Search the pending class ref table for class refs in this range.
- // The class refs may have already been stomped with nonexistentClass,
+ // The class refs may have already been stomped with NULL,
// so there's no way to recover the original class name.
{
if ((uintptr_t)(refs[i]) >= start && (uintptr_t)(refs[i]) < end) {
pendClassReference(&refs[i], refs[i]->name,
(refs[i]->info & CLS_META) ? YES : NO);
- refs[i] = (struct old_class *)_class_getNonexistentObjectClass();
+ refs[i] = NULL;
}
}
}
# a "complex test" has a dedicated testname run rule below
COMPLEX_RUN_TESTS = gcenforcer_noobjc gcenforcer_nogc gcenforcer_supportsgc \
- gcenforcer_requiresgc
+ gcenforcer_requiresgc weak-missing weak-not-missing
# `make fail` just fails
FAIL_TESTS = fail
future2.out: future2.m future.h future0.out test.h Makefile
@ $(CC) -dynamiclib future2.m future0.out -o future2.out $(LIBS)
+weak.out: weak.m weak2.m weak.h test.h Makefile
+ @ $(CC) weak2.m -dynamiclib -o libweak.dylib $(LIBS)
+ @ $(CC) weak2.m -DEMPTY= -dynamiclib -o libweak_empty.dylib $(LIBS)
+ @ $(CC) weak.m -L. -weak-lweak -o weak.out $(LIBS)
+
+weak-not-missing: weak.out
+ @ $(RUN) ./weak.out $(ERR_CHECK) weak-not-missing $(EAT_ERR)
+weak-missing: weak.out
+ @ DYLD_IMAGE_SUFFIX=_empty $(RUN) ./weak.out $(ERR_CHECK) weak-missing $(EAT_ERR)
CONCURRENT_IN=cc1 cc2 cc3 cc4 cc5 cc6 cc7 cc8 cc9 cc10 cc11 cc12 cc13 cc14 cc15
CONCURRENT_DYLIBS=$(addsuffix .out,$(CONCURRENT_IN))
--- /dev/null
+#include "test.h"
+#include <objc/runtime.h>
+
+extern int state;
+
+@interface MissingRoot {
+ id isa;
+}
++(void) initialize;
++(Class) class;
++(id) alloc;
+-(id) init;
++(int) method;
+@end
+
+@interface MissingSuper : MissingRoot {
+ @public
+ int ivar;
+}
+@end
+
+
+@interface NotMissingRoot {
+ id isa;
+}
++(void) initialize;
++(Class) class;
++(id) alloc;
+-(id) init;
++(int) method;
+@end
+
+@interface NotMissingSuper : NotMissingRoot {
+ @public
+ int unused[100];
+ int ivar;
+}
+@end
--- /dev/null
+#include "test.h"
+#include "weak.h"
+
+// Subclass of superclass that isn't there
+@interface MyMissingSuper : MissingSuper
++(int) method;
+@end
+@implementation MyMissingSuper
++(int) method { return 1+[super method]; }
++(void) load { state++; }
+@end
+
+// Subclass of subclass of superclass that isn't there
+@interface MyMissingSub : MyMissingSuper
++(int) method;
+@end
+@implementation MyMissingSub
++(int) method { return 1+[super method]; }
++(void) load { state++; }
+@end
+
+// Subclass of real superclass
+@interface MyNotMissingSuper : NotMissingSuper
++(int) method;
+@end
+@implementation MyNotMissingSuper
++(int) method { return 1+[super method]; }
++(void) load { state++; }
+@end
+
+// Subclass of subclass of superclass that isn't there
+@interface MyNotMissingSub : MyNotMissingSuper
++(int) method;
+@end
+@implementation MyNotMissingSub
++(int) method { return 1+[super method]; }
++(void) load { state++; }
+@end
+
+// Categories on all of the above
+@interface MissingRoot (MissingRootExtras)
++(void)load;
++(int) cat_method;
+@end
+@implementation MissingRoot (MissingRootExtras)
++(void)load { state++; }
++(int) cat_method { return 40; }
+@end
+
+@interface MissingSuper (MissingSuperExtras)
++(void)load;
++(int) cat_method;
+@end
+@implementation MissingSuper (MissingSuperExtras)
++(void)load { state++; }
++(int) cat_method { return 1+[super cat_method]; }
+@end
+
+@interface MyMissingSuper (MyMissingSuperExtras)
++(void)load;
++(int) cat_method;
+@end
+@implementation MyMissingSuper (MyMissingSuperExtras)
++(void)load { state++; }
++(int) cat_method { return 1+[super cat_method]; }
+@end
+
+@interface MyMissingSub (MyMissingSubExtras)
++(void)load;
++(int) cat_method;
+@end
+@implementation MyMissingSub (MyMissingSubExtras)
++(void)load { state++; }
++(int) cat_method { return 1+[super cat_method]; }
+@end
+
+
+@interface NotMissingRoot (NotMissingRootExtras)
++(void)load;
++(int) cat_method;
+@end
+@implementation NotMissingRoot (NotMissingRootExtras)
++(void)load { state++; }
++(int) cat_method { return 30; }
+@end
+
+@interface NotMissingSuper (NotMissingSuperExtras)
++(void)load;
++(int) cat_method;
+@end
+@implementation NotMissingSuper (NotMissingSuperExtras)
++(void)load { state++; }
++(int) cat_method { return 1+[super cat_method]; }
+@end
+
+@interface MyNotMissingSuper (MyNotMissingSuperExtras)
++(void)load;
++(int) cat_method;
+@end
+@implementation MyNotMissingSuper (MyNotMissingSuperExtras)
++(void)load { state++; }
++(int) cat_method { return 1+[super cat_method]; }
+@end
+
+@interface MyNotMissingSub (MyNotMissingSubExtras)
++(void)load;
++(int) cat_method;
+@end
+@implementation MyNotMissingSub (MyNotMissingSubExtras)
++(void)load { state++; }
++(int) cat_method { return 1+[super cat_method]; }
+@end
+
+
+static BOOL classInList(Class *classes, const char *name)
+{
+ Class *cp;
+ for (cp = classes; *cp; cp++) {
+ if (0 == strcmp(class_getName(*cp), name)) return YES;
+ }
+ return NO;
+}
+
+static BOOL classInNameList(const char **names, const char *name)
+{
+ const char **cp;
+ for (cp = names; *cp; cp++) {
+ if (0 == strcmp(*cp, name)) return YES;
+ }
+ return NO;
+}
+
+int main()
+{
+ // DYLD_IMAGE_SUFFIX=_empty loads the weak-missing version
+ BOOL weakMissing = NO;
+ if (getenv("DYLD_IMAGE_SUFFIX")) weakMissing = YES;
+
+ // class and category +load methods
+ if (weakMissing) testassert(state == 8);
+ else testassert(state == 16);
+ state = 0;
+
+ // classes
+ testassert([NotMissingRoot class]);
+ testassert([NotMissingSuper class]);
+ testassert([MyNotMissingSuper class]);
+ testassert([MyNotMissingSub class]);
+ if (weakMissing) {
+ testassert(! [MissingRoot class]);
+ testassert(! [MissingSuper class]);
+ testassert(! [MyMissingSuper class]);
+ testassert(! [MyMissingSub class]);
+ } else {
+ testassert([MissingRoot class]);
+ testassert([MissingSuper class]);
+ testassert([MyMissingSuper class]);
+ testassert([MyMissingSub class]);
+ }
+
+ // objc_getClass
+ testassert(objc_getClass("NotMissingRoot"));
+ testassert(objc_getClass("NotMissingSuper"));
+ testassert(objc_getClass("MyNotMissingSuper"));
+ testassert(objc_getClass("MyNotMissingSub"));
+ if (weakMissing) {
+ testassert(! objc_getClass("MissingRoot"));
+ testassert(! objc_getClass("MissingSuper"));
+ testassert(! objc_getClass("MyMissingSuper"));
+ testassert(! objc_getClass("MyMissingSub"));
+ } else {
+ testassert(objc_getClass("MissingRoot"));
+ testassert(objc_getClass("MissingSuper"));
+ testassert(objc_getClass("MyMissingSuper"));
+ testassert(objc_getClass("MyMissingSub"));
+ }
+
+ // class list
+ Class classes[100];
+ int count = objc_getClassList(classes, 99);
+ classes[count] = NULL;
+ testassert(classInList(classes, "NotMissingRoot"));
+ testassert(classInList(classes, "NotMissingSuper"));
+ testassert(classInList(classes, "MyNotMissingSuper"));
+ testassert(classInList(classes, "MyNotMissingSub"));
+ if (weakMissing) {
+ testassert(! classInList(classes, "MissingRoot"));
+ testassert(! classInList(classes, "MissingSuper"));
+ testassert(! classInList(classes, "MyMissingSuper"));
+ testassert(! classInList(classes, "MyMissingSub"));
+ } else {
+ testassert(classInList(classes, "MissingRoot"));
+ testassert(classInList(classes, "MissingSuper"));
+ testassert(classInList(classes, "MyMissingSuper"));
+ testassert(classInList(classes, "MyMissingSub"));
+ }
+
+ // class name list
+ const char *image = class_getImageName(objc_getClass("NotMissingRoot"));
+ testassert(image);
+ const char **names = objc_copyClassNamesForImage(image, NULL);
+ testassert(names);
+ testassert(classInNameList(names, "NotMissingRoot"));
+ testassert(classInNameList(names, "NotMissingSuper"));
+ if (weakMissing) {
+ testassert(! classInNameList(names, "MissingRoot"));
+ testassert(! classInNameList(names, "MissingSuper"));
+ } else {
+ testassert(classInNameList(names, "MissingRoot"));
+ testassert(classInNameList(names, "MissingSuper"));
+ }
+ free(names);
+
+ image = class_getImageName(objc_getClass("MyNotMissingSub"));
+ testassert(image);
+ names = objc_copyClassNamesForImage(image, NULL);
+ testassert(names);
+ testassert(classInNameList(names, "MyNotMissingSuper"));
+ testassert(classInNameList(names, "MyNotMissingSub"));
+ if (weakMissing) {
+ testassert(! classInNameList(names, "MyMissingSuper"));
+ testassert(! classInNameList(names, "MyMissingSub"));
+ } else {
+ testassert(classInNameList(names, "MyMissingSuper"));
+ testassert(classInNameList(names, "MyMissingSub"));
+ }
+ free(names);
+
+ // methods
+ testassert(20 == [NotMissingRoot method]);
+ testassert(21 == [NotMissingSuper method]);
+ testassert(22 == [MyNotMissingSuper method]);
+ testassert(23 == [MyNotMissingSub method]);
+ if (weakMissing) {
+ testassert(0 == [MissingRoot method]);
+ testassert(0 == [MissingSuper method]);
+ testassert(0 == [MyMissingSuper method]);
+ testassert(0 == [MyMissingSub method]);
+ } else {
+ testassert(10 == [MissingRoot method]);
+ testassert(11 == [MissingSuper method]);
+ testassert(12 == [MyMissingSuper method]);
+ testassert(13 == [MyMissingSub method]);
+ }
+
+ // category methods
+ testassert(30 == [NotMissingRoot cat_method]);
+ testassert(31 == [NotMissingSuper cat_method]);
+ testassert(32 == [MyNotMissingSuper cat_method]);
+ testassert(33 == [MyNotMissingSub cat_method]);
+ if (weakMissing) {
+ testassert(0 == [MissingRoot cat_method]);
+ testassert(0 == [MissingSuper cat_method]);
+ testassert(0 == [MyMissingSuper cat_method]);
+ testassert(0 == [MyMissingSub cat_method]);
+ } else {
+ testassert(40 == [MissingRoot cat_method]);
+ testassert(41 == [MissingSuper cat_method]);
+ testassert(42 == [MyMissingSuper cat_method]);
+ testassert(43 == [MyMissingSub cat_method]);
+ }
+
+ // allocations and ivars
+ id obj;
+ NotMissingSuper *obj2;
+ MissingSuper *obj3;
+ testassert((obj = [[NotMissingRoot alloc] init]));
+ free(obj);
+ testassert((obj2 = [[NotMissingSuper alloc] init]));
+ testassert(obj2->ivar == 200); free(obj2);
+ testassert((obj2 = [[MyNotMissingSuper alloc] init]));
+ testassert(obj2->ivar == 200); free(obj2);
+ testassert((obj2 = [[MyNotMissingSub alloc] init]));
+ testassert(obj2->ivar == 200); free(obj2);
+ if (weakMissing) {
+ testassert(! [[MissingRoot alloc] init]);
+ testassert(! [[MissingSuper alloc] init]);
+ testassert(! [[MyMissingSuper alloc] init]);
+ testassert(! [[MyMissingSub alloc] init]);
+ } else {
+ testassert((obj = [[MissingRoot alloc] init]));
+ free(obj);
+ testassert((obj3 = [[MissingSuper alloc] init]));
+ testassert(obj3->ivar == 100); free(obj3);
+ testassert((obj3 = [[MyMissingSuper alloc] init]));
+ testassert(obj3->ivar == 100); free(obj3);
+ testassert((obj3 = [[MyMissingSub alloc] init]));
+ testassert(obj3->ivar == 100); free(obj3);
+ }
+
+ if (weakMissing) succeed("weak-missing");
+ else succeed("weak-not-missing");
+ return 0;
+}
--- /dev/null
+#include "test.h"
+#include "weak.h"
+
+int state = 0;
+
+#if !defined(EMPTY)
+
+@implementation MissingRoot
++(void) initialize { }
++(Class) class { return self; }
++(id) alloc { return class_createInstance(self, 0); }
+-(id) init { return self; }
++(int) method { return 10; }
++(void) load { state++; }
+@end
+
+@implementation MissingSuper
++(int) method { return 1+[super method]; }
+-(id) init { self = [super init]; ivar = 100; return self; }
++(void) load { state++; }
+@end
+
+#endif
+
+@implementation NotMissingRoot
++(void) initialize { }
++(Class) class { return self; }
++(id) alloc { return class_createInstance(self, 0); }
+-(id) init { return self; }
++(int) method { return 20; }
++(void) load { state++; }
+@end
+
+@implementation NotMissingSuper
++(int) method { return 1+[super method]; }
+-(id) init { self = [super init]; ivar = 200; return self; }
++(void) load { state++; }
+@end
+