From 70b1d3a12ccd73b6e14cc00a4e32e7efa9bb4b0f Mon Sep 17 00:00:00 2001
From: Thomas Munro <thomas.munro@gmail.com>
Date: Tue, 17 Mar 2026 03:29:06 +1300
Subject: [PATCH v4 08/21] pg_stack_alloc: Debugging support for alloca().

1.  If assertions are enabled, pg_stack_free() now asserts that stack
addresses were returned by pg_stack_alloc() and not already freed.

2.  If CLOBBER_FREED_MEMORY is defined, it wipes the memory with 0x7f,
like pfree().

3.  If -fsanitize=address is active, it poisons the freed memory so that
any further access is reported.

It does this by allocating 16 bytes of book-keeping data on the stack
for every allocation, so stack memory might run out sooner.

Reviewed-by:
Discussion:
---
 src/include/utils/pg_stack_alloc.h | 128 ++++++++++++++++++++++++++++-
 src/test/regress/regress.c         |   1 +
 src/tools/pgindent/typedefs.list   |   1 +
 3 files changed, 127 insertions(+), 3 deletions(-)

diff --git a/src/include/utils/pg_stack_alloc.h b/src/include/utils/pg_stack_alloc.h
index a82ed7cdf0e..6286c4cc27a 100644
--- a/src/include/utils/pg_stack_alloc.h
+++ b/src/include/utils/pg_stack_alloc.h
@@ -36,6 +36,10 @@
 #include "utils/palloc.h"
 #include "miscadmin.h"
 
+#ifdef __SANITIZE_ADDRESS__
+#include <sanitizer/asan_interface.h>
+#endif
+
 #include <limits.h>
 #include <unistd.h>
 
@@ -166,6 +170,8 @@
 		void *pg_stack_let_ptr = (ptr);									\
 		Assert(pg_stack_ptr_p(pg_stack_let_ptr) ||						\
 			   pg_stack_maybe_pfree);									\
+		if (pg_stack_ptr_p(pg_stack_let_ptr))							\
+			pg_stack_debug_free(pg_stack_let_ptr);						\
 		if (unlikely(pg_stack_maybe_pfree) &&							\
 			!pg_stack_ptr_p(pg_stack_let_ptr))							\
 			pfree(pg_stack_let_ptr);									\
