From 97196948de301df9667d7bb4521806f6343e4d2f Mon Sep 17 00:00:00 2001 From: Thomas Munro Date: Fri, 27 Feb 2026 22:21:36 +1300 Subject: [PATCH v1] Provide stack allocation API. Several places open-coded an array on the stack for non-escaping memory of dynamic size, with fallback to palloc()/pfree(). Create a standard API for that, and implement it with alloca() when available (in practice, always). alloca() is non-standard and discouraged due to overflow risk and implementation details, but we apply the existing space limit and restrict usage to suitable compilers. Existing users of the technique in pg_local_XXX functions are updated to use the new API, as well as some nearby candidates that were previously using palloc(). Reviewed-by: --- src/backend/utils/adt/pg_locale.c | 7 +- src/backend/utils/adt/pg_locale_icu.c | 59 +++------- src/backend/utils/adt/pg_locale_libc.c | 150 +++++++++--------------- src/include/utils/stack_buffer.h | 154 +++++++++++++++++++++++++ 4 files changed, 226 insertions(+), 144 deletions(-) create mode 100644 src/include/utils/stack_buffer.h diff --git a/src/backend/utils/adt/pg_locale.c b/src/backend/utils/adt/pg_locale.c index ac324ecaad2..c2e9b303639 100644 --- a/src/backend/utils/adt/pg_locale.c +++ b/src/backend/utils/adt/pg_locale.c @@ -50,6 +50,7 @@ #include "utils/pg_locale.h" #include "utils/pg_locale_c.h" #include "utils/relcache.h" +#include "utils/stack_buffer.h" #include "utils/syscache.h" #ifdef WIN32 @@ -60,12 +61,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..8ec13679d90 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/stack_buffer.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_STACK_BUFFER(); + 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 = stack_buffer_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); + stack_buffer_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_STACK_BUFFER(); + /* 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 = stack_buffer_alloc_array(UChar, ulen1 + 1); + uchar2 = stack_buffer_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); + stack_buffer_free(uchar1); + stack_buffer_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_STACK_BUFFER(); + /* 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 = stack_buffer_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); + stack_buffer_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..0f1bb490c32 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/stack_buffer.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_STACK_BUFFER(); + 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 = stack_buffer_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 = stack_buffer_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); + stack_buffer_free(workspace); + stack_buffer_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_STACK_BUFFER(); + 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 = stack_buffer_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 = stack_buffer_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); + stack_buffer_free(workspace); + stack_buffer_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_STACK_BUFFER(); + 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 = stack_buffer_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 = stack_buffer_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); + stack_buffer_free(workspace); + stack_buffer_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_STACK_BUFFER(); /* nul-terminate arguments if necessary */ - if (len1 == -1) - { - arg1n = arg1; - } - else - { - char *buf1 = buf; + if (len1 != -1) + arg1 = cstr1 = stack_buffer_strdup_with_len(arg1, len1); - memcpy(buf1, arg1, len1); - buf1[len1] = '\0'; - arg1n = buf1; - } + if (len2 != -1) + arg2 = cstr2 = stack_buffer_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) + stack_buffer_free(cstr1); + if (cstr2) + stack_buffer_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_STACK_BUFFER(); + 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 = stack_buffer_strdup_with_len(src, srclen); + result = strxfrm_l(dest, cstr, destsize, locale->lt); + stack_buffer_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_STACK_BUFFER(); + 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 = stack_buffer_alloc_array(wchar_t, len1 + 1); + w2p = stack_buffer_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); + stack_buffer_free(w1p); + stack_buffer_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_STACK_BUFFER(); + /* mbstowcs requires ending '\0' */ - char *str = pnstrdup(from, fromlen); + str = stack_buffer_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); + stack_buffer_free(str); } if (result == -1) diff --git a/src/include/utils/stack_buffer.h b/src/include/utils/stack_buffer.h new file mode 100644 index 00000000000..168263e9f61 --- /dev/null +++ b/src/include/utils/stack_buffer.h @@ -0,0 +1,154 @@ +/*------------------------------------------------------------------------- + * + * stack_buffer.h + * Memory allocator for small non-escaping objects. + * + * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/utils/stack_buffer.h + * + *------------------------------------------------------------------------- + */ +#ifndef STACK_BUFFER_H +#define STACK_BUFFER_H + +#include "utils/palloc.h" + +/* + * The alloca()-based implementation uses alloca() in the argument list of a + * function call. GCC, Clang and MSVC can do that, but historical compilers + * could not. + */ +#if defined(__GNUC__) || defined(_MSC_VER) +#define USE_ALLOCA +#endif + +/* + * Unixen traditionally either required or to be included + * to use alloca(). It is not standardized by C or POSIX. GCC and Clang + * understand it as a builtin, so we only need to include a header for MSVC. + */ +#if defined(WIN32) && defined(_MSC_VER) +#include +#endif + +/* + * This should be large enough that most strings and other small objects will + * fit, but small enough that we feel comfortable putting it on the stack. + */ +#define STACK_BUFFER_SIZE 1024 + +/* + * Declare a stack buffer, allowing the following API to be used to allocate + * memory that doesn't escape the present function. + */ +#define DECLARE_STACK_BUFFER() \ + DECLARE_STACK_BUFFER_IMPL(STACK_BUFFER_SIZE) + +/* + * Allocate memory from the stack if the declared limit wouldn't be exceeded, + * and otherwise fall back to palloc(). The returned pointer is aligned to + * MAXIMUM_ALIGNOF (not max_align_t), like palloc(). + */ +#define stack_buffer_alloc(size) \ + (stack_buffer_tmp = (size), /* avoid double evaluation */ \ + stack_buffer_alloc_impl(stack_buffer_tmp)) + +/* Like palloc_array(T, size). */ +#define stack_buffer_alloc_array(T, size) \ + (T *) stack_buffer_alloc((size) * sizeof(T)) + +/* Like palloc_object(T). */ +#define stack_buffer_alloc_object(T) \ + stack_buffer_alloc_array(T, 1) + +/* + * Allocate a fresh NUL-terminated string copied from a string of known size, + * not counting the NUL-terminator. The input string need not be + * NUL-terminated. + */ +#define stack_buffer_strdup_with_len(data, size) \ + (stack_buffer_tmp = (size), /* avoid double evaluation */ \ + stack_buffer_strdup_with_len_impl(stack_buffer_alloc_impl(stack_buffer_tmp + 1), \ + (data), \ + stack_buffer_tmp)) + +/* + * Free memory allocated with stack_buffer_alloc(). A no-op if it came from + * the stack, and otherwise pfree(). + */ +#define stack_buffer_free(ptr) \ + stack_buffer_free_impl(stack_buffer_l, stack_buffer_h, (ptr)) + + + +#ifdef USE_ALLOCA +/* alloca() with a size limit. */ +#define DECLARE_STACK_BUFFER_IMPL(size) \ +const size_t stack_buffer_max_size = (size); \ +char *stack_buffer_l = NULL; \ +char *stack_buffer_h = NULL; \ +size_t stack_buffer_tmp pg_attribute_unused() +#define stack_buffer_alloc_impl(size) \ + (((stack_buffer_h - stack_buffer_l) + (size)) < stack_buffer_max_size ? \ + stack_buffer_alloc_impl2(&stack_buffer_l, \ + &stack_buffer_h, \ + alloca(size)) : \ + palloc(size)) +static inline void * +stack_buffer_alloc_impl2(char **l, char **h, void *ptr) +{ + char *p = (char *) ptr; + + if (*l == NULL || p < *l) + *l = p; + if (*h == NULL || p > *h) + *h = p; + + return p; +} +#else +/* Standard C implementation using a big array. */ +#define DECLARE_STACK_BUFFER_IMPL(size) \ +alignas(MAXIMUM_ALIGNOF) char stack_buffer_array[MAXALIGN(size)]; \ +char *stack_buffer_allocator = stack_buffer_array + sizeof(MAXALIGN(size)); \ +const char *stack_buffer_l = stack_buffer_array; \ +const char *stack_buffer_h = stack_buffer_allocator; \ +size_t stack_buffer_tmp pg_attribute_unused() +#define stack_buffer_alloc_impl(size) \ + stack_buffer_alloc_impl2(stack_buffer_l, \ + &stack_buffer_allocator, \ + (size)) +static inline void * +stack_buffer_alloc_impl2(const char *l, char **allocator, size_t size) +{ + size = MAXALIGN(size); + + if (*allocator - size >= l) + { + *allocator -= size; + return *allocator; + } + return palloc(size); +} +#endif + +static inline char * +stack_buffer_strdup_with_len_impl(char *dst, const char *data, size_t size) +{ + memcpy(dst, data, size); + dst[size] = 0; + return dst; +} + +static inline void +stack_buffer_free_impl(const char *l, const char *h, void *ptr) +{ + char *p = (char *) ptr; + + if (p < l || p > h) + pfree(ptr); +} + +#endif -- 2.50.1 (Apple Git-155)