mirror of
https://github.com/gnustep/libs-base.git
synced 2025-04-24 17:28:59 +00:00
Implemented tracking and reporting all allocated instances of a certain
class git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/base/trunk@9627 72102866-910b-0410-8b05-ffd578937521
This commit is contained in:
parent
5d228bcd91
commit
c1bafe11b6
3 changed files with 258 additions and 16 deletions
|
@ -3,6 +3,8 @@
|
|||
|
||||
Written by: Richard Frith-Macdonald <richard@brainstorm.co.uk>
|
||||
Date: August 1997
|
||||
Extended by: Nicola Pero <n.pero@mi.flashnet.it>
|
||||
Date: December 2000, April 2001
|
||||
|
||||
This file is part of the GNUstep Base Library.
|
||||
|
||||
|
@ -68,11 +70,29 @@ extern int errno;
|
|||
* Returns a newline separated list of the classes which
|
||||
* have had instances allocated at any point, and the total
|
||||
* count of the number of instances allocated.
|
||||
*/
|
||||
*
|
||||
* When the previous functions have allowed you to find a memory leak,
|
||||
* and you know that you are leaking objects of class XXX, but you are
|
||||
* 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
|
||||
* and only in debugging systems):
|
||||
*
|
||||
* GSDebugAllocationStartRecordingObjects()
|
||||
* Starts recording all allocated objects of a certain class
|
||||
*
|
||||
* GSDebugAllocationListRecordedObjects()
|
||||
* Returns an array containing all the allocated objects
|
||||
* of a certain class which have been recorded.
|
||||
* Presumably, you will immediately call -description on
|
||||
* them to find out the objects you are leaking.
|
||||
* Warning - the objects are put in an array, so until
|
||||
* the array is autoreleased, the objects are not released. */
|
||||
|
||||
#ifndef NDEBUG
|
||||
GS_EXPORT void GSDebugAllocationAdd(Class c);
|
||||
GS_EXPORT void GSDebugAllocationRemove(Class c);
|
||||
GS_EXPORT void GSDebugAllocationAdd(Class c, id o);
|
||||
GS_EXPORT void GSDebugAllocationRemove(Class c, id o);
|
||||
|
||||
GS_EXPORT BOOL GSDebugAllocationActive(BOOL active);
|
||||
GS_EXPORT int GSDebugAllocationCount(Class c);
|
||||
|
@ -82,6 +102,9 @@ GS_EXPORT Class* GSDebugAllocationClassList();
|
|||
GS_EXPORT const char* GSDebugAllocationList(BOOL changeFlag);
|
||||
GS_EXPORT const char* GSDebugAllocationListAll();
|
||||
|
||||
GS_EXPORT void GSDebugAllocationActiveRecordingObjects(Class c);
|
||||
GS_EXPORT NSArray *GSDebugAllocationListRecordedObjects(Class c);
|
||||
|
||||
GS_EXPORT NSString* GSDebugFunctionMsg(const char *func, const char *file,
|
||||
int line, NSString *fmt);
|
||||
GS_EXPORT NSString* GSDebugMethodMsg(id obj, SEL sel, const char *file,
|
||||
|
|
229
Source/NSDebug.m
229
Source/NSDebug.m
|
@ -1,8 +1,10 @@
|
|||
/* Debugging utilities for GNUStep and OpenStep
|
||||
Copyright (C) 1997,1999,2000 Free Software Foundation, Inc.
|
||||
Copyright (C) 1997,1999,2000,2001 Free Software Foundation, Inc.
|
||||
|
||||
Written by: Richard Frith-Macdonald <richard@brainstorm.co.uk>
|
||||
Date: August 1997
|
||||
Extended by: Nicola Pero <n.pero@mi.flashnet.it>
|
||||
Date: December 2000, April 2001
|
||||
|
||||
This file is part of the GNUstep Base Library.
|
||||
|
||||
|
@ -45,10 +47,16 @@ strerror(int eno)
|
|||
|
||||
typedef struct {
|
||||
Class class;
|
||||
/* The following are used for statistical info */
|
||||
int count;
|
||||
int lastc;
|
||||
int total;
|
||||
int peak;
|
||||
/* The following are used to record actual objects */
|
||||
BOOL is_recording;
|
||||
id *recorded_objects;
|
||||
int num_recorded_objects;
|
||||
int stack_size;
|
||||
} table_entry;
|
||||
|
||||
static int num_classes = 0;
|
||||
|
@ -104,7 +112,63 @@ GSDebugAllocationActive(BOOL active)
|
|||
}
|
||||
|
||||
void
|
||||
GSDebugAllocationAdd(Class c)
|
||||
GSDebugAllocationActiveRecordingObjects(Class c)
|
||||
{
|
||||
unsigned i;
|
||||
|
||||
GSDebugAllocationActive(YES);
|
||||
|
||||
for (i = 0; i < num_classes; i++)
|
||||
{
|
||||
if (the_table[i].class == c)
|
||||
{
|
||||
if (uniqueLock != nil)
|
||||
[uniqueLock lock];
|
||||
the_table[i].is_recording = YES;
|
||||
if (uniqueLock != nil)
|
||||
[uniqueLock unlock];
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (uniqueLock != nil)
|
||||
[uniqueLock lock];
|
||||
if (num_classes >= table_size)
|
||||
{
|
||||
int more = table_size + 128;
|
||||
table_entry *tmp;
|
||||
|
||||
tmp = NSZoneMalloc(NSDefaultMallocZone(), more * sizeof(table_entry));
|
||||
|
||||
if (tmp == 0)
|
||||
{
|
||||
if (uniqueLock != nil)
|
||||
[uniqueLock unlock];
|
||||
return;
|
||||
}
|
||||
if (the_table)
|
||||
{
|
||||
memcpy(tmp, the_table, num_classes * sizeof(table_entry));
|
||||
NSZoneFree(NSDefaultMallocZone(), the_table);
|
||||
}
|
||||
the_table = tmp;
|
||||
table_size = more;
|
||||
}
|
||||
the_table[num_classes].class = c;
|
||||
the_table[num_classes].count = 0;
|
||||
the_table[num_classes].lastc = 0;
|
||||
the_table[num_classes].total = 0;
|
||||
the_table[num_classes].peak = 0;
|
||||
the_table[num_classes].is_recording = YES;
|
||||
the_table[num_classes].recorded_objects = NULL;
|
||||
the_table[num_classes].num_recorded_objects = 0;
|
||||
the_table[num_classes].stack_size = 0;
|
||||
num_classes++;
|
||||
if (uniqueLock != nil)
|
||||
[uniqueLock unlock];
|
||||
}
|
||||
|
||||
void
|
||||
GSDebugAllocationAdd(Class c, id o)
|
||||
{
|
||||
if (debug_allocation)
|
||||
{
|
||||
|
@ -122,6 +186,39 @@ GSDebugAllocationAdd(Class c)
|
|||
{
|
||||
the_table[i].peak = the_table[i].count;
|
||||
}
|
||||
if (the_table[i].is_recording == YES)
|
||||
{
|
||||
if (the_table[i].num_recorded_objects >=
|
||||
the_table[i].stack_size)
|
||||
{
|
||||
int more = the_table[i].stack_size + 128;
|
||||
id *tmp;
|
||||
|
||||
tmp = NSZoneMalloc(NSDefaultMallocZone(),
|
||||
more * sizeof(id));
|
||||
|
||||
if (tmp == 0)
|
||||
{
|
||||
if (uniqueLock != nil)
|
||||
[uniqueLock unlock];
|
||||
return;
|
||||
}
|
||||
if (the_table[i].recorded_objects != NULL)
|
||||
{
|
||||
memcpy(tmp, the_table[i].recorded_objects,
|
||||
the_table[i].num_recorded_objects
|
||||
* sizeof(id));
|
||||
NSZoneFree(NSDefaultMallocZone(),
|
||||
the_table[i].recorded_objects);
|
||||
}
|
||||
the_table[i].recorded_objects = tmp;
|
||||
the_table[i].stack_size = more;
|
||||
}
|
||||
|
||||
(the_table[i].recorded_objects)
|
||||
[the_table[i].num_recorded_objects] = o;
|
||||
the_table[i].num_recorded_objects++;
|
||||
}
|
||||
if (uniqueLock != nil)
|
||||
[uniqueLock unlock];
|
||||
return;
|
||||
|
@ -133,9 +230,9 @@ GSDebugAllocationAdd(Class c)
|
|||
{
|
||||
int more = table_size + 128;
|
||||
table_entry *tmp;
|
||||
|
||||
|
||||
tmp = NSZoneMalloc(NSDefaultMallocZone(), more * sizeof(table_entry));
|
||||
|
||||
|
||||
if (tmp == 0)
|
||||
{
|
||||
if (uniqueLock != nil)
|
||||
|
@ -155,6 +252,10 @@ GSDebugAllocationAdd(Class c)
|
|||
the_table[num_classes].lastc = 0;
|
||||
the_table[num_classes].total = 1;
|
||||
the_table[num_classes].peak = 1;
|
||||
the_table[num_classes].is_recording = NO;
|
||||
the_table[num_classes].recorded_objects = NULL;
|
||||
the_table[num_classes].num_recorded_objects = 0;
|
||||
the_table[num_classes].stack_size = 0;
|
||||
num_classes++;
|
||||
if (uniqueLock != nil)
|
||||
[uniqueLock unlock];
|
||||
|
@ -396,7 +497,7 @@ _GSDebugAllocationListAll()
|
|||
}
|
||||
|
||||
void
|
||||
GSDebugAllocationRemove(Class c)
|
||||
GSDebugAllocationRemove(Class c, id o)
|
||||
{
|
||||
if (debug_allocation)
|
||||
{
|
||||
|
@ -409,6 +510,36 @@ GSDebugAllocationRemove(Class c)
|
|||
if (uniqueLock != nil)
|
||||
[uniqueLock lock];
|
||||
the_table[i].count--;
|
||||
if (the_table[i].is_recording)
|
||||
{
|
||||
unsigned j, k;
|
||||
|
||||
for (j = 0; j < the_table[i].num_recorded_objects; j++)
|
||||
{
|
||||
if ((the_table[i].recorded_objects)[j] == o)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (j < the_table[i].num_recorded_objects)
|
||||
{
|
||||
for (k = j;
|
||||
k + 1 < the_table[i].num_recorded_objects;
|
||||
k++)
|
||||
{
|
||||
(the_table[i].recorded_objects)[k] =
|
||||
(the_table[i].recorded_objects)[k + 1];
|
||||
}
|
||||
the_table[i].num_recorded_objects--;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Not found - no problem - this happens if the
|
||||
object was allocated before we started
|
||||
recording */
|
||||
;
|
||||
}
|
||||
}
|
||||
if (uniqueLock != nil)
|
||||
[uniqueLock unlock];
|
||||
return;
|
||||
|
@ -417,6 +548,94 @@ GSDebugAllocationRemove(Class c)
|
|||
}
|
||||
}
|
||||
|
||||
NSArray *
|
||||
GSDebugAllocationListRecordedObjects(Class c)
|
||||
{
|
||||
NSArray *answer;
|
||||
unsigned i, k;
|
||||
id *tmp;
|
||||
|
||||
if (debug_allocation == NO)
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
if (uniqueLock != nil)
|
||||
[uniqueLock lock];
|
||||
|
||||
for (i = 0; i < num_classes; i++)
|
||||
{
|
||||
if (the_table[i].class == c)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (i == num_classes)
|
||||
{
|
||||
if (uniqueLock != nil)
|
||||
[uniqueLock unlock];
|
||||
return nil;
|
||||
}
|
||||
|
||||
if (the_table[i].is_recording == NO)
|
||||
{
|
||||
if (uniqueLock != nil)
|
||||
[uniqueLock unlock];
|
||||
return nil;
|
||||
}
|
||||
|
||||
if (the_table[i].num_recorded_objects == 0)
|
||||
{
|
||||
if (uniqueLock != nil)
|
||||
[uniqueLock unlock];
|
||||
return [NSArray array];
|
||||
}
|
||||
|
||||
tmp = NSZoneMalloc(NSDefaultMallocZone(),
|
||||
the_table[i].num_recorded_objects * sizeof(id));
|
||||
|
||||
if (tmp == 0)
|
||||
{
|
||||
if (uniqueLock != nil)
|
||||
[uniqueLock unlock];
|
||||
return nil;
|
||||
}
|
||||
|
||||
/* First, we copy the objects into a temporary buffer */
|
||||
memcpy(tmp, the_table[i].recorded_objects,
|
||||
the_table[i].num_recorded_objects * sizeof(id));
|
||||
|
||||
/* Then, we bravely unlock the lock */
|
||||
if (uniqueLock != nil)
|
||||
[uniqueLock unlock];
|
||||
|
||||
/* 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++)
|
||||
{
|
||||
RETAIN (tmp[k]);
|
||||
}
|
||||
|
||||
/* 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];
|
||||
|
||||
/* Now we release all the objects to balance the retain */
|
||||
for (k = 0; k < the_table[i].num_recorded_objects; k++)
|
||||
{
|
||||
RELEASE (tmp[k]);
|
||||
}
|
||||
|
||||
/* And free the space used by them */
|
||||
NSZoneFree(NSDefaultMallocZone(), tmp);
|
||||
|
||||
return answer;
|
||||
}
|
||||
|
||||
|
||||
|
||||
NSString*
|
||||
GSDebugFunctionMsg(const char *func, const char *file, int line, NSString *fmt)
|
||||
{
|
||||
|
|
|
@ -337,7 +337,7 @@ GSFinalize(void* object, void* data)
|
|||
{
|
||||
[(id)object gcFinalize];
|
||||
#ifndef NDEBUG
|
||||
GSDebugAllocationRemove(((id)object)->class_pointer);
|
||||
GSDebugAllocationRemove(((id)object)->class_pointer, (id)object);
|
||||
#endif
|
||||
((id)object)->class_pointer = (void*)0xdeadface;
|
||||
}
|
||||
|
@ -385,7 +385,7 @@ NSAllocateObject(Class aClass, unsigned extraBytes, NSZone *zone)
|
|||
* finalised - for other objects we have no way of decrementing
|
||||
* the count when the object is collected.
|
||||
*/
|
||||
GSDebugAllocationAdd(aClass);
|
||||
GSDebugAllocationAdd(aClass, new);
|
||||
#endif
|
||||
GC_REGISTER_FINALIZER (new, GSFinalize, NULL, NULL, NULL);
|
||||
}
|
||||
|
@ -428,7 +428,7 @@ inline NSObject *
|
|||
NSAllocateObject (Class aClass, unsigned extraBytes, NSZone *zone)
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
extern void GSDebugAllocationAdd(Class);
|
||||
extern void GSDebugAllocationAdd(Class c, id o);
|
||||
#endif
|
||||
id new = nil;
|
||||
int size = aClass->instance_size + extraBytes + sizeof(struct obj_layout);
|
||||
|
@ -447,7 +447,7 @@ NSAllocateObject (Class aClass, unsigned extraBytes, NSZone *zone)
|
|||
new = (id)&((obj)new)[1];
|
||||
new->class_pointer = aClass;
|
||||
#ifndef NDEBUG
|
||||
GSDebugAllocationAdd(aClass);
|
||||
GSDebugAllocationAdd(aClass, new);
|
||||
#endif
|
||||
}
|
||||
return new;
|
||||
|
@ -457,7 +457,7 @@ inline void
|
|||
NSDeallocateObject(NSObject *anObject)
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
extern void GSDebugAllocationRemove(Class);
|
||||
extern void GSDebugAllocationRemove(Class c, id o);
|
||||
#endif
|
||||
if ((anObject!=nil) && CLS_ISCLASS(((id)anObject)->class_pointer))
|
||||
{
|
||||
|
@ -465,7 +465,7 @@ NSDeallocateObject(NSObject *anObject)
|
|||
NSZone *z = GSObjCZone(anObject);
|
||||
|
||||
#ifndef NDEBUG
|
||||
GSDebugAllocationRemove(((id)anObject)->class_pointer);
|
||||
GSDebugAllocationRemove(((id)anObject)->class_pointer, (id)anObject);
|
||||
#endif
|
||||
((id)anObject)->class_pointer = (void*) 0xdeadface;
|
||||
NSZoneFree(z, o);
|
||||
|
@ -495,7 +495,7 @@ NSAllocateObject (Class aClass, unsigned extraBytes, NSZone *zone)
|
|||
memset (new, 0, size);
|
||||
new->class_pointer = aClass;
|
||||
#ifndef NDEBUG
|
||||
GSDebugAllocationAdd(aClass);
|
||||
GSDebugAllocationAdd(aClass, new);
|
||||
#endif
|
||||
}
|
||||
return new;
|
||||
|
@ -509,7 +509,7 @@ NSDeallocateObject(NSObject *anObject)
|
|||
NSZone *z = [anObject zone];
|
||||
|
||||
#ifndef NDEBUG
|
||||
GSDebugAllocationRemove(((id)anObject)->class_pointer);
|
||||
GSDebugAllocationRemove(((id)anObject)->class_pointer, (id)anObject);
|
||||
#endif
|
||||
((id)anObject)->class_pointer = (void*) 0xdeadface;
|
||||
NSZoneFree(z, anObject);
|
||||
|
|
Loading…
Reference in a new issue