public inbox for [email protected]  
help / color / mirror / Atom feed
From: Thomas Munro <[email protected]>
To: Tom Lane <[email protected]>
Cc: PostgreSQL Hackers <[email protected]>
Subject: Re: A stack allocation API
Date: Tue, 17 Mar 2026 11:10:17 +1300
Message-ID: <CA+hUKG+Ovn6cWxCTjTAdQLmMksg_BfDfDOr6qPRFZbbQz9vFyQ@mail.gmail.com> (raw)
In-Reply-To: <CA+hUKG+Shdfa_QcVWXrHyhWGDJcPsw2aXWRQxnaUpyUa2fMffA@mail.gmail.com>
References: <CA+hUKG+ixUUYOGRcuZpkk5pmJZaUQv6VCPAjdTZXFP5vA8jxcA@mail.gmail.com>
	<[email protected]>
	<CA+hUKG+Shdfa_QcVWXrHyhWGDJcPsw2aXWRQxnaUpyUa2fMffA@mail.gmail.com>

On Thu, Mar 12, 2026 at 2:52 PM Thomas Munro <[email protected]> wrote:
> . o O ( I originally called it stack_buffer because I started with
> "let's standardise the array trick", so it was a buffer.  Perhaps a
> better name for all this stuff would be pg_stack_alloc() or such,
> since it doesn't really have a buffer anymore except in a fallback
> mode... )

* renamed like like
* hopefully improved comments and naming and structure
* replaced macros with inline functions where possible
* added many sanity and overflow checks
* added tests
* added assertions that pg_stack_free() stack pointers are known, not
double-freed
* added CLOBBER_FREED_MEMORY and ASAN use-after-
* abandoned __builtin_alloca_with_align(), it has weird scoping on GCC
* removed alloca(0)-avoidance, as zero-sized objects have normal stack pointers
* removed grows-upwards alloca support, too complicated and
practically untestable
* let's use alloca on all systems, we can just tell a CI task to test array mode
* simplified the default sizing, now it's just: 128 for arrays and
1024 for alloca()
* split main patch into a few incremental pieces that might hopefully
be more digestable

The stack is a dangerous place, so I tried hard to think of all the
ways that anything anywhere could be exploited to overflow
intermediate values etc, while also trying to prove useful things at
compile time.  Ideas for other things to be paranoid about welcome.

Patches:

* 0001 + 0002 are the basic API but still using arrays
* 0003 shows the locales transformation.
* 0004..0008 add alloca()
* collection of example candidate callers, not changed much since v3,
in a tarball

One thing to highlight is how 0007 decides whether a pointer should go
to pfree() or not.  I am reasonably confident that
pg_stack_upper_bound() (which is either __builtin_frame_address(0) or
stack_base_ptr) is a good authoritative bound, and I am very confident
that pg_stack_lower_bound() is reliable when it's computed as the
lowest stack address that pg_stack_alloc() has ever seen.  But it
annoys me to have that book-keeping when:

pg_stack_lower_bound() can instead use __builtin_stack_address(), if
we decree that it is an authoritative lower bound, as is the case in
this patch currently.  The descriptions in the Clang and GCC manuals
sound good, and the generated code is exactly what I expect: it's just
comparing with %rsp, and I haven't  seen it trip my assertion that
%rsp <= every alloca().  But I wonder if some weird edge case
involving storage that is actually in a register or something like
that could allow it to not decrement %rsp, and then I wonder if that
builtin could reveal that, or something like that.  I been wondering
if it might make sense to have a secondary test out-of-line that
includes a fudge factor.  That sounds kinda bogus but perhaps an
argument can be constructed from guard pages: even on a system under
extreme address space pressure, palloc() pointers can only get so
close to %rsp.  But that gets pretty system-specific if you want
something that holds up in court.  Hnggh.  Maybe I'm worrying about
nothing.  Clues welcome.


Attachments:

  [application/octet-stream] v4-0001-pg_stack_alloc-Provide-API-for-stack-allocation.patch (15.5K, 2-v4-0001-pg_stack_alloc-Provide-API-for-stack-allocation.patch)
  download | inline diff:
From baa0b86843b0c23dbaec6d719818f20a5f500f49 Mon Sep 17 00:00:00 2001
From: Thomas Munro <[email protected]>
Date: Fri, 27 Feb 2026 22:21:36 +1300
Subject: [PATCH v4 01/21] pg_stack_alloc: Provide API for stack allocation.

Several places have long used a large array on the stack for temporary
objects of dynamic size, and switched to palloc()/pfree() when it was
full.  This commit provides a centralized API for that pattern.

It is implemented the same way, but the interface looks like palloc.h's,
including the type-safety features added by commit 2016055a and later
developments.  A small set of functions like strdup() is also provided,
for common operations.

Later patches will adopt the new API in various places.

Reviewed-by:
Discussion:
---
 src/include/utils/pg_stack_alloc.h      | 315 ++++++++++++++++++++++++
 src/test/regress/expected/internals.out |  12 +
 src/test/regress/parallel_schedule      |   2 +-
 src/test/regress/regress.c              |  51 ++++
 src/test/regress/sql/internals.sql      |  11 +
 5 files changed, 390 insertions(+), 1 deletion(-)
 create mode 100644 src/include/utils/pg_stack_alloc.h
 create mode 100644 src/test/regress/expected/internals.out
 create mode 100644 src/test/regress/sql/internals.sql

diff --git a/src/include/utils/pg_stack_alloc.h b/src/include/utils/pg_stack_alloc.h
new file mode 100644
index 00000000000..bb8c58597b9
--- /dev/null
+++ b/src/include/utils/pg_stack_alloc.h
@@ -0,0 +1,315 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_stack_alloc.h
+ *		Allocator for objects that don't escape the current function.
+ *
+ * A palloc()-like interface for allocating memory on the stack.  The initial
+ * implementation uses an array declared statically.
+ *
+ * Once stack space is exhausted, allocations silently fall back to using
+ * palloc().  Memory should therefore still be freed explicitly with
+ * pg_stack_free() or MemoryContext-level cleanup.  It is a no-op in the
+ * common case that pfree() doesn't need to be called.
+ *
+ * XXX A space-limited version of alloca() could be added.
+ *
+ * XXX It might be possible to use something like "defer" or equivalent
+ * compiler extensions to clean up palloc()'d memory automatically, in future
+ * work, and then pg_stack_free() would not be necessary.
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/utils/pg_stack_alloc.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_STACK_ALLOC_H
+#define PG_STACK_ALLOC_H
+
+#include "utils/elog.h"
+#include "utils/palloc.h"
+#include "miscadmin.h"
+
+#include <limits.h>
+#include <unistd.h>
+
+
+/* #define PG_STACK_USE_PALLOC_LOG "/tmp/pg_stack_alloc.csv" */
+
+/* Choose which implementation to use, if not already defined manually. */
+#if !defined(PG_STACK_USE_ARRAY) &&									\
+	!defined(PG_STACK_USE_PALLOC) &&								\
+	!defined(PG_STACK_USE_PALLOC_LOG)
+#define PG_STACK_USE_ARRAY
+#endif
+
+
+/*-------------------------------------------------------------------------
+ *
+ * Public API.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+/* This ensures that "align" is a constant and can't cause overflow. */
+#define PG_STACK_MAX_ALIGN 4096
+
+/* Declare a stack allocator with a default size limit. */
+#define DECLARE_PG_STACK()												\
+	DECLARE_PG_STACK_SIZE(128)
+
+/*
+ * As above, but with a caller-supplied limit on stack usage.  The default
+ * should be preferred.
+ */
+#define DECLARE_PG_STACK_SIZE(size) \
+	bool pg_stack_maybe_pfree pg_attribute_unused() = false;			\
+	size_t pg_stack_let_size;		/* temp, avoids double eval */		\
+	DECLARE_PG_STACK_IMPL(size)
+
+/* Allocate memory, optionally with explicit alignment. */
+#define pg_stack_alloc(size)											\
+	pg_stack_alloc_aligned((size), MAXIMUM_ALIGNOF)
+#define pg_stack_alloc_aligned(size, align)								\
+	(pg_stack_sanity_checks(align),										\
+	 pg_stack_let_size = (size),										\
+	 pg_stack_alloc_aligned_impl(pg_stack_let_size, (align)))
+
+/* As above, but also zero the memory. */
+#define pg_stack_alloc0(size) \
+	pg_stack_alloc0_aligned((size), MAXIMUM_ALIGNOF)
+#define pg_stack_alloc0_aligned(size, align)							\
+	(pg_stack_sanity_checks(align),										\
+	 pg_stack_let_size = (size),										\
+	 memset(pg_stack_alloc_aligned_impl(pg_stack_let_size, (align)),	\
+			0,															\
+			pg_stack_let_size))
+
+/* As above, but for a given type T. */
+#define pg_stack_alloc_object(T)										\
+	pg_stack_alloc_array(T, 1)
+#define pg_stack_alloc0_object(T)										\
+	pg_stack_alloc0_array(T, 1)
+
+/* As above, but for an array of objects of size T. */
+#define pg_stack_alloc_array(T, n)										\
+	(pg_stack_sanity_checks(alignof(T)), 								\
+	 StaticAssertExpr(sizeof(n) <= sizeof(size_t), "n too wide"), 		\
+	 pg_stack_let_size = pg_stack_T_mul_n(sizeof(T), sizeof(n), (n)),	\
+	 pg_stack_alloc_aligned_impl(pg_stack_let_size, alignof(T)))
+#define pg_stack_alloc0_array(T, n)										\
+	(pg_stack_sanity_checks(alignof(T)), 								\
+	 StaticAssertExpr(sizeof(n) <= sizeof(size_t), "n too wide"), 		\
+	 pg_stack_let_size = pg_stack_T_mul_n(sizeof(T), sizeof(n), (n)),	\
+	 pg_stack_alloc0_aligned(pg_stack_let_size, (alignof(T))))
+
+/* Copy a string. */
+#define pg_stack_strdup(cstr)											\
+	pg_stack_strdup_with_len((cstr), strlen(cstr))
+#define pg_stack_strndup(cstr, n)										\
+	pg_stack_strdup_with_len((cstr), strnlen((cstr), (n)))
+#define pg_stack_strdup_with_len(data, size)							\
+	(pg_stack_sanity_checks(1),											\
+	 pg_stack_let_size = (size),										\
+	 pg_stack_strdup_with_len_impl(										\
+		 pg_stack_alloc_aligned_impl(pg_stack_let_size + 1,				\
+									 alignof(char)),					\
+		 (data),														\
+		 pg_stack_let_size))
+#define pg_stack_text_to_cstring(text) \
+	pg_stack_strdup_with_len(VARDATA_ANY(text), VARSIZE_ANY_EXHDR(text))
+#define pg_stack_text_datum_to_cstring(datum)							\
+	pg_stack_text_to_cstring((text *) DatumGetPointer(datum))
+
+/*
+ * Free memory allocated with the above interfaces.  We don't expect to
+ * receive pointers allocated by palloc() directly and not via this API.  That
+ * would break the pg_stack_maybe_pfree optimization, and might limit
+ * future implementation techniques.
+ */
+#define pg_stack_free(ptr)												\
+	do																	\
+	{																	\
+		void *pg_stack_let_ptr = (ptr);									\
+		Assert(pg_stack_ptr_p(pg_stack_let_ptr) ||						\
+			   pg_stack_maybe_pfree);									\
+		if (unlikely(pg_stack_maybe_pfree) &&							\
+			!pg_stack_ptr_p(pg_stack_let_ptr))							\
+			pfree(pg_stack_let_ptr);									\
+	}																	\
+	while (0)
+
+
+/*-------------------------------------------------------------------------
+ *
+ * Private helper code common to all implementations.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+/*
+ * Normal usage would have constant align values, so the first two checks
+ * should ideally be static assertions, but they are done this way to allow
+ * regress.c to loop through alignment sizes.
+ */
+#define pg_stack_sanity_checks(align)									\
+	(AssertMacro((align) > 0 && (align) <= PG_STACK_MAX_ALIGN),			\
+	 AssertMacro(((align) & ((align) - 1)) == 0 /* power-of-two? */))
+
+/* For assertions. */
+static inline bool
+pg_stack_ptr_is_aligned_p(const void *p, size_t align)
+{
+	return (uintptr_t) p % align == 0;
+}
+
+/* Maximum value that can be stored in an unsigned integer of given size. */
+static inline size_t
+pg_stack_max_for_uint_size(size_t size)
+{
+	Assert(size <= sizeof(size_t));
+	return SIZE_MAX >> ((sizeof(size_t) * CHAR_BIT) - size * CHAR_BIT);
+}
+
+/* Post-allocation part of pg_stack_strdup_with_len(). */
+static inline char *
+pg_stack_strdup_with_len_impl(char *dst, const char *data, size_t size)
+{
+	memcpy(dst, data, size);
+	dst[size] = 0;
+	return dst;
+}
+
+/* Compute sizeof(T) * n. */
+static inline size_t
+pg_stack_T_mul_n(size_t sizeof_T, size_t sizeof_n, size_t n)
+{
+	/* XXX Could overflow, which might allow the stack to be corrupted. */
+	return sizeof_T * n;
+}
+
+/*
+ * Fall back to palloc() or palloc_aligned() due to lack of space.  We waste a
+ * register remembering if we've ever had to do this, to generate better
+ * straight-line code for the case where we don't have to free anything.
+ */
+#define pg_stack_palloc_aligned(size, align)							\
+	((pg_stack_maybe_pfree = true),										\
+	 ((align) > MAXIMUM_ALIGNOF ?										\
+	  palloc_aligned((size), (align), 0) :								\
+	  palloc(size)))
+
+
+/*-------------------------------------------------------------------------
+ *
+ * Low-level implementations below this point supply the following macros:
+ *
+ * 1. DECLARE_PG_STACK_IMPL(size)
+ * 2. pg_stack_alloc_aligned(size, align)
+ * 3. pg_stack_ptr_p(ptr)
+ *
+ *-------------------------------------------------------------------------
+ */
+
+/*-------------------------------------------------------------------------
+ *
+ * Toy implementations for debugging.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+/* Just use palloc. */
+#ifdef PG_STACK_USE_PALLOC
+#define DECLARE_PG_STACK_IMPL(size)
+#define pg_stack_alloc_aligned_impl(size, align)						\
+	pg_stack_palloc_aligned((size), (align))
+#define pg_stack_ptr_p() false
+#endif
+
+/*
+ * Same, but log "location,function,size,depth" entries to a file, using the
+ * PG_STACK_USE_PALLOC_LOG macro's definition (a ""-quoted string literal) for
+ * the path.
+ */
+#ifdef PG_STACK_USE_PALLOC_LOG
+#define DECLARE_PG_STACK_IMPL(size)										\
+	FILE *pg_stack_log													\
+	__attribute__((cleanup(pg_stack_close_log))) =						\
+	fopen(PG_STACK_USE_PALLOC_LOG, "a+")
+#define pg_stack_alloc_aligned_impl(size, align)						\
+	(fprintf(pg_stack_log,												\
+			 "%s:%d,%s,%zu,%zu\n",										\
+			 __FILE__,													\
+			 __LINE__,													\
+			 __func__,													\
+			 (size_t) (size),											\
+			 ((const char *) stack_base_ptr -							\
+			  (const char *) __builtin_stack_address())),				\
+	 pg_stack_palloc_aligned((size), (align)))
+#define pg_stack_ptr_p(ptr) false
+static inline void
+pg_stack_close_log(FILE **f)
+{
+	fclose(*f);
+}
+#endif
+
+/*-------------------------------------------------------------------------
+ *
+ * Array-based implementation.
+ *
+ * This is entirely standard C requiring no compiler extensions, but it leaves
+ * a big hole in the stack when you call another function and has no ability
+ * to respect the total stack size limit.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifdef PG_STACK_USE_ARRAY
+
+/* Required interface macros. */
+
+#define DECLARE_PG_STACK_IMPL(size)										\
+	char pg_stack_array[(size)];										\
+	char *pg_stack_p = pg_stack_array + (size)
+
+#define pg_stack_alloc_aligned_impl(size, align)						\
+	(pg_stack_alloc_aligned_from_array(&pg_stack_array[0],				\
+									   &pg_stack_p,						\
+									   (size),							\
+									   (align)) ?						\
+	 pg_stack_p :														\
+	 pg_stack_palloc_aligned(size, align))
+
+#define pg_stack_ptr_p(ptr)												\
+	((char *) (ptr) >= &pg_stack_array[0] &&							\
+	 (char *) (ptr) <= &pg_stack_array[sizeof(pg_stack_array)])
+
+
+/* Implementation. */
+
+static inline bool
+pg_stack_alloc_aligned_from_array(char *array,
+								  char **p,
+								  size_t size,
+								  size_t align)
+{
+	if (likely(size <= (uintptr_t) *p)) /* avoid overflow with huge size */
+	{
+		char	   *result = *p - size;
+
+		if (align > 1)
+			result = (char *) TYPEALIGN_DOWN(align, result);
+
+		if (likely(result >= array))
+		{
+			*p = result;
+			return true;
+		}
+	}
+	return false;
+}
+
+#endif
+
+#endif
diff --git a/src/test/regress/expected/internals.out b/src/test/regress/expected/internals.out
new file mode 100644
index 00000000000..532e9f791ed
--- /dev/null
+++ b/src/test/regress/expected/internals.out
@@ -0,0 +1,12 @@
+\getenv libdir PG_LIBDIR
+\getenv dlsuffix PG_DLSUFFIX
+\set regresslib :libdir '/regress' :dlsuffix
+CREATE FUNCTION test_pg_stack_alloc() RETURNS void
+    AS :'regresslib' LANGUAGE C STRICT;
+SELECT test_pg_stack_alloc();
+ test_pg_stack_alloc 
+---------------------
+ 
+(1 row)
+
+DROP FUNCTION test_pg_stack_alloc();
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index e779ada70cb..4986b2c5c53 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -48,7 +48,7 @@ test: create_index create_index_spgist create_view index_including index_includi
 # ----------
 # Another group of parallel tests
 # ----------
