Valgrind's Memcheck tool[1] is handy for finding bugs, but our use of a custom
allocator limits its ability to detect problems in unmodified PostgreSQL.
During the 9.1 beta cycle, I found some bugs[2] with a rough patch adding
instrumentation to aset.c and mcxt.c such that Memcheck understood our
allocator.  I've passed that patch around to a few people over time, and I've
now removed the roughness such that it's ready for upstream.  In hopes of
making things clearer in the commit history, I've split out a preliminary
refactoring patch from the main patch and attached each separately.

Besides the aset.c/mcxt.c instrumentation, this patch adds explicit checks for
undefined memory to PageAddItem() and printtup(); this has caught C-language
functions that fabricate a Datum without initializing all bits.  The inclusion
of all this is controlled by a pg_config_manual.h setting.  The patch also
adds a "suppression file" that directs Valgrind to silences nominal errors we
don't plan to fix.

To smoke-test the instrumentation, I used "make installcheck" runs on x86_64
GNU/Linux and ppc64 GNU/Linux.  This turned up various new and newly-detected
memory bugs, which I will discuss in a separate thread.  With those fixed,
"make installcheck" has a clean report (in my one particular configuration).
I designed the suppression file to work across platforms; I specifically
anticipated eventual use on x86_64 Darwin and x86_64 FreeBSD.  Valgrind 3.8.1
quickly crashed when running PostgreSQL on Darwin; I did not dig further.

Since aset.c and mcxt.c contain the hottest code paths in the backend, I
verified that a !USE_VALGRIND, non-assert build produces the same code before
and after the patch.  Testing that revealed the need to move up the
AllocSizeIsValid() check in repalloc(), though I don't understand why GCC
reacted that way.

Peter Geoghegan and Korry Douglas provided valuable feedback on earlier
versions of this code.


Possible future directions:

- Test "make installcheck-world".  When I last did this in past years, contrib 
did
  trigger some errors.

- Test recovery, such as by running a streaming replica under Memcheck while
  the primary runs "make installcheck-world".

- Test newer compilers and higher optimization levels.  I used GCC 4.2 at -O1.
  A brief look at -O2 results showed a new error that I have not studied.  GCC
  4.8 at -O3 might show still more due to increasingly-aggressive assumptions.

- A buildfarm member running its installcheck steps this way.

- Memcheck has support for detecting leaks.  I have not explored that side at
  all, always passing --leak-check=no.  We could add support for freeing
  "everything" at process exit, thereby making the leak detection meaningful.


Brief notes for folks reproducing my approach: I typically start the
Memcheck-hosted postgres like this:

  valgrind --leak-check=no --gen-suppressions=all \
    --suppressions=src/tools/valgrind.supp --time-stamp=yes \
    --log-file=$HOME/pg-valgrind/%p.log postgres

If that detected an error on which I desired more detail, I would rerun a
smaller test case with "--track-origins=yes --read-var-info=yes".  That slows
things noticeably but gives more-specific messaging.  When even that left the
situation unclear, I would temporarily hack allocChunkLimit so every palloc()
turned into a malloc().

I strongly advise installing the latest-available Valgrind, particularly
because recent releases suffer far less of a performance drop processing the
instrumentation added by this patch.  A "make installcheck" run takes 273
minutes under Vaglrind 3.6.0 but just 27 minutes under Valgrind 3.8.1.

Thanks,
nm

[1] http://valgrind.org/docs/manual/mc-manual.html
[2] 
http://www.postgresql.org/message-id/20110312133224.ga7...@tornado.gateway.2wire.net

-- 
Noah Misch
EnterpriseDB                                 http://www.enterprisedb.com
diff --git a/src/backend/utils/mmgr/aset.c b/src/backend/utils/mmgr/aset.c
index 6a111c7..de64377 100644
--- a/src/backend/utils/mmgr/aset.c
+++ b/src/backend/utils/mmgr/aset.c
@@ -308,6 +308,37 @@ AllocSetFreeIndex(Size size)
        return idx;
 }
 
+#ifdef CLOBBER_FREED_MEMORY
+
+/* Wipe freed memory for debugging purposes */
+static void
+wipe_mem(void *ptr, size_t size)
+{
+       memset(ptr, 0x7F, size);
+}
+#endif
+
+#ifdef MEMORY_CONTEXT_CHECKING
+static void
+set_sentinel(void *base, Size offset)
+{
+       char       *ptr = (char *) base + offset;
+
+       *ptr = 0x7E;
+}
+
+static bool
+sentinel_ok(const void *base, Size offset)
+{
+       const char *ptr = (const char *) base + offset;
+       bool            ret;
+
+       ret = *ptr == 0x7E;
+
+       return ret;
+}
+#endif
+
 #ifdef RANDOMIZE_ALLOCATED_MEMORY
 
 /*
@@ -492,8 +523,7 @@ AllocSetReset(MemoryContext context)
                        char       *datastart = ((char *) block) + 
ALLOC_BLOCKHDRSZ;
 
 #ifdef CLOBBER_FREED_MEMORY
-                       /* Wipe freed memory for debugging purposes */
-                       memset(datastart, 0x7F, block->freeptr - datastart);
+                       wipe_mem(datastart, block->freeptr - datastart);
 #endif
                        block->freeptr = datastart;
                        block->next = NULL;
@@ -502,8 +532,7 @@ AllocSetReset(MemoryContext context)
                {
                        /* Normal case, release the block */
 #ifdef CLOBBER_FREED_MEMORY
-                       /* Wipe freed memory for debugging purposes */
-                       memset(block, 0x7F, block->freeptr - ((char *) block));
+                       wipe_mem(block, block->freeptr - ((char *) block));
 #endif
                        free(block);
                }
@@ -545,8 +574,7 @@ AllocSetDelete(MemoryContext context)
                AllocBlock      next = block->next;
 
 #ifdef CLOBBER_FREED_MEMORY
-               /* Wipe freed memory for debugging purposes */
-               memset(block, 0x7F, block->freeptr - ((char *) block));
+               wipe_mem(block, block->freeptr - ((char *) block));
 #endif
                free(block);
                block = next;
