X-Git-Url: http://sjero.net/git/?a=blobdiff_plain;f=src%2Fxmalloc.c;h=427eefa8895222a0b836e32ecda5a6a28812d5ec;hb=4d7c5e087b2bc82c9f503dff003916d1047903ce;hp=3464cbfe133fe7e431ced9084aac1dd19d7d618e;hpb=370ff7a57640ff98cc399317927e8f3c51abad76;p=wget diff --git a/src/xmalloc.c b/src/xmalloc.c index 3464cbfe..427eefa8 100644 --- a/src/xmalloc.c +++ b/src/xmalloc.c @@ -1,12 +1,12 @@ /* Wrappers around malloc and memory debugging support. - Copyright (C) 2003 Free Software Foundation, Inc. + Copyright (C) 2003-2006 Free Software Foundation, Inc. This file is part of GNU Wget. GNU Wget is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. +the Free Software Foundation; either version 3 of the License, or +(at your option) any later version. GNU Wget is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -14,8 +14,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License -along with Wget; if not, write to the Free Software -Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +along with Wget. If not, see . In addition, as a special exception, the Free Software Foundation gives permission to link the code of its release of Wget with the @@ -31,21 +30,13 @@ so, delete this exception statement from your version. */ #include #include -#ifdef HAVE_STRING_H -# include -#else /* not HAVE_STRING_H */ -# include -#endif /* not HAVE_STRING_H */ -#include +#include #include #include #include "wget.h" #include "xmalloc.h" - -#ifndef errno -extern int errno; -#endif +#include "hash.h" /* for hash_pointer */ /* This file implements several wrappers around the basic allocation routines. This is done for two reasons: first, so that the callers @@ -56,19 +47,20 @@ extern int errno; The second reason why these are useful is that, if DEBUG_MALLOC is defined, they also provide a handy (if crude) malloc debugging - interface that checks memory leaks. */ + interface that checks for memory leaks. */ /* Croak the fatal memory error and bail out with non-zero exit status. */ static void -memfatal (const char *context, long size) +memfatal (const char *context, long attempted_size) { /* Make sure we don't try to store part of the log line, and thus call malloc. */ - log_set_save_context (0); - logprintf (LOG_ALWAYS, _("%s: %s: Cannot allocate %ld bytes.\n"), - exec_name, context, size); + log_set_save_context (false); + logprintf (LOG_ALWAYS, + _("%s: %s: Failed to allocate %ld bytes; memory exhausted.\n"), + exec_name, context, attempted_size); exit (1); } @@ -76,13 +68,13 @@ memfatal (const char *context, long size) distinguished from the debugging functions, and from the macros. Explanation follows: - If memory debugging is not turned on, wget.h defines these: + If memory debugging is not turned on, xmalloc.h defines these: - #define xmalloc xmalloc_real - #define xmalloc0 xmalloc0_real - #define xrealloc xrealloc_real - #define xstrdup xstrdup_real - #define xfree free + #define xmalloc checking_malloc + #define xmalloc0 checking_malloc0 + #define xrealloc checking_realloc + #define xstrdup checking_strdup + #define xfree checking_free In case of memory debugging, the definitions are a bit more complex, because we want to provide more information, *and* we want @@ -90,13 +82,14 @@ memfatal (const char *context, long size) and friends need to be macros in the first place.) Then it looks like this: - #define xmalloc(a) xmalloc_debug (a, __FILE__, __LINE__) - #define xmalloc0(a) xmalloc0_debug (a, __FILE__, __LINE__) - #define xfree(a) xfree_debug (a, __FILE__, __LINE__) - #define xrealloc(a, b) xrealloc_debug (a, b, __FILE__, __LINE__) - #define xstrdup(a) xstrdup_debug (a, __FILE__, __LINE__) + #define xmalloc(a) debugging_malloc (a, __FILE__, __LINE__) + #define xmalloc0(a) debugging_malloc0 (a, __FILE__, __LINE__) + #define xrealloc(a, b) debugging_realloc (a, b, __FILE__, __LINE__) + #define xstrdup(a) debugging_strdup (a, __FILE__, __LINE__) + #define xfree(a) debugging_free (a, __FILE__, __LINE__) - Each of the *_debug function does its magic and calls the real one. */ + Each of the debugging_* functions does its magic and calls the + corresponding checking_* one. */ #ifdef DEBUG_MALLOC # define STATIC_IF_DEBUG static @@ -105,7 +98,7 @@ memfatal (const char *context, long size) #endif STATIC_IF_DEBUG void * -xmalloc_real (size_t size) +checking_malloc (size_t size) { void *ptr = malloc (size); if (!ptr) @@ -114,7 +107,7 @@ xmalloc_real (size_t size) } STATIC_IF_DEBUG void * -xmalloc0_real (size_t size) +checking_malloc0 (size_t size) { /* Using calloc can be faster than malloc+memset because some calloc implementations know when they're dealing with zeroed-out memory @@ -126,7 +119,7 @@ xmalloc0_real (size_t size) } STATIC_IF_DEBUG void * -xrealloc_real (void *ptr, size_t newsize) +checking_realloc (void *ptr, size_t newsize) { void *newptr; @@ -143,7 +136,7 @@ xrealloc_real (void *ptr, size_t newsize) } STATIC_IF_DEBUG char * -xstrdup_real (const char *s) +checking_strdup (const char *s) { char *copy; @@ -162,6 +155,33 @@ xstrdup_real (const char *s) return copy; } +STATIC_IF_DEBUG void +checking_free (void *ptr) +{ + /* Wget's xfree() must not be passed a NULL pointer. This is for + historical reasons: pre-C89 systems were reported to bomb at + free(NULL), and Wget was careful to not call xfree when there was + a possibility of PTR being NULL. (It might have been better to + simply have xfree() do nothing if ptr==NULL.) + + Since the code is already written that way, this assert simply + enforces the existing constraint. The benefit is double-checking + the logic: code that thinks it can't be passed a NULL pointer, + while it in fact can, aborts here. If you trip on this, either + the code has a pointer handling bug or should have called + xfree_null instead of xfree. Correctly written code should never + trigger this assertion. + + The downside is that the uninitiated might not expect xfree(NULL) + to abort. If the assertion proves to be too much of a hassle, it + can be removed and a check that makes NULL a no-op placed in its + stead. If that is done, xfree_null is no longer needed and + should be removed. */ + assert (ptr != NULL); + + free (ptr); +} + #ifdef DEBUG_MALLOC /* Crude home-grown routines for debugging some malloc-related @@ -174,8 +194,10 @@ xstrdup_real (const char *s) * Making malloc store its entry into a simple array and free remove stuff from that array. At the end, print the pointers which have not been freed, along with the source file and the line number. - This also has the side-effect of detecting freeing memory that - was never allocated. + + * Checking for "invalid frees", where free is called on a pointer + not obtained with malloc, or where the same pointer is freed + twice. Note that this kind of memory leak checking strongly depends on every malloc() being followed by a free(), even if the program is @@ -184,58 +206,85 @@ xstrdup_real (const char *s) static int malloc_count, free_count; +/* Home-grown hash table of mallocs: */ + +#define SZ 100003 /* Prime just over 100,000. Increase + it to debug larger Wget runs. */ + static struct { - char *ptr; + const void *ptr; const char *file; int line; -} malloc_debug[100000]; +} malloc_table[SZ]; -/* Both register_ptr and unregister_ptr take O(n) operations to run, - which can be a real problem. It would be nice to use a hash table - for malloc_debug, but the functions in hash.c are not suitable - because they can call malloc() themselves. Maybe it would work if - the hash table were preallocated to a huge size, and if we set the - rehash threshold to 1.0. */ +/* Find PTR's position in malloc_table. If PTR is not found, return + the next available position. */ -/* Register PTR in malloc_debug. Abort if this is not possible +static inline int +ptr_position (const void *ptr) +{ + int i = hash_pointer (ptr) % SZ; + for (; malloc_table[i].ptr != NULL; i = (i + 1) % SZ) + if (malloc_table[i].ptr == ptr) + return i; + return i; +} + +/* Register PTR in malloc_table. Abort if this is not possible (presumably due to the number of current allocations exceeding the - size of malloc_debug.) */ + size of malloc_table.) */ static void -register_ptr (void *ptr, const char *file, int line) +register_ptr (const void *ptr, const char *file, int line) { int i; - for (i = 0; i < countof (malloc_debug); i++) - if (malloc_debug[i].ptr == NULL) - { - malloc_debug[i].ptr = ptr; - malloc_debug[i].file = file; - malloc_debug[i].line = line; - return; - } - abort (); + if (malloc_count - free_count > SZ) + { + fprintf (stderr, "Increase SZ to a larger value and recompile.\n"); + fflush (stderr); + abort (); + } + + i = ptr_position (ptr); + malloc_table[i].ptr = ptr; + malloc_table[i].file = file; + malloc_table[i].line = line; } -/* Unregister PTR from malloc_debug. Abort if PTR is not present in - malloc_debug. (This catches calling free() with a bogus pointer.) */ +/* Unregister PTR from malloc_table. Return false if PTR is not + present in malloc_table. */ -static void +static bool unregister_ptr (void *ptr) { - int i; - for (i = 0; i < countof (malloc_debug); i++) - if (malloc_debug[i].ptr == ptr) - { - malloc_debug[i].ptr = NULL; - return; - } - abort (); + int i = ptr_position (ptr); + if (malloc_table[i].ptr == NULL) + return false; + malloc_table[i].ptr = NULL; + + /* Relocate malloc_table entries immediately following PTR. */ + for (i = (i + 1) % SZ; malloc_table[i].ptr != NULL; i = (i + 1) % SZ) + { + const void *ptr2 = malloc_table[i].ptr; + /* Find the new location for the key. */ + int j = hash_pointer (ptr2) % SZ; + for (; malloc_table[j].ptr != NULL; j = (j + 1) % SZ) + if (ptr2 == malloc_table[j].ptr) + /* No need to relocate entry at [i]; it's already at or near + its hash position. */ + goto cont_outer; + malloc_table[j] = malloc_table[i]; + malloc_table[i].ptr = NULL; + cont_outer: + ; + } + return true; } -/* Print the malloc debug stats that can be gathered from the above - information. Currently this is the count of mallocs, frees, the - difference between the two, and the dump of the contents of - malloc_debug. The last part are the memory leaks. */ +/* Print the malloc debug stats gathered from the above information. + Currently this is the count of mallocs, frees, the difference + between the two, and the dump of the contents of malloc_table. The + last part are the memory leaks. */ void print_malloc_debug_stats (void) @@ -243,34 +292,34 @@ print_malloc_debug_stats (void) int i; printf ("\nMalloc: %d\nFree: %d\nBalance: %d\n\n", malloc_count, free_count, malloc_count - free_count); - for (i = 0; i < countof (malloc_debug); i++) - if (malloc_debug[i].ptr != NULL) - printf ("0x%08ld: %s:%d\n", (long)malloc_debug[i].ptr, - malloc_debug[i].file, malloc_debug[i].line); + for (i = 0; i < SZ; i++) + if (malloc_table[i].ptr != NULL) + printf ("0x%0*lx: %s:%d\n", PTR_FORMAT (malloc_table[i].ptr), + malloc_table[i].file, malloc_table[i].line); } void * -xmalloc_debug (size_t size, const char *source_file, int source_line) +debugging_malloc (size_t size, const char *source_file, int source_line) { - void *ptr = xmalloc_real (size); + void *ptr = checking_malloc (size); ++malloc_count; register_ptr (ptr, source_file, source_line); return ptr; } void * -xmalloc0_debug (size_t size, const char *source_file, int source_line) +debugging_malloc0 (size_t size, const char *source_file, int source_line) { - void *ptr = xmalloc0_real (size); + void *ptr = checking_malloc0 (size); ++malloc_count; register_ptr (ptr, source_file, source_line); return ptr; } void * -xrealloc_debug (void *ptr, size_t newsize, const char *source_file, int source_line) +debugging_realloc (void *ptr, size_t newsize, const char *source_file, int source_line) { - void *newptr = xrealloc_real (ptr, newsize); + void *newptr = checking_realloc (ptr, newsize); if (!ptr) { ++malloc_count; @@ -285,21 +334,35 @@ xrealloc_debug (void *ptr, size_t newsize, const char *source_file, int source_l } char * -xstrdup_debug (const char *s, const char *source_file, int source_line) +debugging_strdup (const char *s, const char *source_file, int source_line) { - char *copy = xstrdup_real (s); + char *copy = checking_strdup (s); ++malloc_count; register_ptr (copy, source_file, source_line); return copy; } void -xfree_debug (void *ptr, const char *source_file, int source_line) +debugging_free (void *ptr, const char *source_file, int source_line) { - assert (ptr != NULL); + /* See checking_free for rationale of this abort. We repeat it here + because we can print the file and the line where the offending + free occurred. */ + if (ptr == NULL) + { + fprintf (stderr, "%s: xfree(NULL) at %s:%d\n", + exec_name, source_file, source_line); + abort (); + } + if (!unregister_ptr (ptr)) + { + fprintf (stderr, "%s: bad xfree(0x%0*lx) at %s:%d\n", + exec_name, PTR_FORMAT (ptr), source_file, source_line); + abort (); + } ++free_count; - unregister_ptr (ptr); - free (ptr); + + checking_free (ptr); } #endif /* DEBUG_MALLOC */