v1-0001-Reinstate-MemoryContextContains-to-work-with-arbi.patch

text/plain

Filename: v1-0001-Reinstate-MemoryContextContains-to-work-with-arbi.patch
Type: text/plain
Part: 0
Message: Re: Reducing the chunk header sizes on all memory context types

Patch

Format: format-patch
Series: patch v1-0001
Subject: Reinstate MemoryContextContains to work with arbitrary pointers
File+
src/backend/utils/mmgr/aset.c 42 0
src/backend/utils/mmgr/generation.c 45 0
src/backend/utils/mmgr/mcxt.c 33 23
src/backend/utils/mmgr/slab.c 44 0
src/include/nodes/memnodes.h 3 1
src/include/utils/memutils_internal.h 3 0
src/include/utils/memutils_memorychunk.h 21 0
From a9fcf078bc8a87742860abee527199f772f39f5b Mon Sep 17 00:00:00 2001
From: David Rowley <dgrowley@gmail.com>
Date: Thu, 8 Sep 2022 23:32:03 +1200
Subject: [PATCH v1 1/4] Reinstate MemoryContextContains to work with arbitrary
 pointers

---
 src/backend/utils/mmgr/aset.c            | 42 ++++++++++++++++++
 src/backend/utils/mmgr/generation.c      | 45 +++++++++++++++++++
 src/backend/utils/mmgr/mcxt.c            | 56 ++++++++++++++----------
 src/backend/utils/mmgr/slab.c            | 44 +++++++++++++++++++
 src/include/nodes/memnodes.h             |  4 +-
 src/include/utils/memutils_internal.h    |  3 ++
 src/include/utils/memutils_memorychunk.h | 21 +++++++++
 7 files changed, 191 insertions(+), 24 deletions(-)

diff --git a/src/backend/utils/mmgr/aset.c b/src/backend/utils/mmgr/aset.c
index ec423375ae..24ab7399a3 100644
--- a/src/backend/utils/mmgr/aset.c
+++ b/src/backend/utils/mmgr/aset.c
@@ -1333,6 +1333,48 @@ AllocSetGetChunkContext(void *pointer)
 	return &set->header;
 }
 