-test: create_aggregate create_function_sql create_cast constraints triggers select inherit typed_table vacuum drop_if_exists updatable_views roleattributes create_am hash_func errors infinite_recurse create_property_graph
+test: create_aggregate create_function_sql create_cast constraints triggers select inherit typed_table vacuum drop_if_exists updatable_views roleattributes create_am hash_func errors infinite_recurse create_property_graph internals
 
 # ----------
 # sanity_check does a vacuum, affecting the sort order of SELECT *
diff --git a/src/test/regress/regress.c b/src/test/regress/regress.c
index 68a01a1dde0..8f9a7ac4221 100644
--- a/src/test/regress/regress.c
+++ b/src/test/regress/regress.c
@@ -27,6 +27,7 @@
 #include "catalog/pg_type.h"
 #include "commands/sequence.h"
 #include "commands/trigger.h"
+#include "common/int.h"
 #include "executor/executor.h"
 #include "executor/functions.h"
 #include "executor/spi.h"
@@ -45,6 +46,7 @@
 #include "utils/builtins.h"
 #include "utils/geo_decls.h"
 #include "utils/memutils.h"
+#include "utils/pg_stack_alloc.h"
 #include "utils/rel.h"
 #include "utils/typcache.h"
 
@@ -1384,3 +1386,52 @@ test_translation(PG_FUNCTION_ARGS)
 
 	PG_RETURN_VOID();
 }
+
+PG_FUNCTION_INFO_V1(test_pg_stack_alloc);
+Datum
+test_pg_stack_alloc(PG_FUNCTION_ARGS)
+{
+	char	   *p;
+	char	   *p2 PG_USED_FOR_ASSERTS_ONLY;
+
+	/* Need extra space to run under ASAN. */
+	DECLARE_PG_STACK_SIZE(1024 * 8);
+
+	/* Too big for the stack. */
+	p = pg_stack_alloc(10000);
+	Assert(!pg_stack_ptr_p(p));
+	pg_stack_free(p);
+
+	/* Acceptable size. */
+	p = pg_stack_alloc(10);
+	Assert(pg_stack_ptr_p(p));
+
+	/* Addresses should move downwards. */
+	p2 = pg_stack_alloc(10);
+	Assert(pg_stack_ptr_p(p));
+	Assert(p2 < p);
+
+	/* A zero-sized allocation is identifiable as a stack address. */
+	p = pg_stack_alloc(0);
+	Assert(pg_stack_ptr_p(p));
+
+	/* Test a range of alignments. */
+#define TEST_ALIGN MAXIMUM_ALIGNOF
+	for (int i = 1; i <= TEST_ALIGN * 8; i *= 2)
+	{
+		/* Allocate and check alignment is as requested, when we use palloc(). */
+		p = pg_stack_alloc_aligned(1024 * 8, i);
+
+		Assert(!pg_stack_ptr_p(p));
+		Assert(pg_stack_ptr_is_aligned_p(p, i));
+		pg_stack_free(p);
+
+		/* Allocate and check alignment is as requested. */
+		p = pg_stack_alloc_aligned(i, i);
+
+		Assert(pg_stack_ptr_p(p));
+		Assert(pg_stack_ptr_is_aligned_p(p, i));
+	}
+
+	PG_RETURN_VOID();
+}
diff --git a/src/test/regress/sql/internals.sql b/src/test/regress/sql/internals.sql
new file mode 100644
index 00000000000..bea61780f8b
--- /dev/null
+++ b/src/test/regress/sql/internals.sql
@@ -0,0 +1,11 @@
+\getenv libdir PG_LIBDIR
+\getenv dlsuffix PG_DLSUFFIX
+
+\set regresslib :libdir '/regress' :dlsuffix
+
+CREATE FUNCTION test_pg_stack_alloc() RETURNS void
+    AS :'regresslib' LANGUAGE C STRICT;
+
+SELECT test_pg_stack_alloc();
+
+DROP FUNCTION test_pg_stack_alloc();
-- 
2.50.1 (Apple Git-155)



  [application/octet-stream] v4-0002-pg_stack_alloc-Defend-against-arithmetic-overflow.patch (4.4K, 3-v4-0002-pg_stack_alloc-Defend-against-arithmetic-overflow.patch)
  download | inline diff:
From c7d1a03ad7d1e90b18049534860bdd87956b9952 Mon Sep 17 00:00:00 2001
From: Thomas Munro <[email protected]>
Date: Mon, 16 Mar 2026 15:15:18 +1300
Subject: [PATCH v4 02/21] pg_stack_alloc: Defend against arithmetic overflow.

Add checks that pg_stack_alloc_array(T, n) and similar can't overflow
size_t and allow the stack to be overrun.

XXX Separated from initial patch for review.

Reviewed-by:
Discussion:
---
 src/include/utils/pg_stack_alloc.h | 52 ++++++++++++++++++++++++++++--
 src/test/regress/regress.c         | 30 +++++++++++++++++
 2 files changed, 79 insertions(+), 3 deletions(-)

diff --git a/src/include/utils/pg_stack_alloc.h b/src/include/utils/pg_stack_alloc.h
index bb8c58597b9..cd3c3838013 100644
--- a/src/include/utils/pg_stack_alloc.h
+++ b/src/include/utils/pg_stack_alloc.h
@@ -181,12 +181,58 @@ pg_stack_strdup_with_len_impl(char *dst, const char *data, size_t size)
 	return dst;
 }
 
-/* Compute sizeof(T) * n. */
+/* Is it impossible for sizeof(T) * maximum possible n to overflow size_t? */
+static inline bool
+pg_stack_T_mul_n_cannot_overflow_p(size_t sizeof_T, size_t sizeof_n)
+{
+	/* See static assertion that n is not wider than size_t. */
+	if (sizeof_T == 1)
+		return true;
+
+	/* Can't overflow if both factors fit in the lower half of size_t. */
+	if (sizeof_n <= sizeof(size_t) / 2 &&
+		(sizeof_T <= pg_stack_max_for_uint_size(sizeof(size_t) / 2)))
+		return true;
+
+	return false;
+}
+
+/* Would sizeof(T) * n overflow? */
+static inline bool
+pg_stack_T_mul_n_overflows_p(size_t sizeof_T, size_t n)
+{
+	return n > SIZE_MAX / sizeof_T;
+}
+
+/* Compute sizeof(T) * n or raise an error if that would overflow size_t. */
 static inline size_t
 pg_stack_T_mul_n(size_t sizeof_T, size_t sizeof_n, size_t n)
 {
-	/* XXX Could overflow, which might allow the stack to be corrupted. */
-	return sizeof_T * n;
+	size_t		result;
+
+	/*
+	 * These functions are split up so that we can sanity-check them
+	 * individually on 32-bit CI.  For the common case of sizeof(n) == 4 and
+	 * sizeof(size_t) == 8, this should reduce to simple multiplication.
+	 * 32-bit systems can only skip the runtime test for sizeof(T) == 1,
+	 * sizeof(n) == 2 or constexpr n <= UINT16_MAX.
+	 */
+	if (pg_stack_T_mul_n_cannot_overflow_p(sizeof_T, sizeof_n) ||
+		!pg_stack_T_mul_n_overflows_p(sizeof_T, n))
+		result = sizeof_T * n;
+	else
+		elog(ERROR, "pg_stack_alloc: %zu * %zu would overflow size_t",
+			 sizeof_T, n);
+
+	/*
+	 * Explain this theory in terms that GCC's -Werror=stringop-overflow
+	 * understands, so it doesn't complain when the return value is passed to
+	 * memset().  This also serves as an assertion.
+	 */
+	pg_assume(result == n ||
+			  result <= pg_stack_max_for_uint_size(sizeof_n));
+
+	return result;
 }
 
 /*
diff --git a/src/test/regress/regress.c b/src/test/regress/regress.c
index 8f9a7ac4221..f82a0a12e8f 100644
--- a/src/test/regress/regress.c
+++ b/src/test/regress/regress.c
@@ -1393,6 +1393,7 @@ test_pg_stack_alloc(PG_FUNCTION_ARGS)
 {
 	char	   *p;
 	char	   *p2 PG_USED_FOR_ASSERTS_ONLY;
+	bool		raised_error PG_USED_FOR_ASSERTS_ONLY;
 
 	/* Need extra space to run under ASAN. */
 	DECLARE_PG_STACK_SIZE(1024 * 8);
