From cd71e84dee73ca672f1d62c946668f2da9da8f0d Mon Sep 17 00:00:00 2001 From: CaS Date: Wed, 27 Mar 2002 09:55:57 +0000 Subject: [PATCH] Implemented NSZombie stuff git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/base/trunk@13247 72102866-910b-0410-8b05-ffd578937521 --- ChangeLog | 8 ++ Headers/gnustep/base/NSDebug.h | 37 +++++++ Source/GSPrivate.h | 7 +- Source/NSException.m | 32 +----- Source/NSObject.m | 179 +++++++++++++++++++++++++++------ Source/NSProcessInfo.m | 31 ++++++ Testing/basic.m | 1 + 7 files changed, 232 insertions(+), 63 deletions(-) diff --git a/ChangeLog b/ChangeLog index 636d5cb92..e307bc1b6 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,11 @@ +2002-03-27 Richard Frith-Macdonald + + * Source/GSPrivate.h: Added function to fetch boolean value from env + * Source/NSException.m: Use new function. + * Source/NSProcesInfo.m: Implement new function. + * Source/NSObject.m: Implement NSZombie functionality. + * Headers/Foundation/NSDebug.h: Document NSZombie functionality. + 2002-03-25 Richard Frith-Macdonald * Source/NSTask.m: Implement code to watch for child process exit diff --git a/Headers/gnustep/base/NSDebug.h b/Headers/gnustep/base/NSDebug.h index 75aa29733..c1e6f6234 100644 --- a/Headers/gnustep/base/NSDebug.h +++ b/Headers/gnustep/base/NSDebug.h @@ -111,6 +111,43 @@ GS_EXPORT NSString* GSDebugMethodMsg(id obj, SEL sel, const char *file, int line, NSString *fmt); #endif +/** + * Enable/disable zombies. + *

When an object is deallocated, its isa pointer is normally modified + * to the hexadecimal value 0xdeadface, so that any attempt to send a + * message to the deallocated object will cause a crash, and examination + * of the object within the debugger will show the 0xdeadface value ... + * making it obvious why the program crashed. + *

+ *

Turning on zombies changes this behavior so that the isa pointer + * is modified to be that of the NSZombie class. When messages are + * sent to the object, intead of crashing, NSZombie will use NSLog() to + * produce an error message. By default the memory used by the object + * will not really be freed, so error messages will continue to + * be generated whenever a message is sent to the object, and the object + * instance variables will remain available for examination by the debugger. + *

