public inbox for [email protected]help / color / mirror / Atom feed
Re: Speed up ICU case conversion by using ucasemap_utf8To*() 9+ messages / 3 participants [nested] [flat]
* Re: Speed up ICU case conversion by using ucasemap_utf8To*() @ 2026-01-03 03:12 Andreas Karlsson <[email protected]> 0 siblings, 1 reply; 9+ messages in thread From: Andreas Karlsson @ 2026-01-03 03:12 UTC (permalink / raw) To: zengman <[email protected]>; Jeff Davis <[email protected]>; pgsql-hackers On 1/3/26 4:05 AM, zengman wrote: > I don't have any major objections, but I noticed a few minor details that might need a bit more tweaking. > > `signficant` -> `significant` > `realtively` -> `relatively` > `if (status != U_BUFFER_OVERFLOW_ERROR && U_FAILURE(status))` -> `if (U_FAILURE(status) && status != U_BUFFER_OVERFLOW_ERROR)` Fixed, thanks! Andreas Attachments: [text/x-patch] v5-0001-Use-optimized-versions-of-ICU-case-conversion-for.patch (15.4K, 2-v5-0001-Use-optimized-versions-of-ICU-case-conversion-for.patch) download | inline diff: From d01e1ae7c3a9353b3c33c184eaedb95d08a59942 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson <[email protected]> Date: Tue, 17 Dec 2024 22:47:00 +0100 Subject: [PATCH v5] Use optimized versions of ICU case conversion for UTF-8 Instead of converting to and from UChar when doing case conversions we use the UTF-8 versions of the functions. This can give a significant speedup, 30-40%, on short to medium length strings. The only cost we incur is that we have to allocate a casemap object on locale initialization for UTF-8 databases but the object is realtively small and the assumption is that most users will at some point want to run case conversion functions. While at it we also remove some duplication in the non-UTF-8 code. --- src/backend/utils/adt/pg_locale_icu.c | 300 ++++++++++++++++++-------- src/include/utils/pg_locale.h | 2 + 2 files changed, 208 insertions(+), 94 deletions(-) diff --git a/src/backend/utils/adt/pg_locale_icu.c b/src/backend/utils/adt/pg_locale_icu.c index de80642f9dc..ac01524b51c 100644 --- a/src/backend/utils/adt/pg_locale_icu.c +++ b/src/backend/utils/adt/pg_locale_icu.c @@ -52,6 +52,7 @@ extern pg_locale_t create_pg_locale_icu(Oid collid, MemoryContext context); #ifdef USE_ICU extern UCollator *pg_ucol_open(const char *loc_str); +static UCaseMap *pg_ucasemap_open(const char *loc_str); static size_t strlower_icu(char *dest, size_t destsize, const char *src, ssize_t srclen, pg_locale_t locale); @@ -61,6 +62,14 @@ static size_t strupper_icu(char *dest, size_t destsize, const char *src, ssize_t srclen, pg_locale_t locale); static size_t strfold_icu(char *dest, size_t destsize, const char *src, ssize_t srclen, pg_locale_t locale); +static size_t strlower_icu_utf8(char *dest, size_t destsize, const char *src, + ssize_t srclen, pg_locale_t locale); +static size_t strtitle_icu_utf8(char *dest, size_t destsize, const char *src, + ssize_t srclen, pg_locale_t locale); +static size_t strupper_icu_utf8(char *dest, size_t destsize, const char *src, + ssize_t srclen, pg_locale_t locale); +static size_t strfold_icu_utf8(char *dest, size_t destsize, const char *src, + ssize_t srclen, pg_locale_t locale); static size_t downcase_ident_icu(char *dst, size_t dstsize, const char *src, ssize_t srclen, pg_locale_t locale); static int strncoll_icu(const char *arg1, ssize_t len1, @@ -111,9 +120,12 @@ static size_t icu_from_uchar(char *dest, size_t destsize, const UChar *buff_uchar, int32_t len_uchar); static void icu_set_collation_attributes(UCollator *collator, const char *loc, UErrorCode *status); -static int32_t icu_convert_case(ICU_Convert_Func func, pg_locale_t mylocale, - UChar **buff_dest, UChar *buff_source, - int32_t len_source); +static int32_t icu_convert_case(ICU_Convert_Func func, char *dest, + size_t destsize, const char *src, + ssize_t srclen, pg_locale_t locale); +static int32_t icu_convert_case_uchar(ICU_Convert_Func func, pg_locale_t mylocale, + UChar **buff_dest, UChar *buff_source, + int32_t len_source); static int32_t u_strToTitle_default_BI(UChar *dest, int32_t destCapacity, const UChar *src, int32_t srcLength, const char *locale, @@ -140,6 +152,8 @@ tolower_icu(pg_wchar wc, pg_locale_t locale) return u_tolower(wc); } +static int32_t icu_foldcase_options(const char *locale); + static const struct collate_methods collate_methods_icu = { .strncoll = strncoll_icu, .strnxfrm = strnxfrm_icu, @@ -245,6 +259,27 @@ static const struct ctype_methods ctype_methods_icu = { .wc_tolower = tolower_icu, }; +static const struct ctype_methods ctype_methods_icu_utf8 = { + .strlower = strlower_icu_utf8, + .strtitle = strtitle_icu_utf8, + .strupper = strupper_icu_utf8, + .strfold = strfold_icu_utf8, + .downcase_ident = downcase_ident_icu, + .wc_isdigit = wc_isdigit_icu, + .wc_isalpha = wc_isalpha_icu, + .wc_isalnum = wc_isalnum_icu, + .wc_isupper = wc_isupper_icu, + .wc_islower = wc_islower_icu, + .wc_isgraph = wc_isgraph_icu, + .wc_isprint = wc_isprint_icu, + .wc_ispunct = wc_ispunct_icu, + .wc_isspace = wc_isspace_icu, + .wc_isxdigit = wc_isxdigit_icu, + .wc_iscased = wc_iscased_icu, + .wc_toupper = toupper_icu, + .wc_tolower = tolower_icu, +}; + /* * ICU still depends on libc for compatibility with certain historical * behavior for single-byte encodings. See downcase_ident_icu(). @@ -347,10 +382,16 @@ create_pg_locale_icu(Oid collid, MemoryContext context) result->collate_is_c = false; result->ctype_is_c = false; if (GetDatabaseEncoding() == PG_UTF8) + { + result->icu.ucasemap = pg_ucasemap_open(iculocstr); result->collate = &collate_methods_icu_utf8; + result->ctype = &ctype_methods_icu_utf8; + } else + { result->collate = &collate_methods_icu; - result->ctype = &ctype_methods_icu; + result->ctype = &ctype_methods_icu; + } return result; #else @@ -366,41 +407,18 @@ create_pg_locale_icu(Oid collid, MemoryContext context) #ifdef USE_ICU /* - * Wrapper around ucol_open() to handle API differences for older ICU - * versions. - * - * Ensure that no path leaks a UCollator. + * In ICU versions 54 and earlier, "und" is not a recognized spelling of the + * root locale. If the first component of the locale is "und", replace with + * "root" before opening. */ -UCollator * -pg_ucol_open(const char *loc_str) +static char * +fix_icu_locale_str(const char *loc_str) { - UCollator *collator; - UErrorCode status; - const char *orig_str = loc_str; - char *fixed_str = NULL; - - /* - * Must never open default collator, because it depends on the environment - * and may change at any time. Should not happen, but check here to catch - * bugs that might be hard to catch otherwise. - * - * NB: the default collator is not the same as the collator for the root - * locale. The root locale may be specified as the empty string, "und", or - * "root". The default collator is opened by passing NULL to ucol_open(). - */ - if (loc_str == NULL) - elog(ERROR, "opening default collator is not supported"); - - /* - * In ICU versions 54 and earlier, "und" is not a recognized spelling of - * the root locale. If the first component of the locale is "und", replace - * with "root" before opening. - */ if (U_ICU_VERSION_MAJOR_NUM < 55) { char lang[ULOC_LANG_CAPACITY]; + UErrorCode status = U_ZERO_ERROR; - status = U_ZERO_ERROR; uloc_getLanguage(loc_str, lang, ULOC_LANG_CAPACITY, &status); if (U_FAILURE(status) || status == U_STRING_NOT_TERMINATED_WARNING) { @@ -413,15 +431,49 @@ pg_ucol_open(const char *loc_str) if (strcmp(lang, "und") == 0) { const char *remainder = loc_str + strlen("und"); + char *fixed_str; fixed_str = palloc(strlen("root") + strlen(remainder) + 1); strcpy(fixed_str, "root"); strcat(fixed_str, remainder); - loc_str = fixed_str; + return fixed_str; } } + return NULL; +} + +/* + * Wrapper around ucol_open() to handle API differences for older ICU + * versions. + * + * Ensure that no path leaks a UCollator. + */ +UCollator * +pg_ucol_open(const char *loc_str) +{ + UCollator *collator; + UErrorCode status; + const char *orig_str = loc_str; + char *fixed_str; + + /* + * Must never open default collator, because it depends on the environment + * and may change at any time. Should not happen, but check here to catch + * bugs that might be hard to catch otherwise. + * + * NB: the default collator is not the same as the collator for the root + * locale. The root locale may be specified as the empty string, "und", or + * "root". The default collator is opened by passing NULL to ucol_open(). + */ + if (loc_str == NULL) + elog(ERROR, "opening default collator is not supported"); + + fixed_str = fix_icu_locale_str(loc_str); + if (fixed_str) + loc_str = fixed_str; + status = U_ZERO_ERROR; collator = ucol_open(loc_str, &status); if (U_FAILURE(status)) @@ -456,6 +508,37 @@ pg_ucol_open(const char *loc_str) return collator; } +/* + * Wrapper around ucasemap_open() to handle API differences for older ICU + * versions. + * + * Additionally makes sure we get the right options for case folding. + */ +static UCaseMap * +pg_ucasemap_open(const char *loc_str) +{ + UErrorCode status = U_ZERO_ERROR; + UCaseMap *casemap; + const char *orig_str = loc_str; + char *fixed_str; + + fixed_str = fix_icu_locale_str(loc_str); + if (fixed_str) + loc_str = fixed_str; + + casemap = ucasemap_open(loc_str, icu_foldcase_options(loc_str), &status); + if (U_FAILURE(status)) + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("could not open casemap for locale \"%s\": %s", + orig_str, u_errorName(status))); + + if (fixed_str != NULL) + pfree(fixed_str); + + return casemap; +} + /* * Create a UCollator with the given locale string and rules. * @@ -528,80 +611,84 @@ static size_t strlower_icu(char *dest, size_t destsize, const char *src, ssize_t srclen, pg_locale_t locale) { - int32_t len_uchar; - int32_t len_conv; - UChar *buff_uchar; - UChar *buff_conv; - size_t result_len; - - len_uchar = icu_to_uchar(&buff_uchar, src, srclen); - len_conv = icu_convert_case(u_strToLower, locale, - &buff_conv, buff_uchar, len_uchar); - result_len = icu_from_uchar(dest, destsize, buff_conv, len_conv); - pfree(buff_uchar); - pfree(buff_conv); - - return result_len; + return icu_convert_case(u_strToLower, dest, destsize, src, srclen, locale); } static size_t strtitle_icu(char *dest, size_t destsize, const char *src, ssize_t srclen, pg_locale_t locale) { - int32_t len_uchar; - int32_t len_conv; - UChar *buff_uchar; - UChar *buff_conv; - size_t result_len; - - len_uchar = icu_to_uchar(&buff_uchar, src, srclen); - len_conv = icu_convert_case(u_strToTitle_default_BI, locale, - &buff_conv, buff_uchar, len_uchar); - result_len = icu_from_uchar(dest, destsize, buff_conv, len_conv); - pfree(buff_uchar); - pfree(buff_conv); - - return result_len; + return icu_convert_case(u_strToTitle_default_BI, dest, destsize, src, srclen, locale); } static size_t strupper_icu(char *dest, size_t destsize, const char *src, ssize_t srclen, pg_locale_t locale) { - int32_t len_uchar; - int32_t len_conv; - UChar *buff_uchar; - UChar *buff_conv; - size_t result_len; - - len_uchar = icu_to_uchar(&buff_uchar, src, srclen); - len_conv = icu_convert_case(u_strToUpper, locale, - &buff_conv, buff_uchar, len_uchar); - result_len = icu_from_uchar(dest, destsize, buff_conv, len_conv); - pfree(buff_uchar); - pfree(buff_conv); - - return result_len; + return icu_convert_case(u_strToUpper, dest, destsize, src, srclen, locale); } static size_t strfold_icu(char *dest, size_t destsize, const char *src, ssize_t srclen, pg_locale_t locale) { - int32_t len_uchar; - int32_t len_conv; - UChar *buff_uchar; - UChar *buff_conv; - size_t result_len; + return icu_convert_case(u_strFoldCase_default, dest, destsize, src, srclen, locale); +} - len_uchar = icu_to_uchar(&buff_uchar, src, srclen); - len_conv = icu_convert_case(u_strFoldCase_default, locale, - &buff_conv, buff_uchar, len_uchar); - result_len = icu_from_uchar(dest, destsize, buff_conv, len_conv); - pfree(buff_uchar); - pfree(buff_conv); +static size_t +strlower_icu_utf8(char *dest, size_t destsize, const char *src, ssize_t srclen, + pg_locale_t locale) +{ + UErrorCode status = U_ZERO_ERROR; + int32_t needed; - return result_len; + needed = ucasemap_utf8ToLower(locale->icu.ucasemap, dest, destsize, src, srclen, &status); + if (U_FAILURE(status) && status != U_BUFFER_OVERFLOW_ERROR) + ereport(ERROR, + errmsg("case conversion failed: %s", u_errorName(status))); + return needed; +} + +static size_t +strtitle_icu_utf8(char *dest, size_t destsize, const char *src, ssize_t srclen, + pg_locale_t locale) +{ + UErrorCode status = U_ZERO_ERROR; + int32_t needed; + + needed = ucasemap_utf8ToTitle(locale->icu.ucasemap, dest, destsize, src, srclen, &status); + if (U_FAILURE(status) && status != U_BUFFER_OVERFLOW_ERROR) + ereport(ERROR, + errmsg("case conversion failed: %s", u_errorName(status))); + return needed; +} + +static size_t +strupper_icu_utf8(char *dest, size_t destsize, const char *src, ssize_t srclen, + pg_locale_t locale) +{ + UErrorCode status = U_ZERO_ERROR; + int32_t needed; + + needed = ucasemap_utf8ToUpper(locale->icu.ucasemap, dest, destsize, src, srclen, &status); + if (U_FAILURE(status) && status != U_BUFFER_OVERFLOW_ERROR) + ereport(ERROR, + errmsg("case conversion failed: %s", u_errorName(status))); + return needed; +} + +static size_t +strfold_icu_utf8(char *dest, size_t destsize, const char *src, ssize_t srclen, + pg_locale_t locale) +{ + UErrorCode status = U_ZERO_ERROR; + int32_t needed; + + needed = ucasemap_utf8FoldCase(locale->icu.ucasemap, dest, destsize, src, srclen, &status); + if (U_FAILURE(status) && status != U_BUFFER_OVERFLOW_ERROR) + ereport(ERROR, + errmsg("case conversion failed: %s", u_errorName(status))); + return needed; } /* @@ -829,8 +916,28 @@ icu_from_uchar(char *dest, size_t destsize, const UChar *buff_uchar, int32_t len } static int32_t -icu_convert_case(ICU_Convert_Func func, pg_locale_t mylocale, - UChar **buff_dest, UChar *buff_source, int32_t len_source) +icu_convert_case(ICU_Convert_Func func, char *dest, size_t destsize, + const char *src, ssize_t srclen, pg_locale_t locale) +{ + int32_t len_uchar; + int32_t len_conv; + UChar *buff_uchar; + UChar *buff_conv; + size_t result_len; + + len_uchar = icu_to_uchar(&buff_uchar, src, srclen); + len_conv = icu_convert_case_uchar(func, locale, &buff_conv, + buff_uchar, len_uchar); + result_len = icu_from_uchar(dest, destsize, buff_conv, len_conv); + pfree(buff_uchar); + pfree(buff_conv); + + return result_len; +} + +static int32_t +icu_convert_case_uchar(ICU_Convert_Func func, pg_locale_t mylocale, + UChar **buff_dest, UChar *buff_source, int32_t len_source) { UErrorCode status; int32_t len_dest; @@ -870,10 +977,17 @@ u_strFoldCase_default(UChar *dest, int32_t destCapacity, const UChar *src, int32_t srcLength, const char *locale, UErrorCode *pErrorCode) +{ + return u_strFoldCase(dest, destCapacity, src, srcLength, + icu_foldcase_options(locale), pErrorCode); +} + +static int32_t +icu_foldcase_options(const char *locale) { uint32 options = U_FOLD_CASE_DEFAULT; char lang[3]; - UErrorCode status; + UErrorCode status = U_ZERO_ERROR; /* * Unlike the ICU APIs for lowercasing, titlecasing, and uppercasing, case @@ -881,7 +995,6 @@ u_strFoldCase_default(UChar *dest, int32_t destCapacity, * option relevant to Turkic languages 'az' and 'tr'; check for those * languages to enable the option. */ - status = U_ZERO_ERROR; uloc_getLanguage(locale, lang, 3, &status); if (U_SUCCESS(status)) { @@ -893,8 +1006,7 @@ u_strFoldCase_default(UChar *dest, int32_t destCapacity, options = U_FOLD_CASE_EXCLUDE_SPECIAL_I; } - return u_strFoldCase(dest, destCapacity, src, srcLength, - options, pErrorCode); + return options; } /* diff --git a/src/include/utils/pg_locale.h b/src/include/utils/pg_locale.h index b1ee5fb0ef5..465f170ba79 100644 --- a/src/include/utils/pg_locale.h +++ b/src/include/utils/pg_locale.h @@ -21,6 +21,7 @@ #undef U_SHOW_CPLUSPLUS_HEADER_API #define U_SHOW_CPLUSPLUS_HEADER_API 0 #include <unicode/ucol.h> +#include <unicode/ucasemap.h> #endif /* use for libc locale names */ @@ -168,6 +169,7 @@ struct pg_locale_struct const char *locale; UCollator *ucol; locale_t lt; + UCaseMap *ucasemap; } icu; #endif }; -- 2.47.3 ^ permalink raw reply [nested|flat] 9+ messages in thread
* Re: Speed up ICU case conversion by using ucasemap_utf8To*() @ 2026-01-06 22:10 Jeff Davis <[email protected]> parent: Andreas Karlsson <[email protected]> 0 siblings, 1 reply; 9+ messages in thread From: Jeff Davis @ 2026-01-06 22:10 UTC (permalink / raw) To: Andreas Karlsson <[email protected]>; zengman <[email protected]>; pgsql-hackers On Sat, 2026-01-03 at 04:12 +0100, Andreas Karlsson wrote: > On 1/3/26 4:05 AM, zengman wrote: > > I don't have any major objections, but I noticed a few minor > > details that might need a bit more tweaking. > > > > `signficant` -> `significant` > > `realtively` -> `relatively` > > `if (status != U_BUFFER_OVERFLOW_ERROR && U_FAILURE(status))` -> > > `if (U_FAILURE(status) && status != U_BUFFER_OVERFLOW_ERROR)` > Fixed, thanks! Committed, thank you! Regards, Jeff Davis ^ permalink raw reply [nested|flat] 9+ messages in thread
* Re: Speed up ICU case conversion by using ucasemap_utf8To*() @ 2026-03-12 04:00 Alexander Lakhin <[email protected]> parent: Jeff Davis <[email protected]> 0 siblings, 1 reply; 9+ messages in thread From: Alexander Lakhin @ 2026-03-12 04:00 UTC (permalink / raw) To: Jeff Davis <[email protected]>; Andreas Karlsson <[email protected]>; zengman <[email protected]>; pgsql-hackers Hello Jeff, 07.01.2026 00:10, Jeff Davis wrote: > Committed, thank you! I've discovered that starting from c4ff35f10, the following query: CREATE COLLATION c (provider = icu, locale = 'icu_something'); makes asan detect (maybe dubious, but still..) stack-buffer-overflow: ==21963==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffd386d4e63 at pc 0x650cd7972a76 bp 0x7ffd386d4e00 sp 0x7ffd386d45a8 ... Address 0x7ffd386d4e63 is located in stack of thread T0 at offset 67 in frame #0 0x650cd86962ef in foldcase_options (.../usr/local/pgsql/bin/postgres+0x12322ef) (BuildId: e441a9634858193e7358e5901e7948606ff5b1b1) This frame has 2 object(s): [48, 52) 'status' (line 993) [64, 67) 'lang' (line 992) <== Memory access at offset 67 overflows this variable I use a build made with: CC=gcc-13 CPPFLAGS="-fsanitize=address" LDFLAGS="-fsanitize=address -static-libasan" ./configure --with-icu ... Could you please have a look? Best regards, Alexander ^ permalink raw reply [nested|flat] 9+ messages in thread
* Re: Speed up ICU case conversion by using ucasemap_utf8To*() @ 2026-04-01 00:46 Andreas Karlsson <[email protected]> parent: Alexander Lakhin <[email protected]> 0 siblings, 2 replies; 9+ messages in thread From: Andreas Karlsson @ 2026-04-01 00:46 UTC (permalink / raw) To: Alexander Lakhin <[email protected]>; Jeff Davis <[email protected]>; zengman <[email protected]>; pgsql-hackers On 3/12/26 5:00 AM, Alexander Lakhin wrote: > I've discovered that starting from c4ff35f10, the following query: > CREATE COLLATION c (provider = icu, locale = 'icu_something'); > > makes asan detect (maybe dubious, but still..) stack-buffer-overflow: > ==21963==ERROR: AddressSanitizer: stack-buffer-overflow on address > 0x7ffd386d4e63 at pc 0x650cd7972a76 bp 0x7ffd386d4e00 sp 0x7ffd386d45a8 > ... > Address 0x7ffd386d4e63 is located in stack of thread T0 at offset 67 in > frame > #0 0x650cd86962ef in foldcase_options (.../usr/local/pgsql/bin/ > postgres+0x12322ef) (BuildId: e441a9634858193e7358e5901e7948606ff5b1b1) > > This frame has 2 object(s): > [48, 52) 'status' (line 993) > [64, 67) 'lang' (line 992) <== Memory access at offset 67 overflows > this variable > > I use a build made with: > CC=gcc-13 CPPFLAGS="-fsanitize=address" LDFLAGS="-fsanitize=address - > static-libasan" ./configure --with-icu ... > > Could you please have a look? Thanks for finding this! Interestingly this bug seems like it would be there even before my patch, but maybe something I did made it when moving code around made it possible or easier to trigger. As far as I can tell the issue is that uloc_getLanguage(locale, lang, 3, &status); will populate lang with a string which is not zero terminated if the language is 3 or more characters, e.g. "und". And for some reason which I am not entirely strcmp("tr", {'u','n','d'}) can cause an overflow. Maybe due to some optimization? My proposed fix is that we allocate a ULOC_LANG_CAPACITY buffer for the language like we do in fix_icu_locale_str() instead of trying to be clever. An alternative would be to use strncmp("tr", lang, 3) but that seems too clever for my taste in something which is not performance critical. A third option would be to check for U_STRING_NOT_TERMINATED_WARNING but I think that would just be unnecessarily convoluted code. I have attached my proposed fix. Andreas Attachments: [text/x-patch] v1-0001-Fix-overrun-when-comparing-with-unterminated-ICU-.patch (1.3K, 2-v1-0001-Fix-overrun-when-comparing-with-unterminated-ICU-.patch) download | inline diff: From 9d9a13917f53de690d70dcfb62adb1f0c5acad2a Mon Sep 17 00:00:00 2001 From: Andreas Karlsson <[email protected]> Date: Wed, 1 Apr 2026 02:39:09 +0200 Subject: [PATCH v1] Fix overrun when comparing with unterminated ICU language string When uloc_getLanguage() returns an unterminated string when the language is too long to fit in our buffer, in this case 3 bytes. This could cause a later strcmp() to read outside the buffer. Since this is not a performance cirtical code path just increase the buffer size to ULOC_LANG_CAPACITY to match the code on fix_icu_locale_str() instead of trying to do something clever. --- src/backend/utils/adt/pg_locale_icu.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/adt/pg_locale_icu.c b/src/backend/utils/adt/pg_locale_icu.c index 5ad05fcd016..96d66dd4f8a 100644 --- a/src/backend/utils/adt/pg_locale_icu.c +++ b/src/backend/utils/adt/pg_locale_icu.c @@ -989,10 +989,10 @@ static int32_t foldcase_options(const char *locale) { uint32 options = U_FOLD_CASE_DEFAULT; - char lang[3]; + char lang[ULOC_LANG_CAPACITY]; UErrorCode status = U_ZERO_ERROR; - uloc_getLanguage(locale, lang, 3, &status); + uloc_getLanguage(locale, lang, ULOC_LANG_CAPACITY, &status); if (U_SUCCESS(status)) { /* -- 2.47.3 ^ permalink raw reply [nested|flat] 9+ messages in thread
* Re: Speed up ICU case conversion by using ucasemap_utf8To*() @ 2026-04-13 06:35 Andreas Karlsson <[email protected]> parent: Andreas Karlsson <[email protected]> 1 sibling, 0 replies; 9+ messages in thread From: Andreas Karlsson @ 2026-04-13 06:35 UTC (permalink / raw) To: [email protected]; Alexander Lakhin <[email protected]>; Jeff Davis <[email protected]>; zengman <[email protected]>; pgsql-hackers On 1 April 2026 02:46:23 CEST, Andreas Karlsson <[email protected]> wrote: >My proposed fix is that we allocate a ULOC_LANG_CAPACITY buffer for the language like we do in fix_icu_locale_str() instead of trying to be clever. An alternative would be to use strncmp("tr", lang, 3) but that seems too clever for my taste in something which is not performance critical. A third option would be to check for U_STRING_NOT_TERMINATED_WARNING but I think that would just be unnecessarily convoluted code. > >I have attached my proposed fix. Since it is likely I introduced or at least exposed this bug somehow I am adding this to the open items for PG 19. Andreas ^ permalink raw reply [nested|flat] 9+ messages in thread
* Re: Speed up ICU case conversion by using ucasemap_utf8To*() @ 2026-04-13 18:40 Jeff Davis <[email protected]> parent: Andreas Karlsson <[email protected]> 1 sibling, 1 reply; 9+ messages in thread From: Jeff Davis @ 2026-04-13 18:40 UTC (permalink / raw) To: Andreas Karlsson <[email protected]>; Alexander Lakhin <[email protected]>; zengman <[email protected]>; pgsql-hackers On Wed, 2026-04-01 at 02:46 +0200, Andreas Karlsson wrote: > On 3/12/26 5:00 AM, Alexander Lakhin wrote: > > I've discovered that starting from c4ff35f10, the following query: > > CREATE COLLATION c (provider = icu, locale = 'icu_something'); > > > > makes asan detect (maybe dubious, but still..) stack-buffer- > > overflow: > > ==21963==ERROR: AddressSanitizer: stack-buffer-overflow on address > > My proposed fix is that we allocate a ULOC_LANG_CAPACITY buffer for > the > language like we do in fix_icu_locale_str() instead of trying to be > clever. Thank you both! Committed with minor revisions: * also check the status code, just to be sure * backport to 18 where the original code was introduced Regards, Jeff Davis ^ permalink raw reply [nested|flat] 9+ messages in thread
* Re: Speed up ICU case conversion by using ucasemap_utf8To*() @ 2026-04-14 00:20 Andreas Karlsson <[email protected]> parent: Jeff Davis <[email protected]> 0 siblings, 1 reply; 9+ messages in thread From: Andreas Karlsson @ 2026-04-14 00:20 UTC (permalink / raw) To: Jeff Davis <[email protected]>; Alexander Lakhin <[email protected]>; zengman <[email protected]>; pgsql-hackers On 4/13/26 20:40, Jeff Davis wrote: > Thank you both! Thanks! > Committed with minor revisions: > > * also check the status code, just to be sure If we do that shouldn't we also do the same in the other callsites in initdb.c uloc_getLanguage()? Maybe something like the attached. Also I wonder if maybe other ICU functions have the same risk. Andreas Attachments: [text/x-patch] v1-0001-Always-check-for-untermianted-strings-when-callin.patch (1.2K, 2-v1-0001-Always-check-for-untermianted-strings-when-callin.patch) download | inline diff: From 86703cae627f2d1a12fecb3a6ab7fbc0f0511330 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson <[email protected]> Date: Tue, 14 Apr 2026 02:12:15 +0200 Subject: [PATCH v1] Always check for untermianted strings when calling uloc_getLanguage() --- src/bin/initdb/initdb.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c index 509f1114ef6..21f25915ab2 100644 --- a/src/bin/initdb/initdb.c +++ b/src/bin/initdb/initdb.c @@ -2403,7 +2403,7 @@ icu_validate_locale(const char *loc_str) /* validate that we can extract the language */ status = U_ZERO_ERROR; uloc_getLanguage(loc_str, lang, ULOC_LANG_CAPACITY, &status); - if (U_FAILURE(status)) + if (U_FAILURE(status) || status == U_STRING_NOT_TERMINATED_WARNING) { pg_fatal("could not get language from locale \"%s\": %s", loc_str, u_errorName(status)); @@ -2423,7 +2423,7 @@ icu_validate_locale(const char *loc_str) status = U_ZERO_ERROR; uloc_getLanguage(otherloc, otherlang, ULOC_LANG_CAPACITY, &status); - if (U_FAILURE(status)) + if (U_FAILURE(status) || status === U_STRING_NOT_TERMINATED_WARNING) continue; if (strcmp(lang, otherlang) == 0) -- 2.43.0 ^ permalink raw reply [nested|flat] 9+ messages in thread
* Re: Speed up ICU case conversion by using ucasemap_utf8To*() @ 2026-04-14 00:28 Andreas Karlsson <[email protected]> parent: Andreas Karlsson <[email protected]> 0 siblings, 1 reply; 9+ messages in thread From: Andreas Karlsson @ 2026-04-14 00:28 UTC (permalink / raw) To: Jeff Davis <[email protected]>; Alexander Lakhin <[email protected]>; zengman <[email protected]>; pgsql-hackers On 4/14/26 02:20, Andreas Karlsson wrote: > If we do that shouldn't we also do the same in the other callsites in > initdb.c uloc_getLanguage()? Maybe something like the attached. Also I > wonder if maybe other ICU functions have the same risk. Now attached without a stupid typo. Andreas Attachments: [text/x-patch] v2-0001-Always-check-for-untermianted-strings-when-callin.patch (1.2K, 2-v2-0001-Always-check-for-untermianted-strings-when-callin.patch) download | inline diff: From 24bb803005d0af0bb371f22d0b6ac20fb50bdc0d Mon Sep 17 00:00:00 2001 From: Andreas Karlsson <[email protected]> Date: Tue, 14 Apr 2026 02:12:15 +0200 Subject: [PATCH v2] Always check for untermianted strings when calling uloc_getLanguage() --- src/bin/initdb/initdb.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c index 509f1114ef6..2ee834f0765 100644 --- a/src/bin/initdb/initdb.c +++ b/src/bin/initdb/initdb.c @@ -2403,7 +2403,7 @@ icu_validate_locale(const char *loc_str) /* validate that we can extract the language */ status = U_ZERO_ERROR; uloc_getLanguage(loc_str, lang, ULOC_LANG_CAPACITY, &status); - if (U_FAILURE(status)) + if (U_FAILURE(status) || status == U_STRING_NOT_TERMINATED_WARNING) { pg_fatal("could not get language from locale \"%s\": %s", loc_str, u_errorName(status)); @@ -2423,7 +2423,7 @@ icu_validate_locale(const char *loc_str) status = U_ZERO_ERROR; uloc_getLanguage(otherloc, otherlang, ULOC_LANG_CAPACITY, &status); - if (U_FAILURE(status)) + if (U_FAILURE(status) || status == U_STRING_NOT_TERMINATED_WARNING) continue; if (strcmp(lang, otherlang) == 0) -- 2.43.0 ^ permalink raw reply [nested|flat] 9+ messages in thread
* Re: Speed up ICU case conversion by using ucasemap_utf8To*() @ 2026-04-14 21:49 Jeff Davis <[email protected]> parent: Andreas Karlsson <[email protected]> 0 siblings, 0 replies; 9+ messages in thread From: Jeff Davis @ 2026-04-14 21:49 UTC (permalink / raw) To: Andreas Karlsson <[email protected]>; Alexander Lakhin <[email protected]>; zengman <[email protected]>; pgsql-hackers On Tue, 2026-04-14 at 02:28 +0200, Andreas Karlsson wrote: > On 4/14/26 02:20, Andreas Karlsson wrote: > > If we do that shouldn't we also do the same in the other callsites > > in > > initdb.c uloc_getLanguage()? Maybe something like the attached. > > Also I > > wonder if maybe other ICU functions have the same risk. Committed, thank you. Regards, Jeff Davis ^ permalink raw reply [nested|flat] 9+ messages in thread
end of thread, other threads:[~2026-04-14 21:49 UTC | newest] Thread overview: 9+ messages (download: mbox mbox.gz follow: Atom feed) -- links below jump to the message on this page -- 2026-01-03 03:12 Re: Speed up ICU case conversion by using ucasemap_utf8To*() Andreas Karlsson <[email protected]> 2026-01-06 22:10 ` Jeff Davis <[email protected]> 2026-03-12 04:00 ` Alexander Lakhin <[email protected]> 2026-04-01 00:46 ` Andreas Karlsson <[email protected]> 2026-04-13 06:35 ` Andreas Karlsson <[email protected]> 2026-04-13 18:40 ` Jeff Davis <[email protected]> 2026-04-14 00:20 ` Andreas Karlsson <[email protected]> 2026-04-14 00:28 ` Andreas Karlsson <[email protected]> 2026-04-14 21:49 ` Jeff Davis <[email protected]>
This inbox is served by agora; see mirroring instructions for how to clone and mirror all data and code used for this inbox