@@ -1415,6 +1416,35 @@ test_pg_stack_alloc(PG_FUNCTION_ARGS)
 	p = pg_stack_alloc(0);
 	Assert(pg_stack_ptr_p(p));
 
+	/* Overflow safety in pg_stack_alloc_array(T, n). */
+	Assert(pg_stack_T_mul_n_cannot_overflow_p(1, sizeof(size_t)));
+	Assert(!pg_stack_T_mul_n_cannot_overflow_p(2, sizeof(size_t)));
+	Assert(pg_stack_T_mul_n_cannot_overflow_p(2, sizeof(size_t) / 2));
+	Assert(!pg_stack_T_mul_n_cannot_overflow_p(2, sizeof(size_t)));
+	Assert(!pg_stack_T_mul_n_overflows_p(1, SIZE_MAX));
+	Assert(pg_stack_T_mul_n_overflows_p(2, SIZE_MAX / 2 + 1));
+	Assert(!pg_stack_T_mul_n_overflows_p(2, SIZE_MAX / 2));
+
+	/* When sizeof_T == 1, always returns n. */
+	Assert(pg_stack_T_mul_n(1, sizeof(uint32), SIZE_MAX) == SIZE_MAX);
+	Assert(pg_stack_T_mul_n(1, sizeof(uint64), SIZE_MAX) == SIZE_MAX);
+
+	/* The largest successful value of size_t n. */
+	Assert(pg_stack_T_mul_n(2, sizeof(size_t), SIZE_MAX / 2) == SIZE_MAX - 1);
+
+	/* Exceed value by one for size_t n. */
+	raised_error = false;
+	PG_TRY();
+	{
+		pg_stack_T_mul_n(2, sizeof(size_t), SIZE_MAX / 2 + 1);
+	}
+	PG_CATCH();
+	{
+		raised_error = true;
+	}
+	PG_END_TRY();
+	Assert(raised_error);
+
 	/* Test a range of alignments. */
 #define TEST_ALIGN MAXIMUM_ALIGNOF
 	for (int i = 1; i <= TEST_ALIGN * 8; i *= 2)
-- 
2.50.1 (Apple Git-155)



  [application/octet-stream] v4-0003-Use-pg_stack_alloc-in-locale-code.patch (15.4K, 4-v4-0003-Use-pg_stack_alloc-in-locale-code.patch)
  download | inline diff:
From b8770f7a523ca8519fcbfbf4f80e76de22dd6ee4 Mon Sep 17 00:00:00 2001
From: Thomas Munro <[email protected]>
Date: Thu, 5 Mar 2026 21:38:46 +1300
Subject: [PATCH v4 03/21] Use pg_stack_alloc() in locale code.

Use stack buffers for temporary C strings, wchar_t strings, pg_wchar
strings with the new API.  Some cases were already using open-coded
arrays, while others nearby were not but are obvious candidates, so
let's change them too.

These cases used TEXTBUFLEN (1KB), and they inherit the same limit by
requesting a non-default size:

  DECLARE_STACK_BUFFER_SIZE(LOCALE_STACK_SIZE);

Common operations that were open-coded as pointer arithmetic and
memcpy() are changed to pg_stack_strdup_with_len(...), and operations
that worked with wider characters gain a small amount of type-safety.

Reviewed-by:
Discussion:
---
 src/backend/utils/adt/pg_locale.c      |   6 -
 src/backend/utils/adt/pg_locale_icu.c  |  59 +++-------
 src/backend/utils/adt/pg_locale_libc.c | 150 +++++++++----------------
 src/include/utils/pg_locale.h          |   6 +
 4 files changed, 77 insertions(+), 144 deletions(-)

diff --git a/src/backend/utils/adt/pg_locale.c b/src/backend/utils/adt/pg_locale.c
index 6c5c1019e1e..3a6190892a7 100644
--- a/src/backend/utils/adt/pg_locale.c
+++ b/src/backend/utils/adt/pg_locale.c
@@ -60,12 +60,6 @@
 #define		PGLOCALE_SUPPORT_ERROR(provider) \
 	elog(ERROR, "unsupported collprovider for %s: %c", __func__, provider)
 
-/*
- * This should be large enough that most strings will fit, but small enough
- * that we feel comfortable putting it on the stack
- */
-#define		TEXTBUFLEN			1024
-
 #define		MAX_L10N_DATA		80
 
 /* pg_locale_builtin.c */
diff --git a/src/backend/utils/adt/pg_locale_icu.c b/src/backend/utils/adt/pg_locale_icu.c
index 352b4c3885f..3849ac04a8c 100644
--- a/src/backend/utils/adt/pg_locale_icu.c
+++ b/src/backend/utils/adt/pg_locale_icu.c
@@ -39,16 +39,9 @@
 #include "utils/formatting.h"
 #include "utils/memutils.h"
 #include "utils/pg_locale.h"
+#include "utils/pg_stack_alloc.h"
 #include "utils/syscache.h"
 
-/*
- * Size of stack buffer to use for string transformations, used to avoid heap
- * allocations in typical cases. This should be large enough that most strings
- * will fit, but small enough that we feel comfortable putting it on the
- * stack.
- */
-#define		TEXTBUFLEN			1024
-
 extern pg_locale_t create_pg_locale_icu(Oid collid, MemoryContext context);
 
 #ifdef USE_ICU