@@ -598,7 +626,7 @@ AllocSetAlloc(MemoryContext context, Size size)
                chunk->requested_size = size;
                /* set mark to catch clobber of "unused" space */
                if (size < chunk_size)
-                       ((char *) AllocChunkGetPointer(chunk))[size] = 0x7E;
+                       set_sentinel(AllocChunkGetPointer(chunk), size);
 #endif
 #ifdef RANDOMIZE_ALLOCATED_MEMORY
                /* fill the allocated space with junk */
@@ -644,7 +672,7 @@ AllocSetAlloc(MemoryContext context, Size size)
                chunk->requested_size = size;
                /* set mark to catch clobber of "unused" space */
                if (size < chunk->size)
-                       ((char *) AllocChunkGetPointer(chunk))[size] = 0x7E;
+                       set_sentinel(AllocChunkGetPointer(chunk), size);
 #endif
 #ifdef RANDOMIZE_ALLOCATED_MEMORY
                /* fill the allocated space with junk */
@@ -801,7 +829,7 @@ AllocSetAlloc(MemoryContext context, Size size)
        chunk->requested_size = size;
        /* set mark to catch clobber of "unused" space */
        if (size < chunk->size)
-               ((char *) AllocChunkGetPointer(chunk))[size] = 0x7E;
+               set_sentinel(AllocChunkGetPointer(chunk), size);
 #endif
 #ifdef RANDOMIZE_ALLOCATED_MEMORY
        /* fill the allocated space with junk */
@@ -827,7 +855,7 @@ AllocSetFree(MemoryContext context, void *pointer)
 #ifdef MEMORY_CONTEXT_CHECKING
        /* Test for someone scribbling on unused space in chunk */
        if (chunk->requested_size < chunk->size)
-               if (((char *) pointer)[chunk->requested_size] != 0x7E)
+               if (!sentinel_ok(pointer, chunk->requested_size))
                        elog(WARNING, "detected write past chunk end in %s %p",
                                 set->header.name, chunk);
 #endif
@@ -860,8 +888,7 @@ AllocSetFree(MemoryContext context, void *pointer)
                else
                        prevblock->next = block->next;
 #ifdef CLOBBER_FREED_MEMORY
-               /* Wipe freed memory for debugging purposes */
-               memset(block, 0x7F, block->freeptr - ((char *) block));
+               wipe_mem(block, block->freeptr - ((char *) block));
 #endif
                free(block);
        }
@@ -873,8 +900,7 @@ AllocSetFree(MemoryContext context, void *pointer)
                chunk->aset = (void *) set->freelist[fidx];
 
 #ifdef CLOBBER_FREED_MEMORY
-               /* Wipe freed memory for debugging purposes */
-               memset(pointer, 0x7F, chunk->size);
+               wipe_mem(pointer, chunk->size);
 #endif
 
 #ifdef MEMORY_CONTEXT_CHECKING
@@ -901,7 +927,7 @@ AllocSetRealloc(MemoryContext context, void *pointer, Size 
size)
 #ifdef MEMORY_CONTEXT_CHECKING
        /* Test for someone scribbling on unused space in chunk */
        if (chunk->requested_size < oldsize)
-               if (((char *) pointer)[chunk->requested_size] != 0x7E)
+               if (!sentinel_ok(pointer, chunk->requested_size))
                        elog(WARNING, "detected write past chunk end in %s %p",
                                 set->header.name, chunk);
 #endif
@@ -924,7 +950,7 @@ AllocSetRealloc(MemoryContext context, void *pointer, Size 
size)
                chunk->requested_size = size;
                /* set mark to catch clobber of "unused" space */
                if (size < oldsize)
-                       ((char *) pointer)[size] = 0x7E;
+                       set_sentinel(pointer, size);
 #endif
                return pointer;
        }
@@ -987,7 +1013,7 @@ AllocSetRealloc(MemoryContext context, void *pointer, Size 
size)
                chunk->requested_size = size;
                /* set mark to catch clobber of "unused" space */
                if (size < chunk->size)
-                       ((char *) AllocChunkGetPointer(chunk))[size] = 0x7E;
+                       set_sentinel(AllocChunkGetPointer(chunk), size);
 #endif
 
                return AllocChunkGetPointer(chunk);
@@ -1136,11 +1162,9 @@ AllocSetCheck(MemoryContext context)
                        AllocChunk      chunk = (AllocChunk) bpoz;
                        Size            chsize,
                                                dsize;
-                       char       *chdata_end;
 
                        chsize = chunk->size;           /* aligned chunk size */
                        dsize = chunk->requested_size;          /* real data */
-                       chdata_end = ((char *) chunk) + (ALLOC_CHUNKHDRSZ + 
dsize);
 
                        /*
                         * Check chunk size
@@ -1170,7 +1194,8 @@ AllocSetCheck(MemoryContext context)
                        /*
                         * Check for overwrite of "unallocated" space in chunk
                         */
-                       if (dsize > 0 && dsize < chsize && *chdata_end != 0x7E)
+                       if (dsize > 0 && dsize < chsize &&
+                               !sentinel_ok(chunk, ALLOC_CHUNKHDRSZ + dsize))
                                elog(WARNING, "problem in alloc set %s: 
detected write past chunk end in block %p, chunk %p",
                                         name, block, chunk);
 
