Memory leak improvments

This commit is contained in:
rfm 2024-06-18 11:23:15 +01:00
parent c9af996377
commit 4d0b00776c
4 changed files with 231 additions and 62 deletions

View file

@ -1,8 +1,18 @@
2024-06-18 Richard Frith-Macdonald <rfm@gnu.org>
* Headers/Foundation/NSDebug.h: Remove deprecated function and add
new function to turn on tracing objects of a specific class.
* Source/NSDebug.m: Fix locking issues when recording object allocation.
Fix bug removing recoded objects. Implement tracing recorded object
allocation with stack trace (also option to veto recording of
individual objects).
* Source/GSFileHandle.m: Fix memory leak found using changes above.
2024-06-10 Marco Rebhan <me@dblsaiko.net>
* Headers/Foundation/NSFileManager.h:
* Source/NSFileManager.m:
Implement -[NSFileManager URLsForDirectory:inDomains:].
* Headers/Foundation/NSFileManager.h:
* Source/NSFileManager.m:
Implement -[NSFileManager URLsForDirectory:inDomains:].
2024-06-10 Richard Frith-Macdonald <rfm@gnu.org>

View file

@ -40,6 +40,9 @@
# endif
#endif
@class NSArray;
@class NSMapTable;
#if defined(__cplusplus)
extern "C" {
#endif
@ -97,12 +100,14 @@ extern "C" {
* hopeless about actually finding out where the leak is, the
* following functions could come handy as they allow you to find
* exactly *what* objects you are leaking (warning! these functions
* could slow down your system appreciably - use them only temporarily
* eill slow down your system appreciably - use them only temporarily
* and only in debugging systems):
*
* GSDebugAllocationRecordAndTrace()
* GSDebugAllocationRecordObjects()
* GSDebugAllocationListRecordedObjects()
* GSDebugAllocationTagRecordedObject()
* GSDebugAllocationTaggedObjects()
*/
/**
@ -238,31 +243,37 @@ GS_EXPORT const char* GSDebugAllocationList(BOOL changeFlag);
*/
GS_EXPORT const char* GSDebugAllocationListAll(void);
/**
* DEPRECATED ... use GSDebugAllocationRecordObjects instead.
*/
GS_EXPORT void GSDebugAllocationActiveRecordingObjects(Class c);
/**
* This function activates (or deactivates) tracking all allocated
* instances of the specified class c.<br />
* Turning on tracking implicitly turns on memory debug (counts)
* for all classes (GSAllocationActive()).<br />
* Deactivation of tracking releases all currently tracked instances
* Deactivation of tracking removes all currently tracked instances
* of the class (but deactivation of general counting does not).<br />
* The previous tracking state as reported as the return value of
* If a trace function is supplied, it takes the object to be recorded
* as an argument and either returns a value used as the tag for the
* object to be recorded, or nil to prevent the recording of that
* particular object.<br />
* As a special case the trace function may be the integer 1
* (cast to a function) to tag recorded objects by stack trace. This
* allows you to see where each leaked object was allocated.<br />
* The previous tracking state is reported as the return value of
* this function.<br />
* This tracking can slow your application down, so you should use it
* This tracking will slow your application down, so you should use it
* only when you are into serious debugging.
* Usually, you will monitor your application by using the functions
* GSDebugAllocationList() and similar, which do not slow things down
* much and return * the number of allocated instances; when
* GSDebugAllocationList() and similar (which do not slow things down
* much) and return the number of allocated instances; when
* (if) by studying the reports generated by these functions
* you have found a leak of objects of a certain class, and
* if you can't figure out how to fix it by looking at the
* code, you can use this function to start tracking
* allocated instances of that class, and the following one
* can sometime allow you to list the leaked objects directly.
* allocated instances of that class.
*/
GS_EXPORT BOOL GSDebugAllocationRecordAndTrace(
Class c, BOOL record, NSObject* (*traceFunction)(id));
/** Calls GSDebugAllocationRecordAndTrace() with a null trace function.
*/
GS_EXPORT BOOL GSDebugAllocationRecordObjects(Class c, BOOL newState);
@ -270,7 +281,7 @@ GS_EXPORT BOOL GSDebugAllocationRecordObjects(Class c, BOOL newState);
* This function returns an array
* containing all the allocated objects of a certain class
* which have been recorded ... to start the recording, you need
* to invoke GSDebugAllocationRecordObjects().
* to invoke GSDebugAllocationRecordAndTrace().
* Presumably, you will immediately call [NSObject-description] on them
* to find out the objects you are leaking. The objects are
* returned in an autoreleased array, so until the array is deallocated,
@ -278,6 +289,14 @@ GS_EXPORT BOOL GSDebugAllocationRecordObjects(Class c, BOOL newState);
*/
GS_EXPORT NSArray *GSDebugAllocationListRecordedObjects(Class c);
/** Returns a map containing recorded objects as keys and their corresponding
* tags as values. This does not return any objects which do not have tags,
* and returns nil if there are no tagged objects to report. The returned
* map table is autoreleased and will retain the objects and their tags
* until it is deallocated.
*/
GS_EXPORT NSMapTable *GSDebugAllocationTaggedObjects(Class c);
/**
* This function associates the supplied tag with a recorded
* object and returns the tag which was previously associated
@ -286,8 +305,7 @@ GS_EXPORT NSArray *GSDebugAllocationListRecordedObjects(Class c);
* The tag is retained while it is associated with the object.<br />
* If the tagged object is deallocated, the tag is released
* (so you can track the lifetime of the object by having the tag
* perform some operation when it is released).<br />
* See also the NSDebugFRLog() and NSDebugMRLog() macros.
* perform some operation when it is released).
*/
GS_EXPORT id GSDebugAllocationTagRecordedObject(id object, id tag);

View file

@ -230,6 +230,7 @@ static NSString* NotificationKey = @"NSFileHandleNotificationKey";
{
[self ignoreWriteDescriptor];
[writeInfo removeAllObjects];
DESTROY(writeInfo);
}
/* Finalize *after* ending read and write operations so that, if the

View file

@ -64,6 +64,7 @@ typedef struct {
uint32_t nominal_size;
/* The following are used to record actual objects */
BOOL is_recording;
NSObject *(*trace_function)(id);
id *recorded_objects;
id *recorded_tags;
uint32_t num_recorded_objects;
@ -106,6 +107,23 @@ static void (*_GSDebugAllocationAddFunc)(Class c, id o)
static void (*_GSDebugAllocationRemoveFunc)(Class c, id o)
= _GSDebugAllocationRemove;
static NSObject*
_GSDebugAllocationTrace(id object)
{
NSArray *a = [NSThread callStackSymbols];
NSUInteger c = [a count];
/* Remove uninteresting frames incide the allocation debug system
*/
if (c > 3)
{
NSRange r = NSMakeRange(3, c - 3);
a = [a subarrayWithRange: r];
}
return [a description];
}
#define doLock() GS_MUTEX_LOCK(uniqueLock)
#define unLock() GS_MUTEX_UNLOCK(uniqueLock)
@ -161,44 +179,52 @@ GSDebugAllocationBytes(BOOL active)
}
BOOL
GSDebugAllocationRecordObjects(Class c, BOOL newState)
GSDebugAllocationRecordAndTrace(Class c, BOOL record, NSObject *(*trace)(id))
{
BOOL oldState = NO;
BOOL wasRecording = NO;
unsigned int i;
if (newState)
if (record)
{
GSDebugAllocationActive(YES);
}
if (trace == (NSObject *(*)(id))1)
{
trace = _GSDebugAllocationTrace;
}
for (i = 0; i < num_classes; i++)
{
if (the_table[i].class == c)
{
doLock();
oldState = (YES == the_table[i].is_recording) ? YES : NO;
if (newState)
wasRecording = (YES == the_table[i].is_recording) ? YES : NO;
if (record)
{
the_table[i].is_recording = YES;
the_table[i].trace_function = trace;
}
else if (YES == oldState)
else if (wasRecording)
{
while (the_table[i].num_recorded_objects > 0)
{
int j = the_table[i].num_recorded_objects;
int j = the_table[i].num_recorded_objects - 1;
id o = the_table[i].recorded_objects[j];
id t = the_table[i].recorded_tags[j];
the_table[i].num_recorded_objects = --j;
[the_table[i].recorded_objects[j] release];
the_table[i].recorded_objects[j] = nil;
[the_table[i].recorded_tags[j] release];
the_table[i].recorded_tags[j] = nil;
the_table[i].num_recorded_objects = j;
RELEASE(o);
RELEASE(t);
}
}
unLock();
return oldState;
return wasRecording;
}
}
if (YES == newState)
if (record)
{
doLock();
if (num_classes >= table_size)
@ -231,6 +257,7 @@ GSDebugAllocationRecordObjects(Class c, BOOL newState)
the_table[num_classes].totalb = 0;
the_table[num_classes].nominal_size = class_getInstanceSize(c);
the_table[num_classes].is_recording = YES;
the_table[num_classes].trace_function = trace;
the_table[num_classes].recorded_objects = NULL;
the_table[num_classes].recorded_tags = NULL;
the_table[num_classes].num_recorded_objects = 0;
@ -238,7 +265,13 @@ GSDebugAllocationRecordObjects(Class c, BOOL newState)
num_classes++;
unLock();
}
return oldState;
return wasRecording;
}
BOOL
GSDebugAllocationRecordObjects(Class c, BOOL newState)
{
return GSDebugAllocationRecordAndTrace(c, newState, NULL);
}
void
@ -256,16 +289,19 @@ GSDebugAllocationAdd(Class c, id o)
void
_GSDebugAllocationAdd(Class c, id o)
{
if (debug_allocation == YES)
if (debug_allocation)
{
unsigned int i;
unsigned bytes;
doLock();
for (i = 0; i < num_classes; i++)
{
if (the_table[i].class == c)
{
doLock();
NSObject *tag = nil;
BOOL shouldRecord;
the_table[i].count++;
the_table[i].totalc++;
if (YES == debug_byte_size)
@ -282,7 +318,43 @@ _GSDebugAllocationAdd(Class c, id o)
{
the_table[i].peak = the_table[i].count;
}
if (the_table[i].is_recording == YES)
shouldRecord = the_table[i].is_recording;
if (shouldRecord)
{
NSObject *(*trace)(id);
if ((trace = the_table[i].trace_function) != NULL)
{
/* Tracing means adding a stack trace as the tag of
* each recorded objct ... we don't want to have any
* recursion while doing that so we temporarily turn
* recording off.
*/
the_table[i].is_recording = NO;
unLock();
ENTER_POOL
tag = RETAIN((*trace)(o));
LEAVE_POOL
doLock();
for (i = 0; i < num_classes; i++)
{
if (the_table[i].class == c)
{
the_table[i].is_recording = YES;
break;
}
}
if (i >= num_classes)
{
DESTROY(tag); // Can't record it
}
if (nil == tag)
{
shouldRecord = NO; // Don't want to record
}
}
}
if (shouldRecord)
{
if (the_table[i].num_recorded_objects
>= the_table[i].stack_size)
@ -308,7 +380,6 @@ _GSDebugAllocationAdd(Class c, id o)
return;
}
if (the_table[i].recorded_objects != NULL)
{
memcpy(tmp, the_table[i].recorded_objects,
@ -329,15 +400,15 @@ _GSDebugAllocationAdd(Class c, id o)
(the_table[i].recorded_objects)
[the_table[i].num_recorded_objects] = o;
(the_table[i].recorded_tags)
[the_table[i].num_recorded_objects] = nil;
(the_table[i].recorded_tags)
[the_table[i].num_recorded_objects] = tag;
the_table[i].num_recorded_objects++;
}
unLock();
return;
}
}
doLock();
if (num_classes >= table_size)
{
unsigned int more = table_size + 128;
@ -376,6 +447,7 @@ _GSDebugAllocationAdd(Class c, id o)
the_table[num_classes].totalc = 1;
the_table[num_classes].peak = 1;
the_table[num_classes].is_recording = NO;
the_table[num_classes].trace_function = NULL;
the_table[num_classes].recorded_objects = NULL;
the_table[num_classes].recorded_tags = NULL;
the_table[num_classes].num_recorded_objects = 0;
@ -647,10 +719,11 @@ GSDebugAllocationRemove(Class c, id o)
void
_GSDebugAllocationRemove(Class c, id o)
{
if (debug_allocation == YES)
if (debug_allocation)
{
unsigned int i;
doLock();
for (i = 0; i < num_classes; i++)
{
if (the_table[i].class == c)
@ -658,7 +731,6 @@ _GSDebugAllocationRemove(Class c, id o)
id tag = nil;
unsigned bytes;
doLock();
if (YES == debug_byte_size)
{
bytes = [o sizeOfInstance];
@ -669,11 +741,12 @@ _GSDebugAllocationRemove(Class c, id o)
}
the_table[i].count--;
the_table[i].bytes -= bytes;
if (the_table[i].is_recording)
if (the_table[i].num_recorded_objects > 0)
{
unsigned j, k;
unsigned j, k, n;
for (j = 0; j < the_table[i].num_recorded_objects; j++)
n = the_table[i].num_recorded_objects;
for (j = 0; j < n; j++)
{
if ((the_table[i].recorded_objects)[j] == o)
{
@ -681,16 +754,14 @@ _GSDebugAllocationRemove(Class c, id o)
break;
}
}
if (j < the_table[i].num_recorded_objects)
if (j < n)
{
for (k = j;
k + 1 < the_table[i].num_recorded_objects;
k++)
for (k = j + 1; k < n; k++)
{
(the_table[i].recorded_objects)[k] =
(the_table[i].recorded_objects)[k + 1];
(the_table[i].recorded_tags)[k] =
(the_table[i].recorded_tags)[k + 1];
(the_table[i].recorded_objects)[k - 1] =
(the_table[i].recorded_objects)[k];
(the_table[i].recorded_tags)[k - 1] =
(the_table[i].recorded_tags)[k];
}
the_table[i].num_recorded_objects--;
}
@ -707,6 +778,7 @@ _GSDebugAllocationRemove(Class c, id o)
return;
}
}
unLock();
}
}
@ -758,7 +830,7 @@ NSArray *
GSDebugAllocationListRecordedObjects(Class c)
{
NSArray *answer;
unsigned int i, k;
unsigned int i, k, n;
id *tmp;
if (debug_allocation == NO)
@ -788,14 +860,13 @@ GSDebugAllocationListRecordedObjects(Class c)
return nil;
}
if (the_table[i].num_recorded_objects == 0)
if ((n = the_table[i].num_recorded_objects) == 0)
{
unLock();
return [NSArray array];
}
tmp = NSZoneMalloc(NSDefaultMallocZone(),
the_table[i].num_recorded_objects * sizeof(id));
tmp = NSZoneMalloc(NSDefaultMallocZone(), n * sizeof(id));
if (tmp == 0)
{
unLock();
@ -803,12 +874,11 @@ GSDebugAllocationListRecordedObjects(Class c)
}
/* First, we copy the objects into a temporary buffer */
memcpy(tmp, the_table[i].recorded_objects,
the_table[i].num_recorded_objects * sizeof(id));
memcpy(tmp, the_table[i].recorded_objects, n * sizeof(id));
/* Retain all the objects - NB: if retaining one of the objects as a
* side effect releases another one of them , we are broken ... */
for (k = 0; k < the_table[i].num_recorded_objects; k++)
for (k = 0; k < n; k++)
{
[tmp[k] retain];
}
@ -818,11 +888,10 @@ GSDebugAllocationListRecordedObjects(Class c)
/* Only then we create an array with them - this is now safe as we
* have copied the objects out, unlocked, and retained them. */
answer = [NSArray arrayWithObjects: tmp
count: the_table[i].num_recorded_objects];
answer = [NSArray arrayWithObjects: tmp count: n];
/* Now we release all the objects to balance the retain */
for (k = 0; k < the_table[i].num_recorded_objects; k++)
for (k = 0; k < n; k++)
{
[tmp[k] release];
}
@ -834,6 +903,77 @@ GSDebugAllocationListRecordedObjects(Class c)
}
NSMapTable *
GSDebugAllocationTaggedObjects(Class c)
{
NSMapTable *answer;
unsigned int i, j, n, t;
if (debug_allocation == NO)
{
return nil;
}
/* Create the map table outside the locked region to minimise time
* locked and to avoid deadlock.
*/
answer = [NSMapTable mapTableWithStrongToStrongObjects];
doLock();
for (i = 0; i < num_classes; i++)
{
if (the_table[i].class == c)
{
break;
}
}
if (i == num_classes)
{
unLock();
return nil;
}
if (the_table[i].is_recording == NO)
{
unLock();
return nil;
}
if ((n = the_table[i].num_recorded_objects) == 0)
{
unLock();
return nil;
}
for (j = t = 0; j < n; j++)
{
if (the_table[i].recorded_tags[j])
{
t++;
}
}
if (0 == t)
{
unLock();
return nil;
}
for (j = 0; j < n; j++)
{
if (the_table[i].recorded_tags[j])
{
[answer setObject: the_table[i].recorded_tags[j]
forKey: the_table[i].recorded_objects[j]];
}
}
unLock();
return answer;
}
const char *
_NSPrintForDebugger(id object)
{