+/*
+ * AllocSetContains
+ *		Attempt to determine if 'pointer' is memory which was allocated by
+ *		'context'.  Return true if it is, otherwise false.
+ */
+bool
+AllocSetContains(MemoryContext context, void *pointer)
+{
+	MemoryChunk *chunk = PointerGetMemoryChunk(pointer);
+	AllocBlock	block;
+	AllocSet	set = (AllocSet) context;
+
+	/*
+	 * We must use MemoryChunkIsExternalUnSafePointer() instead of
+	 * MemoryChunkIsExternal() here as 'chunk' may not be a MemoryChunk if
+	 * 'pointer' is not pointing to palloced memory.  Below we're careful
+	 * never to dereference 'block' as it could point to memory not owned by
+	 * this process.
+	 */
+	if (MemoryChunkIsExternalUnSafePointer(chunk))
+		block = ExternalChunkGetBlock(chunk);
+	else
+		block = (AllocBlock) MemoryChunkGetBlock(chunk);
+
+	for (AllocBlock blk = set->blocks; blk != NULL; blk = blk->next)
+	{
+		if (block == blk)
+		{
+			/*
+			 * The block matches. Now check if 'pointer' falls within the
+			 * block's memory.  We don't detect if the pointer is pointing to
+			 * an allocated chunk as that would require looping over the
+			 * freelist for this chunk's size.
+			 */
+			if ((char *) pointer >= (char *) blk + ALLOC_BLOCKHDRSZ + ALLOC_CHUNKHDRSZ &&
+				(char *) pointer < blk->endptr)
+				return true;
+		}
+	}
+	return false;
+}
+
 /*
  * AllocSetGetChunkSpace
  *		Given a currently-allocated chunk, determine the total space
diff --git a/src/backend/utils/mmgr/generation.c b/src/backend/utils/mmgr/generation.c
index c743b24fa7..212aba6bf3 100644
--- a/src/backend/utils/mmgr/generation.c
+++ b/src/backend/utils/mmgr/generation.c
@@ -848,6 +848,51 @@ GenerationGetChunkContext(void *pointer)
 	return &block->context->header;
 }
 
+/*
+ * GenerationContains
+ *		Attempt to determine if 'pointer' is memory which was allocated by
+ *		'context'.  Return true if it is, otherwise false.
+ */
+bool
+GenerationContains(MemoryContext context, void *pointer)
+{
+	MemoryChunk *chunk = PointerGetMemoryChunk(pointer);
+	GenerationBlock *block;
+	GenerationContext *set = (GenerationContext *) context;
+	dlist_iter	iter;
+
+	/*
+	 * We must use MemoryChunkIsExternalUnSafePointer() instead of
+	 * MemoryChunkIsExternal() here as 'chunk' may not be a MemoryChunk if
+	 * 'pointer' is not pointing to palloced memory.  Below we're careful
+	 * never to dereference 'block' as it could point to memory not owned by
+	 * this process.
+	 */
+	if (MemoryChunkIsExternalUnSafePointer(chunk))
+		block = ExternalChunkGetBlock(chunk);
+	else
+		block = (GenerationBlock *) MemoryChunkGetBlock(chunk);
+
+	dlist_foreach(iter, &set->blocks)
+	{
+		GenerationBlock *blk = dlist_container(GenerationBlock, node, iter.cur);
+
+		if (block == blk)
+		{
+			/*
+			 * The block matches. Now check if 'pointer' falls within the
+			 * block's memory.  We don't detect if the pointer is pointing to
+			 * an allocated chunk as that would require looping over the
+			 * freelist for this chunk's size.
+			 */
+			if ((char *) pointer >= (char *) blk + Generation_BLOCKHDRSZ + Generation_CHUNKHDRSZ &&
+				(char *) pointer < blk->endptr)
+				return true;
+		}
+	}
+	return false;
+}
+
 /*
  * GenerationGetChunkSpace
  *		Given a currently-allocated chunk, determine the total space
diff --git a/src/backend/utils/mmgr/mcxt.c b/src/backend/utils/mmgr/mcxt.c
index 115a64cfe4..3615a83d53 100644
--- a/src/backend/utils/mmgr/mcxt.c
+++ b/src/backend/utils/mmgr/mcxt.c
@@ -44,6 +44,7 @@ static const MemoryContextMethods mcxt_methods[] = {
 	[MCTX_ASET_ID].reset = AllocSetReset,
 	[MCTX_ASET_ID].delete_context = AllocSetDelete,
 	[MCTX_ASET_ID].get_chunk_context = AllocSetGetChunkContext,
+	[MCTX_ASET_ID].contains = AllocSetContains,
 	[MCTX_ASET_ID].get_chunk_space = AllocSetGetChunkSpace,
 	[MCTX_ASET_ID].is_empty = AllocSetIsEmpty,
 	[MCTX_ASET_ID].stats = AllocSetStats,
@@ -58,6 +59,7 @@ static const MemoryContextMethods mcxt_methods[] = {
 	[MCTX_GENERATION_ID].reset = GenerationReset,
 	[MCTX_GENERATION_ID].delete_context = GenerationDelete,
 	[MCTX_GENERATION_ID].get_chunk_context = GenerationGetChunkContext,
+	[MCTX_GENERATION_ID].contains = GenerationContains,
 	[MCTX_GENERATION_ID].get_chunk_space = GenerationGetChunkSpace,
 	[MCTX_GENERATION_ID].is_empty = GenerationIsEmpty,
 	[MCTX_GENERATION_ID].stats = GenerationStats,
@@ -72,6 +74,7 @@ static const MemoryContextMethods mcxt_methods[] = {
 	[MCTX_SLAB_ID].reset = SlabReset,
 	[MCTX_SLAB_ID].delete_context = SlabDelete,
 	[MCTX_SLAB_ID].get_chunk_context = SlabGetChunkContext,
+	[MCTX_SLAB_ID].contains = SlabContains,
 	[MCTX_SLAB_ID].get_chunk_space = SlabGetChunkSpace,
 	[MCTX_SLAB_ID].is_empty = SlabIsEmpty,
 	[MCTX_SLAB_ID].stats = SlabStats
@@ -818,28 +821,16 @@ MemoryContextCheck(MemoryContext context)
  *		Detect whether an allocated chunk of memory belongs to a given
  *		context or not.
  *
- * Caution: 'pointer' must point to a pointer which was allocated by a
- * MemoryContext.  It's not safe or valid to use this function on arbitrary
- * pointers as obtaining the MemoryContext which 'pointer' belongs to requires
- * possibly several pointer dereferences.
+ * Caution: this test is reliable as long as 'pointer' does point to
+ * a chunk of memory allocated from *some* context.  If 'pointer' points
+ * at memory obtained in some other way, there is a chance of a segmentation
+ * violation due to accessing the chunk header, which look for in the 8 bytes
+ * prior to the pointer.
  */
 bool
 MemoryContextContains(MemoryContext context, void *pointer)
 {
 	/*
-	 * Temporarily make this always return false as we don't yet have a fully
-	 * baked idea on how to make it work correctly with the new MemoryChunk
-	 * code.
-	 */
-	return false;
-
-#ifdef NOT_USED
-	MemoryContext ptr_context;
-
-	/*
-	 * NB: We must perform run-time checks here which GetMemoryChunkContext()
-	 * does as assertions before calling GetMemoryChunkContext().
-	 *
 	 * Try to detect bogus pointers handed to us, poorly though we can.
 	 * Presumably, a pointer that isn't MAXALIGNED isn't pointing at an
 	 * allocated chunk.
@@ -847,13 +838,10 @@ MemoryContextContains(MemoryContext context, void *pointer)
 	if (pointer == NULL || pointer != (void *) MAXALIGN(pointer))
 		return false;
 
-	/*
-	 * OK, it's probably safe to look at the context.
-	 */
-	ptr_context = GetMemoryChunkContext(pointer);
+	if (GetMemoryChunkMethodID(pointer) != context->method_id)
+		return false;
 
-	return ptr_context == context;
-#endif
+	return context->methods->contains(context, pointer);
 }
 
 /*
@@ -900,6 +888,8 @@ MemoryContextCreate(MemoryContext node,
 
 	/* Initialize all standard fields of memory context header */
 	node->type = tag;