diff --git a/src/backend/utils/mmgr/mcxt.c b/src/backend/utils/mmgr/mcxt.c
index 8dd3cf4..c72e347 100644
--- a/src/backend/utils/mmgr/mcxt.c
+++ b/src/backend/utils/mmgr/mcxt.c
@@ -569,6 +569,8 @@ MemoryContextCreate(NodeTag tag, Size size,
 void *
 MemoryContextAlloc(MemoryContext context, Size size)
 {
+       void       *ret;
+
        AssertArg(MemoryContextIsValid(context));
 
        if (!AllocSizeIsValid(size))
@@ -577,7 +579,9 @@ MemoryContextAlloc(MemoryContext context, Size size)
 
        context->isReset = false;
 
-       return (*context->methods->alloc) (context, size);
+       ret = (*context->methods->alloc) (context, size);
+
+       return ret;
 }
 
 /*
@@ -638,6 +642,8 @@ void *
 palloc(Size size)
 {
        /* duplicates MemoryContextAlloc to avoid increased overhead */
+       void       *ret;
+
        AssertArg(MemoryContextIsValid(CurrentMemoryContext));
 
        if (!AllocSizeIsValid(size))
@@ -646,7 +652,9 @@ palloc(Size size)
 
        CurrentMemoryContext->isReset = false;
 
-       return (*CurrentMemoryContext->methods->alloc) (CurrentMemoryContext, 
size);
+       ret = (*CurrentMemoryContext->methods->alloc) (CurrentMemoryContext, 
size);
+
+       return ret;
 }
 
 void *
@@ -677,7 +685,7 @@ palloc0(Size size)
 void
 pfree(void *pointer)
 {
-       StandardChunkHeader *header;
+       MemoryContext context;
 
        /*
         * Try to detect bogus pointers handed to us, poorly though we can.
@@ -690,12 +698,12 @@ pfree(void *pointer)
        /*
         * OK, it's probably safe to look at the chunk header.
         */
-       header = (StandardChunkHeader *)
-               ((char *) pointer - STANDARDCHUNKHEADERSIZE);
+       context = ((StandardChunkHeader *)
+                          ((char *) pointer - 
STANDARDCHUNKHEADERSIZE))->context;
 
-       AssertArg(MemoryContextIsValid(header->context));
+       AssertArg(MemoryContextIsValid(context));
 
-       (*header->context->methods->free_p) (header->context, pointer);
+       (*context->methods->free_p) (context, pointer);
 }
 
 /*
@@ -705,7 +713,12 @@ pfree(void *pointer)
 void *
 repalloc(void *pointer, Size size)
 {
-       StandardChunkHeader *header;
+       MemoryContext context;
+       void       *ret;
+
+       if (!AllocSizeIsValid(size))
+               elog(ERROR, "invalid memory alloc request size %lu",
+                        (unsigned long) size);
 
        /*
         * Try to detect bogus pointers handed to us, poorly though we can.
@@ -718,20 +731,17 @@ repalloc(void *pointer, Size size)
        /*
         * OK, it's probably safe to look at the chunk header.
         */
-       header = (StandardChunkHeader *)
-               ((char *) pointer - STANDARDCHUNKHEADERSIZE);
+       context = ((StandardChunkHeader *)
+                          ((char *) pointer - 
STANDARDCHUNKHEADERSIZE))->context;
 
-       AssertArg(MemoryContextIsValid(header->context));
-
-       if (!AllocSizeIsValid(size))
-               elog(ERROR, "invalid memory alloc request size %lu",
-                        (unsigned long) size);
+       AssertArg(MemoryContextIsValid(context));
 
        /* isReset must be false already */
-       Assert(!header->context->isReset);
+       Assert(!context->isReset);
+
+       ret = (*context->methods->realloc) (context, pointer, size);
 
-       return (*header->context->methods->realloc) (header->context,
-                                                                               
                 pointer, size);
+       return ret;
 }
 
 /*
*** a/src/backend/access/common/printtup.c
--- b/src/backend/access/common/printtup.c
***************
*** 20,25 ****
--- 20,26 ----
  #include "libpq/pqformat.h"
  #include "tcop/pquery.h"
  #include "utils/lsyscache.h"
+ #include "utils/memdebug.h"
  
  
  static void printtup_startup(DestReceiver *self, int operation,
***************
*** 324,332 **** printtup(TupleTableSlot *slot, DestReceiver *self)
--- 325,350 ----
                /*
                 * If we have a toasted datum, forcibly detoast it here to avoid
                 * memory leakage inside the type's output routine.
+                *
+                * Here we catch undefined bytes in tuples that are returned to 
the
+                * client without hitting disk; see comments at the related 
check in
+                * PageAddItem().  Whether to test before or after detoast is 
somewhat
+                * arbitrary, as is whether to test external/compressed data at 
all.
+                * Undefined bytes in the pre-toast datum will have triggered 
Valgrind
+                * errors in the compressor or toaster; any error detected here 
for
+                * such datums would indicate an (unlikely) bug in a 
type-independent
+                * facility.  Therefore, this test is most useful for 
uncompressed,
+                * non-external datums.
+                *
+                * We don't presently bother checking non-varlena datums for 
undefined
+                * data.  PageAddItem() does check them.
                 */
                if (thisState->typisvarlena)
+               {
+                       VALGRIND_CHECK_MEM_IS_DEFINED(origattr, 
VARSIZE_ANY(origattr));
+ 
                        attr = PointerGetDatum(PG_DETOAST_DATUM(origattr));
+               }
                else
                        attr = origattr;
  
*** a/src/backend/storage/page/bufpage.c
--- b/src/backend/storage/page/bufpage.c
***************
*** 17,22 ****
--- 17,23 ----
  #include "access/htup_details.h"
  #include "access/xlog.h"
  #include "storage/checksum.h"
+ #include "utils/memdebug.h"
  
  bool          ignore_checksum_failure = false;
  
***************
*** 298,303 **** PageAddItem(Page page,
--- 299,318 ----
        /* set the item pointer */
        ItemIdSetNormal(itemId, upper, size);
  
+       /*
+        * Items normally contain no uninitialized bytes.  Core bufpage 
consumers
+        * conform, but this is not a necessary coding rule; a new index AM 
could
+        * opt to depart from it.  However, data type input functions and other
+        * C-language functions that synthesize datums should initialize all
+        * bytes; datumIsEqual() relies on this.  Testing here, along with the
+        * similar check in printtup(), helps to catch such mistakes.
+        *
+        * Values of the "name" type retrieved via index-only scans may contain
+        * uninitialized bytes; see comment in btrescan().  Valgrind will report
+        * this as an error, but it is safe to ignore.
+        */
+       VALGRIND_CHECK_MEM_IS_DEFINED(item, size);
+ 
        /* copy the item's data onto the page */
        memcpy((char *) page + upper, item, size);
  
*** a/src/backend/tcop/postgres.c
--- b/src/backend/tcop/postgres.c
***************
*** 69,74 ****
--- 69,75 ----
  #include "tcop/tcopprot.h"
  #include "tcop/utility.h"
  #include "utils/lsyscache.h"
+ #include "utils/memdebug.h"
  #include "utils/memutils.h"
  #include "utils/ps_status.h"
  #include "utils/snapmgr.h"
***************
*** 846,851 **** exec_simple_query(const char *query_string)
--- 847,856 ----
  
        TRACE_POSTGRESQL_QUERY_START(query_string);
  
+ #ifdef USE_VALGRIND
+       VALGRIND_PRINTF("statement: %s\n", query_string);
+ #endif
+ 
        /*
         * We use save_log_statement_stats so ShowUsage doesn't report incorrect
         * results because ResetUsage wasn't called.
*** a/src/backend/utils/mmgr/aset.c
--- b/src/backend/utils/mmgr/aset.c
***************
*** 59,69 ****
--- 59,92 ----
   *    the requested space whenever the request is less than the actual chunk
   *    size, and verifies that the byte is undamaged when the chunk is freed.
   *
+  *
+  *    About USE_VALGRIND and Valgrind client requests:
+  *
+  *    Valgrind provides "client request" macros that exchange information with
+  *    the host Valgrind (if any).  Under !USE_VALGRIND, memdebug.h stubs out
+  *    currently-used macros.
+  *
+  *    When running under Valgrind, we want a NOACCESS memory region both 
before
+  *    and after the allocation.  The chunk header is tempting as the preceding
+  *    region, but mcxt.c expects to able to examine the standard chunk header
+  *    fields.  Therefore, we use, when available, the requested_size field and
+  *    any subsequent padding.  requested_size is made NOACCESS before 
returning
+  *    a chunk pointer to a caller.  However, to reduce client request traffic,
+  *    it is kept DEFINED in chunks on the free list.
+  *
+  *    The rounded-up capacity of the chunk usually acts as a post-allocation
+  *    NOACCESS region.  If the request consumes precisely the entire chunk,
+  *    there is no such region; another chunk header may immediately follow.  
In
+  *    that case, Valgrind will not detect access beyond the end of the chunk.
+  *
+  *    See also the cooperating Valgrind client requests in mcxt.c.
+  *
   *-------------------------------------------------------------------------
   */
  
  #include "postgres.h"
  
+ #include "utils/memdebug.h"
  #include "utils/memutils.h"
  
  /* Define this to detail debug alloc information */
***************
*** 116,121 ****
--- 139,157 ----
  #define ALLOC_BLOCKHDRSZ      MAXALIGN(sizeof(AllocBlockData))
  #define ALLOC_CHUNKHDRSZ      MAXALIGN(sizeof(AllocChunkData))
  
+ /* Portion of ALLOC_CHUNKHDRSZ examined outside aset.c. */
+ #define ALLOC_CHUNK_PUBLIC    \
+       (offsetof(AllocChunkData, size) + sizeof(Size))
+ 
+ /* Portion of ALLOC_CHUNKHDRSZ excluding trailing padding. */
+ #ifdef MEMORY_CONTEXT_CHECKING
+ #define ALLOC_CHUNK_USED      \
+       (offsetof(AllocChunkData, requested_size) + sizeof(Size))
+ #else
+ #define ALLOC_CHUNK_USED      \
+       (offsetof(AllocChunkData, size) + sizeof(Size))
+ #endif
+ 
  typedef struct AllocBlockData *AllocBlock;            /* forward reference */
  typedef struct AllocChunkData *AllocChunk;
  
***************
*** 314,320 **** AllocSetFreeIndex(Size size)
--- 350,358 ----
  static void
  wipe_mem(void *ptr, size_t size)
  {
+       VALGRIND_MAKE_MEM_UNDEFINED(ptr, size);
        memset(ptr, 0x7F, size);
+       VALGRIND_MAKE_MEM_NOACCESS(ptr, size);
  }
  #endif
  
***************
*** 324,330 **** set_sentinel(void *base, Size offset)
--- 362,370 ----
  {
        char       *ptr = (char *) base + offset;
  
+       VALGRIND_MAKE_MEM_UNDEFINED(ptr, 1);
        *ptr = 0x7E;
+       VALGRIND_MAKE_MEM_NOACCESS(ptr, 1);
  }
  
  static bool
***************
*** 333,339 **** sentinel_ok(const void *base, Size offset)
--- 373,381 ----
        const char *ptr = (const char *) base + offset;
        bool            ret;
  
+       VALGRIND_MAKE_MEM_DEFINED(ptr, 1);
        ret = *ptr == 0x7E;
+       VALGRIND_MAKE_MEM_NOACCESS(ptr, 1);
  
        return ret;
  }
***************
*** 346,365 **** sentinel_ok(const void *base, Size offset)
   * very random, just a repeating sequence with a length that's prime.  What
   * we mainly want out of it is to have a good probability that two palloc's
   * of the same number of bytes start out containing different data.
   */
  static void
  randomize_mem(char *ptr, size_t size)
  {
        static int      save_ctr = 1;
        int                     ctr;
  
        ctr = save_ctr;
!       while (size-- > 0)
        {
                *ptr++ = ctr;
                if (++ctr > 251)
                        ctr = 1;
        }
        save_ctr = ctr;
  }
  #endif   /* RANDOMIZE_ALLOCATED_MEMORY */
--- 388,416 ----
   * very random, just a repeating sequence with a length that's prime.  What
   * we mainly want out of it is to have a good probability that two palloc's
   * of the same number of bytes start out containing different data.