@@ -408,6 +414,7 @@ pg_stack_alloc_aligned_from_array(char *array,
 /* Required interface macros. */
 
 #define DECLARE_PG_STACK_IMPL(size)										\
+	pg_stack_debug_decl													\
 	pg_stack_impl_decl													\
 	const char *pg_stack_limit =										\
 		pg_stack_compute_limit(pg_stack_lower_bound(), (size))
@@ -509,9 +516,12 @@ pg_stack_compute_limit(const char *sp, size_t size)
  * tracking the lower bound if necessary.
  */
 #define pg_stack_alloca_aligned(size, align)							\
-	pg_stack_realign(													\
-		pg_stack_track(pg_stack_alloca(pg_stack_pad((size),	(align)))), \
-		(align))
+	pg_stack_debug_alloc(												\
+		pg_stack_realign(												\
+			pg_stack_track(												\
+				pg_stack_alloca(pg_stack_pad((size), (align)))),		\
+			(align)),													\
+		(size))
 
 /* Reserve padding space for pg_stack_realign() if stricter than default. */
 static inline size_t
@@ -582,6 +592,118 @@ pg_stack_alloca_would_fit_p(const char *lower, const char *limit,
 		pg_stack_estimate_alloca(lower, size, align) >= limit;
 }
 
+/* Debugging support for PG_STACK_USE_ALLOCA. */
+
+#if defined(USE_ASSERT_CHECKING) ||										\
+	defined(CLOBBER_FREED_MEMORY) ||									\
+	defined(__SANITIZE_ADDRESS__)
+
+#define PG_STACK_DEBUG_NIL INT32_MAX
+
+/*
+ * Entries in a list of not-yet-freed allocations.  Since these are also
+ * allocated with alloca(), relative pointers are used to squeeze it down to
+ * 16 bytes rather than 32 (considering typical ALIGNOF_ALLOCA).
+ */
+typedef struct pg_stack_debug_node
+{
+	void	   *ptr;
+	uint32		size;
+	int32		next;
+} pg_stack_debug_node;
+
+#define pg_stack_debug_decl												\
+	pg_stack_debug_node *pg_stack_debug_head = NULL;					\
+
+/* Called with alloca() result already realigned if required. */
+#define pg_stack_debug_alloc(ptr, size)									\
+	pg_stack_debug_link(&pg_stack_debug_head,							\
+						alloca(sizeof(pg_stack_debug_node)),			\
+						(ptr),											\
+						(size))
+
+/* Called by pg_stack_free(). */
+#define pg_stack_debug_free(ptr)										\
+	pg_stack_debug_unlink(&pg_stack_debug_head, (ptr))
+
+/* Get pointer to the next node. */
+static inline pg_stack_debug_node *
+pg_stack_debug_get_next(const pg_stack_debug_node *node)
+{
+	if (node->next == PG_STACK_DEBUG_NIL)
+		return NULL;
+	else
+		return (pg_stack_debug_node *) ((char *) node + node->next);
+}
+
+/* Set pointer to the next node. */
+static inline void
+pg_stack_debug_set_next(pg_stack_debug_node *node,
+						const pg_stack_debug_node *next)
+{
+	if (next == NULL)
+		node->next = PG_STACK_DEBUG_NIL;
+	else
+		node->next = (const char *) next - (const char *) node;
+}
+
+/* Populate and insert a new entry. */
+static inline void *
+pg_stack_debug_link(pg_stack_debug_node **head,
+					pg_stack_debug_node *node,
+					void *ptr,
+					size_t size)
+{
+	node->ptr = ptr;
+	node->size = size;
+	pg_stack_debug_set_next(node, *head);
+	*head = node;
+	return ptr;
+}
+
+/* Search for an entry and clobber/poison it. */
+static inline void
+pg_stack_debug_unlink(pg_stack_debug_node **head, void *ptr)
+{
+	pg_stack_debug_node *prev = NULL;
+	pg_stack_debug_node *node = *head;
+
+	while (node)
+	{
+		if (node->ptr == ptr)
+		{
+#ifdef CLOBBER_FREED_MEMORY
+			memset(ptr, 0x7f, node->size);
+#endif
+#ifdef __SANITIZE_ADDRESS__
+			ASAN_POISON_MEMORY_REGION(node->ptr, node->size);
+#endif
+			if (prev)
+				pg_stack_debug_set_next(prev,
+										pg_stack_debug_get_next(node));
+			else
+				*head = pg_stack_debug_get_next(node);
+			return;
+		}
+		prev = node;
+		node = pg_stack_debug_get_next(node);
+	}
+	Assert(false && "pg_stack_free() of unknown pointer");
+}
+
+#else
+
+/* No debug support needed. */
+#define pg_stack_debug_decl
+#define pg_stack_debug_alloc(ptr, size) (ptr)
+
+#endif
+
+#endif
+
+/* Define if not already defined. */
+#ifndef pg_stack_debug_free
+#define pg_stack_debug_free(ptr)
 #endif
 
 #endif
diff --git a/src/test/regress/regress.c b/src/test/regress/regress.c
index 396c58a3394..c32650d8b78 100644
--- a/src/test/regress/regress.c
+++ b/src/test/regress/regress.c
@@ -1409,6 +1409,7 @@ test_pg_stack_alloc(PG_FUNCTION_ARGS)
 	/* Acceptable size. */
 	p = pg_stack_alloc(10);
 	Assert(pg_stack_ptr_p(p));
+	pg_stack_free(p);
 
 	/* Addresses should move downwards. */
 	p2 = pg_stack_alloc(10);
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 52f8603a7be..e1ed8363c69 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3983,6 +3983,7 @@ pg_sha512_ctx
 pg_snapshot
 pg_special_case
 pg_stack_base_t
+pg_stack_debug_node
 pg_ternary
 pg_time_t
 pg_time_usec_t
-- 
2.50.1 (Apple Git-155)

