]> sjero.net Git - wget/blobdiff - src/xmalloc.c
Merging to bring en@*.po back in (and removed from .hgignore).
[wget] / src / xmalloc.c
index 311506ec19faf68a72959624665670377d1cd1a1..ac80fb38c996e3e20990d4ca1e75a2f42907d27f 100644 (file)
@@ -1,12 +1,13 @@
 /* Wrappers around malloc and memory debugging support.
-   Copyright (C) 2003 Free Software Foundation, Inc.
+   Copyright (C) 2003, 2004, 2005, 2006, 2007 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 +15,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 <http://www.gnu.org/licenses/>.
 
 In addition, as a special exception, the Free Software Foundation
 gives permission to link the code of its release of Wget with the
@@ -27,25 +27,16 @@ modify this file, you may extend this exception to your version of the
 file, but you are not obligated to do so.  If you do not wish to do
 so, delete this exception statement from your version.  */
 
-#include <config.h>
+#include "wget.h"
 
 #include <stdio.h>
 #include <stdlib.h>
-#ifdef HAVE_STRING_H
-# include <string.h>
-#else  /* not HAVE_STRING_H */
-# include <strings.h>
-#endif /* not HAVE_STRING_H */
-#include <sys/types.h>
+#include <string.h>
 #include <errno.h>
 #include <assert.h>
 
-#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
@@ -66,10 +57,10 @@ 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);
+  log_set_save_context (false);
   logprintf (LOG_ALWAYS,
-            _("%s: %s: Failed to allocate %ld bytes; memory exhausted.\n"),
-            exec_name, context, attempted_size);
+             _("%s: %s: Failed to allocate %ld bytes; memory exhausted.\n"),
+             exec_name, context, attempted_size);
   exit (1);
 }
 
@@ -79,11 +70,11 @@ memfatal (const char *context, long attempted_size)
 
    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 xfree_real
+     #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
@@ -91,13 +82,14 @@ memfatal (const char *context, long attempted_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 xrealloc(a, b) xrealloc_debug (a, b, __FILE__, __LINE__)
-     #define xstrdup(a) xstrdup_debug (a, __FILE__, __LINE__)
-     #define xfree(a) xfree_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
@@ -106,7 +98,7 @@ memfatal (const char *context, long attempted_size)
 #endif
 
 STATIC_IF_DEBUG void *
-xmalloc_real (size_t size)
+checking_malloc (size_t size)
 {
   void *ptr = malloc (size);
   if (!ptr)
@@ -115,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
@@ -127,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;
 
@@ -144,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;
 
@@ -164,31 +156,31 @@ xstrdup_real (const char *s)
 }
 
 STATIC_IF_DEBUG void
-xfree_real (void *ptr)
+checking_free (void *ptr)
 {
   /* Wget's xfree() must not be passed a NULL pointer.  This is for
-     historical reasons: many pre-C89 systems were known to bomb at
-     free(NULL), and Wget was careful to use xfree_null when there is
+     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 that constraint.  Code that thinks it doesn't deal with
-     NULL, and it in fact does, aborts immediately.  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.
-
-     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.  */
+     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);
 }
-
-/* xfree_real is unnecessary because free doesn't require any special
-   functionality.  */
 \f
 #ifdef DEBUG_MALLOC
 
@@ -202,8 +194,10 @@ xfree_real (void *ptr)
    * 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
@@ -212,93 +206,120 @@ xfree_real (void *ptr)
 
 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];
+
+/* Find PTR's position in malloc_table.  If PTR is not found, return
+   the next available position.  */
 
-/* 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.  */
+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_debug.  Abort if this is not possible
+/* 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)
 {
   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);
+          malloc_count, free_count, malloc_count - free_count);
+  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;
@@ -313,29 +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)
 {
-  /* See xfree_real for rationale of this abort.  We repeat it here
+  /* 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 ("%s: xfree(NULL) at %s:%d\n",
-              exec_name, source_file, source_line);
+      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);
-  xfree_real (ptr);
+
+  checking_free (ptr);
 }
 
 #endif /* DEBUG_MALLOC */