nautilus/test/nautilus-leak-checker.c
Ramiro Estrugo a356df0de7 Moved widgets and preferences test to nautilus/test.
* libnautilus-extensions/test-nautilus-preferences.c:
* libnautilus-extensions/test-nautilus-widgets.c:
* libnautilus-extensions/test-preferences.c:
Moved widgets and preferences test to nautilus/test.

* test/.cvsignore:
* test/Makefile.am:
* test/test-nautilus-preferences.c: (main),
(test_preferences_item), (test_preferences_group),
(create_enum_item), (register_global_preferences):
* test/test-nautilus-widgets.c: (main), (test_radio_group),
(test_caption_table), (test_string_picker), (test_text_caption),
(test_authenticate_boink_callback),
(string_picker_changed_callback), (text_caption_changed_callback),
(test_password_dialog), (test_radio_changed_callback),
(test_caption_table_activate_callback):
Ressurect my widgets/preferences test over here.  They got whacked
in the nautilus-widgets -> libnautilus-extensions renamings.
2000-07-05 14:14:25 +00:00

761 lines
20 KiB
C

/* nautilus-leak-checker.c - simple leak checking library
Virtual File System Library
Copyright (C) 2000 Eazel
The Gnome Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.
The Gnome Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with the Gnome Library; see the file COPYING.LIB. If not,
write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.
Author: Pavel Cisler <pavel@eazel.com>
based on MemProf by Owen Taylor, <otaylor@redhat.com>
*/
#include "nautilus-leak-checker.h"
/* included first, defines following switch*/
#if LEAK_CHECKER
#include <malloc.h>
#include <dlfcn.h>
#include <malloc.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "nautilus-leak-checker-stubs.h"
#include "nautilus-leak-hash-table.h"
#include "nautilus-leak-symbol-lookup.h"
/* this is the maximum number of stack crawl levels we can capture */
enum {
TRACE_ARRAY_MAX = 512
};
static volatile gboolean nautilus_leak_hooks_initialized;
static volatile gboolean nautilus_leak_initializing_hooks;
static volatile gboolean nautilus_leak_check_leaks;
void *(* real_malloc) (size_t size);
void *(* real_memalign) (size_t boundary, size_t size);
void *(* real_realloc) (void *ptr, size_t size);
void *(* real_calloc) (void *ptr, size_t size);
void (* real_free) (void *ptr);
int (* real_start_main) (int (*main) (int, char **, char **), int argc,
char **argv, void (*init) (void), void (*fini) (void),
void (*rtld_fini) (void), void *stack_end);
const char *app_path;
void
nautilus_leak_allocation_record_init (NautilusLeakAllocationRecord *record,
void *block, size_t initial_size, void **stack_crawl, int max_depth)
{
int stack_depth, index;
record->block = block;
record->size = initial_size;
for (index = 0; index < max_depth; index++) {
if (stack_crawl[index] == NULL)
break;
}
stack_depth = index;
/* call real_malloc to avoid recursion and messing up results */
record->stack_crawl = real_malloc ((stack_depth + 1) * sizeof(void *));
memcpy (record->stack_crawl, stack_crawl, stack_depth * sizeof(void *));
record->stack_crawl[stack_depth] = NULL;
}
NautilusLeakAllocationRecord *
nautilus_leak_allocation_record_copy (const NautilusLeakAllocationRecord *record)
{
int stack_depth, index;
NautilusLeakAllocationRecord *result;
result = real_malloc (sizeof(*result));
result->block = record->block;
result->size = record->size;
for (index = 0; ; index++) {
if (record->stack_crawl[index] == NULL)
break;
}
stack_depth = index;
result->stack_crawl = real_malloc ((stack_depth + 1) * sizeof(void *));
memcpy (result->stack_crawl, record->stack_crawl, (stack_depth + 1) * sizeof(void *));
return result;
}
void
nautilus_leak_allocation_record_finalize (NautilusLeakAllocationRecord *record)
{
/* call real_free to avoid recursion and messing up results */
real_free (record->stack_crawl);
}
void
nautilus_leak_allocation_record_free (NautilusLeakAllocationRecord *record)
{
/* call real_free to avoid recursion and messing up results */
real_free (record->stack_crawl);
real_free (record);
}
/* return a strcmp-like result to be used in sort funcitons */
int
nautilus_leak_stack_crawl_compare (void **stack_crawl1, void **stack_crawl2, int levels)
{
int index;
for (index = 0; index < levels; index++) {
if (stack_crawl1 [index] == NULL && stack_crawl2 [index] == NULL) {
return 0;
}
if (stack_crawl1 [index] < stack_crawl2 [index]) {
return -1;
} else if (stack_crawl1 [index] > stack_crawl2 [index]) {
return 1;
}
}
return 0;
}
static void
nautilus_leak_initialize (void)
{
/* Locate the original malloc calls. The dlsym calls
* will allocate memory when doing lookups. We use a special
* trick to deal with the fact that real_malloc is not set up
* yet while we are doing the first dlsym -- see malloc.
*/
real_malloc = dlsym (RTLD_NEXT, "__libc_malloc");
real_realloc = dlsym (RTLD_NEXT, "__libc_realloc");
real_free = dlsym (RTLD_NEXT, "__libc_free");
real_memalign = dlsym (RTLD_NEXT, "__libc_memalign");
real_calloc = dlsym (RTLD_NEXT, "__libc_calloc");
real_start_main = dlsym (RTLD_NEXT, "__libc_start_main");
nautilus_leak_hooks_initialized = TRUE;
nautilus_leak_check_leaks = TRUE;
}
typedef struct StackFrame StackFrame;
struct StackFrame {
StackFrame *next_frame;
void *return_address;
/* first argument is here */
};
static void
get_stack_trace (void **trace_array, int trace_array_max)
{
int index;
const StackFrame *stack_frame;
/* point to the stack frame pointer, two words before the
* address of the first argument
*/
stack_frame = (const StackFrame *)((void **)&trace_array - 2);
/* Record stack frames; skip first two in the malloc calls. */
for (index = -2; index < trace_array_max; index++) {
if (index >= 0) {
/* Return address is the next pointer after
* stack frame.
*/
trace_array [index] = stack_frame->return_address;
}
stack_frame = stack_frame->next_frame;
if (stack_frame == NULL) {
break;
}
}
if (index < trace_array_max) {
trace_array [index] = NULL;
}
}
/* Figure out if one of the malloc calls is reentering itself.
* There seems no cleaner way to handle this -- we want to override
* malloc calls at the __libc_* level.
* The malloc hooks recurse when initializing themselves.
* When this happens we need a reliable way to tell that a recursion is
* underway so as to not record the corresponding malloc call twice
*/
static gboolean
detect_reentry (void *parent_caller)
{
int count;
const StackFrame *stack_frame;
stack_frame = (const StackFrame *)((void **)&parent_caller - 2);
stack_frame = stack_frame->next_frame;
for (count = 0; count < 7; count++) {
/* See if we return address on the stack
* that is near the parent_caller -- the start address
* of the calling function.
* We are using two arbitrary numbers here - 5 should be a "deep enough"
* check to detect the recursion, 0x40 bytes should be a large enough distance
* from the start of the malloc call to the point where the old malloc call is
* being called.
* FIXME: this the value 0x40 works well only with certain function sizes, optimization levels,
* etc. need a more robust way of doing this. One way might be adding stuffing into the
* __libc_* calls that is never executed but makes the funcitons longer. We could
* then up the value without the danger of hitting the next function
*/
if (stack_frame->return_address >= parent_caller
&& stack_frame->return_address < (void *)((char *)parent_caller + 0x40)) {
/* printf("detected reentry at level %d\n", count); */
return TRUE;
}
stack_frame = stack_frame->next_frame;
if (stack_frame == NULL) {
break;
}
}
return FALSE;
}
static pthread_mutex_t nautilus_leak_hash_table_mutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;
static NautilusLeakHashTable *hash_table;
static int nautilus_leak_malloc_count;
static size_t nautilus_leak_malloc_outstanding_size;
static void
nautilus_leak_record_malloc (void *ptr, size_t size)
{
void *trace_array [TRACE_ARRAY_MAX];
NautilusHashEntry *element;
/* printf("new block %p, %d\n", ptr, size); */
if (!nautilus_leak_check_leaks)
return;
++nautilus_leak_malloc_count;
nautilus_leak_malloc_outstanding_size += size;
get_stack_trace (trace_array, TRACE_ARRAY_MAX);
pthread_mutex_lock (&nautilus_leak_hash_table_mutex);
if (hash_table == NULL) {
hash_table = nautilus_leak_hash_table_new (10 * 1024);
}
if (nautilus_leak_hash_table_find (hash_table, (gulong)ptr) != NULL) {
printf("*** block %p appears to already be allocated "
"- someone must have sneaked a free past us\n", ptr);
nautilus_leak_hash_table_remove (hash_table, (gulong)ptr);
}
/* insert a new item into the hash table, using the block address as the key */
element = nautilus_leak_hash_table_add (hash_table, (gulong)ptr);
/* fill out the new allocated element */
nautilus_leak_allocation_record_init (&element->data, ptr, size, trace_array, TRACE_ARRAY_MAX);
pthread_mutex_unlock (&nautilus_leak_hash_table_mutex);
}
static void
nautilus_leak_record_realloc (void *old_ptr, void *new_ptr, size_t size)
{
void *trace_array [TRACE_ARRAY_MAX];
NautilusHashEntry *element;
/* printf("reallocing block %p, %d was %p\n", new_ptr, size, old_ptr); */
if (!nautilus_leak_check_leaks)
return;
get_stack_trace (trace_array, TRACE_ARRAY_MAX);
pthread_mutex_lock (&nautilus_leak_hash_table_mutex);
/* must have hash table by now */
g_assert (hash_table != NULL);
/* must have seen the block already */
element = nautilus_leak_hash_table_find (hash_table, (gulong)old_ptr);
if (element == NULL) {
printf("*** we haven't seen block %p yet "
"- someone must have sneaked a malloc past us\n", old_ptr);
} else {
nautilus_leak_malloc_outstanding_size -= element->data.size;
nautilus_leak_hash_table_remove (hash_table, (gulong)old_ptr);
}
/* shouldn't have this block yet */
if (nautilus_leak_hash_table_find (hash_table, (gulong)new_ptr) != NULL) {
printf("*** block %p appears to already be allocated "
"- someone must have sneaked a free past us\n", new_ptr);
nautilus_leak_hash_table_remove (hash_table, (gulong)new_ptr);
}
/* insert a new item into the hash table, using the block address as the key */
element = nautilus_leak_hash_table_add (hash_table, (gulong)new_ptr);
nautilus_leak_malloc_outstanding_size += size;
/* Fill out the new allocated element.
* This way the last call to relloc will be the stack crawl that shows up in the
* final balance.
*/
nautilus_leak_allocation_record_init (&element->data, new_ptr, size, trace_array,
TRACE_ARRAY_MAX);
pthread_mutex_unlock (&nautilus_leak_hash_table_mutex);
}
static void
nautilus_leak_record_free (void *ptr)
{
NautilusHashEntry *element;
/* printf("freeing block %p\n", ptr); */
if (!nautilus_leak_check_leaks)
return;
--nautilus_leak_malloc_count;
pthread_mutex_lock (&nautilus_leak_hash_table_mutex);
/* must have hash table by now */
g_assert (hash_table != NULL);
/* must have seen the block already */
element = nautilus_leak_hash_table_find (hash_table, (gulong)ptr);
if (element == NULL) {
printf("*** we haven't seen block %p yet "
"- someone must have sneaked a malloc past us\n", ptr);
} else {
nautilus_leak_malloc_outstanding_size -= element->data.size;
nautilus_leak_hash_table_remove (hash_table, (gulong)ptr);
}
pthread_mutex_unlock (&nautilus_leak_hash_table_mutex);
}
static void
nautilus_leak_initialize_if_needed (void)
{
if (nautilus_leak_hooks_initialized)
return;
if (nautilus_leak_initializing_hooks)
/* guard against reentrancy */
return;
nautilus_leak_initializing_hooks = TRUE;
nautilus_leak_initialize ();
nautilus_leak_initializing_hooks = FALSE;
}
/* we are overlaying the original __libc_malloc */
enum {
STARTUP_FALLBACK_MEMORY_SIZE = 1024
};
static int startup_fallback_memory_index = 0;
static char startup_fallback_memory [STARTUP_FALLBACK_MEMORY_SIZE];
/* If our malloc hook is not installed yet - for instance when we are being
* called from the initialize_if_needed routine. We have to fall back on
* returning a chunk of static memory to carry us through the initialization.
*/
static void *
allocate_temporary_fallback_memory (ssize_t size)
{
void *result;
/* align to natural word boundary */
size = (size + sizeof(void *) - 1 ) & ~(sizeof(void *) - 1);
if (size + startup_fallback_memory_index
> STARTUP_FALLBACK_MEMORY_SIZE) {
g_warning ("trying to allocate to much space during startup");
return NULL;
}
result = &startup_fallback_memory [startup_fallback_memory_index];
startup_fallback_memory_index += size;
return result;
}
void *
__libc_malloc (size_t size)
{
void *result;
nautilus_leak_initialize_if_needed ();
if (real_malloc == NULL) {
return allocate_temporary_fallback_memory (size);
}
result = (*real_malloc) (size);
if (result != NULL) {
if (detect_reentry(&__libc_malloc)
|| detect_reentry(&__libc_realloc)
|| detect_reentry(&__libc_memalign)) {
/* printf("avoiding reentry in __libc_malloc, block %p\n", result); */
} else {
nautilus_leak_record_malloc (result, size);
}
}
return result;
}
void *
__libc_memalign (size_t boundary, size_t size)
{
void *result;
nautilus_leak_initialize_if_needed ();
result = (*real_memalign) (boundary, size);
if (result != NULL) {
if (detect_reentry(&__libc_memalign)) {
/* printf("avoiding reentry in __libc_memalign, block %p\n", result); */
} else {
nautilus_leak_record_malloc (result, size);
}
}
return result;
}
/* We are implementing __libc_calloc by calling __libc_malloc and memset
* instead of calling real_calloc for a reason. dlsym calls
* calloc and this way we prevent recursion during initialization.
* If we didn't do this, we would have to teach __libc_calloc to use
* fallback startup memory like __libc_malloc does.
*/
void *
__libc_calloc (size_t count, size_t size)
{
size_t total;
void *result;
total = count * size;
nautilus_leak_initialize_if_needed ();
result = __libc_malloc (total);
if (result != NULL) {
memset (result, 0, total);
}
return result;
}
void *
__libc_realloc (void *ptr, size_t size)
{
void *result;
nautilus_leak_initialize_if_needed ();
result = (*real_realloc) (ptr, size);
if (detect_reentry(&__libc_realloc)) {
/* printf("avoiding reentry in __libc_realloc, block %p, old block %p\n",
result, ptr); */
} else {
if (result != NULL && ptr == NULL) {
/* we are allocating a new block */
nautilus_leak_record_malloc (result, size);
} else {
nautilus_leak_record_realloc (ptr, result, size);
}
}
return result;
}
void
__libc_free (void *ptr)
{
nautilus_leak_initialize_if_needed ();
if (ptr > (void *)&startup_fallback_memory[0]
&& ptr < (void *)&startup_fallback_memory[STARTUP_FALLBACK_MEMORY_SIZE]) {
/* this is a temporary startup fallback block, don't do anything
* with it
*/
return;
}
if (ptr != NULL) {
if (detect_reentry(&__libc_realloc)) {
/* printf("avoiding reentry in __libc_free, block %p\n", ptr); */
} else {
nautilus_leak_record_free (ptr);
}
}
(real_free) (ptr);
}
int
__libc_start_main (int (*main) (int, char **, char **), int argc,
char **argv, void (*init) (void), void (*fini) (void),
void (*rtld_fini) (void), void *stack_end)
{
nautilus_leak_initialize_if_needed ();
nautilus_leak_checker_init (argv[0]);
printf ("once\n");
return real_start_main (main, argc, argv, init, fini, rtld_fini, stack_end);
}
/* We try to keep a lot of code in between __libc_free and malloc to make
* the reentry detection that depends on call address proximity work.
*/
typedef struct {
int max_count;
int counter;
int stack_print_depth;
int stack_match_depth;
} PrintOneLeakParams;
/* we don't care if printf, etc. allocates (as long as it doesn't leak)
* because by now we have a snapshot of the leaks at the time of
* calling nautilus_leak_print_leaks
*/
static gboolean
print_one_leak (NautilusLeakTableEntry *entry, void *context)
{
int index;
PrintOneLeakParams *params = (PrintOneLeakParams *)context;
printf("----------------- total_size %ld count %d -------------------\n",
(long)entry->total_size, entry->count);
for (index = 0; index < params->stack_print_depth; index++) {
/* only print stack_grouping worth of stack crawl -
* beyond that different blocks may have different addresses
* and we would be printing a lie
*/
if (entry->sample_allocation->stack_crawl[index] == NULL)
break;
printf(" %c ", index >= params->stack_match_depth ? '?' : ' ');
nautilus_leak_print_symbol_address (app_path,
entry->sample_allocation->stack_crawl[index]);
}
/* only print max_counter groups */
return params->counter++ < params->max_count;
}
void
nautilus_leak_print_leaks (int stack_grouping_depth, int stack_print_depth,
int max_count, gboolean sort_by_count)
{
NautilusLeakTable *temp_leak_table;
PrintOneLeakParams each_context;
pthread_mutex_lock (&nautilus_leak_hash_table_mutex);
/* must have hash table by now */
g_assert (hash_table != NULL);
/* Build a leak table by grouping blocks with the same
* stackcrawls (stack_grouping_depth levels considered)
* from the allocated block hash table.
*/
temp_leak_table = nautilus_leak_table_new (hash_table, stack_grouping_depth);
pthread_mutex_unlock (&nautilus_leak_hash_table_mutex);
printf("%d outstanding allocations %d bytes total ============ \n",
nautilus_leak_malloc_count, nautilus_leak_malloc_outstanding_size);
printf("stack trace match depth %d\n", stack_grouping_depth);
/* sort the leak table */
if (sort_by_count) {
nautilus_leak_table_sort_by_count (temp_leak_table);
} else {
nautilus_leak_table_sort_by_size (temp_leak_table);
}
/* we have a sorted table of all the leakers, we can print it out. */
each_context.counter = 0;
each_context.max_count = max_count;
each_context.stack_print_depth = stack_print_depth;
each_context.stack_match_depth = stack_grouping_depth;
nautilus_leak_table_each_item (temp_leak_table, print_one_leak, &each_context);
/* we are done with it, free the leak table */
nautilus_leak_table_free (temp_leak_table);
/* we are done with it, clean up cached up data used by the symbol lookup */
nautilus_leak_print_symbol_cleanup ();
}
static void
print_leaks_at_exit (void)
{
/* If leak checking, dump all the outstanding allocations just before exiting. */
nautilus_leak_print_leaks (8, 15, 40, TRUE);
}
void
nautilus_leak_checker_init (const char *path)
{
/* we should get rid of this and find another way to find our
* binary's name
*/
printf("setting up the leakchecker for %s\n", path);
app_path = path;
g_atexit (print_leaks_at_exit);
}
void *
malloc (size_t size)
{
return __libc_malloc (size);
}
void *
realloc (void *ptr, size_t size)
{
return __libc_realloc (ptr, size);
}
void *
memalign (size_t boundary, size_t size)
{
return __libc_memalign (boundary, size);
}
void *
calloc (size_t nmemb, size_t size)
{
return __libc_calloc (nmemb, size);
}
void
free (void *ptr)
{
__libc_free (ptr);
}
#endif
#ifdef LEAK_CHECK_TESTING
/* normally disabled */
static void
allocate_lots (int count)
{
GList *list;
GList *p;
list = NULL;
for (; count > 0; count--) {
list = g_list_prepend (list, g_malloc (rand() % 256));
list = g_list_prepend (list, NULL);
}
for (p = list; p != NULL; p = p->next) {
g_free (p->data);
p->data = NULL;
}
g_list_free (list);
}
static void
leak_mem2 (void)
{
int i;
for (i = 0; i < 40; i++) {
g_strdup("bla");
}
allocate_lots (1280);
}
static void
leak_mem (void)
{
int i;
for (i = 0; i < 1010; i++) {
malloc(13);
}
leak_mem2();
allocate_lots (200);
}
int
main (int argc, char **argv)
{
void *non_leak;
void *leak;
int i;
nautilus_leak_checker_init (*argv);
non_leak = g_malloc(100);
leak = g_malloc(200);
g_assert(non_leak != NULL);
non_leak = g_realloc(non_leak, 1000);
g_assert(non_leak != NULL);
non_leak = g_realloc(non_leak, 10000);
leak = g_malloc(200);
non_leak = g_realloc(non_leak, 100000);
leak = g_malloc(200);
g_assert(non_leak != NULL);
g_free(non_leak);
non_leak = calloc(1, 100);
g_assert(non_leak != NULL);
g_free(non_leak);
leak = g_malloc(200);
non_leak = memalign(16, 100);
g_assert(non_leak != NULL);
g_free(non_leak);
leak = g_malloc(200);
leak = memalign(16, 100);
leak = memalign(16, 100);
leak = memalign(16, 100);
leak = memalign(16, 100);
leak = memalign(16, 100);
leak = memalign(16, 100);
for (i = 0; i < 13; i++) {
leak = malloc(13);
}
leak_mem();
leak_mem2();
allocate_lots (1);
for (i = 0; i < 100; i++) {
allocate_lots(rand() % 40);
}
printf("done\n");
nautilus_leak_print_leaks (6, 12, 100, TRUE);
return 0;
}
#endif