@@ -755,23 +748,17 @@ size_t
 strnxfrm_icu(char *dest, size_t destsize, const char *src, ssize_t srclen,
 			 pg_locale_t locale)
 {
-	char		sbuf[TEXTBUFLEN];
-	char	   *buf = sbuf;
 	UChar	   *uchar;
 	int32_t		ulen;
-	size_t		uchar_bsize;
 	Size		result_bsize;
 
+	DECLARE_PG_STACK_SIZE(LOCALE_STACK_SIZE);
+
 	init_icu_converter();
 
 	ulen = uchar_length(icu_converter, src, srclen);
 
-	uchar_bsize = (ulen + 1) * sizeof(UChar);
-
-	if (uchar_bsize > TEXTBUFLEN)
-		buf = palloc(uchar_bsize);
-
-	uchar = (UChar *) buf;
+	uchar = pg_stack_alloc_array(UChar, ulen + 1);
 
 	ulen = uchar_convert(icu_converter, uchar, ulen + 1, src, srclen);
 
@@ -786,8 +773,7 @@ strnxfrm_icu(char *dest, size_t destsize, const char *src, ssize_t srclen,
 	Assert(result_bsize > 0);
 	result_bsize--;
 
-	if (buf != sbuf)
-		pfree(buf);
+	pg_stack_free(uchar);
 
 	/* if dest is defined, it should be nul-terminated */
 	Assert(result_bsize >= destsize || dest[result_bsize] == '\0');
@@ -1020,16 +1006,14 @@ static int
 strncoll_icu(const char *arg1, ssize_t len1,
 			 const char *arg2, ssize_t len2, pg_locale_t locale)
 {
-	char		sbuf[TEXTBUFLEN];
-	char	   *buf = sbuf;
 	int32_t		ulen1;
 	int32_t		ulen2;
-	size_t		bufsize1;
-	size_t		bufsize2;
 	UChar	   *uchar1,
 			   *uchar2;
 	int			result;
 
+	DECLARE_PG_STACK_SIZE(LOCALE_STACK_SIZE);
+
 	/* if encoding is UTF8, use more efficient strncoll_icu_utf8 */
 #ifdef HAVE_UCOL_STRCOLLUTF8
 	Assert(GetDatabaseEncoding() != PG_UTF8);
@@ -1040,14 +1024,8 @@ strncoll_icu(const char *arg1, ssize_t len1,
 	ulen1 = uchar_length(icu_converter, arg1, len1);
 	ulen2 = uchar_length(icu_converter, arg2, len2);
 
-	bufsize1 = (ulen1 + 1) * sizeof(UChar);
-	bufsize2 = (ulen2 + 1) * sizeof(UChar);
-
-	if (bufsize1 + bufsize2 > TEXTBUFLEN)
-		buf = palloc(bufsize1 + bufsize2);
-
-	uchar1 = (UChar *) buf;
-	uchar2 = (UChar *) (buf + bufsize1);
+	uchar1 = pg_stack_alloc_array(UChar, ulen1 + 1);
+	uchar2 = pg_stack_alloc_array(UChar, ulen2 + 1);
 
 	ulen1 = uchar_convert(icu_converter, uchar1, ulen1 + 1, arg1, len1);
 	ulen2 = uchar_convert(icu_converter, uchar2, ulen2 + 1, arg2, len2);
@@ -1056,8 +1034,8 @@ strncoll_icu(const char *arg1, ssize_t len1,
 						  uchar1, ulen1,
 						  uchar2, ulen2);
 
-	if (buf != sbuf)
-		pfree(buf);
+	pg_stack_free(uchar1);
+	pg_stack_free(uchar2);
 
 	return result;
 }
@@ -1068,16 +1046,15 @@ strnxfrm_prefix_icu(char *dest, size_t destsize,
 					const char *src, ssize_t srclen,
 					pg_locale_t locale)
 {
-	char		sbuf[TEXTBUFLEN];
-	char	   *buf = sbuf;
 	UCharIterator iter;
 	uint32_t	state[2];
 	UErrorCode	status;
 	int32_t		ulen = -1;
 	UChar	   *uchar = NULL;
-	size_t		uchar_bsize;
 	Size		result_bsize;
 
+	DECLARE_PG_STACK_SIZE(LOCALE_STACK_SIZE);
+
 	/* if encoding is UTF8, use more efficient strnxfrm_prefix_icu_utf8 */
 	Assert(GetDatabaseEncoding() != PG_UTF8);
 
@@ -1085,12 +1062,7 @@ strnxfrm_prefix_icu(char *dest, size_t destsize,
 
 	ulen = uchar_length(icu_converter, src, srclen);
 
-	uchar_bsize = (ulen + 1) * sizeof(UChar);
-
-	if (uchar_bsize > TEXTBUFLEN)
-		buf = palloc(uchar_bsize);
-
-	uchar = (UChar *) buf;
+	uchar = pg_stack_alloc_array(UChar, ulen + 1);
 
 	ulen = uchar_convert(icu_converter, uchar, ulen + 1, src, srclen);
 
@@ -1108,8 +1080,7 @@ strnxfrm_prefix_icu(char *dest, size_t destsize,
 				(errmsg("sort key generation failed: %s",
 						u_errorName(status))));
 
-	if (buf != sbuf)
-		pfree(buf);
+	pg_stack_free(uchar);
 
 	return result_bsize;
 }
diff --git a/src/backend/utils/adt/pg_locale_libc.c b/src/backend/utils/adt/pg_locale_libc.c
index 78f6ea161a0..874eb161c25 100644
--- a/src/backend/utils/adt/pg_locale_libc.c
+++ b/src/backend/utils/adt/pg_locale_libc.c
@@ -23,6 +23,7 @@
 #include "utils/formatting.h"
 #include "utils/memutils.h"
 #include "utils/pg_locale.h"
+#include "utils/pg_stack_alloc.h"
 #include "utils/syscache.h"
 
 #ifdef __GLIBC__
@@ -72,14 +73,6 @@
  * NB: the coding here assumes pg_wchar is an unsigned type.
  */
 
-/*
- * Size of stack buffer to use for string transformations, used to avoid heap
- * allocations in typical cases. This should be large enough that most strings
- * will fit, but small enough that we feel comfortable putting it on the
- * stack.
- */
-#define		TEXTBUFLEN			1024
-
 extern pg_locale_t create_pg_locale_libc(Oid collid, MemoryContext context);
 
 static int	strncoll_libc(const char *arg1, ssize_t len1,
@@ -502,6 +495,8 @@ strlower_libc_mb(char *dest, size_t destsize, const char *src, ssize_t srclen,
 	size_t		curr_char;
 	size_t		max_size;
 
+	DECLARE_PG_STACK_SIZE(LOCALE_STACK_SIZE);
+
 	if (srclen < 0)
 		srclen = strlen(src);
 
@@ -512,7 +507,7 @@ strlower_libc_mb(char *dest, size_t destsize, const char *src, ssize_t srclen,
 				 errmsg("out of memory")));
 
 	/* Output workspace cannot have more codes than input bytes */
-	workspace = palloc_array(wchar_t, srclen + 1);
+	workspace = pg_stack_alloc_array(wchar_t, srclen + 1);
 
 	char2wchar(workspace, srclen + 1, src, srclen, loc);
 
@@ -523,7 +518,7 @@ strlower_libc_mb(char *dest, size_t destsize, const char *src, ssize_t srclen,
 	 * Make result large enough; case change might change number of bytes
 	 */
 	max_size = curr_char * pg_database_encoding_max_length();
-	result = palloc(max_size + 1);
+	result = pg_stack_alloc(max_size + 1);
 
 	result_size = wchar2char(result, workspace, max_size + 1, loc);
 
@@ -533,8 +528,8 @@ strlower_libc_mb(char *dest, size_t destsize, const char *src, ssize_t srclen,
 		dest[result_size] = '\0';
 	}
 
-	pfree(workspace);
-	pfree(result);
+	pg_stack_free(workspace);
+	pg_stack_free(result);
 
 	return result_size;
 }
@@ -607,6 +602,8 @@ strtitle_libc_mb(char *dest, size_t destsize, const char *src, ssize_t srclen,
 	size_t		curr_char;
 	size_t		max_size;
 
+	DECLARE_PG_STACK_SIZE(LOCALE_STACK_SIZE);
+
 	if (srclen < 0)
 		srclen = strlen(src);
 
@@ -617,7 +614,7 @@ strtitle_libc_mb(char *dest, size_t destsize, const char *src, ssize_t srclen,
 				 errmsg("out of memory")));
 
 	/* Output workspace cannot have more codes than input bytes */
-	workspace = palloc_array(wchar_t, srclen + 1);
+	workspace = pg_stack_alloc_array(wchar_t, srclen + 1);
 
 	char2wchar(workspace, srclen + 1, src, srclen, loc);
 
@@ -634,7 +631,7 @@ strtitle_libc_mb(char *dest, size_t destsize, const char *src, ssize_t srclen,
 	 * Make result large enough; case change might change number of bytes
 	 */
 	max_size = curr_char * pg_database_encoding_max_length();
-	result = palloc(max_size + 1);
+	result = pg_stack_alloc(max_size + 1);
 
 	result_size = wchar2char(result, workspace, max_size + 1, loc);
 
@@ -644,8 +641,8 @@ strtitle_libc_mb(char *dest, size_t destsize, const char *src, ssize_t srclen,
 		dest[result_size] = '\0';
 	}
 
-	pfree(workspace);
-	pfree(result);
+	pg_stack_free(workspace);
+	pg_stack_free(result);
 
 	return result_size;
 }
@@ -700,6 +697,8 @@ strupper_libc_mb(char *dest, size_t destsize, const char *src, ssize_t srclen,
 	size_t		curr_char;
 	size_t		max_size;
 
+	DECLARE_PG_STACK_SIZE(LOCALE_STACK_SIZE);
+
 	if (srclen < 0)
 		srclen = strlen(src);
 
@@ -710,7 +709,7 @@ strupper_libc_mb(char *dest, size_t destsize, const char *src, ssize_t srclen,
 				 errmsg("out of memory")));
 
 	/* Output workspace cannot have more codes than input bytes */
-	workspace = palloc_array(wchar_t, srclen + 1);
+	workspace = pg_stack_alloc_array(wchar_t, srclen + 1);
 
 	char2wchar(workspace, srclen + 1, src, srclen, loc);
 
@@ -721,7 +720,7 @@ strupper_libc_mb(char *dest, size_t destsize, const char *src, ssize_t srclen,
 	 * Make result large enough; case change might change number of bytes
 	 */
 	max_size = curr_char * pg_database_encoding_max_length();
-	result = palloc(max_size + 1);
+	result = pg_stack_alloc(max_size + 1);
 
 	result_size = wchar2char(result, workspace, max_size + 1, loc);
 
@@ -731,8 +730,8 @@ strupper_libc_mb(char *dest, size_t destsize, const char *src, ssize_t srclen,
 		dest[result_size] = '\0';
 	}
 
-	pfree(workspace);
-	pfree(result);
+	pg_stack_free(workspace);
+	pg_stack_free(result);
 
 	return result_size;
 }
@@ -896,48 +895,25 @@ int
 strncoll_libc(const char *arg1, ssize_t len1, const char *arg2, ssize_t len2,
 			  pg_locale_t locale)
 {
-	char		sbuf[TEXTBUFLEN];
-	char	   *buf = sbuf;
-	size_t		bufsize1 = (len1 == -1) ? 0 : len1 + 1;
-	size_t		bufsize2 = (len2 == -1) ? 0 : len2 + 1;
-	const char *arg1n;
-	const char *arg2n;
+	char	   *cstr1 = NULL;
+	char	   *cstr2 = NULL;
 	int			result;
 
-	if (bufsize1 + bufsize2 > TEXTBUFLEN)
-		buf = palloc(bufsize1 + bufsize2);
+	DECLARE_PG_STACK_SIZE(LOCALE_STACK_SIZE);
 
 	/* nul-terminate arguments if necessary */
-	if (len1 == -1)
-	{
-		arg1n = arg1;
-	}
-	else
-	{
-		char	   *buf1 = buf;
+	if (len1 != -1)
+		arg1 = cstr1 = pg_stack_strdup_with_len(arg1, len1);
 
-		memcpy(buf1, arg1, len1);
-		buf1[len1] = '\0';
-		arg1n = buf1;
-	}
+	if (len2 != -1)
+		arg2 = cstr2 = pg_stack_strdup_with_len(arg2, len2);
 
-	if (len2 == -1)
-	{
-		arg2n = arg2;
-	}
-	else
-	{
-		char	   *buf2 = buf + bufsize1;
-
-		memcpy(buf2, arg2, len2);
-		buf2[len2] = '\0';
-		arg2n = buf2;
-	}
+	result = strcoll_l(arg1, arg2, locale->lt);
 
-	result = strcoll_l(arg1n, arg2n, locale->lt);
-
-	if (buf != sbuf)
-		pfree(buf);
+	if (cstr1)
+		pg_stack_free(cstr1);
+	if (cstr2)
+		pg_stack_free(cstr2);
 
 	return result;
 }
@@ -953,25 +929,17 @@ size_t
 strnxfrm_libc(char *dest, size_t destsize, const char *src, ssize_t srclen,
 			  pg_locale_t locale)
 {
-	char		sbuf[TEXTBUFLEN];
-	char	   *buf = sbuf;
-	size_t		bufsize = srclen + 1;
+	char	   *cstr;
 	size_t		result;
 
+	DECLARE_PG_STACK_SIZE(LOCALE_STACK_SIZE);
+
 	if (srclen == -1)
 		return strxfrm_l(dest, src, destsize, locale->lt);
 
-	if (bufsize > TEXTBUFLEN)
-		buf = palloc(bufsize);
-
-	/* nul-terminate argument */
-	memcpy(buf, src, srclen);
-	buf[srclen] = '\0';
-
-	result = strxfrm_l(dest, buf, destsize, locale->lt);
-
-	if (buf != sbuf)
-		pfree(buf);
+	cstr = pg_stack_strdup_with_len(src, srclen);
+	result = strxfrm_l(dest, cstr, destsize, locale->lt);
+	pg_stack_free(cstr);
 
 	/* if dest is defined, it should be nul-terminated */
 	Assert(result >= destsize || dest[result] == '\0');
@@ -1057,15 +1025,13 @@ static int
 strncoll_libc_win32_utf8(const char *arg1, ssize_t len1, const char *arg2,
 						 ssize_t len2, pg_locale_t locale)
 {
-	char		sbuf[TEXTBUFLEN];
-	char	   *buf = sbuf;
-	char	   *a1p,
-			   *a2p;
-	int			a1len;
-	int			a2len;
+	wchar_t    *w1p;
+	wchar_t    *w2p;
 	int			r;
 	int			result;
 
+	DECLARE_PG_STACK_SIZE(LOCALE_STACK_SIZE);
+
 	Assert(GetDatabaseEncoding() == PG_UTF8);
 
 	if (len1 == -1)
@@ -1073,50 +1039,42 @@ strncoll_libc_win32_utf8(const char *arg1, ssize_t len1, const char *arg2,
 	if (len2 == -1)
 		len2 = strlen(arg2);
 
-	a1len = len1 * 2 + 2;
-	a2len = len2 * 2 + 2;
-
-	if (a1len + a2len > TEXTBUFLEN)
-		buf = palloc(a1len + a2len);
-
-	a1p = buf;
-	a2p = buf + a1len;
+	w1p = pg_stack_alloc_array(wchar_t, len1 + 1);
+	w2p = pg_stack_alloc_array(wchar_t, len2 + 1);
 
 	/* API does not work for zero-length input */
 	if (len1 == 0)
 		r = 0;
 	else
 	{
-		r = MultiByteToWideChar(CP_UTF8, 0, arg1, len1,
-								(LPWSTR) a1p, a1len / 2);
+		r = MultiByteToWideChar(CP_UTF8, 0, arg1, len1, w1p, len1);
 		if (!r)
 			ereport(ERROR,
 					(errmsg("could not convert string to UTF-16: error code %lu",
 							GetLastError())));
 	}
-	((LPWSTR) a1p)[r] = 0;
+	w1p[r] = 0;
 
 	if (len2 == 0)
 		r = 0;
 	else
 	{
-		r = MultiByteToWideChar(CP_UTF8, 0, arg2, len2,
-								(LPWSTR) a2p, a2len / 2);
+		r = MultiByteToWideChar(CP_UTF8, 0, arg2, len2, w2p, len2);
 		if (!r)
 			ereport(ERROR,
 					(errmsg("could not convert string to UTF-16: error code %lu",
 							GetLastError())));
 	}
-	((LPWSTR) a2p)[r] = 0;
+	w2p[r] = 0;
 
 	errno = 0;
-	result = wcscoll_l((LPWSTR) a1p, (LPWSTR) a2p, locale->lt);
+	result = wcscoll_l(w1p, w2p, locale->lt);
 	if (result == 2147483647)	/* _NLSCMPERROR; missing from mingw headers */
 		ereport(ERROR,
 				(errmsg("could not compare Unicode strings: %m")));
 
-	if (buf != sbuf)
-		pfree(buf);
+	pg_stack_free(w1p);
+	pg_stack_free(w2p);
 
 	return result;
 }
@@ -1289,8 +1247,12 @@ char2wchar(wchar_t *to, size_t tolen, const char *from, size_t fromlen,
 	else
 #endif							/* WIN32 */
 	{
+		char	   *str;
+
+		DECLARE_PG_STACK_SIZE(LOCALE_STACK_SIZE);
+
 		/* mbstowcs requires ending '\0' */
-		char	   *str = pnstrdup(from, fromlen);
+		str = pg_stack_strdup_with_len(from, fromlen);
 
 		if (loc == (locale_t) 0)
 		{
@@ -1303,7 +1265,7 @@ char2wchar(wchar_t *to, size_t tolen, const char *from, size_t fromlen,
 			result = mbstowcs_l(to, str, tolen, loc);
 		}
 
-		pfree(str);
+		pg_stack_free(str);
 	}
 
 	if (result == -1)
diff --git a/src/include/utils/pg_locale.h b/src/include/utils/pg_locale.h
index 444350bb803..377d47ff49d 100644
--- a/src/include/utils/pg_locale.h
+++ b/src/include/utils/pg_locale.h
@@ -17,6 +17,12 @@
 /* use for libc locale names */
 #define LOCALE_NAME_BUFLEN 128
 
+/*
+ * The default limit for DECLARE_PG_STACK() is very low.  The code in this
+ * module requests a custom size for temporary string conversions.
+ */
+#define LOCALE_STACK_SIZE 1024
+
 /*
  * Maximum number of bytes needed to map a single codepoint. Useful for
  * mapping and processing a single input codepoint at a time with a
-- 
2.50.1 (Apple Git-155)



  [application/octet-stream] v4-0004-Provide-PG_STACK_DIRECTION-configuration-macro.patch (2.0K, 5-v4-0004-Provide-PG_STACK_DIRECTION-configuration-macro.patch)
  download | inline diff:
From d6b2d7f7398392fe765ceefb7869dc6ec4d757f4 Mon Sep 17 00:00:00 2001
From: Thomas Munro <[email protected]>
Date: Wed, 4 Mar 2026 18:12:54 +1300
Subject: [PATCH v4 04/21] Provide PG_STACK_DIRECTION configuration macro.

All modern architectures and ABIs have stacks that grow downward.
Define this in pg_config_manual.h, instead of assuming it could go
either way in every run-time check performed in stack_depth.c.

A proposed patch would make greater use of this information at
compile time.

An automatic way of determining the value at compile-time could always
be developed if another system with upward-growing-stack ever appears.

Reviewed-by:
Discussion:
---
 src/backend/utils/misc/stack_depth.c | 10 ++++++++--
 src/include/pg_config_manual.h       |  6 ++++++
 2 files changed, 14 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index 61a07cf824e..c6aeee5f475 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -118,9 +118,15 @@ stack_is_too_deep(void)
 
 	/*
 	 * Take abs value, since stacks grow up on some machines, down on others
+	 * (historical).
 	 */
-	if (stack_depth < 0)
-		stack_depth = -stack_depth;
+	stack_depth *= -(PG_STACK_DIRECTION);
+
+	/*
+	 * If this assertion fails, either PG_STACK_DIRECTION is wrong or this
+	 * system doesn't have a traditional stack as we expect.
+	 */
+	Assert(stack_depth >= 0);
 
 	/*
 	 * Trouble?
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index 521b49b8888..abe371f5d69 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -222,6 +222,12 @@
  */
 #define PG_IO_ALIGN_SIZE		4096
 
+/*
+ * Assumed direction of the stack, -1 or +1.  Historically this varied, but all
+ * modern systems have stacks that grow downward.
+ */
+#define PG_STACK_DIRECTION		-1
+
 /*
  *------------------------------------------------------------------------
  * The following symbols are for enabling debugging code, not for
-- 
2.50.1 (Apple Git-155)



  [application/octet-stream] v4-0005-Refactor-check_stack_depth-mechanism.patch (10.1K, 6-v4-0005-Refactor-check_stack_depth-mechanism.patch)
  download | inline diff:
From 7d74ce5d6292fa8e4c9e785dba50e10827f7f88c Mon Sep 17 00:00:00 2001
From: Thomas Munro <[email protected]>
Date: Fri, 6 Mar 2026 20:53:23 +1300
Subject: [PATCH v4 05/21] Refactor check_stack_depth() mechanism.

* The check is now inline.
* __builtin_stack_address() is used if available.
* The stack is now assumed to grow in PG_STACK_DIRECTION.
* A new "soft" limit is exposed, for a later patch to use.

Unfortunately HAVE__BUILTIN_STACK_ADDRESS causes problems when building
bitcode if configure was run against gcc, because Clang only added this
builtin in recent version 22.  Defend against that with an explicit
test for Clang.
---
 configure                            |  38 +++++++++
 configure.ac                         |   1 +
 meson.build                          |   1 +
 src/backend/utils/misc/stack_depth.c | 113 +++++++++------------------
 src/include/miscadmin.h              |  54 ++++++++++++-
 src/include/pg_config.h.in           |   3 +
 6 files changed, 134 insertions(+), 76 deletions(-)

diff --git a/configure b/configure
index f69331f0748..2b80b62dffe 100755
--- a/configure
+++ b/configure
@@ -16094,6 +16094,44 @@ cat >>confdefs.h <<_ACEOF
 #define HAVE__BUILTIN_FRAME_ADDRESS 1
 _ACEOF
 
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for __builtin_stack_address" >&5
+$as_echo_n "checking for __builtin_stack_address... " >&6; }
+if ${pgac_cv__builtin_stack_address+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+void *
+call__builtin_stack_address(void)
+{
+    return __builtin_stack_address();
+}
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  pgac_cv__builtin_stack_address=yes
+else
+  pgac_cv__builtin_stack_address=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $pgac_cv__builtin_stack_address" >&5
+$as_echo "$pgac_cv__builtin_stack_address" >&6; }
+if test x"${pgac_cv__builtin_stack_address}" = xyes ; then
+
+cat >>confdefs.h <<_ACEOF
+#define HAVE__BUILTIN_STACK_ADDRESS 1
+_ACEOF
+
 fi
 
 # We require 64-bit fseeko() to be available, but run this check anyway
diff --git a/configure.ac b/configure.ac
index fead9a6ce99..e0c4a089f61 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1867,6 +1867,7 @@ PGAC_CHECK_BUILTIN_FUNC([__builtin_ctz], [unsigned int x])
 # __builtin_frame_address may draw a diagnostic for non-constant argument,
 # so it needs a different test function.
 PGAC_CHECK_BUILTIN_FUNC_PTR([__builtin_frame_address], [0])
+PGAC_CHECK_BUILTIN_FUNC_PTR([__builtin_stack_address], [])
 
 # We require 64-bit fseeko() to be available, but run this check anyway
 # in case it finds that _LARGEFILE_SOURCE has to be #define'd for that.
diff --git a/meson.build b/meson.build
index f7a87edcc94..12dc989a5ab 100644
--- a/meson.build
+++ b/meson.build
@@ -2049,6 +2049,7 @@ builtins = [
   'ctz',
   'constant_p',
   'frame_address',
+  'stack_address',
   'unreachable',
 ]
 
diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c
index c6aeee5f475..aea589e1a27 100644
--- a/src/backend/utils/misc/stack_depth.c
+++ b/src/backend/utils/misc/stack_depth.c
@@ -25,15 +25,28 @@
 /* GUC variable for maximum stack depth (measured in kilobytes) */
 int			max_stack_depth = 100;
 
-/* max_stack_depth converted to bytes for speed of checking */
-static ssize_t max_stack_depth_bytes = 100 * (ssize_t) 1024;
-
 /*
- * Stack base pointer -- initialized by set_stack_base(), which
- * should be called from main().
+ * Thresholds -- initialized by set_stack_base().  These have external linkage
+ * so they can be used by inlined code.
  */
-static char *stack_base_ptr = NULL;
+const void *stack_base_ptr = NULL;
+const void *stack_soft_limit_ptr = NULL;
+const void *stack_hard_limit_ptr = NULL;
+
+
+static void
+compute_limit_ptrs(int kb)
+{
+	ssize_t		bytes = kb * (ssize_t) 1024;
+
+	/* Advertise a soft limit halfway through the allowed size. */
+	stack_soft_limit_ptr = (const char *) stack_base_ptr +
+		(bytes / 2) * PG_STACK_DIRECTION;
 
+	/* This is the size at which check_stack_depth() will fail. */
+	stack_hard_limit_ptr = (const char *) stack_base_ptr +
+		bytes * PG_STACK_DIRECTION;
+}
 
 /*
  * set_stack_base: set up reference point for stack depth checking
@@ -48,7 +61,7 @@ set_stack_base(void)
 #endif
 	pg_stack_base_t old;
 
-	old = stack_base_ptr;
+	old = (pg_stack_base_t) stack_base_ptr;
 
 	/*
 	 * Set up reference point for stack depth checking.  On recent gcc we use
@@ -61,6 +74,8 @@ set_stack_base(void)
 	stack_base_ptr = &stack_base;
 #endif
 
+	compute_limit_ptrs(max_stack_depth);
+
 	return old;
 }
 
@@ -76,73 +91,11 @@ set_stack_base(void)
 void
 restore_stack_base(pg_stack_base_t base)
 {
-	stack_base_ptr = base;
-}
-
-
-/*
- * check_stack_depth/stack_is_too_deep: check for excessively deep recursion
- *
- * This should be called someplace in any recursive routine that might possibly
- * recurse deep enough to overflow the stack.  Most Unixen treat stack
- * overflow as an unrecoverable SIGSEGV, so we want to error out ourselves
- * before hitting the hardware limit.
- *
- * check_stack_depth() just throws an error summarily.  stack_is_too_deep()
- * can be used by code that wants to handle the error condition itself.
- */
-void
-check_stack_depth(void)
-{
-	if (stack_is_too_deep())
-	{
-		ereport(ERROR,
-				(errcode(ERRCODE_STATEMENT_TOO_COMPLEX),
-				 errmsg("stack depth limit exceeded"),
-				 errhint("Increase the configuration parameter \"max_stack_depth\" (currently %dkB), "
-						 "after ensuring the platform's stack depth limit is adequate.",
-						 max_stack_depth)));
-	}
-}
-
-bool
-stack_is_too_deep(void)
-{
-	char		stack_top_loc;
-	ssize_t		stack_depth;
-
-	/*
-	 * Compute distance from reference point to my local variables
-	 */
-	stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc);
+	stack_base_ptr = (const void *) base;
 
-	/*
-	 * Take abs value, since stacks grow up on some machines, down on others
-	 * (historical).
-	 */
-	stack_depth *= -(PG_STACK_DIRECTION);
-
-	/*
-	 * If this assertion fails, either PG_STACK_DIRECTION is wrong or this
-	 * system doesn't have a traditional stack as we expect.
-	 */
-	Assert(stack_depth >= 0);
-
-	/*
-	 * Trouble?
-	 *
-	 * The test on stack_base_ptr prevents us from erroring out if called
-	 * before that's been set.  Logically it should be done first, but putting
-	 * it last avoids wasting cycles during normal cases.
-	 */
-	if (stack_depth > max_stack_depth_bytes &&
-		stack_base_ptr != NULL)
-		return true;
-
-	return false;
+	compute_limit_ptrs(max_stack_depth);
 }
 
-
 /* GUC check hook for max_stack_depth */
 bool
 check_max_stack_depth(int *newval, void **extra, GucSource source)
@@ -160,13 +113,25 @@ check_max_stack_depth(int *newval, void **extra, GucSource source)
 	return true;
 }
 
+/*
+ * Out-of-line part of check_stack_depth().
+ */
+void
+report_stack_is_too_deep(void)
+{
+	ereport(ERROR,
+			(errcode(ERRCODE_STATEMENT_TOO_COMPLEX),
+			 errmsg("stack depth limit exceeded"),
+			 errhint("Increase the configuration parameter \"max_stack_depth\" (currently %dkB), "
+					 "after ensuring the platform's stack depth limit is adequate.",
+					 max_stack_depth)));
+}
+
 /* GUC assign hook for max_stack_depth */
 void
 assign_max_stack_depth(int newval, void *extra)
 {
-	ssize_t		newval_bytes = newval * (ssize_t) 1024;
-
-	max_stack_depth_bytes = newval_bytes;
+	compute_limit_ptrs(newval);
 }
 
 /*
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index f16f35659b9..28cfdd05782 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -293,15 +293,65 @@ extern PGDLLIMPORT bool VacuumCostActive;
 
 extern PGDLLIMPORT int max_stack_depth;
 
+extern PGDLLIMPORT const void *stack_base_ptr;
+extern PGDLLIMPORT const void *stack_soft_limit_ptr;
+extern PGDLLIMPORT const void *stack_hard_limit_ptr;
+
 /* Required daylight between max_stack_depth and the kernel limit, in bytes */
 #define STACK_DEPTH_SLOP (512 * 1024)
 
+/* Check if stack pointer p1 is deeper than p2. */
+static inline bool
+stack_ptr_deeper_p(const void *p1, const void *p2)
+{
+	const char *c1 = (const char *) p1;
+	const char *c2 = (const char *) p2;
+
+	if (PG_STACK_DIRECTION < 0)
+		return c1 < c2;
+	else
+		return c1 > c2;
+}
+
+/* Check if the stack has exceeded the configured hard limit. */
+static inline bool
+stack_is_too_deep(void)
+{
+#if defined(HAVE__BUILTIN_STACK_ADDRESS) &&								\
+	(!defined(__clang__) || __clang_major__ >= 22)
+	const char *sp = (const char *) __builtin_stack_address();
+#else
+	char		c;
+	const char *sp = &c;
+#endif
+
+	return stack_ptr_deeper_p(sp, stack_hard_limit_ptr);
+}
+
+extern void report_stack_is_too_deep(void);
+
+/*
+ * check_stack_depth/stack_is_too_deep: check for excessively deep recursion
+ *
+ * This should be called someplace in any recursive routine that might possibly
+ * recurse deep enough to overflow the stack.  Most Unixen treat stack
+ * overflow as an unrecoverable SIGSEGV, so we want to error out ourselves
+ * before hitting the hardware limit.
+ *
+ * check_stack_depth() just throws an error summarily.  stack_is_too_deep()
+ * can be used by code that wants to handle the error condition itself.
+ */
+static inline void
+check_stack_depth(void)
+{
+	if (unlikely(stack_is_too_deep()))
+		report_stack_is_too_deep();
+}
+
 typedef char *pg_stack_base_t;
 
 extern pg_stack_base_t set_stack_base(void);
 extern void restore_stack_base(pg_stack_base_t base);
-extern void check_stack_depth(void);
-extern bool stack_is_too_deep(void);
 extern ssize_t get_stack_depth_rlimit(void);
 
 /* in tcop/utility.c */
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 79379a4d125..c4574406e04 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -538,6 +538,9 @@
 /* Define to 1 if your compiler understands __builtin_$op_overflow. */
 #undef HAVE__BUILTIN_OP_OVERFLOW
 
+/* Define to 1 if your compiler understands __builtin_stack_address. */
+#undef HAVE__BUILTIN_STACK_ADDRESS
+
 /* Define to 1 if your compiler understands __builtin_types_compatible_p. */
 #undef HAVE__BUILTIN_TYPES_COMPATIBLE_P
 
-- 
2.50.1 (Apple Git-155)



  [application/octet-stream] v4-0006-Provide-way-for-macros-to-detect-PG_TRY-scope.patch (10.9K, 7-v4-0006-Provide-way-for-macros-to-detect-PG_TRY-scope.patch)
  download | inline diff:
From 18257373a6a972f154352985232e1051e075deff Mon Sep 17 00:00:00 2001
From: Thomas Munro <[email protected]>
Date: Wed, 4 Mar 2026 17:30:46 +1300
Subject: [PATCH v4 06/21] Provide way for macros to detect PG_TRY scope.

For code that can't work safely around setjmp()/longjmp() and thus in a
PG_TRY/CATCH/FINALLY block, provide a macro
"pg_in_lexical_scope(PG_TRY)" that can be tested at compile time.
---
 configure                  | 101 +++++++++++++++++++++++++++++++++++++
 configure.ac               |  13 +++++
 meson.build                |   9 ++++
 src/include/c.h            |  27 ++++++++++
 src/include/pg_config.h.in |   6 +++
 src/include/utils/elog.h   |  36 +++++++++++++
 6 files changed, 192 insertions(+)

diff --git a/configure b/configure
index 2b80b62dffe..a5ee71cf728 100755
--- a/configure
+++ b/configure
@@ -5992,6 +5992,107 @@ if test x"$pgac_cv_prog_CXX_cxxflags__Wshadow_compatible_local" = x"yes"; then
 fi
 
 
+  # We also want to make the usage of the above options available to macros.
+  NOT_THE_CFLAGS=""
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether ${CC} supports -Wshadow=compatible-local, for NOT_THE_CFLAGS" >&5
+$as_echo_n "checking whether ${CC} supports -Wshadow=compatible-local, for NOT_THE_CFLAGS... " >&6; }
+if ${pgac_cv_prog_CC_cflags__Wshadow_compatible_local+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  pgac_save_CFLAGS=$CFLAGS
+pgac_save_CC=$CC
+CC=${CC}
+CFLAGS="${NOT_THE_CFLAGS} -Wshadow=compatible-local"
+ac_save_c_werror_flag=$ac_c_werror_flag
+ac_c_werror_flag=yes
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  pgac_cv_prog_CC_cflags__Wshadow_compatible_local=yes
+else
+  pgac_cv_prog_CC_cflags__Wshadow_compatible_local=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ac_c_werror_flag=$ac_save_c_werror_flag
+CFLAGS="$pgac_save_CFLAGS"
+CC="$pgac_save_CC"
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $pgac_cv_prog_CC_cflags__Wshadow_compatible_local" >&5
+$as_echo "$pgac_cv_prog_CC_cflags__Wshadow_compatible_local" >&6; }
+if test x"$pgac_cv_prog_CC_cflags__Wshadow_compatible_local" = x"yes"; then
+  NOT_THE_CFLAGS="${NOT_THE_CFLAGS} -Wshadow=compatible-local"
+fi
+
+  if test -n "$NOT_THE_CFLAGS"; then
+
+$as_echo "#define WARNING_CC_SHADOW_COMPATIBLE_LOCAL 1" >>confdefs.h
+
+  fi
+  NOT_THE_CFLAGS=""
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether ${CXX} supports -Wshadow=compatible-local, for NOT_THE_CFLAGS" >&5
+$as_echo_n "checking whether ${CXX} supports -Wshadow=compatible-local, for NOT_THE_CFLAGS... " >&6; }
+if ${pgac_cv_prog_CXX_cxxflags__Wshadow_compatible_local+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  pgac_save_CXXFLAGS=$CXXFLAGS
+pgac_save_CXX=$CXX
+CXX=${CXX}
+CXXFLAGS="${NOT_THE_CFLAGS} -Wshadow=compatible-local"
+ac_save_cxx_werror_flag=$ac_cxx_werror_flag
+ac_cxx_werror_flag=yes
+ac_ext=cpp
+ac_cpp='$CXXCPP $CPPFLAGS'
+ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_cxx_compiler_gnu
+
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_cxx_try_compile "$LINENO"; then :
+  pgac_cv_prog_CXX_cxxflags__Wshadow_compatible_local=yes
+else
+  pgac_cv_prog_CXX_cxxflags__Wshadow_compatible_local=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+ac_cxx_werror_flag=$ac_save_cxx_werror_flag
+CXXFLAGS="$pgac_save_CXXFLAGS"
+CXX="$pgac_save_CXX"
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $pgac_cv_prog_CXX_cxxflags__Wshadow_compatible_local" >&5
+$as_echo "$pgac_cv_prog_CXX_cxxflags__Wshadow_compatible_local" >&6; }
+if test x"$pgac_cv_prog_CXX_cxxflags__Wshadow_compatible_local" = x"yes"; then
+  NOT_THE_CFLAGS="${NOT_THE_CFLAGS} -Wshadow=compatible-local"
+fi
+
+  if test -n "$NOT_THE_CFLAGS"; then
+
+$as_echo "#define WARNING_CXX_SHADOW_COMPATIBLE_LOCAL 1" >>confdefs.h
+
+  fi
   # This was included in -Wall/-Wformat in older GCC versions
 
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether ${CC} supports -Wformat-security, for CFLAGS" >&5
diff --git a/configure.ac b/configure.ac
index e0c4a089f61..b13cfa1813f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -577,6 +577,19 @@ if test "$GCC" = yes -a "$ICC" = no; then
   PGAC_PROG_CXX_CFLAGS_OPT([-Wcast-function-type])
   PGAC_PROG_CC_CFLAGS_OPT([-Wshadow=compatible-local])
   PGAC_PROG_CXX_CFLAGS_OPT([-Wshadow=compatible-local])
+  # We also want to make the usage of the above options available to macros.
+  NOT_THE_CFLAGS=""
+  PGAC_PROG_VARCC_VARFLAGS_OPT(CC, NOT_THE_CFLAGS, [-Wshadow=compatible-local])
+  if test -n "$NOT_THE_CFLAGS"; then
+    AC_DEFINE([WARNING_CC_SHADOW_COMPATIBLE_LOCAL], 1,
+              [Define to 1 if your C compiler understands -Wshadow=compatible-local])
+  fi
+  NOT_THE_CFLAGS=""
+  PGAC_PROG_VARCXX_VARFLAGS_OPT(CXX, NOT_THE_CFLAGS, [-Wshadow=compatible-local])
+  if test -n "$NOT_THE_CFLAGS"; then
+    AC_DEFINE([WARNING_CXX_SHADOW_COMPATIBLE_LOCAL], 1,
+              [Define to 1 if your C++ compiler understands -Wshadow=compatible-local])
+  fi
   # This was included in -Wall/-Wformat in older GCC versions
   PGAC_PROG_CC_CFLAGS_OPT([-Wformat-security])
   PGAC_PROG_CXX_CFLAGS_OPT([-Wformat-security])
diff --git a/meson.build b/meson.build
index 12dc989a5ab..db1f38dff93 100644
--- a/meson.build
+++ b/meson.build
@@ -2208,6 +2208,15 @@ if have_cxx
   cxxflags_warn += cxx.get_supported_arguments(common_warning_flags)
 endif
 
+# Advertise whether -Wshadow=compatible-local is active, so that it can be
+# disabled in cases where that is expected.
+if cc.has_argument('-Wshadow=compatible-local')
+  cdata.set('WARNING_CC_SHADOW_COMPATIBLE_LOCAL', 1)
+endif
+if have_cxx and cxx.has_argument('-Wshadow=compatible-local')
+  cdata.set('WARNING_CXX_SHADOW_COMPATIBLE_LOCAL', 1)
+endif
+
 # To require fallthrough attribute annotations, use
 # -Wimplicit-fallthrough=5 with gcc and -Wimplicit-fallthrough with
 # clang.  The latter is also accepted on gcc but does not enforce
diff --git a/src/include/c.h b/src/include/c.h
index 29fef2f54e1..97ba778eb90 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -561,6 +561,33 @@ typedef void (*pg_funcptr_t) (void);
 #define HAVE_PRAGMA_GCC_SYSTEM_HEADER	1
 #endif
 
+/*
+ * Sometimes it is useful to be able to disable GCC's shadow warnings for a
+ * specific declaration.
+ *
+ * -Wdeclaration-after-statement is also temporarily suppressed, because the
+ * pragma itself is treated as a statement while the purpose of these macros
+ * is to wrap a declaration.
+ */
+#if !defined(__clang__) && \
+	((defined(__cplusplus) && defined(WARNING_CXX_SHADOW_COMPATIBLE_LOCAL)) || \
+	 (!defined(__cplusplus) && defined(WARNING_CC_SHADOW_COMPATIBLE_LOCAL)))
+#if !defined(__cplusplus)
+#define pg_pragma_ignore_declaration_after_statement \
+	_Pragma("GCC diagnostic ignored \"-Wdeclaration-after-statement\"");
+#else
+#define pg_pragma_ignore_declaration_after_statement
+#endif
+#define pg_begin_ignore_shadow_warning()								\
+	_Pragma("GCC diagnostic push");										\
+	_Pragma("GCC diagnostic ignored \"-Wshadow=compatible-local\"");	\
+	pg_pragma_ignore_declaration_after_statement
+#define pg_end_ignore_shadow_warning()			\
+	_Pragma("GCC diagnostic pop")
+#else
+#define pg_begin_ignore_shadow_warning()
+#define pg_end_ignore_shadow_warning()
+#endif
 
 /* ----------------------------------------------------------------
  *				Section 2:	bool, true, false
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index c4574406e04..f956012be3d 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -762,6 +762,12 @@
 /* Define to 1 to build with ZSTD support. (--with-zstd) */
 #undef USE_ZSTD
 
+/* Define to 1 if your C compiler understands -Wshadow=compatible-local */
+#undef WARNING_CC_SHADOW_COMPATIBLE_LOCAL
+
+/* Define to 1 if your C++ compiler understands -Wshadow=compatible-local */
+#undef WARNING_CXX_SHADOW_COMPATIBLE_LOCAL
+
 /* Define WORDS_BIGENDIAN to 1 if your processor stores words with the most
    significant byte first (like Motorola and SPARC, unlike Intel). */
 #if defined AC_APPLE_UNIVERSAL_BUILD
diff --git a/src/include/utils/elog.h b/src/include/utils/elog.h
index a12b379e09a..30afcfbf453 100644
--- a/src/include/utils/elog.h
+++ b/src/include/utils/elog.h
@@ -316,6 +316,39 @@ typedef struct ErrorContextCallback
 extern PGDLLIMPORT ErrorContextCallback *error_context_stack;
 
 
+/* Support for testing if a macro is inside a PG_TRY/... block. */
+typedef char pg_lexical_scope_false_type;
+typedef int pg_lexical_scope_true_type;
+
+/* Declare a tag as a variable in global scope.  It has no storage. */
+#define pg_declare_lexical_scope_tag(name)			\
+	extern pg_lexical_scope_false_type \
+	lexical_scope_tag_##name pg_attribute_unused()
+
+/*
+ * Declare a tag as a local typedef that hides the name of the variable,
+ * within the current lexical scope.
+ *
+ * On its own, "shadowing" a variable name with a type name doesn't trigger
+ * GCC's -Wshadow=compatible-local warning, but we want to be able to handle
+ * nested PG_TRY blocks using the same tag name.  Disable the warning locally,
+ * if it's enabled.
+ */
+#define pg_set_lexical_scope_tag(name)									\
+	pg_begin_ignore_shadow_warning()									\
+	typedef pg_lexical_scope_true_type									\
+		lexical_scope_tag_##name pg_attribute_unused();					\
+	pg_end_ignore_shadow_warning()
+
+/* Test whether we are inside a lexical scope that has "set" the tag. */
+#define pg_in_lexical_scope_p(name)				\
+	(sizeof(lexical_scope_tag_##name) == sizeof(pg_lexical_scope_true_type))
+
+pg_declare_lexical_scope_tag(PG_TRY);
+pg_declare_lexical_scope_tag(PG_CATCH);
+pg_declare_lexical_scope_tag(PG_FINALLY);
+
+
 /*----------
  * API for catching ereport(ERROR) exits.  Use these macros like so:
  *
@@ -391,12 +424,14 @@ extern PGDLLIMPORT ErrorContextCallback *error_context_stack;
 		bool _do_rethrow##__VA_ARGS__ = false; \
 		if (sigsetjmp(_local_sigjmp_buf##__VA_ARGS__, 0) == 0) \
 		{ \
+			pg_set_lexical_scope_tag(PG_TRY); \
 			PG_exception_stack = &_local_sigjmp_buf##__VA_ARGS__
 
 #define PG_CATCH(...)	\
 		} \
 		else \
 		{ \
+			pg_set_lexical_scope_tag(PG_CATCH); \
 			PG_exception_stack = _save_exception_stack##__VA_ARGS__; \
 			error_context_stack = _save_context_stack##__VA_ARGS__
 
@@ -405,6 +440,7 @@ extern PGDLLIMPORT ErrorContextCallback *error_context_stack;
 		else \
 			_do_rethrow##__VA_ARGS__ = true; \
 		{ \
+			pg_set_lexical_scope_tag(PG_FINALLY); \
 			PG_exception_stack = _save_exception_stack##__VA_ARGS__; \
 			error_context_stack = _save_context_stack##__VA_ARGS__
 
-- 
2.50.1 (Apple Git-155)



  [application/octet-stream] v4-0007-pg_stack_alloc-Use-alloca-for-allocation.patch (17.3K, 8-v4-0007-pg_stack_alloc-Use-alloca-for-allocation.patch)
  download | inline diff:
From 26854803952ca19d9eafa877e452eda59799a223 Mon Sep 17 00:00:00 2001
From: Thomas Munro <[email protected]>
Date: Mon, 16 Mar 2026 15:43:38 +1300
Subject: [PATCH v4 07/21] pg_stack_alloc: Use alloca() for allocation.

Add support for alloca(), with defenses against stack overflow.  The
existing per-function limit applies, and a secondary limit that stops
allocating on the stack if the total stack depth has reached 50% of the
size that would cause check_stack_depth() to ereport().  The default
per-function limit is increased to 1024 when using alloca().

This is available only on systems where the stack grows down towards
zero using GCC, Clang or MSVC, but that's all the systems we support.
The array-based implementation is still there but won't be used by
default.

Reviewed-by:
Discussion:
---
 configure                          |  38 +++++
 configure.ac                       |   1 +
 meson.build                        |   1 +
 src/include/pg_config.h.in         |   3 +
 src/include/utils/pg_stack_alloc.h | 236 ++++++++++++++++++++++++++++-
 src/test/regress/regress.c         |  78 ++++++++++
 6 files changed, 352 insertions(+), 5 deletions(-)

diff --git a/configure b/configure
index a5ee71cf728..efe6249b283 100755
--- a/configure
+++ b/configure
@@ -16233,6 +16233,44 @@ cat >>confdefs.h <<_ACEOF
 #define HAVE__BUILTIN_STACK_ADDRESS 1
 _ACEOF
 
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for __builtin_alloca" >&5
+$as_echo_n "checking for __builtin_alloca... " >&6; }
+if ${pgac_cv__builtin_alloca+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+void *
+call__builtin_alloca(void)
+{
+    return __builtin_alloca(0);
+}
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  pgac_cv__builtin_alloca=yes
+else
+  pgac_cv__builtin_alloca=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $pgac_cv__builtin_alloca" >&5
+$as_echo "$pgac_cv__builtin_alloca" >&6; }
+if test x"${pgac_cv__builtin_alloca}" = xyes ; then
+
+cat >>confdefs.h <<_ACEOF
+#define HAVE__BUILTIN_ALLOCA 1
+_ACEOF
+
 fi
 
 # We require 64-bit fseeko() to be available, but run this check anyway
diff --git a/configure.ac b/configure.ac
index b13cfa1813f..50e874a3b70 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1881,6 +1881,7 @@ PGAC_CHECK_BUILTIN_FUNC([__builtin_ctz], [unsigned int x])
 # so it needs a different test function.
 PGAC_CHECK_BUILTIN_FUNC_PTR([__builtin_frame_address], [0])
 PGAC_CHECK_BUILTIN_FUNC_PTR([__builtin_stack_address], [])
+PGAC_CHECK_BUILTIN_FUNC_PTR([__builtin_alloca], [0])
 
 # We require 64-bit fseeko() to be available, but run this check anyway
 # in case it finds that _LARGEFILE_SOURCE has to be #define'd for that.
diff --git a/meson.build b/meson.build
index db1f38dff93..25272c43a36 100644
--- a/meson.build
+++ b/meson.build
@@ -2042,6 +2042,7 @@ endif
 # Check if various builtins exist. Some builtins are tested separately,
 # because we want to test something more complicated than the generic case.
 builtins = [
+  'alloca',
   'bswap16',
   'bswap32',
   'bswap64',
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index f956012be3d..66df0e833c8 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -514,6 +514,9 @@
 /* Define to 1 if you have XSAVE intrinsics. */
 #undef HAVE_XSAVE_INTRINSICS
 
+/* Define to 1 if your compiler understands __builtin_alloca. */
+#undef HAVE__BUILTIN_ALLOCA
+
 /* Define to 1 if your compiler understands __builtin_bswap16. */
 #undef HAVE__BUILTIN_BSWAP16
 
diff --git a/src/include/utils/pg_stack_alloc.h b/src/include/utils/pg_stack_alloc.h
index cd3c3838013..a82ed7cdf0e 100644
--- a/src/include/utils/pg_stack_alloc.h
+++ b/src/include/utils/pg_stack_alloc.h
@@ -3,16 +3,20 @@
  * pg_stack_alloc.h
  *		Allocator for objects that don't escape the current function.
  *
- * A palloc()-like interface for allocating memory on the stack.  The initial
- * implementation uses an array declared statically.
+ * A palloc()-like interface to alloca(), for allocating memory on the stack.
+ * Raw alloca() is usually considered dangerous because of its inherent stack
+ * overflow risk, but this interface imposes limits on stack size and falls
+ * back to regular palloc() when they would be exceeded.
+ *
+ * GCC, Clang and MSVC are supported, as long as PG_STACK_DIRECTION is
+ * downwards (all modern systems).  A simple array-based emulation
+ * is provided for systems that can't use that.
  *
  * Once stack space is exhausted, allocations silently fall back to using
  * palloc().  Memory should therefore still be freed explicitly with
  * pg_stack_free() or MemoryContext-level cleanup.  It is a no-op in the
  * common case that pfree() doesn't need to be called.
  *
- * XXX A space-limited version of alloca() could be added.
- *
  * XXX It might be possible to use something like "defer" or equivalent
  * compiler extensions to clean up palloc()'d memory automatically, in future
  * work, and then pg_stack_free() would not be necessary.
@@ -28,6 +32,7 @@
 #define PG_STACK_ALLOC_H
 
 #include "utils/elog.h"
+#include "utils/memutils.h"		/* for MaxAllocSize */
 #include "utils/palloc.h"
 #include "miscadmin.h"
 
@@ -36,13 +41,35 @@
 
 
 /* #define PG_STACK_USE_PALLOC_LOG "/tmp/pg_stack_alloc.csv" */
+/* #define PG_STACK_USE_ARRAY */
+
+/* Spelling and alignment of alloca() on this system. */
+#ifdef HAVE__BUILTIN_ALLOCA
+/*
+ * Using the builtin avoids the need to figure out which header to include on
+ * each platform, and ensures we get GCC/Clang's documented behavior and not
+ * some other alloca() implementation technique with unknown characteristics.
+ */
+#define pg_stack_alloca(size) __builtin_alloca(size)
+#define	ALIGNOF_ALLOCA __BIGGEST_ALIGNMENT__
+#elif defined(_MSC_VER)
+#include <malloc.h>
+#define pg_stack_alloca(size) alloca(size)
+/* https://learn.microsoft.com/en-us/cpp/build/stack-usage?view=msvc-170 */
+#define ALIGNOF_ALLOCA 16
+#endif
 
 /* Choose which implementation to use, if not already defined manually. */
 #if !defined(PG_STACK_USE_ARRAY) &&									\
+	!defined(PG_STACK_USE_ALLOC) &&									\
 	!defined(PG_STACK_USE_PALLOC) &&								\
 	!defined(PG_STACK_USE_PALLOC_LOG)
+#if PG_STACK_DIRECTION < 0 && defined(pg_stack_alloca)
+#define PG_STACK_USE_ALLOCA
+#else
 #define PG_STACK_USE_ARRAY
 #endif
+#endif
 
 
 /*-------------------------------------------------------------------------
@@ -56,8 +83,13 @@
 #define PG_STACK_MAX_ALIGN 4096
 
 /* Declare a stack allocator with a default size limit. */
+#ifdef PG_STACK_USE_ARRAY
 #define DECLARE_PG_STACK()												\
 	DECLARE_PG_STACK_SIZE(128)
+#else
+#define DECLARE_PG_STACK()												\
+	DECLARE_PG_STACK_SIZE(1024)
+#endif
 
 /*
  * As above, but with a caller-supplied limit on stack usage.  The default
@@ -155,7 +187,13 @@
  */
 #define pg_stack_sanity_checks(align)									\
 	(AssertMacro((align) > 0 && (align) <= PG_STACK_MAX_ALIGN),			\
-	 AssertMacro(((align) & ((align) - 1)) == 0 /* power-of-two? */))
+	 AssertMacro(((align) & ((align) - 1)) == 0 /* power-of-two? */),	\
+	 StaticAssertExpr(!pg_in_lexical_scope_p(PG_TRY),					\
+					  "pg_stack API not allowed in PG_TRY"),			\
+	 StaticAssertExpr(!pg_in_lexical_scope_p(PG_CATCH),					\
+					  "pg_stack API not allowed in PG_CATCH"),			\
+	 StaticAssertExpr(!pg_in_lexical_scope_p(PG_FINALLY),				\
+					  "pg_stack API not allowed in PG_FINALLY"))
 
 /* For assertions. */
 static inline bool
@@ -358,4 +396,192 @@ pg_stack_alloc_aligned_from_array(char *array,
 
 #endif
 
+
+/*-------------------------------------------------------------------------
+ *
+ * alloca()-based implementation.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifdef PG_STACK_USE_ALLOCA
+
+/* Required interface macros. */
+
+#define DECLARE_PG_STACK_IMPL(size)										\
+	pg_stack_impl_decl													\
+	const char *pg_stack_limit =										\
+		pg_stack_compute_limit(pg_stack_lower_bound(), (size))
+
+#define pg_stack_alloc_aligned_impl(size, align)						\
+	(likely(pg_stack_alloca_would_fit_p(pg_stack_lower_bound(),			\
+										pg_stack_limit,					\
+										(size), (align))) ?				\
+	 pg_stack_alloca_aligned((size), (align)) :							\
+	 pg_stack_palloc_aligned((size), (align)))
+
+#define pg_stack_ptr_p(ptr)												\
+	((char *) (ptr) >= pg_stack_lower_bound() &&						\
+	 (char *) (ptr) <= pg_stack_upper_bound())
+
+
+/* Compiler-specific ways to inspect the stack's bounds. */
+
+/* The stack pointer. */
+#if defined(HAVE__BUILTIN_STACK_ADDRESS) &&								\
+	(!defined(__clang__) || __clang_major__ >= 22)
+/* Prefer builtin if available. */
+#define pg_stack_lower_bound() ((const char *) __builtin_stack_address())
+#else
+/*
+ * Calling alloca(0) effectively reads the stack pointer on GCC/Clang/MSVC,
+ * but not reliably enough for pg_stack_lower_bound()'s purposes due to
+ * optimizations.  It seems good enough for the initial value that determines
+ * pg_stack_limit though, and better than taking a local variable's address,
+ * which would be less accurate and hard to get past static analyzers.
+ * Instead we'll maintain a reliable lower bound in pg_stack_lower_bound_var.
+ *
+ * XXX I've only actually seen it break on Apple Clang, where if you're
+ * unlucky you can see a stack pointer ~16 bytes higher than a recent alloca()
+ * result, and then pg_stack_ptr_p() is confused and we send it pfree().
+ */
+#define pg_stack_lower_bound_init() ((const char *) pg_stack_alloca(0))
+#endif
+
+/* Upper bound of stack addresses, inclusive. */
+#ifdef HAVE__BUILTIN_FRAME_ADDRESS
+#define pg_stack_upper_bound() ((const char *) __builtin_frame_address(0))
+#endif
+
+
+/* Replacement implementations of pg_stack_{upper,lower}_bound(). */
+
+/* The base address from stack_depth.c will do if we don't have a builtin. */
+#ifndef pg_stack_upper_bound
+#define pg_stack_upper_bound() ((const char *) stack_base_ptr)
+#endif
+
+#ifdef pg_stack_lower_bound
+/* Assert that all alloca() results are bounded by the builtin. */
+#define pg_stack_impl_decl												\
+	char *pg_stack_let_ptr;
+#define pg_stack_track(ptr)												\
+	(pg_stack_let_ptr = (ptr),			/* needed for sequencing */		\
+	 AssertMacro(pg_stack_lower_bound() <= pg_stack_let_ptr),			\
+	 pg_stack_let_ptr)
+#else
+/* Remember the lowest address ever returned by alloca(). */
+#define pg_stack_impl_decl												\
+	const char *pg_stack_lower_bound_var = pg_stack_lower_bound_init();
+#define pg_stack_lower_bound() pg_stack_lower_bound_var
+#define pg_stack_track(ptr)												\
+	pg_stack_set_lower_bound(&pg_stack_lower_bound_var, (ptr))
+static inline void *
+pg_stack_set_lower_bound(const char **lower_bound, void *ptr)
+{
+	/*
+	 * Don't assume that alloca()'s result is lower each time.  That is
+	 * usually true, but not always.
+	 */
+	if ((const char *) ptr < *lower_bound)
+		*lower_bound = (const char *) ptr;
+	return ptr;
+}
+#endif
+
+
+/* Implementation code. */
+
+/* Choose a limit address. */
+static inline const char *
+pg_stack_compute_limit(const char *sp, size_t size)
+{
+	const char *limit = sp - size;
+
+	/* stack_depth.c's soft limit overrides the requested size if closer. */
+	if ((const char *) stack_soft_limit_ptr > limit)
+		limit = (const char *) stack_soft_limit_ptr;
+
+	return limit;
+}
+
+/*
+ * Call alloca(), adjusting for align > ALIGN_ALLOCA if necessary, and
+ * 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))
+
+/* Reserve padding space for pg_stack_realign() if stricter than default. */
+static inline size_t
+pg_stack_pad(size_t size, size_t align)
+{
+	if (align <= ALIGNOF_ALLOCA)
+		return size;
+
+	return (align - ALIGNOF_ALLOCA) + TYPEALIGN(ALIGNOF_ALLOCA, size);
+}
+
+/* Realign alloca()'s result if stricter than default. */
+static inline void *
+pg_stack_realign(void *ptr, size_t align)
+{
+	Assert(pg_stack_ptr_is_aligned_p(ptr, ALIGNOF_ALLOCA));
+
+	if (align <= ALIGNOF_ALLOCA)
+		return ptr;
+
+	return (void *) TYPEALIGN(align, ptr);
+}
+
+/* Would we overflow pg_stack_estimate_alloca()'s arithmetic? */
+static inline bool
+pg_stack_alloca_would_overflow_p(const char *lower, size_t size, size_t align)
+{
+	if (align <= ALIGNOF_ALLOCA)
+	{
+		/*
+		 * pg_stack_pad() doesn't bother to align its result to ALIGNOF_ALLOCA
+		 * unless requested alignment is stricter and the padding could affect
+		 * the result of x < pg_stack_limit.  The latter is usually
+		 * ALIGNOF_ALLOCA-aligned itself, so it'd be a waste of cycles here
+		 * and in pg_stack_estimate_alloca().
+		 */
+		Assert(pg_stack_pad(size, align) == size);
+		return size > (uintptr_t) lower;
+	}
+
+	/* Don't let pg_stack_pad() overflow. */
+	if (size > MaxAllocSize)
+		return true;
+
+	/* Don't let pg_stack_estimate_alloca() underflow. */
+	return pg_stack_pad(size, align) > (uintptr_t) lower;
+}
+
+/*
+ * Estimate result of a proposed alloca(), which would become the new
+ * pg_stack_lower_bound().  The only permitted use of this pointer is to check
+ * if it'd be below pg_stack_limit.
+ */
+static inline const char *
+pg_stack_estimate_alloca(const char *lower, size_t size, size_t align)
+{
+	Assert(!pg_stack_alloca_would_overflow_p(lower, size, align));
+
+	return lower - pg_stack_pad(size, align);
+}
+
+/* Would a proposed alloca() call exceed our limit? */
+static inline bool
+pg_stack_alloca_would_fit_p(const char *lower, const char *limit,
+							size_t size, size_t align)
+{
+	return !pg_stack_alloca_would_overflow_p(lower, size, align) &&
+		pg_stack_estimate_alloca(lower, size, align) >= limit;
+}
+
+#endif
+
 #endif
diff --git a/src/test/regress/regress.c b/src/test/regress/regress.c
index f82a0a12e8f..396c58a3394 100644
--- a/src/test/regress/regress.c
+++ b/src/test/regress/regress.c
@@ -1399,6 +1399,9 @@ test_pg_stack_alloc(PG_FUNCTION_ARGS)
 	DECLARE_PG_STACK_SIZE(1024 * 8);
 
 	/* Too big for the stack. */
+#ifdef PG_STACK_USE_ALLOCA
+	Assert(!pg_stack_alloca_would_fit_p(pg_stack_lower_bound(), pg_stack_limit, 10000, 8));
+#endif
 	p = pg_stack_alloc(10000);
 	Assert(!pg_stack_ptr_p(p));
 	pg_stack_free(p);
@@ -1445,10 +1448,33 @@ test_pg_stack_alloc(PG_FUNCTION_ARGS)
 	PG_END_TRY();
 	Assert(raised_error);
 
+#ifdef PG_STACK_USE_ALLOCA
+	/* Overflow defenses in limit computations. */
+	Assert(pg_stack_alloca_would_overflow_p(pg_stack_lower_bound(), (size_t) -1, 1024));
+	Assert(pg_stack_alloca_would_overflow_p(pg_stack_lower_bound(), (size_t) -1, 8));
+	Assert(!pg_stack_alloca_would_overflow_p(pg_stack_lower_bound(),
+											 MaxAllocSize,
+											 1024));
+	Assert(pg_stack_alloca_would_overflow_p(pg_stack_lower_bound(),
+											MaxAllocSize + 1,
+											1024));
+	Assert(pg_stack_alloca_would_overflow_p(pg_stack_lower_bound(),
+											(size_t) pg_stack_lower_bound() + 1,
+											8));
+#endif
+
 	/* Test a range of alignments. */
+#ifdef PG_STACK_USE_ALLOCA
+#define TEST_ALIGN ALIGNOF_ALLOCA
+#else
 #define TEST_ALIGN MAXIMUM_ALIGNOF
+#endif
 	for (int i = 1; i <= TEST_ALIGN * 8; i *= 2)
 	{
+#ifdef PG_STACK_USE_ALLOCA
+		const char *estimate PG_USED_FOR_ASSERTS_ONLY;
+#endif
+
 		/* Allocate and check alignment is as requested, when we use palloc(). */
 		p = pg_stack_alloc_aligned(1024 * 8, i);
 
@@ -1456,11 +1482,63 @@ test_pg_stack_alloc(PG_FUNCTION_ARGS)
 		Assert(pg_stack_ptr_is_aligned_p(p, i));
 		pg_stack_free(p);
 
+#ifdef PG_STACK_USE_ALLOCA
+		/* Lower bound should be ALIGNOF_ALLOCA-aligned at all times. */
+		Assert(pg_stack_ptr_is_aligned_p(pg_stack_lower_bound(),
+										 ALIGNOF_ALLOCA));
+
+		/* Estimate what alloca() will return. */
+		estimate = pg_stack_estimate_alloca(pg_stack_lower_bound(), i, i);
+
+		/* Basic sanity checks on the estimates. */
+		if (i > ALIGNOF_ALLOCA)
+		{
+			size_t		size PG_USED_FOR_ASSERTS_ONLY = i;
+			size_t		align PG_USED_FOR_ASSERTS_ONLY = i;
+			size_t		padding PG_USED_FOR_ASSERTS_ONLY = align - ALIGNOF_ALLOCA;
+
+			Assert(pg_stack_ptr_is_aligned_p(estimate, ALIGNOF_ALLOCA));
+			Assert(estimate == pg_stack_lower_bound() - size - padding);
+		}
+		else
+		{
+			size_t		size PG_USED_FOR_ASSERTS_ONLY = i;
+
+			Assert(estimate == pg_stack_lower_bound() - size);
+		}
+#endif
+
 		/* Allocate and check alignment is as requested. */
 		p = pg_stack_alloc_aligned(i, i);
 
 		Assert(pg_stack_ptr_p(p));
 		Assert(pg_stack_ptr_is_aligned_p(p, i));
+
+#ifdef PG_STACK_COMPARE_ALLOCA_ESTIMATE
+
+		/*
+		 * Check alloca()'s observed behavior against our estimate.
+		 *
+		 * Estimates aren't expected to match perfectly, and this would fail
+		 * on CI because -fsanitize=address changes the results.  There may be
+		 * any number of minor details we get wrong on some system or other.
+		 * It passes in regular builds, and is useful for investigating
+		 * implementation details.
+		 */
+		if (i > ALIGNOF_ALLOCA)
+		{
+			/* Estimate is for the lower bound, and p is realigned. */
+			Assert(estimate == pg_stack_lower_bound());
+			Assert(p == (const char *) TYPEALIGN(i, estimate));
+		}
+		else
+		{
+			/* We don't bother to align default-alignment estimates. */
+			estimate = (const char *) TYPEALIGN_DOWN(ALIGNOF_ALLOCA, estimate);
+			Assert(p == (const char *) TYPEALIGN_DOWN(ALIGNOF_ALLOCA, estimate));
+			Assert(p == pg_stack_lower_bound());
+		}
+#endif
 	}
 
 	PG_RETURN_VOID();
-- 
2.50.1 (Apple Git-155)



  [application/octet-stream] v4-0008-pg_stack_alloc-Debugging-support-for-alloca.patch (6.3K, 9-v4-0008-pg_stack_alloc-Debugging-support-for-alloca.patch)
  download | inline diff:
From 70b1d3a12ccd73b6e14cc00a4e32e7efa9bb4b0f Mon Sep 17 00:00:00 2001
From: Thomas Munro <[email protected]>
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)



  [application/octet-stream] v4-0009-ci-Define-PG_STACK_USE_ARRAY-for-FreeBSD-task.patch (1.0K, 10-v4-0009-ci-Define-PG_STACK_USE_ARRAY-for-FreeBSD-task.patch)
  download | inline diff:
From 24192340cb7e8c7fc905f4ade3e21c29f9aaf37a Mon Sep 17 00:00:00 2001
From: Thomas Munro <[email protected]>
Date: Thu, 12 Mar 2026 23:07:33 +1300
Subject: [PATCH v4 09/21] ci: Define PG_STACK_USE_ARRAY for FreeBSD task.

This provides test coverage for the fallback code in pg_stack_alloc.h,
that we would otherwise lack since all modern systems have MSVC alloca()
or GCC/Clang builtins.

Reviewed-by:
Discussion:
---
 .cirrus.tasks.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index 4841a204248..235886103e0 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -180,7 +180,7 @@ task:
     DISK_SIZE: 50
 
     CCACHE_DIR: /tmp/ccache_dir
-    CPPFLAGS: -DRELCACHE_FORCE_RELEASE -DENFORCE_REGRESSION_TEST_NAME_RESTRICTIONS
+    CPPFLAGS: -DRELCACHE_FORCE_RELEASE -DENFORCE_REGRESSION_TEST_NAME_RESTRICTIONS -DPG_STACK_USE_ARRAY
     CFLAGS: -Og -ggdb
 
     # Several buildfarm animals enable these options. Without testing them
-- 
2.50.1 (Apple Git-155)



  [application/x-gzip] v4-pg_stack_alloc-candidate-patches.tgz (29.7K, 11-v4-pg_stack_alloc-candidate-patches.tgz)
  download

view thread (6+ messages)  latest in thread

reply

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Reply to all the recipients using the --to and --cc options:
  reply via email

  To: [email protected]
  Cc: [email protected], [email protected], [email protected]
  Subject: Re: A stack allocation API
  In-Reply-To: <CA+hUKG+Ovn6cWxCTjTAdQLmMksg_BfDfDOr6qPRFZbbQz9vFyQ@mail.gmail.com>

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox