/* there must not be any padding to reach a MAXALIGN boundary here! */
} AllocChunkData;
+/*
+ * Only the "aset" field should be accessed outside this module.
+ * We keep the rest of an allocated chunk's header marked NOACCESS when using
+ * valgrind. But note that chunk headers that are in a freelist are kept
+ * accessible, for simplicity.
+ */
+#define ALLOCCHUNK_PRIVATE_LEN offsetof(AllocChunkData, aset)
+
/*
* AllocPointerIsValid
* True iff pointer is valid allocation pointer.
* No request may exceed:
* MAXALIGN_DOWN(SIZE_MAX) - ALLOC_BLOCKHDRSZ - ALLOC_CHUNKHDRSZ
* All callers use a much-lower limit.
+ *
+ * Note: when using valgrind, it doesn't matter how the returned allocation
+ * is marked, as mcxt.c will set it to UNDEFINED. In some paths we will
+ * return space that is marked NOACCESS - AllocSetRealloc has to beware!
*/
static void *
AllocSetAlloc(MemoryContext context, Size size)
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)
AllocAllocInfo(set, chunk);
- /*
- * Chunk's metadata 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_CHUNKHDRSZ,
- chunk_size - ALLOC_CHUNKHDRSZ);
+ /* Ensure any padding bytes are marked NOACCESS. */
+ VALGRIND_MAKE_MEM_NOACCESS((char *) AllocChunkGetPointer(chunk) + size,
+ chunk_size - size);
+
+ /* Disallow external access to private part of chunk header. */
+ VALGRIND_MAKE_MEM_NOACCESS(chunk, ALLOCCHUNK_PRIVATE_LEN);
return AllocChunkGetPointer(chunk);
}
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);
#endif
AllocAllocInfo(set, chunk);
+
+ /* Ensure any padding bytes are marked NOACCESS. */
+ VALGRIND_MAKE_MEM_NOACCESS((char *) AllocChunkGetPointer(chunk) + size,
+ chunk->size - size);
+
+ /* Disallow external access to private part of chunk header. */
+ VALGRIND_MAKE_MEM_NOACCESS(chunk, ALLOCCHUNK_PRIVATE_LEN);
+
return AllocChunkGetPointer(chunk);
}
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);
#endif
AllocAllocInfo(set, chunk);
+
+ /* Ensure any padding bytes are marked NOACCESS. */
+ VALGRIND_MAKE_MEM_NOACCESS((char *) AllocChunkGetPointer(chunk) + size,
+ chunk_size - size);
+
+ /* Disallow external access to private part of chunk header. */
+ VALGRIND_MAKE_MEM_NOACCESS(chunk, ALLOCCHUNK_PRIVATE_LEN);
+
return AllocChunkGetPointer(chunk);
}
AllocSet set = (AllocSet) context;
AllocChunk chunk = AllocPointerGetChunk(pointer);
+ /* Allow access to private part of chunk header. */
+ VALGRIND_MAKE_MEM_DEFINED(chunk, ALLOCCHUNK_PRIVATE_LEN);
+
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))
{
AllocSet set = (AllocSet) context;
AllocChunk chunk = AllocPointerGetChunk(pointer);
- Size oldsize = chunk->size;
+ Size oldsize;
+
+ /* Allow access to private part of chunk header. */
+ VALGRIND_MAKE_MEM_DEFINED(chunk, ALLOCCHUNK_PRIVATE_LEN);
+
+ 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))
#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.
VALGRIND_MAKE_MEM_DEFINED(pointer, size);
#endif
+ /* Disallow external access to private part of chunk header. */
+ VALGRIND_MAKE_MEM_NOACCESS(chunk, ALLOCCHUNK_PRIVATE_LEN);
+
return pointer;
}
blksize = chksize + ALLOC_BLOCKHDRSZ + ALLOC_CHUNKHDRSZ;
block = (AllocBlock) realloc(block, blksize);
if (block == NULL)
+ {
+ /* Disallow external access to private part of chunk header. */
+ VALGRIND_MAKE_MEM_NOACCESS(chunk, ALLOCCHUNK_PRIVATE_LEN);
return NULL;
+ }
block->freeptr = block->endptr = ((char *) block) + blksize;
/* Update pointers since block has likely been moved */
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)
VALGRIND_MAKE_MEM_DEFINED(pointer, oldsize);
#endif
- /* Make any trailing alignment padding NOACCESS. */
+ /* Ensure any padding bytes are marked NOACCESS. */
VALGRIND_MAKE_MEM_NOACCESS((char *) pointer + size, chksize - size);
+ /* Disallow external access to private part of chunk header. */
+ VALGRIND_MAKE_MEM_NOACCESS(chunk, ALLOCCHUNK_PRIVATE_LEN);
+
return pointer;
}
else
/* leave immediately if request was not completed */
if (newPointer == NULL)
+ {
+ /* Disallow external access to private part of chunk header. */
+ VALGRIND_MAKE_MEM_NOACCESS(chunk, ALLOCCHUNK_PRIVATE_LEN);
return NULL;
+ }
/*
- * 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.
+ * AllocSetAlloc() may have returned a region that is still 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
AllocSetGetChunkSpace(MemoryContext context, void *pointer)
{
AllocChunk chunk = AllocPointerGetChunk(pointer);
+ Size result;
- return chunk->size + ALLOC_CHUNKHDRSZ;
+ VALGRIND_MAKE_MEM_DEFINED(chunk, ALLOCCHUNK_PRIVATE_LEN);
+ result = chunk->size + ALLOC_CHUNKHDRSZ;
+ VALGRIND_MAKE_MEM_NOACCESS(chunk, ALLOCCHUNK_PRIVATE_LEN);
+ return result;
}
/*
Size chsize,
dsize;
+ /* Allow access to private part of chunk header. */
+ VALGRIND_MAKE_MEM_DEFINED(chunk, ALLOCCHUNK_PRIVATE_LEN);
+
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
/*
* If chunk is allocated, check for correct aset pointer. (If it's
* free, the aset is the freelist pointer, which we can't check as
- * easily...)
+ * easily...) Note this is an incomplete test, since palloc(0)
+ * produces an allocated chunk with requested_size == 0.
*/
if (dsize > 0 && chunk->aset != (void *) set)
elog(WARNING, "problem in alloc set %s: bogus aset link in block %p, chunk %p",
name, block, chunk);
/*
- * Check for overwrite of "unallocated" space in chunk
+ * Check for overwrite of padding space in an allocated chunk.
*/
- if (dsize > 0 && dsize < chsize &&
+ if (chunk->aset == (void *) set && 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);
+ /*
+ * If chunk is allocated, disallow external access to private part
+ * of chunk header.
+ */
+ if (chunk->aset == (void *) set)
+ VALGRIND_MAKE_MEM_NOACCESS(chunk, ALLOCCHUNK_PRIVATE_LEN);
+
blk_data += chsize;
nchunks++;
MemoryContextData header; /* Standard memory-context fields */
/* Generational context parameters */
- Size blockSize; /* block size */
+ Size blockSize; /* standard block size */
GenerationBlock *block; /* current (most recently allocated) block */
dlist_head blocks; /* list of blocks */
struct GenerationBlock
{
dlist_node node; /* doubly-linked list of blocks */
+ Size blksize; /* allocated size of this block */
int nchunks; /* number of chunks in the block */
int nfree; /* number of free chunks */
char *freeptr; /* start of free space in this block */
#endif
GenerationBlock *block; /* block owning this chunk */
- GenerationContext *context; /* owning context */
+ GenerationContext *context; /* owning context, or NULL if freed chunk */
/* there must not be any padding to reach a MAXALIGN boundary here! */
};
+/*
+ * Only the "context" field should be accessed outside this module.
+ * We keep the rest of an allocated chunk's header marked NOACCESS when using
+ * valgrind. But note that freed chunk headers are kept accessible, for
+ * simplicity.
+ */
+#define GENERATIONCHUNK_PRIVATE_LEN offsetof(GenerationChunk, context)
/*
* GenerationIsValid
name);
set->blockSize = blockSize;
- set->block = NULL;
return (MemoryContext) set;
}
{
GenerationContext *set = (GenerationContext *) context;
+ set->block = NULL;
dlist_init(&set->blocks);
}
dlist_delete(miter.cur);
- /* Normal case, release the block */
#ifdef CLOBBER_FREED_MEMORY
- wipe_mem(block, set->blockSize);
+ wipe_mem(block, block->blksize);
#endif
free(block);
/*
* GenerationDelete
* Frees all memory which is allocated in the given set, in preparation
- * for deletion of the set. We simply call GenerationReset() which does all the
- * dirty work.
+ * for deletion of the set. We simply call GenerationReset() which does
+ * all the dirty work.
*/
static void
GenerationDelete(MemoryContext context)
{
- /* just reset (although not really necessary) */
+ /* just reset to release all the GenerationBlocks */
GenerationReset(context);
+ /* we are not responsible for deleting the context node itself */
}
/*
* No request may exceed:
* MAXALIGN_DOWN(SIZE_MAX) - Generation_BLOCKHDRSZ - Generation_CHUNKHDRSZ
* All callers use a much-lower limit.
+ *
+ * Note: when using valgrind, it doesn't matter how the returned allocation
+ * is marked, as mcxt.c will set it to UNDEFINED. In some paths we will
+ * return space that is marked NOACCESS - GenerationRealloc has to beware!
*/
static void *
GenerationAlloc(MemoryContext context, Size size)
return NULL;
/* block with a single (used) chunk */
+ block->blksize = blksize;
block->nchunks = 1;
block->nfree = 0;
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)
/* add the block to the list of allocated blocks */
dlist_push_head(&set->blocks, &block->node);
- /*
- * Chunk header 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 + Generation_CHUNKHDRSZ + size,
+ GenerationAllocInfo(set, chunk);
+
+ /* Ensure any padding bytes are marked NOACCESS. */
+ VALGRIND_MAKE_MEM_NOACCESS((char *) GenerationChunkGetPointer(chunk) + size,
chunk_size - size);
- GenerationAllocInfo(set, chunk);
+ /* Disallow external access to private part of chunk header. */
+ VALGRIND_MAKE_MEM_NOACCESS(chunk, GENERATIONCHUNK_PRIVATE_LEN);
+
return GenerationChunkGetPointer(chunk);
}
/*
- * Not an over-sized chunk. Is there enough space on the current block? If
+ * Not an over-sized chunk. Is there enough space in the current block? If
* not, allocate a new "regular" block.
*/
block = set->block;
if (block == NULL)
return NULL;
+ block->blksize = blksize;
block->nchunks = 0;
block->nfree = 0;
Assert(block->freeptr <= block->endptr);
chunk->block = block;
-
chunk->context = set;
chunk->size = chunk_size;
#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(GenerationChunkGetPointer(chunk), size);
#endif
GenerationAllocInfo(set, chunk);
+
+ /* Ensure any padding bytes are marked NOACCESS. */
+ VALGRIND_MAKE_MEM_NOACCESS((char *) GenerationChunkGetPointer(chunk) + size,
+ chunk_size - size);
+
+ /* Disallow external access to private part of chunk header. */
+ VALGRIND_MAKE_MEM_NOACCESS(chunk, GENERATIONCHUNK_PRIVATE_LEN);
+
return GenerationChunkGetPointer(chunk);
}
{
GenerationContext *set = (GenerationContext *) context;
GenerationChunk *chunk = GenerationPointerGetChunk(pointer);
- GenerationBlock *block = chunk->block;
+ GenerationBlock *block;
+
+ /* Allow access to private part of chunk header. */
+ VALGRIND_MAKE_MEM_DEFINED(chunk, GENERATIONCHUNK_PRIVATE_LEN);
+
+ block = chunk->block;
#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))
wipe_mem(pointer, chunk->size);
#endif
+ /* Reset context to NULL in freed chunks */
+ chunk->context = NULL;
+
#ifdef MEMORY_CONTEXT_CHECKING
- /* Reset requested_size to 0 in chunks that are on freelist */
+ /* Reset requested_size to 0 in freed chunks */
chunk->requested_size = 0;
#endif
Assert(block->nchunks > 0);
Assert(block->nfree <= block->nchunks);
- /* If there are still allocated chunks on the block, we're done. */
+ /* If there are still allocated chunks in the block, we're done. */
if (block->nfree < block->nchunks)
return;
GenerationContext *set = (GenerationContext *) context;
GenerationChunk *chunk = GenerationPointerGetChunk(pointer);
GenerationPointer newPointer;
- Size oldsize = chunk->size;
+ Size oldsize;
+
+ /* Allow access to private part of chunk header. */
+ VALGRIND_MAKE_MEM_DEFINED(chunk, GENERATIONCHUNK_PRIVATE_LEN);
+
+ 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))
* Maybe the allocated area already is >= the new size. (In particular,
* we always fall out here if the requested size is a decrease.)
*
- * This memory context is not use the power-of-2 chunk sizing and instead
+ * This memory context does not use power-of-2 chunk sizing and instead
* carves the chunks to be as small as possible, so most repalloc() calls
* will end up in the palloc/memcpy/pfree branch.
*
#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.
VALGRIND_MAKE_MEM_DEFINED(pointer, size);
#endif
+ /* Disallow external access to private part of chunk header. */
+ VALGRIND_MAKE_MEM_NOACCESS(chunk, GENERATIONCHUNK_PRIVATE_LEN);
+
return pointer;
}
/* leave immediately if request was not completed */
if (newPointer == NULL)
+ {
+ /* Disallow external access to private part of chunk header. */
+ VALGRIND_MAKE_MEM_NOACCESS(chunk, GENERATIONCHUNK_PRIVATE_LEN);
return NULL;
+ }
/*
- * GenerationSetAlloc() 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.
+ * GenerationAlloc() may have returned a region that is still 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
GenerationGetChunkSpace(MemoryContext context, void *pointer)
{
GenerationChunk *chunk = GenerationPointerGetChunk(pointer);
+ Size result;
- return chunk->size + Generation_CHUNKHDRSZ;
+ VALGRIND_MAKE_MEM_DEFINED(chunk, GENERATIONCHUNK_PRIVATE_LEN);
+ result = chunk->size + Generation_CHUNKHDRSZ;
+ VALGRIND_MAKE_MEM_NOACCESS(chunk, GENERATIONCHUNK_PRIVATE_LEN);
+ return result;
}
/*
* GenerationIsEmpty
- * Is an Generation empty of any allocated space?
+ * Is a GenerationContext empty of any allocated space?
*/
static bool
GenerationIsEmpty(MemoryContext context)
MemoryContextCounters *totals)
{
GenerationContext *set = (GenerationContext *) context;
-
Size nblocks = 0;
Size nchunks = 0;
Size nfreechunks = 0;
Size totalspace = 0;
Size freespace = 0;
-
dlist_iter iter;
dlist_foreach(iter, &set->blocks)
nblocks++;
nchunks += block->nchunks;
nfreechunks += block->nfree;
- totalspace += set->blockSize;
+ totalspace += block->blksize;
freespace += (block->endptr - block->freeptr);
}
/* walk all blocks in this context */
dlist_foreach(iter, &gen->blocks)
{
+ GenerationBlock *block = dlist_container(GenerationBlock, node, iter.cur);
int nfree,
nchunks;
char *ptr;
- GenerationBlock *block = dlist_container(GenerationBlock, node, iter.cur);
- /* We can't free more chunks than allocated. */
- if (block->nfree <= block->nchunks)
+ /*
+ * nfree > nchunks is surely wrong, and we don't expect to see
+ * equality either, because such a block should have gotten freed.
+ */
+ if (block->nfree >= block->nchunks)
elog(WARNING, "problem in Generation %s: number of free chunks %d in block %p exceeds %d allocated",
name, block->nfree, block, block->nchunks);
{
GenerationChunk *chunk = (GenerationChunk *) ptr;
+ /* Allow access to private part of chunk header. */
+ VALGRIND_MAKE_MEM_DEFINED(chunk, GENERATIONCHUNK_PRIVATE_LEN);
+
/* move to the next chunk */
ptr += (chunk->size + Generation_CHUNKHDRSZ);
+ nchunks += 1;
+
/* chunks have both block and context pointers, so check both */
if (chunk->block != block)
elog(WARNING, "problem in Generation %s: bogus block link in block %p, chunk %p",
name, block, chunk);
- if (chunk->context != gen)
+ /*
+ * Check for valid context pointer. Note this is an incomplete
+ * test, since palloc(0) produces an allocated chunk with
+ * requested_size == 0.
+ */
+ if ((chunk->requested_size > 0 && chunk->context != gen) ||
+ (chunk->context != gen && chunk->context != NULL))
elog(WARNING, "problem in Generation %s: bogus context link in block %p, chunk %p",
name, block, chunk);
- nchunks += 1;
+ /* now make sure the chunk size is correct */
+ if (chunk->size < chunk->requested_size ||
+ chunk->size != MAXALIGN(chunk->size))
+ elog(WARNING, "problem in Generation %s: bogus chunk size in block %p, chunk %p",
+ name, block, chunk);
- /* if requested_size==0, the chunk was freed */
- if (chunk->requested_size > 0)
+ /* is chunk allocated? */
+ if (chunk->context != NULL)
{
- /* if the chunk was not freed, we can trigger valgrind checks */
- VALGRIND_MAKE_MEM_DEFINED(&chunk->requested_size,
- sizeof(chunk->requested_size));
-
- /* we're in a no-freelist branch */
- VALGRIND_MAKE_MEM_NOACCESS(&chunk->requested_size,
- sizeof(chunk->requested_size));
-
- /* now make sure the chunk size is correct */
- if (chunk->size != MAXALIGN(chunk->requested_size))
- elog(WARNING, "problem in Generation %s: bogus chunk size in block %p, chunk %p",
- name, block, chunk);
-
- /* there might be sentinel (thanks to alignment) */
+ /* check sentinel, but only in allocated blocks */
if (chunk->requested_size < chunk->size &&
!sentinel_ok(chunk, Generation_CHUNKHDRSZ + chunk->requested_size))
elog(WARNING, "problem in Generation %s: detected write past chunk end in block %p, chunk %p",
}
else
nfree += 1;
+
+ /*
+ * If chunk is allocated, disallow external access to private part
+ * of chunk header.
+ */
+ if (chunk->context != NULL)
+ VALGRIND_MAKE_MEM_NOACCESS(chunk, GENERATIONCHUNK_PRIVATE_LEN);
}
/*