mirror of
https://github.com/gnustep/libs-base.git
synced 2025-04-23 09:04:13 +00:00
Memory leak improvments
This commit is contained in:
parent
c9af996377
commit
4d0b00776c
4 changed files with 231 additions and 62 deletions
16
ChangeLog
16
ChangeLog
|
@ -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>
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
226
Source/NSDebug.m
226
Source/NSDebug.m
|
@ -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)
|
||||
{
|
||||
|
|
Loading…
Reference in a new issue