+  *
+  * The region may be NOACCESS, so make it UNDEFINED first to avoid errors as
+  * we fill it.  Filling the region makes it DEFINED, so make it UNDEFINED
+  * again afterward.  Whether to finally make it UNDEFINED or NOACCESS is
+  * fairly arbitrary.  UNDEFINED is more convenient for AllocSetRealloc(), and
+  * other callers have no preference.
   */
  static void
  randomize_mem(char *ptr, size_t size)
  {
        static int      save_ctr = 1;
+       size_t          remaining = size;
        int                     ctr;
  
        ctr = save_ctr;
!       VALGRIND_MAKE_MEM_UNDEFINED(ptr, size);
!       while (remaining-- > 0)
        {
                *ptr++ = ctr;
                if (++ctr > 251)
                        ctr = 1;
        }
+       VALGRIND_MAKE_MEM_UNDEFINED(ptr - size, size);
        save_ctr = ctr;
  }
  #endif   /* RANDOMIZE_ALLOCATED_MEMORY */
***************
*** 455,460 **** AllocSetContextCreate(MemoryContext parent,
--- 506,515 ----
                context->blocks = block;
                /* Mark block as not to be released at reset time */
                context->keeper = block;
+ 
+               /* Mark unallocated space NOACCESS; leave the block header 
alone. */
+               VALGRIND_MAKE_MEM_NOACCESS(block->freeptr,
+                                                                  blksize - 
ALLOC_BLOCKHDRSZ);
        }
  
        return (MemoryContext) context;
***************
*** 524,529 **** AllocSetReset(MemoryContext context)
--- 579,587 ----
  
  #ifdef CLOBBER_FREED_MEMORY
                        wipe_mem(datastart, block->freeptr - datastart);
+ #else
+                       /* wipe_mem() would have done this */
+                       VALGRIND_MAKE_MEM_NOACCESS(datastart, block->freeptr - 
datastart);
  #endif
                        block->freeptr = datastart;
                        block->next = NULL;
***************
*** 623,628 **** AllocSetAlloc(MemoryContext context, Size size)
--- 681,687 ----
                chunk->aset = set;
                chunk->size = chunk_size;
  #ifdef MEMORY_CONTEXT_CHECKING
+               /* Valgrind: Will be made NOACCESS below. */
                chunk->requested_size = size;
                /* set mark to catch clobber of "unused" space */
                if (size < chunk_size)
***************
*** 649,654 **** AllocSetAlloc(MemoryContext context, Size size)
--- 708,723 ----
                }
  
                AllocAllocInfo(set, chunk);
+ 
+               /*
+                * Chunk header public fields remain DEFINED.  The requested
+                * allocation itself can be NOACCESS or UNDEFINED; our caller 
will
+                * soon make it UNDEFINED.  Make extra space at the end of the 
chunk,
+                * if any, NOACCESS.
+                */
+               VALGRIND_MAKE_MEM_NOACCESS((char *) chunk + ALLOC_CHUNK_PUBLIC,
+                                                chunk_size + ALLOC_CHUNKHDRSZ 
- ALLOC_CHUNK_PUBLIC);
+ 
                return AllocChunkGetPointer(chunk);
        }
  
***************
*** 669,675 **** AllocSetAlloc(MemoryContext context, Size size)
--- 738,747 ----
                chunk->aset = (void *) set;
  
  #ifdef MEMORY_CONTEXT_CHECKING
+               /* Valgrind: Free list requested_size should be DEFINED. */
                chunk->requested_size = size;
+               VALGRIND_MAKE_MEM_NOACCESS(&chunk->requested_size,
+                                                                  
sizeof(chunk->requested_size));
                /* set mark to catch clobber of "unused" space */
                if (size < chunk->size)
                        set_sentinel(AllocChunkGetPointer(chunk), size);
***************
*** 730,735 **** AllocSetAlloc(MemoryContext context, Size size)
--- 802,810 ----
  
                                chunk = (AllocChunk) (block->freeptr);
  
+                               /* Prepare to initialize the chunk header. */
+                               VALGRIND_MAKE_MEM_UNDEFINED(chunk, 
ALLOC_CHUNK_USED);
+ 
                                block->freeptr += (availchunk + 
ALLOC_CHUNKHDRSZ);
                                availspace -= (availchunk + ALLOC_CHUNKHDRSZ);
  
***************
*** 811,816 **** AllocSetAlloc(MemoryContext context, Size size)
--- 886,895 ----
                if (set->keeper == NULL && blksize == set->initBlockSize)
                        set->keeper = block;
  
+               /* Mark unallocated space NOACCESS. */
+               VALGRIND_MAKE_MEM_NOACCESS(block->freeptr,
+                                                                  blksize - 
ALLOC_BLOCKHDRSZ);
+ 
                block->next = set->blocks;
                set->blocks = block;
        }
***************
*** 820,825 **** AllocSetAlloc(MemoryContext context, Size size)
--- 899,907 ----
         */
        chunk = (AllocChunk) (block->freeptr);
  
+       /* Prepare to initialize the chunk header. */
+       VALGRIND_MAKE_MEM_UNDEFINED(chunk, ALLOC_CHUNK_USED);
+ 
        block->freeptr += (chunk_size + ALLOC_CHUNKHDRSZ);
        Assert(block->freeptr <= block->endptr);
  
***************
*** 827,832 **** AllocSetAlloc(MemoryContext context, Size size)
--- 909,916 ----
        chunk->size = chunk_size;
  #ifdef MEMORY_CONTEXT_CHECKING
        chunk->requested_size = size;
+       VALGRIND_MAKE_MEM_NOACCESS(&chunk->requested_size,
+                                                          
sizeof(chunk->requested_size));
        /* set mark to catch clobber of "unused" space */
        if (size < chunk->size)
                set_sentinel(AllocChunkGetPointer(chunk), size);
***************
*** 853,858 **** AllocSetFree(MemoryContext context, void *pointer)
--- 937,944 ----
        AllocFreeInfo(set, chunk);
  
  #ifdef MEMORY_CONTEXT_CHECKING
+       VALGRIND_MAKE_MEM_DEFINED(&chunk->requested_size,
+                                                         
sizeof(chunk->requested_size));
        /* Test for someone scribbling on unused space in chunk */
        if (chunk->requested_size < chunk->size)
                if (!sentinel_ok(pointer, chunk->requested_size))
***************
*** 916,921 **** AllocSetFree(MemoryContext context, void *pointer)
--- 1002,1012 ----
   *            Returns new pointer to allocated memory of given size; this 
