From 7d74ce5d6292fa8e4c9e785dba50e10827f7f88c Mon Sep 17 00:00:00 2001 From: Thomas Munro 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)