+	/* Use uint8 to prevent increasing sizeof(MemoryContextData) */
+	node->method_id = (uint8) method_id;
 	node->isReset = true;
 	node->methods = &mcxt_methods[method_id];
 	node->parent = parent;
@@ -969,6 +959,8 @@ MemoryContextAlloc(MemoryContext context, Size size)
 
 	VALGRIND_MEMPOOL_ALLOC(context, ret, size);
 
+	Assert(MemoryContextContains(context, ret));
+
 	return ret;
 }
 
@@ -1007,6 +999,8 @@ MemoryContextAllocZero(MemoryContext context, Size size)
 
 	MemSetAligned(ret, 0, size);
 
+	Assert(MemoryContextContains(context, ret));
+
 	return ret;
 }
 
@@ -1045,6 +1039,8 @@ MemoryContextAllocZeroAligned(MemoryContext context, Size size)
 
 	MemSetLoop(ret, 0, size);
 
+	Assert(MemoryContextContains(context, ret));
+
 	return ret;
 }
 
@@ -1086,6 +1082,8 @@ MemoryContextAllocExtended(MemoryContext context, Size size, int flags)
 	if ((flags & MCXT_ALLOC_ZERO) != 0)
 		MemSetAligned(ret, 0, size);
 
+	Assert(MemoryContextContains(context, ret));
+
 	return ret;
 }
 
@@ -1169,6 +1167,8 @@ palloc(Size size)
 
 	VALGRIND_MEMPOOL_ALLOC(context, ret, size);
 
+	Assert(MemoryContextContains(context, ret));
+
 	return ret;
 }
 
@@ -1202,6 +1202,8 @@ palloc0(Size size)
 
 	MemSetAligned(ret, 0, size);
 