memory
   *            is added to the set.  Memory associated with given pointer is 
copied
   *            into the new memory, and the old memory is freed.
+  *
+  * Without MEMORY_CONTEXT_CHECKING, we don't know the old request size.  This
+  * makes our Valgrind client requests less-precise, hazarding false negatives.
+  * (In principle, we could use VALGRIND_GET_VBITS() to rediscover the old
+  * request size.)
   */
  static void *
  AllocSetRealloc(MemoryContext context, void *pointer, Size size)
***************
*** 925,930 **** AllocSetRealloc(MemoryContext context, void *pointer, Size 
size)
--- 1016,1023 ----
        Size            oldsize = chunk->size;
  
  #ifdef MEMORY_CONTEXT_CHECKING
+       VALGRIND_MAKE_MEM_DEFINED(&chunk->requested_size,
+                                                         
sizeof(chunk->requested_size));
        /* Test for someone scribbling on unused space in chunk */
        if (chunk->requested_size < oldsize)
                if (!sentinel_ok(pointer, chunk->requested_size))
***************
*** 940,957 **** AllocSetRealloc(MemoryContext context, void *pointer, Size 
size)
        if (oldsize >= size)
        {
  #ifdef MEMORY_CONTEXT_CHECKING
  #ifdef RANDOMIZE_ALLOCATED_MEMORY
                /* We can only fill the extra space if we know the prior 
request */
!               if (size > chunk->requested_size)
!                       randomize_mem((char *) AllocChunkGetPointer(chunk) + 
chunk->requested_size,
!                                                 size - chunk->requested_size);
  #endif
  
                chunk->requested_size = size;
                /* set mark to catch clobber of "unused" space */
                if (size < oldsize)
                        set_sentinel(pointer, size);
  #endif
                return pointer;
        }
  
--- 1033,1076 ----
        if (oldsize >= size)
        {
  #ifdef MEMORY_CONTEXT_CHECKING
+               Size            oldrequest = chunk->requested_size;
+ 
  #ifdef RANDOMIZE_ALLOCATED_MEMORY
                /* We can only fill the extra space if we know the prior 
request */
!               if (size > oldrequest)
!                       randomize_mem((char *) pointer + oldrequest,
!                                                 size - oldrequest);
  #endif
  
                chunk->requested_size = size;
+               VALGRIND_MAKE_MEM_NOACCESS(&chunk->requested_size,
+                                                                  
sizeof(chunk->requested_size));
+ 
+               /*
+                * If this is an increase, mark any newly-available part 
UNDEFINED.
+                * Otherwise, mark the obsolete part NOACCESS.
+                */
+               if (size > oldrequest)
+                       VALGRIND_MAKE_MEM_UNDEFINED((char *) pointer + 
oldrequest,
+                                                                               
size - oldrequest);
+               else
+                       VALGRIND_MAKE_MEM_NOACCESS((char *) pointer + size,
+                                                                          
oldsize - size);
+ 
                /* set mark to catch clobber of "unused" space */
                if (size < oldsize)
                        set_sentinel(pointer, size);
+ #else                                                 /* 
!MEMORY_CONTEXT_CHECKING */
+ 
+               /*
+                * We don't have the information to determine whether we're 
growing
+                * the old request or shrinking it, so we conservatively mark 
the
+                * entire new allocation DEFINED.
+                */
+               VALGRIND_MAKE_MEM_NOACCESS(pointer, oldsize);
+               VALGRIND_MAKE_MEM_DEFINED(pointer, size);
  #endif
+ 
                return pointer;
        }
  
***************
*** 997,1002 **** AllocSetRealloc(MemoryContext context, void *pointer, Size 
size)
--- 1116,1122 ----
  
                /* Update pointers since block has likely been moved */
                chunk = (AllocChunk) (((char *) block) + ALLOC_BLOCKHDRSZ);
+               pointer = AllocChunkGetPointer(chunk);
                if (prevblock == NULL)
                        set->blocks = block;
                else
***************
*** 1006,1021 **** AllocSetRealloc(MemoryContext context, void *pointer, Size 
size)
  #ifdef MEMORY_CONTEXT_CHECKING
  #ifdef RANDOMIZE_ALLOCATED_MEMORY
                /* We can only fill the extra space if we know the prior 
request */
!               randomize_mem((char *) AllocChunkGetPointer(chunk) + 
chunk->requested_size,
                                          size - chunk->requested_size);
  #endif
  
                chunk->requested_size = size;
                /* set mark to catch clobber of "unused" space */
                if (size < chunk->size)
                        set_sentinel(AllocChunkGetPointer(chunk), size);
  #endif
  
                return AllocChunkGetPointer(chunk);
        }
        else
--- 1126,1162 ----
  #ifdef MEMORY_CONTEXT_CHECKING
  #ifdef RANDOMIZE_ALLOCATED_MEMORY
                /* We can only fill the extra space if we know the prior 
request */
!               randomize_mem((char *) pointer + chunk->requested_size,
                                          size - chunk->requested_size);
  #endif
  
+               /*
+                * realloc() (or randomize_mem()) will have left the 
newly-allocated
+                * part UNDEFINED, but we may need to adjust trailing bytes 
from the
+                * old allocation.
+                */
+               VALGRIND_MAKE_MEM_UNDEFINED((char *) pointer + 
chunk->requested_size,
+                                                                       oldsize 
- chunk->requested_size);
+ 
                chunk->requested_size = size;
+               VALGRIND_MAKE_MEM_NOACCESS(&chunk->requested_size,
+                                                                  
sizeof(chunk->requested_size));
+ 
                /* set mark to catch clobber of "unused" space */
                if (size < chunk->size)
                        set_sentinel(AllocChunkGetPointer(chunk), size);
+ #else                                                 /* 
!MEMORY_CONTEXT_CHECKING */
+ 
+               /*
+                * We don't know how much of the old chunk size was the actual
+                * allocation; it could have been as small as one byte.  We 
have to be
+                * conservative and just mark the entire old portion DEFINED.
+                */
+               VALGRIND_MAKE_MEM_DEFINED(pointer, oldsize);
  #endif
  
