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

View file

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

View file

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

View file

@ -48,7 +48,6 @@ static NSNull *null = 0;
if (null == 0) if (null == 0)
{ {
null = (NSNull*)NSAllocateObject(self, 0, NSDefaultMallocZone()); 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) if (c == 0)
{ {
NSLog(@"*** -[??? %@]: message sent to deallocated instance %p", fprintf(stderr, "*** -[??? %s]: message sent to deallocated instance %p",
NSStringFromSelector(sel), o); sel_getName(sel), o);
} }
else else
{ {
NSLog(@"*** -[%@ %@]: message sent to deallocated instance %p", fprintf(stderr, "*** -[%s %s]: message sent to deallocated instance %p",
c, NSStringFromSelector(sel), o); class_getName(c), sel_getName(sel), o);
} }
if (GSPrivateEnvironmentFlag("CRASH_ON_ZOMBIE", NO) == YES) if (GSPrivateEnvironmentFlag("CRASH_ON_ZOMBIE", NO) == YES)
{ {
@ -814,7 +814,7 @@ NSDeallocateObject(id anObject)
(*finalize_imp)(anObject, finalize_sel); (*finalize_imp)(anObject, finalize_sel);
AREM(aClass, (id)anObject); AREM(aClass, (id)anObject);
if (NSZombieEnabled == YES) if (NSZombieEnabled)
{ {
#ifdef OBJC_CAP_ARC #ifdef OBJC_CAP_ARC
if (0 != zombieMap) if (0 != zombieMap)
@ -1086,12 +1086,14 @@ static id gs_weak_load(id obj)
+ (void) _atExit + (void) _atExit
{ {
/*
NSMapTable *m = nil; NSMapTable *m = nil;
GS_MUTEX_LOCK(allocationLock); GS_MUTEX_LOCK(allocationLock);
m = zombieMap; m = zombieMap;
zombieMap = nil; zombieMap = nil;
GS_MUTEX_UNLOCK(allocationLock); GS_MUTEX_UNLOCK(allocationLock);
DESTROY(m); DESTROY(m);
*/
} }
/** /**

View file

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

View file

@ -18,7 +18,7 @@ int main()
httpURL = [NSURL URLWithString: @"http://www.gnustep.org"]; httpURL = [NSURL URLWithString: @"http://www.gnustep.org"];
foobarURL = [NSURL URLWithString: @"foobar://localhost/madeupscheme"]; foobarURL = [NSURL URLWithString: @"foobar://localhost/madeupscheme"];
TEST_FOR_CLASS(@"NSURLHandle", [NSURLHandle alloc], TEST_FOR_CLASS(@"NSURLHandle", AUTORELEASE([NSURLHandle alloc]),
"NSURLHandle +alloc returns an NSURLHandle"); "NSURLHandle +alloc returns an NSURLHandle");
PASS_EXCEPTION([DummyHandle cachedHandleForURL: httpURL];, PASS_EXCEPTION([DummyHandle cachedHandleForURL: httpURL];,
@ -29,14 +29,11 @@ int main()
PASS([cls canInitWithURL: httpURL] == YES, PASS([cls canInitWithURL: httpURL] == YES,
"Appropriate subclass found for +URLHandleClassForURL:"); "Appropriate subclass found for +URLHandleClassForURL:");
handle1 = [[cls alloc] initWithURL: httpURL cached: YES]; handle1 = AUTORELEASE([[cls alloc] initWithURL: httpURL cached: YES]);
handle2 = [NSURLHandle cachedHandleForURL: httpURL]; handle2 = [NSURLHandle cachedHandleForURL: httpURL];
PASS(handle2 != nil, "Available handle returned from cache"); PASS(handle2 != nil, "Available handle returned from cache");
[handle1 autorelease];
[cls autorelease];
#if !defined(GNUSTEP_BASE_LIBRARY) #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"); 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 #else

View file

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

View file

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