+	Assert(MemoryContextContains(context, ret));
+
 	return ret;
 }
 
@@ -1241,6 +1243,8 @@ palloc_extended(Size size, int flags)
 	if ((flags & MCXT_ALLOC_ZERO) != 0)
 		MemSetAligned(ret, 0, size);
 
+	Assert(MemoryContextContains(context, ret));
+
 	return ret;
 }
 
@@ -1294,6 +1298,8 @@ repalloc(void *pointer, Size size)
 
 	VALGRIND_MEMPOOL_CHANGE(context, pointer, ret, size);
 
+	Assert(MemoryContextContains(context, ret));
+
 	return ret;
 }
 
@@ -1329,6 +1335,8 @@ MemoryContextAllocHuge(MemoryContext context, Size size)
 
 	VALGRIND_MEMPOOL_ALLOC(context, ret, size);
 
+	Assert(MemoryContextContains(context, ret));
+
 	return ret;
 }
 
@@ -1368,6 +1376,8 @@ repalloc_huge(void *pointer, Size size)
 
 	VALGRIND_MEMPOOL_CHANGE(context, pointer, ret, size);
 
+	Assert(MemoryContextContains(context, ret));
+
 	return ret;
 }
 
diff --git a/src/backend/utils/mmgr/slab.c b/src/backend/utils/mmgr/slab.c
index 9149aaafcb..b5c801eb01 100644
--- a/src/backend/utils/mmgr/slab.c
+++ b/src/backend/utils/mmgr/slab.c
@@ -567,6 +567,50 @@ SlabGetChunkContext(void *pointer)
 	return &slab->header;
 }
 
+/*
+ * SlabContains
+ *		Attempt to determine if 'pointer' is memory which was allocated by
+ *		'context'.  Return true if it is, otherwise false.
+ */
+bool
+SlabContains(MemoryContext context, void *pointer)
+{
+	MemoryChunk *chunk = PointerGetMemoryChunk(pointer);
+	SlabBlock  *block;
+	SlabContext *set = (SlabContext *) context;
+
+	/*
+	 * Careful not to dereference anything in 'block' as if 'pointer' is not a
+	 * pointer to an allocated chunk then 'block' could be pointing to about
+	 * anything.
+	 */
+	block = (SlabBlock *) MemoryChunkGetBlock(chunk);
+
+	for (int i = 0; i <= set->chunksPerBlock; i++)
+	{
+		dlist_iter	iter;
+
+		dlist_foreach(iter, &set->freelist[i])
+		{
+			SlabBlock  *blk = dlist_container(SlabBlock, node, iter.cur);
+
+			if (block == blk)
+			{
+				/*
+				 * The block matches. Now check if 'pointer' falls within the
+				 * block's memory.  We don't detect if the pointer is pointing
+				 * to an allocated chunk as that would require looping over
+				 * the freelist for this chunk's size.
+				 */
+				if ((char *) pointer >= (char *) blk + Slab_BLOCKHDRSZ + Slab_CHUNKHDRSZ &&
+					(char *) pointer < (char *) blk + set->blockSize)
+					return true;
+			}
+		}
+	}
+	return false;
+}
+
 /*
  * SlabGetChunkSpace
  *		Given a currently-allocated chunk, determine the total space
diff --git a/src/include/nodes/memnodes.h b/src/include/nodes/memnodes.h
index 63d07358cd..6a8d4f4e4e 100644
--- a/src/include/nodes/memnodes.h
+++ b/src/include/nodes/memnodes.h
@@ -64,6 +64,7 @@ typedef struct MemoryContextMethods
 	void		(*reset) (MemoryContext context);
 	void		(*delete_context) (MemoryContext context);
 	MemoryContext (*get_chunk_context) (void *pointer);
+	bool		(*contains) (MemoryContext context, void *pointer);
 	Size		(*get_chunk_space) (void *pointer);
 	bool		(*is_empty) (MemoryContext context);
 	void		(*stats) (MemoryContext context,
@@ -81,7 +82,8 @@ typedef struct MemoryContextData
 	pg_node_attr(abstract)		/* there are no nodes of this type */
 
 	NodeTag		type;			/* identifies exact kind of context */