+ * The default value of this boolean is NO, but this can be controlled + * by the NSZombieEnabled environment variable. + */ +GS_EXPORT BOOL NSZombieEnabled; + +/** + * Enable/disable object deallocation. + *

If zombies are enabled, objects are by default not + * deallocated, and memory leaks. The NSDeallocateZombies variable + * lets you say that the the memory used by zombies should be freed. + *

+ *

Doing this makes the behavior of zombies similar to that when zombies + * are not enabled ... the memory occupied by the zombie may be re-used for + * other purposes, at which time the isa pointer may be overwritten and the + * zombie behavior will cease. + *

+ * The default value of this boolean is NO, but this can be controlled + * by the NSDeallocateZombies environment variable. + */ +GS_EXPORT BOOL NSDeallocateZombies; + /* Debug logging which can be enabled/disabled by defining GSDIAGNOSE diff --git a/Source/GSPrivate.h b/Source/GSPrivate.h index 888a891c8..77a0fc33f 100644 --- a/Source/GSPrivate.h +++ b/Source/GSPrivate.h @@ -80,7 +80,12 @@ NSDictionary *GSUserDefaultsDictionaryRepresentation(); /* * Get one of several potentially useful flags. */ -BOOL GSUserDefaultsFlag(GSUserDefaultFlagType type); +BOOL GSUserDefaultsFlag(GSUserDefaultFlagType type); + +/** + * Get a flag from an environment variable - return def if not defined. + */ +BOOL GSEnvironmentFlag(const char *name, BOOL def); #endif /* __GSPrivate_h_ */ diff --git a/Source/NSException.m b/Source/NSException.m index 8890aa974..01c5d917a 100644 --- a/Source/NSException.m +++ b/Source/NSException.m @@ -33,7 +33,8 @@ #include #include #include -#include // for getenv() + +#include "GSPrivate.h" static void _preventRecursion (NSException *exception) @@ -43,7 +44,6 @@ _preventRecursion (NSException *exception) static void _NSFoundationUncaughtExceptionHandler (NSException *exception) { - const char *c = getenv("CRASH_ON_ABORT"); BOOL a; _NSUncaughtExceptionHandler = _preventRecursion; @@ -60,33 +60,7 @@ _NSFoundationUncaughtExceptionHandler (NSException *exception) #else a = NO; // exit() by default. #endif - if (c != 0) - { - /* - * Use the CRASH_ON_ABORT environment variable ... if it's defined - * then we use abort(), unless it's 'no', 'false', or '0, in which - * case we use exit() - */ - if (c[0] == '0' && c[1] == 0) - { - a = NO; - } - else if ((c[0] == 'n' || c[0] == 'N') && (c[1] == 'o' || c[1] == 'O') - && c[2] == 0) - { - a = NO; - } - else if ((c[0] == 'f' || c[0] == 'F') && (c[1] == 'a' || c[1] == 'A') - && (c[2] == 'l' || c[2] == 'L') && (c[3] == 's' || c[3] == 'S') - && (c[4] == 'e' || c[4] == 'E') && c[5] == 0) - { - a = NO; - } - else - { - a = YES; - } - } + a = GSEnvironmentFlag("CRASH_ON_ABORT", a); if (a == YES) { abort(); diff --git a/Source/NSObject.m b/Source/NSObject.m index 798d2620c..f3fb8cc22 100644 --- a/Source/NSObject.m +++ b/Source/NSObject.m @@ -42,8 +42,10 @@ #include #include #include +#include #include +#include "GSPrivate.h" #ifndef NeXT_RUNTIME @@ -61,6 +63,68 @@ static Class NSConstantStringClass; static BOOL deallocNotifications = NO; +/* + * allocationLock is needed when running multi-threaded for retain/release + * to work reliably. + * We also use it for protecting the map table of zombie information. + */ +static objc_mutex_t allocationLock = NULL; + + +BOOL NSZombieEnabled = NO; +BOOL NSDeallocateZombies = NO; + +@class NSZombie; +static Class zombieClass; +static NSMapTable zombieMap; + +static void GSMakeZombie(NSObject *o) +{ + Class c = ((id)o)->class_pointer; + + ((id)o)->class_pointer = zombieClass; + if (NSDeallocateZombies == NO) + { + if (allocationLock == 0) + { + objc_mutex_lock(allocationLock); + } + NSMapInsert(zombieMap, (void*)o, (void*)c); + if (allocationLock == 0) + { + objc_mutex_unlock(allocationLock); + } + } +} + +static void GSLogZombie(id o, SEL sel) +{ + Class c = 0; + + if (NSDeallocateZombies == NO) + { + if (allocationLock == 0) + { + objc_mutex_lock(allocationLock); + } + c = NSMapGet(zombieMap, (void*)o); + if (allocationLock == 0) + { + objc_mutex_unlock(allocationLock); + } + } + if (c == 0) + { + NSLog(@"Deallocated object (0x%x) sent %@", + o, NSStringFromSelector(sel)); + } + else + { + NSLog(@"Deallocated %@ (0x%x) sent %@", + NSStringFromClass(c), o, NSStringFromSelector(sel)); + } +} + /* * Reference count and memory management @@ -76,12 +140,6 @@ static BOOL deallocNotifications = NO; */ -/* - * retain_counts_gate is needed when running multi-threaded for retain/release - * to work reliably. - */ -static objc_mutex_t retain_counts_gate = NULL; - #if GS_WITH_GC == 0 && !defined(NeXT_RUNTIME) #define REFCNT_LOCAL 1 #define CACHE_ZONE 1 @@ -165,11 +223,11 @@ NSExtraRefCount(id anObject) void NSIncrementExtraRefCount(id anObject) { - if (retain_counts_gate != 0) + if (allocationLock != 0) { - objc_mutex_lock(retain_counts_gate); + objc_mutex_lock(allocationLock); ((obj)anObject)[-1].retained++; - objc_mutex_unlock (retain_counts_gate); + objc_mutex_unlock (allocationLock); } else { @@ -178,11 +236,11 @@ NSIncrementExtraRefCount(id anObject) } #define NSIncrementExtraRefCount(X) ({ \ - if (retain_counts_gate != 0) \ + if (allocationLock != 0) \ { \ - objc_mutex_lock(retain_counts_gate); \ + objc_mutex_lock(allocationLock); \ ((obj)(X))[-1].retained++; \ - objc_mutex_unlock(retain_counts_gate); \ + objc_mutex_unlock(allocationLock); \ } \ else \ { \ @@ -193,17 +251,17 @@ NSIncrementExtraRefCount(id anObject) BOOL NSDecrementExtraRefCountWasZero(id anObject) { - if (retain_counts_gate != 0) + if (allocationLock != 0) { - objc_mutex_lock(retain_counts_gate); + objc_mutex_lock(allocationLock); if (((obj)anObject)[-1].retained-- == 0) { - objc_mutex_unlock(retain_counts_gate); + objc_mutex_unlock(allocationLock); return YES; } else { - objc_mutex_unlock(retain_counts_gate); + objc_mutex_unlock(allocationLock); return NO; } } @@ -245,9 +303,9 @@ NSIncrementExtraRefCount (id anObject) { GSIMapNode node; - if (retain_counts_gate != 0) + if (allocationLock != 0) { - objc_mutex_lock(retain_counts_gate); + objc_mutex_lock(allocationLock); node = GSIMapNodeForKey(&retain_counts, (GSIMapKey)anObject); if (node != 0) { @@ -257,7 +315,7 @@ NSIncrementExtraRefCount (id anObject) { GSIMapAddPair(&retain_counts, (GSIMapKey)anObject, (GSIMapVal)1); } - objc_mutex_unlock(retain_counts_gate); + objc_mutex_unlock(allocationLock); } else { @@ -278,13 +336,13 @@ NSDecrementExtraRefCountWasZero (id anObject) { GSIMapNode node; - if (retain_counts_gate != 0) + if (allocationLock != 0) { - objc_mutex_lock(retain_counts_gate); + objc_mutex_lock(allocationLock); node = GSIMapNodeForKey(&retain_counts, (GSIMapKey)anObject); if (node == 0) { - objc_mutex_unlock(retain_counts_gate); + objc_mutex_unlock(allocationLock); return YES; } NSCAssert(node->value.uint > 0, NSInternalInconsistencyException); @@ -292,7 +350,7 @@ NSDecrementExtraRefCountWasZero (id anObject) { GSIMapRemoveKey((GSIMapTable)&retain_counts, (GSIMapKey)anObject); } - objc_mutex_unlock(retain_counts_gate); + objc_mutex_unlock(allocationLock); } else { @@ -316,9 +374,9 @@ NSExtraRefCount (id anObject) GSIMapNode node; unsigned ret; - if (retain_counts_gate != 0) + if (allocationLock != 0) { - objc_mutex_lock(retain_counts_gate); + objc_mutex_lock(allocationLock); node = GSIMapNodeForKey(&retain_counts, (GSIMapKey)anObject); if (node == 0) { @@ -328,7 +386,7 @@ NSExtraRefCount (id anObject) { ret = node->value.uint; } - objc_mutex_unlock(retain_counts_gate); + objc_mutex_unlock(allocationLock); } else { @@ -501,8 +559,19 @@ NSDeallocateObject(NSObject *anObject) #ifndef NDEBUG GSDebugAllocationRemove(((id)anObject)->class_pointer, (id)anObject); #endif - ((id)anObject)->class_pointer = (void*) 0xdeadface; - NSZoneFree(z, o); + if (NSZombieEnabled == YES) + { + GSMakeZombie(anObject); + if (NSDeallocateZombies == YES) + { + NSZoneFree(z, o); + } + } + else + { + ((id)anObject)->class_pointer = (void*) 0xdeadface; + NSZoneFree(z, o); + } } return; } @@ -545,8 +614,19 @@ NSDeallocateObject(NSObject *anObject) #ifndef NDEBUG GSDebugAllocationRemove(((id)anObject)->class_pointer, (id)anObject); #endif - ((id)anObject)->class_pointer = (void*) 0xdeadface; - NSZoneFree(z, anObject); + if (NSZombieEnabled == YES) + { + GSMakeZombie(anObject); + if (NSDeallocateZombies == YES) + { + NSZoneFree(z, anObject); + } + } + else + { + ((id)anObject)->class_pointer = (void*) 0xdeadface; + NSZoneFree(z, anObject); + } } return; } @@ -562,7 +642,7 @@ NSShouldRetainWithZone (NSObject *anObject, NSZone *requestedZone) return YES; #else return (!requestedZone || requestedZone == NSDefaultMallocZone() - || GSObjCZone(anObject) == requestedZone); + || GSObjCZone(anObject) == requestedZone); #endif } @@ -587,9 +667,9 @@ static BOOL double_release_check_enabled = NO; + (void) _becomeMultiThreaded: (NSNotification)aNotification { - if (retain_counts_gate == 0) + if (allocationLock == 0) { - retain_counts_gate = objc_mutex_allocate(); + allocationLock = objc_mutex_allocate(); } } @@ -630,6 +710,14 @@ static BOOL double_release_check_enabled = NO; // Create the global lock gnustep_global_lock = [[NSRecursiveLock alloc] init]; + + // Zombie management stuff. + zombieClass = [NSZombie class]; + zombieMap = NSCreateMapTable(NSNonOwnedPointerMapKeyCallBacks, + NSNonOwnedPointerMapValueCallBacks, 0); + NSZombieEnabled = GSEnvironmentFlag("NSZombieEnabled", NO); + NSDeallocateZombies = GSEnvironmentFlag("NSDeallocateZombies", NO); + autorelease_class = [NSAutoreleasePool class]; autorelease_sel = @selector(addObject:); autorelease_imp = [autorelease_class methodForSelector: autorelease_sel]; @@ -1542,3 +1630,28 @@ _fastMallocBuffer(unsigned size) } @end + + +@interface NSZombie +- (retval_t) forward:(SEL)aSel :(arglist_t)argFrame; +- (void) forwardInvocation: (NSInvocation*)anInvocation; +@end + +@implementation NSZombie +- (retval_t) forward:(SEL)aSel :(arglist_t)argFrame +{ + GSLogZombie(self, aSel); + return 0; +} +- (void) forwardInvocation: (NSInvocation*)anInvocation +{ + unsigned size = [[anInvocation methodSignature] methodReturnLength]; + unsigned char v[size]; + + memset(v, '\0', size); + GSLogZombie(self, [anInvocation selector]); + [anInvocation setReturnValue: (void*)v]; + return; +} +@end + diff --git a/Source/NSProcessInfo.m b/Source/NSProcessInfo.m index 6ee940a5b..630e6967d 100644 --- a/Source/NSProcessInfo.m +++ b/Source/NSProcessInfo.m @@ -78,6 +78,8 @@ #include #include +#include "GSPrivate.h" + /* This error message should be called only if the private main function * was not executed successfully. This may happen ONLY if another library * or kit defines its own main function (as gnustep-base does). @@ -679,3 +681,32 @@ BOOL GSDebugSet(NSString *level) return YES; } + +BOOL +GSEnvironmentFlag(const char *name, BOOL def) +{ + const char *c = getenv(name); + BOOL a = def; + + if (c != 0) + { + a = NO; + if ((c[0] == 'y' || c[0] == 'Y') && (c[1] == 'e' || c[1] == 'E') + && (c[2] == 's' || c[2] == 'S') && c[3] == 0) + { + a = YES; + } + else if ((c[0] == 't' || c[0] == 'T') && (c[1] == 'r' || c[1] == 'R') + && (c[2] == 'u' || c[2] == 'U') && (c[3] == 'e' || c[3] == 'E') + && c[4] == 0) + { + a = YES; + } + else if (isdigit(c[0]) && c[0] != '0') + { + a = YES; + } + } + return a; +} + diff --git a/Testing/basic.m b/Testing/basic.m index 08d170f5b..88fbea9cf 100644 --- a/Testing/basic.m +++ b/Testing/basic.m @@ -9,6 +9,7 @@ int main () id o = [NSObject new]; printf ("Hello from object at 0x%x\n", (unsigned)[o self]); + [o release]; o = [NSString stringWithFormat: @"/proc/%d/status", getpid()]; NSLog(@"'%@'", o); o = [NSString stringWithContentsOfFile: o];