mirror of
https://github.com/gnustep/libs-base.git
synced 2025-04-22 16:33:29 +00:00
Implemented NSZombie stuff
git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/base/trunk@13247 72102866-910b-0410-8b05-ffd578937521
This commit is contained in:
parent
e64c0a1282
commit
c0b0abdbd1
7 changed files with 232 additions and 63 deletions
|
@ -1,3 +1,11 @@
|
|||
2002-03-27 Richard Frith-Macdonald <rfm@gnu.org>
|
||||
|
||||
* 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 <rfm@gnu.org>
|
||||
|
||||
* Source/NSTask.m: Implement code to watch for child process exit
|
||||
|
|
|
@ -111,6 +111,43 @@ GS_EXPORT NSString* GSDebugMethodMsg(id obj, SEL sel, const char *file,
|
|||
int line, NSString *fmt);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Enable/disable zombies.
|
||||
* <p>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.
|
||||
* </p>
|
||||
* <p>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.
|
||||
* </p>
|
||||
* 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.
|
||||
* <p>If zombies are enabled, objects are by default <em>not</em>
|
||||
* deallocated, and memory leaks. The NSDeallocateZombies variable
|
||||
* lets you say that the the memory used by zombies should be freed.
|
||||
* </p>
|
||||
* <p>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.
|
||||
* </p>
|
||||
* 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
|
||||
|
|
|
@ -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_ */
|
||||
|
||||
|
|
|
@ -33,7 +33,8 @@
|
|||
#include <Foundation/NSThread.h>
|
||||
#include <Foundation/NSDictionary.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h> // 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();
|
||||
|
|
|
@ -42,8 +42,10 @@
|
|||
#include <Foundation/NSThread.h>
|
||||
#include <Foundation/NSNotification.h>
|
||||
#include <Foundation/NSObjCRuntime.h>
|
||||
#include <Foundation/NSMapTable.h>
|
||||
#include <limits.h>
|
||||
|
||||
#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
|
||||
|
||||
|
|
|
@ -78,6 +78,8 @@
|
|||
#include <Foundation/NSAutoreleasePool.h>
|
||||
#include <Foundation/NSHost.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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];
|
||||
|
|
Loading…
Reference in a new issue