-	/* these two fields are placed here to minimize alignment wastage: */
+	/* these three fields are placed here to minimize alignment wastage: */
+	uint8		method_id;		/* MemoryContextMethodID for this context */
 	bool		isReset;		/* T = no space alloced since last reset */
 	bool		allowInCritSection; /* allow palloc in critical section */
 	Size		mem_allocated;	/* track memory allocated for this context */
diff --git a/src/include/utils/memutils_internal.h b/src/include/utils/memutils_internal.h
index 377cee7a84..ebc1d60393 100644
--- a/src/include/utils/memutils_internal.h
+++ b/src/include/utils/memutils_internal.h
@@ -25,6 +25,7 @@ extern void *AllocSetRealloc(void *pointer, Size size);
 extern void AllocSetReset(MemoryContext context);
 extern void AllocSetDelete(MemoryContext context);
 extern MemoryContext AllocSetGetChunkContext(void *pointer);
+extern bool AllocSetContains(MemoryContext context, void *pointer);
 extern Size AllocSetGetChunkSpace(void *pointer);
 extern bool AllocSetIsEmpty(MemoryContext context);
 extern void AllocSetStats(MemoryContext context,
@@ -42,6 +43,7 @@ extern void *GenerationRealloc(void *pointer, Size size);
 extern void GenerationReset(MemoryContext context);
 extern void GenerationDelete(MemoryContext context);
 extern MemoryContext GenerationGetChunkContext(void *pointer);
+extern bool GenerationContains(MemoryContext context, void *pointer);
 extern Size GenerationGetChunkSpace(void *pointer);
 extern bool GenerationIsEmpty(MemoryContext context);
 extern void GenerationStats(MemoryContext context,
@@ -60,6 +62,7 @@ extern void *SlabRealloc(void *pointer, Size size);
 extern void SlabReset(MemoryContext context);
 extern void SlabDelete(MemoryContext context);
 extern MemoryContext SlabGetChunkContext(void *pointer);
+extern bool SlabContains(MemoryContext context, void *pointer);
 extern Size SlabGetChunkSpace(void *pointer);
 extern bool SlabIsEmpty(MemoryContext context);
 extern void SlabStats(MemoryContext context,
diff --git a/src/include/utils/memutils_memorychunk.h b/src/include/utils/memutils_memorychunk.h
index 2eefc138e3..721c7d004c 100644
--- a/src/include/utils/memutils_memorychunk.h
+++ b/src/include/utils/memutils_memorychunk.h
@@ -54,6 +54,12 @@
  *		Determine if the given MemoryChunk is externally managed, i.e.
  *		MemoryChunkSetHdrMaskExternal() was called on the chunk.
  *
+ * MemoryChunkIsExternalUnSafePointer:
+ *		As MemoryChunkIsExternal but safe to use on pointers that we're
+ *		uncertain are pointers to MemoryChunks.  Callers must be careful
+ *		not to put too much trust into the return value.  The primary usecase
+ *		for this is to implement MemoryContextContains.
+ *
  * MemoryChunkGetValue:
  *		For non-external chunks, return the stored 30-bit value as it was set
  *		in the call to MemoryChunkSetHdrMask().
@@ -198,6 +204,21 @@ MemoryChunkIsExternal(MemoryChunk *chunk)
 	return HdrMaskIsExternal(chunk->hdrmask);
 }
 
+/*
+ * MemoryChunkIsExternalUnSafePointer
+ *		As MemoryChunkIsExternal only without any Assert checking.  This
+ *		version should only be used when we're uncertain of 'chunk' is
+ *		actually a pointer to a MemoryChunk.
+ *
+ * Note: Callers must be careful not to put too much trust into the return
+ * value as 'chunk' may not be a MemoryChunk.
+ */
+static inline bool
+MemoryChunkIsExternalUnSafePointer(MemoryChunk *chunk)
+{
+	return HdrMaskIsExternal(chunk->hdrmask);
+}
+
 /*
  * MemoryChunkGetValue
  *		For non-external chunks, returns the value field as it was set in
-- 
2.34.1