+               /* Make any trailing alignment padding NOACCESS. */
+               VALGRIND_MAKE_MEM_NOACCESS((char *) pointer + size, chksize - 
size);
                return AllocChunkGetPointer(chunk);
        }
        else
***************
*** 1036,1041 **** AllocSetRealloc(MemoryContext context, void *pointer, Size 
size)
--- 1177,1196 ----
                /* allocate new chunk */
                newPointer = AllocSetAlloc((MemoryContext) set, size);
  
+               /*
+                * AllocSetAlloc() just made the region NOACCESS.  Change it to
+                * UNDEFINED for the moment; memcpy() will then transfer 
definedness
+                * from the old allocation to the new.  If we know the old 
allocation,
+                * copy just that much.  Otherwise, make the entire old chunk 
defined
+                * to avoid errors as we copy the currently-NOACCESS trailing 
bytes.
+                */
+               VALGRIND_MAKE_MEM_UNDEFINED(newPointer, size);
+ #ifdef MEMORY_CONTEXT_CHECKING
+               oldsize = chunk->requested_size;
+ #else
+               VALGRIND_MAKE_MEM_DEFINED(pointer, oldsize);
+ #endif
+ 
                /* transfer existing data (certain to fit) */
                memcpy(newPointer, pointer, oldsize);
  
***************
*** 1164,1170 **** AllocSetCheck(MemoryContext context)
--- 1319,1330 ----
                                                dsize;
  
                        chsize = chunk->size;           /* aligned chunk size */
+                       VALGRIND_MAKE_MEM_DEFINED(&chunk->requested_size,
+                                                                         
sizeof(chunk->requested_size));
                        dsize = chunk->requested_size;          /* real data */
+                       if (dsize > 0)          /* not on a free list */
+                               
VALGRIND_MAKE_MEM_NOACCESS(&chunk->requested_size,
+                                                                               
   sizeof(chunk->requested_size));
  
                        /*
                         * Check chunk size
*** a/src/backend/utils/mmgr/mcxt.c
--- b/src/backend/utils/mmgr/mcxt.c
***************
*** 24,29 ****
--- 24,30 ----
  
  #include "postgres.h"
  
+ #include "utils/memdebug.h"
  #include "utils/memutils.h"
  
  
***************
*** 135,140 **** MemoryContextReset(MemoryContext context)
--- 136,143 ----
        {
                (*context->methods->reset) (context);
                context->isReset = true;
+               VALGRIND_DESTROY_MEMPOOL(context);
+               VALGRIND_CREATE_MEMPOOL(context, 0, false);
        }
  }
  
***************
*** 184,189 **** MemoryContextDelete(MemoryContext context)
--- 187,193 ----
        MemoryContextSetParent(context, NULL);
  
        (*context->methods->delete_context) (context);
+       VALGRIND_DESTROY_MEMPOOL(context);
        pfree(context);
  }
  
***************
*** 555,560 **** MemoryContextCreate(NodeTag tag, Size size,
--- 559,566 ----
                parent->firstchild = node;
        }
  
+       VALGRIND_CREATE_MEMPOOL(node, 0, false);
+ 
        /* Return to type-specific creation routine to finish up */
        return node;
  }
***************
*** 580,585 **** MemoryContextAlloc(MemoryContext context, Size size)
--- 586,592 ----
        context->isReset = false;
  
        ret = (*context->methods->alloc) (context, size);
+       VALGRIND_MEMPOOL_ALLOC(context, ret, size);
  
        return ret;
  }
***************
*** 605,610 **** MemoryContextAllocZero(MemoryContext context, Size size)
--- 612,618 ----
        context->isReset = false;
  
        ret = (*context->methods->alloc) (context, size);
+       VALGRIND_MEMPOOL_ALLOC(context, ret, size);
  
        MemSetAligned(ret, 0, size);
  
***************
*** 632,637 **** MemoryContextAllocZeroAligned(MemoryContext context, Size size)
--- 640,646 ----
        context->isReset = false;
  
        ret = (*context->methods->alloc) (context, size);
+       VALGRIND_MEMPOOL_ALLOC(context, ret, size);
  
        MemSetLoop(ret, 0, size);
  
***************
*** 653,658 **** palloc(Size size)
--- 662,668 ----
        CurrentMemoryContext->isReset = false;
  
        ret = (*CurrentMemoryContext->methods->alloc) (CurrentMemoryContext, 
size);
+       VALGRIND_MEMPOOL_ALLOC(CurrentMemoryContext, ret, size);
  
        return ret;
  }
***************
*** 672,677 **** palloc0(Size size)
--- 682,688 ----
        CurrentMemoryContext->isReset = false;
  
        ret = (*CurrentMemoryContext->methods->alloc) (CurrentMemoryContext, 
size);
+       VALGRIND_MEMPOOL_ALLOC(CurrentMemoryContext, ret, size);
  
        MemSetAligned(ret, 0, size);
  
***************
*** 704,709 **** pfree(void *pointer)
--- 715,721 ----
        AssertArg(MemoryContextIsValid(context));
  
        (*context->methods->free_p) (context, pointer);
+       VALGRIND_MEMPOOL_FREE(context, pointer);
  }
  
  /*
***************
*** 740,745 **** repalloc(void *pointer, Size size)
--- 752,758 ----
        Assert(!context->isReset);
  
        ret = (*context->methods->realloc) (context, pointer, size);
+       VALGRIND_MEMPOOL_CHANGE(context, pointer, ret, size);
  
        return ret;
  }
*** a/src/include/pg_config_manual.h
--- b/src/include/pg_config_manual.h
***************
*** 207,212 ****
--- 207,227 ----
   */
  
  /*
+  * Include Valgrind "client requests", mostly in the memory allocator, so
+  * Valgrind understands PostgreSQL memory contexts.  This permits detecting
+  * memory errors that Valgrind would not detect on a vanilla build.  See also
+  * src/tools/valgrind.supp.  "make installcheck" runs 20-30x longer under
+  * Valgrind.  Note that USE_VALGRIND slowed older versions of Valgrind by an
+  * additional order of magnitude; Valgrind 3.8.1 does not have this problem.
+  * The client requests fall in hot code paths, so USE_VALGRIND also slows
+  * native execution by a few percentage points.
+  *
+  * You should normally use MEMORY_CONTEXT_CHECKING with USE_VALGRIND;
+  * instrumentation of repalloc() is inferior without it.
+  */
+ /* #define USE_VALGRIND */
+ 
+ /*
   * Define this to cause pfree()'d memory to be cleared immediately, to
   * facilitate catching bugs that refer to already-freed values.
   * Right now, this gets defined automatically if --enable-cassert.
***************
*** 218,226 ****
  /*
   * Define this to check memory allocation errors (scribbling on more
   * bytes than were allocated).        Right now, this gets defined
!  * automatically if --enable-cassert.
   */
