More leak management fixes

This commit is contained in:
rfm 2024-11-17 16:16:46 +00:00
parent 90082eccac
commit cd3e69245d
9 changed files with 119 additions and 79 deletions

View file

@ -219,9 +219,9 @@ extern "C" {
* set to YES).<br />
* Your class then has two options for performing clean-up when the process
* ends:
* <p>1. Use the +leaked: method to register addresses whose contents are to
* be either ignored or released depending on the clean-up setting in force
* when the program exits.
* <p>1. Use the +leak: at: method to register addresses whose contents are
* to be either ignored or released depending on the clean-up setting in
* force when the program exits.
* This mechanism is simple and should be sufficient for many classes.
* </p>
* <p>2. Implement a +atExit method to be run when the process ends and,
@ -241,36 +241,30 @@ extern "C" {
*/
+ (BOOL) isExiting;
/** This method informs the system that anObject should be retained to
* persist until the process exits. If clean-up is enabled the object
* should be released upon process exit.
* If this method is called while the process is already existing it
* returns nil, otherwise it returnes the retained argument.
* Raises an exception if anObject has already been leaked or if it is
* nil (unless the process is exiting).
/** This method stored anObject at anAddress (retaining it) and notes
* that the object should persist until the process exits. If clean-up
* is enabled the object should be released (and the address content
* zeroed out) upon process exit.
* If this method is called while the process is already exiting it
* simply zeros out the memory location then returns nil, otherwise
* it returns the object stored at the memory location.
* Raises an exception if anObject is nil or anAddress is NULL (unless
* the process is already exiting).
*/
+ (id) leak: (id)anObject at: (id*)anAddress;
/** DEPRECATED ... use +leak: at: instead.
*/
+ (id) NS_RETURNS_RETAINED leak: (id)anObject;
/** This method informs the system that the object at anAddress has been
* retained to persist until the process exits. If clean-up is enabled
* the object should be released (and the address content zeroed out)
* upon process exit.
* If this method is called while the process is already existing it releases
* the object and zeros out the memory location then returns nil, otherwise
* it returns the object found at the memory location.
* Raises an exception if anAddress (or the object at anAddress) has already
* been leaked or if it is nil (unless the process is exiting).
*/
+ (void) leaked: (id*)anAddress;
/** DEPRECATED ... use +leaked: instead.
/** DEPRECATED ... use +leak: at: instead.
*/
+ (id) NS_RETURNS_RETAINED leakAt: (id*)anAddress;
/** Sets the receiver to have its +atExit method called at the point when
* the process terminates.<br />
* Returns YES on success and NO on failure (if the class does not implement
* the method or if it is already registered to call it).<br />
* +atExit or if it is already registered to call it).<br />
* Implemented as a call to +registerAtExit: with the selector for the +atExit
* method as its argument.
*/
@ -279,7 +273,7 @@ extern "C" {
/** Sets the receiver to have the specified method called at the point when
* the process terminates.<br />
* Returns YES on success and NO on failure (if the class does not implement
* the method ir if it is already registered to call it).
* the method or if it is already registered to call a method at exit).
*/
+ (BOOL) registerAtExit: (SEL)aSelector;

View file

@ -180,6 +180,10 @@ handleExit()
BOOL unknownThread;
isExiting = YES;
/* We turn off zombies during exiting so that we don't leak deallocated
* objects during cleanup.
*/
// NSZombieEnabled = NO;
unknownThread = GSRegisterCurrentThread();
ENTER_POOL
@ -193,6 +197,7 @@ handleExit()
Method method;
IMP msg;
fprintf(stderr, "*** +[%s %s]\n", class_getName(tmp->obj), sel_getName(tmp->sel));
method = class_getClassMethod(tmp->obj, tmp->sel);
msg = method_getImplementation(method);
if (0 != msg)
@ -200,13 +205,18 @@ handleExit()
(*msg)(tmp->obj, tmp->sel);
}
}
else if (YES == shouldCleanUp)
else if (shouldCleanUp)
{
if (tmp->at)
{
if (tmp->obj != *(tmp->at))
{
fprintf(stderr, "*** leaked value %p at %p changed to %p\n", tmp->obj, (const void*)tmp->at, *(tmp->at));
tmp->obj = *(tmp->at);
}
*(tmp->at) = nil;
}
fprintf(stderr, "*** -[%s release] %p %p\n", class_getName(object_getClass(tmp->obj)), tmp->obj, (const void*)tmp->at);
[tmp->obj release];
}
free(tmp);
@ -227,35 +237,22 @@ handleExit()
return isExiting;
}
+ (id) leakAt: (id*)anAddress
+ (id) leak: (id)anObject at: (id*)anAddress
{
struct exitLink *l;
l = (struct exitLink*)malloc(sizeof(struct exitLink));
l->at = anAddress;
l->obj = [*anAddress retain];
l->sel = 0;
setup();
[exitLock lock];
l->next = exited;
exited = l;
[exitLock unlock];
return l->obj;
}
+ (void) leaked: (id*)anAddress
{
struct exitLink *l;
NSAssert(anAddress != NULL, NSInvalidArgumentException);
if (isExiting)
{
if (anAddress)
{
[*anAddress release];
*anAddress = nil;
}
return nil;
}
NSAssert([*anAddress isKindOfClass: [NSObject class]],
NSAssert([anObject isKindOfClass: [NSObject class]],
NSInvalidArgumentException);
NSAssert(anAddress != NULL, NSInvalidArgumentException);
setup();
[exitLock lock];
for (l = exited; l != NULL; l = l->next)
@ -266,16 +263,17 @@ handleExit()
[NSException raise: NSInvalidArgumentException
format: @"Repeated use of leak address %p", anAddress];
}
if (*anAddress != nil && *anAddress == l->obj)
if (anObject != nil && anObject == l->obj)
{
[exitLock unlock];
[NSException raise: NSInvalidArgumentException
format: @"Repeated use of leak object %p", *anAddress];
format: @"Repeated use of leak object %p", anObject];
}
}
ASSIGN(*anAddress, anObject);
l = (struct exitLink*)malloc(sizeof(struct exitLink));
l->at = anAddress;
l->obj = *anAddress;
l->obj = anObject;
l->sel = 0;
l->next = exited;
exited = l;
@ -283,6 +281,22 @@ handleExit()
return l->obj;
}
+ (id) leakAt: (id*)anAddress
{
struct exitLink *l;
l = (struct exitLink*)malloc(sizeof(struct exitLink));
l->at = anAddress;
l->obj = [*anAddress retain];
l->sel = 0;
setup();
[exitLock lock];
l->next = exited;
exited = l;
[exitLock unlock];
return l->obj;
}
+ (id) leak: (id)anObject
{
struct exitLink *l;
@ -344,12 +358,18 @@ handleExit()
[exitLock lock];
for (l = exited; l != 0; l = l->next)
{
if (l->obj == self && sel_isEqual(l->sel, sel))
if (l->obj == self)
{
if (sel_isEqual(l->sel, sel))
{
fprintf(stderr,
"*** +[%s registerAtExit: %s] already registered for %s.\n",
class_getName(self), sel_getName(sel), sel_getName(l->sel));
[exitLock unlock];
return NO; // Already registered
}
}
}
l = (struct exitLink*)malloc(sizeof(struct exitLink));
l->obj = self;
l->sel = sel;
@ -502,6 +522,12 @@ handleExit()
*/
@implementation NSObject(GSCleanup)
+ (id) leak: (id)anObject at: (id*)anAddress
{
ASSIGN(*anAddress, anObject);
return *anAddress;
}
+ (id) leakAt: (id*)anAddress
{
[*anAddress retain];

View file

@ -223,8 +223,7 @@ GSDomainFromDefaultLocale(void)
*/
if (saved == nil)
{
saved = [dict copy];
[NSObject leaked: &saved];
[NSObject leak: AUTORELEASE([dict copy]) at: &saved];
}
/**

View file

@ -48,7 +48,6 @@ static NSNull *null = 0;
if (null == 0)
{
null = (NSNull*)NSAllocateObject(self, 0, NSDefaultMallocZone());
[[NSObject leakAt: &null] release];
}
}

View file

@ -171,13 +171,13 @@ extern void GSLogZombie(id o, SEL sel)
}
if (c == 0)
{
NSLog(@"*** -[??? %@]: message sent to deallocated instance %p",
NSStringFromSelector(sel), o);
fprintf(stderr, "*** -[??? %s]: message sent to deallocated instance %p",
sel_getName(sel), o);
}
else
{
NSLog(@"*** -[%@ %@]: message sent to deallocated instance %p",
c, NSStringFromSelector(sel), o);
fprintf(stderr, "*** -[%s %s]: message sent to deallocated instance %p",
class_getName(c), sel_getName(sel), o);
}
if (GSPrivateEnvironmentFlag("CRASH_ON_ZOMBIE", NO) == YES)
{
@ -814,7 +814,7 @@ NSDeallocateObject(id anObject)
(*finalize_imp)(anObject, finalize_sel);
AREM(aClass, (id)anObject);
if (NSZombieEnabled == YES)
if (NSZombieEnabled)
{
#ifdef OBJC_CAP_ARC
if (0 != zombieMap)
@ -1086,12 +1086,14 @@ static id gs_weak_load(id obj)
+ (void) _atExit
{
/*
NSMapTable *m = nil;
GS_MUTEX_LOCK(allocationLock);
m = zombieMap;
zombieMap = nil;
GS_MUTEX_UNLOCK(allocationLock);
DESTROY(m);
*/
}
/**

View file

@ -180,6 +180,13 @@ debugWrite(id handle, int len, const unsigned char *ptr)
static NSMutableArray *pairCache = nil;
static NSLock *pairLock = nil;
+ (void) atExit
{
[[NSNotificationCenter defaultCenter] removeObserver: self];
DESTROY(pairLock);
DESTROY(pairCache);
}
+ (void) initialize
{
if (pairCache == nil)
@ -196,6 +203,7 @@ static NSLock *pairLock = nil;
[[NSNotificationCenter defaultCenter] addObserver: self
selector: @selector(purge:)
name: @"GSHousekeeping" object: nil];
[self registerAtExit];
}
}
@ -468,19 +476,36 @@ typedef struct {
return o;
}
+ (void) atExit
{
if (placeholder)
{
id o = placeholder;
placeholder = nil;
NSDeallocateObject(o);
}
fprintf(stderr, "Registered retain count %d\n", (int)[registered retainCount]);
DESTROY(registered);
DESTROY(regLock);
}
+ (void) initialize
{
if (registered == nil)
static BOOL beenHere = NO;
if (NO == beenHere)
{
beenHere = YES;
abstractClass = [NSURLProtocol class];
placeholderClass = [NSURLProtocolPlaceholder class];
[self registerAtExit];
placeholder = (NSURLProtocol*)NSAllocateObject(placeholderClass, 0,
NSDefaultMallocZone());
[[NSObject leakAt: &placeholder] release];
registered = [NSMutableArray new];
[[NSObject leakAt: &registered] release];
regLock = [NSLock new];
[[NSObject leakAt: &regLock] release];
[self registerClass: [_NSHTTPURLProtocol class]];
[self registerClass: [_NSHTTPSURLProtocol class]];
[self registerClass: [_NSFTPURLProtocol class]];

View file

@ -18,7 +18,7 @@ int main()
httpURL = [NSURL URLWithString: @"http://www.gnustep.org"];
foobarURL = [NSURL URLWithString: @"foobar://localhost/madeupscheme"];
TEST_FOR_CLASS(@"NSURLHandle", [NSURLHandle alloc],
TEST_FOR_CLASS(@"NSURLHandle", AUTORELEASE([NSURLHandle alloc]),
"NSURLHandle +alloc returns an NSURLHandle");
PASS_EXCEPTION([DummyHandle cachedHandleForURL: httpURL];,
@ -29,14 +29,11 @@ int main()
PASS([cls canInitWithURL: httpURL] == YES,
"Appropriate subclass found for +URLHandleClassForURL:");
handle1 = [[cls alloc] initWithURL: httpURL cached: YES];
handle1 = AUTORELEASE([[cls alloc] initWithURL: httpURL cached: YES]);
handle2 = [NSURLHandle cachedHandleForURL: httpURL];
PASS(handle2 != nil, "Available handle returned from cache");
[handle1 autorelease];
[cls autorelease];
#if !defined(GNUSTEP_BASE_LIBRARY)
PASS(NO, "URLHandleClassForURL: seems to hang on MacOS-X when given an unknown URL scheme ... you may want to check to see if it has been fixed");
#else

View file

@ -11,10 +11,10 @@ int main()
httpURL = [NSURL URLWithString: @"http://www.gnustep.org"];
TEST_FOR_CLASS(@"NSURLProtocol", [NSURLProtocol alloc],
TEST_FOR_CLASS(@"NSURLProtocol", AUTORELEASE([NSURLProtocol alloc]),
"NSURLProtocol +alloc returns an NSURLProtocol");
mutable = [[NSMutableURLRequest requestWithURL: httpURL] retain];
mutable = [NSMutableURLRequest requestWithURL: httpURL];
PASS_EXCEPTION([NSURLProtocol canInitWithRequest: mutable], nil,
"NSURLProtocol +canInitWithRequest throws an exeception (subclasses should be used)");
@ -22,13 +22,12 @@ int main()
TEST_FOR_CLASS(@"NSURLRequest", canon,
"NSURLProtocol +canonicalRequestForRequest: returns an NSURLProtocol");
copy = [mutable copy];
copy = AUTORELEASE([mutable copy]);
PASS([NSURLProtocol requestIsCacheEquivalent: mutable toRequest: copy],
"NSURLProtocol +requestIsCacheEquivalent:toRequest returns YES with a request and its copy");
[copy setHTTPMethod: @"POST"];
PASS([NSURLProtocol requestIsCacheEquivalent: mutable toRequest: copy] == NO,
"NSURLProtocol +requestIsCacheEquivalent:toRequest returns NO after a method change");
[copy release];
[arp release]; arp = nil;
return 0;

View file

@ -13,7 +13,7 @@ int main()
httpURL = [NSURL URLWithString: @"http://www.gnustep.org"];
foobarURL = [NSURL URLWithString: @"foobar://localhost/madeupscheme"];
TEST_FOR_CLASS(@"NSURLRequest", [NSURLRequest alloc],
TEST_FOR_CLASS(@"NSURLRequest", AUTORELEASE([NSURLRequest alloc]),
"NSURLRequest +alloc returns an NSURLRequest");
request = [NSURLRequest requestWithURL: httpURL];
@ -28,7 +28,7 @@ int main()
PASS(request != nil,
"NSURLRequest +requestWithURL returns a request from an invalid URL (unknown scheme)");
mutable = [request mutableCopy];
mutable = AUTORELEASE([request mutableCopy]);
PASS(mutable != nil && [mutable isKindOfClass:[NSMutableURLRequest class]],
"NSURLRequest -mutableCopy returns a mutable request");
[mutable setHTTPMethod: @"POST"];
@ -53,9 +53,8 @@ int main()
[mutable setValue: nil forHTTPHeaderField: @"gnustep"];
expected = [NSDictionary dictionaryWithObjectsAndKeys:@"object", @"key", nil];
PASS_EQUAL([mutable allHTTPHeaderFields], expected, "Remove header field");
[mutable release];
mutable = [NSMutableURLRequest new];
mutable = AUTORELEASE([NSMutableURLRequest new]);
PASS(mutable != nil && [mutable isKindOfClass:[NSMutableURLRequest class]],
"NSURLRequest +new returns a mutable request");