! #ifdef USE_ASSERT_CHECKING
  #define MEMORY_CONTEXT_CHECKING
  #endif
  
--- 233,241 ----
  /*
   * Define this to check memory allocation errors (scribbling on more
   * bytes than were allocated).        Right now, this gets defined
!  * automatically if --enable-cassert or USE_VALGRIND.
   */
! #if defined(USE_ASSERT_CHECKING) || defined(USE_VALGRIND)
  #define MEMORY_CONTEXT_CHECKING
  #endif
  
*** /dev/null
--- b/src/include/utils/memdebug.h
***************
*** 0 ****
--- 1,34 ----
+ /*-------------------------------------------------------------------------
+  *
+  * memdebug.h
+  *      Memory debugging support.
+  *
+  * Currently, this file either wraps <valgrind/memcheck.h> or substitutes
+  * empty definitions for Valgrind client request macros we use.
+  *
+  *
+  * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group
+  * Portions Copyright (c) 1994, Regents of the University of California
+  *
+  * src/include/utils/memdebug.h
+  *
+  *-------------------------------------------------------------------------
+  */
+ #ifndef MEMDEBUG_H
+ #define MEMDEBUG_H
+ 
+ #ifdef USE_VALGRIND
+ #include <valgrind/memcheck.h>
+ #else
+ #define VALGRIND_CHECK_MEM_IS_DEFINED(addr, size)                     do {} 
while (0)
+ #define VALGRIND_CREATE_MEMPOOL(context, redzones, zeroed)    do {} while (0)
+ #define VALGRIND_DESTROY_MEMPOOL(context)                                     
do {} while (0)
+ #define VALGRIND_MAKE_MEM_DEFINED(addr, size)                         do {} 
while (0)
+ #define VALGRIND_MAKE_MEM_NOACCESS(addr, size)                                
do {} while (0)
+ #define VALGRIND_MAKE_MEM_UNDEFINED(addr, size)                               
do {} while (0)
+ #define VALGRIND_MEMPOOL_ALLOC(context, addr, size)                   do {} 
while (0)
+ #define VALGRIND_MEMPOOL_FREE(context, addr)                          do {} 
while (0)
+ #define VALGRIND_MEMPOOL_CHANGE(context, optr, nptr, size)    do {} while (0)
+ #endif
+ 
+ #endif   /* MEMDEBUG_H */
*** /dev/null
--- b/src/tools/valgrind.supp
***************
*** 0 ****
--- 1,94 ----
+ # This is a suppression file for use with Valgrind tools.  File format
+ # documentation:
+ #     http://valgrind.org/docs/manual/mc-manual.html#mc-manual.suppfiles
+ 
+ # The libc symbol that implements a particular standard interface is
+ # implementation-dependent.  For example, strncpy() shows up as "__GI_strncpy"
+ # on some platforms.  Use wildcards to avoid mentioning such specific names.
+ 
+ 
+ # We have occasion to write raw binary structures to disk or to the network.
+ # These may contain uninitialized padding bytes.  Since recipients also ignore
+ # those bytes as padding, this is harmless.
+ 
+ {
+       padding_pgstat_send
+       Memcheck:Param
+       socketcall.send(msg)
+ 
+       fun:*send*
+       fun:pgstat_send
+ }
+ 
+ {
+       padding_pgstat_sendto
+       Memcheck:Param
+       socketcall.sendto(msg)
+ 
+       fun:*send*
+       fun:pgstat_send
+ }
+ 
+ {
+       padding_pgstat_write
+       Memcheck:Param
+       write(buf)
+ 
+       ...
+       fun:pgstat_write_statsfiles
+ }
+ 
+ {
+       padding_XLogRecData_CRC
+       Memcheck:Value8
+ 
+       fun:XLogInsert
+ }
+ 
+ {
+       padding_XLogRecData_write
+       Memcheck:Param
+       write(buf)
+ 
+     ...
+       fun:XLogWrite
+ }
+ 
+ {
+       padding_relcache
+       Memcheck:Param
+       write(buf)
+ 
+       ...
+       fun:write_relcache_init_file
+ }
+ 
+ 
+ # resolve_polymorphic_tupdesc(), a subroutine of internal_get_result_type(),
+ # can instigate a memcpy() call where the two pointer arguments are exactly
+ # equal.  The behavior thereof is formally undefined, but implementations
+ # where it's anything other than a no-op are thought unlikely.
+ {
+       noopmemcpy_internal_get_result_type
+       Memcheck:Overlap
+ 
+       fun:*strncpy*
+       fun:namestrcpy
+       fun:TupleDescInitEntry
+       ...
+       fun:internal_get_result_type
+ }
+ 
+ 
+ # gcc on ppc64 can generate a four-byte read to fetch the final "char" fields
+ # of a FormData_pg_cast.  This is valid compiler behavior, because a proper
+ # FormData_pg_cast has trailing padding.  Tuples we treat as structures omit
+ # that padding, so Valgrind reports an invalid read.  Practical trouble would
+ # entail the missing pad bytes falling in a different memory page.  So long as
+ # the structure is aligned, that will not happen.
+ {
+       overread_tuplestruct_pg_cast
+       Memcheck:Addr4
+ 
+       fun:IsBinaryCoercible
+ }
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to