From: Bruno Haible Date: Sun, 7 Jun 2026 12:57:38 +0000 (+0200) Subject: setlocale: Detect invalid writes to the returned string in some cases. X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=8724b4400aacd18cbf9697aec79e1e94a081ddd9;p=thirdparty%2Fgnulib.git setlocale: Detect invalid writes to the returned string in some cases. * lib/locale.in.h (setlocale): Change the return type to 'const char *'. * lib/setlocale.c (setlocale_mtsafe, setlocale_unixlike): Change the return type to 'const char *'. Remove a cast. (setlocale_single): Change the return type to 'const char *'. (setlocale_improved): Change the return type to 'const char *'. Change three variables from 'char *' to 'const char *'. Add local variable old_locale. * lib/localcharset.c (locale_charset): Change two variables from 'char *' to 'const char *'. * doc/posix-functions/setlocale.texi: Document that Gnulib uses the return type 'const char *'. * NEWS: Mention the change. * m4/mbrtowc.m4 (gl_MBRTOWC_C_LOCALE): Change a variable from 'char *' to 'const char *'. * m4/mbrtoc32.m4 (gl_MBRTOC32_C_LOCALE, gl_MBRTOC32_UTF8_LOCALE): Likewise. * m4/mbrtoc16.m4 (gl_MBRTOC16_C_LOCALE): Likewise. * tests/test-setlocale-w32utf8.c (main): Likewise. * tests/test-setlocale1.c (setlocale): Update expected signature. * tests/test-locale-h-c++.cc (GNULIB_NAMESPACE::setlocale): Likewise. --- diff --git a/ChangeLog b/ChangeLog index 8f97ecaf8e..e8b41a636d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,27 @@ +2026-06-07 Bruno Haible + + setlocale: Detect invalid writes to the returned string in some cases. + * lib/locale.in.h (setlocale): Change the return type to 'const char *'. + * lib/setlocale.c (setlocale_mtsafe, setlocale_unixlike): Change the + return type to 'const char *'. Remove a cast. + (setlocale_single): Change the return type to 'const char *'. + (setlocale_improved): Change the return type to 'const char *'. Change + three variables from 'char *' to 'const char *'. Add local variable + old_locale. + * lib/localcharset.c (locale_charset): Change two variables from + 'char *' to 'const char *'. + * doc/posix-functions/setlocale.texi: Document that Gnulib uses the + return type 'const char *'. + * NEWS: Mention the change. + * m4/mbrtowc.m4 (gl_MBRTOWC_C_LOCALE): Change a variable from 'char *' + to 'const char *'. + * m4/mbrtoc32.m4 (gl_MBRTOC32_C_LOCALE, gl_MBRTOC32_UTF8_LOCALE): + Likewise. + * m4/mbrtoc16.m4 (gl_MBRTOC16_C_LOCALE): Likewise. + * tests/test-setlocale-w32utf8.c (main): Likewise. + * tests/test-setlocale1.c (setlocale): Update expected signature. + * tests/test-locale-h-c++.cc (GNULIB_NAMESPACE::setlocale): Likewise. + 2026-06-07 Bruno Haible tests: Avoid conflict between mingw standard C++ library and Gnulib. diff --git a/NEWS b/NEWS index faaece85b2..9369ba9acc 100644 --- a/NEWS +++ b/NEWS @@ -78,6 +78,13 @@ User visible incompatible changes Date Modules Changes +2026-06-07 setlocale The return type of the setlocale function is now + 'const char *' on some platforms. You may need to + adjust assignments of the form + char *l = setlocale (...); + to + const char *l = setlocale (...); + 2026-03-31 lock This module no longer defines the once-only execution primitives. For these, request the 'once' module and #include "glthread/once.h". diff --git a/doc/posix-functions/setlocale.texi b/doc/posix-functions/setlocale.texi index ecc342a476..25f8a9500a 100644 --- a/doc/posix-functions/setlocale.texi +++ b/doc/posix-functions/setlocale.texi @@ -65,6 +65,13 @@ the @code{"C"} or @code{"POSIX"} locale actually sets an equivalent of the @code{"C.UTF-8"} locale. @end itemize +Note: The return type of the @code{setlocale} function +is @code{char *} in POSIX, with the constraint that +``The application shall not modify the string returned.'' +The Gnulib-overridden @code{setlocale} function +has a return type of @code{const char *}. +This helps detecting code that attempts to write into that string. + Note: The names of locales with UTF-8 encoding are platform dependent: @itemize @item diff --git a/lib/localcharset.c b/lib/localcharset.c index 48409137f4..7c00d078d9 100644 --- a/lib/localcharset.c +++ b/lib/localcharset.c @@ -882,8 +882,8 @@ locale_charset (void) 'setlocale' call specified. So we use it as a last resort, in case the string returned by 'setlocale' doesn't specify the codepage. */ - char *current_locale = setlocale (LC_CTYPE, NULL); - char *pdot = strrchr (current_locale, '.'); + const char *current_locale = setlocale (LC_CTYPE, NULL); + const char *pdot = strrchr (current_locale, '.'); if (pdot && 2 + strlen (pdot + 1) + 1 <= sizeof (buf)) sprintf (buf, "CP%s", pdot + 1); diff --git a/lib/locale.in.h b/lib/locale.in.h index e529111e48..7db5621452 100644 --- a/lib/locale.in.h +++ b/lib/locale.in.h @@ -272,16 +272,21 @@ _GL_WARN_ON_USE (localeconv, #endif #if @GNULIB_SETLOCALE@ +/* The return type 'const char *' serves the purpose of producing warnings + for invalid uses of the value returned from this function. */ # if @REPLACE_SETLOCALE@ # if !(defined __cplusplus && defined GNULIB_NAMESPACE) # undef setlocale # define setlocale rpl_setlocale # define GNULIB_defined_setlocale 1 # endif -_GL_FUNCDECL_RPL (setlocale, char *, (int category, const char *locale), ); -_GL_CXXALIAS_RPL (setlocale, char *, (int category, const char *locale)); +_GL_FUNCDECL_RPL (setlocale, const char *, + (int category, const char *locale), ); +_GL_CXXALIAS_RPL (setlocale, const char *, + (int category, const char *locale)); # else -_GL_CXXALIAS_SYS (setlocale, char *, (int category, const char *locale)); +_GL_CXXALIAS_SYS_CAST (setlocale, const char *, + (int category, const char *locale)); # endif # if __GLIBC__ >= 2 _GL_CXXALIASWARN (setlocale); diff --git a/lib/setlocale.c b/lib/setlocale.c index d40e1e2efd..ee10a4425d 100644 --- a/lib/setlocale.c +++ b/lib/setlocale.c @@ -73,11 +73,11 @@ extern void gl_locale_name_canonicalize (char *name); # if NEED_SETLOCALE_IMPROVED static # endif -char * +const char * setlocale_mtsafe (int category, const char *locale) { if (locale == NULL) - return (char *) setlocale_null (category); + return setlocale_null (category); else return setlocale (category, locale); } @@ -668,7 +668,7 @@ search (const struct table_entry *table, size_t table_size, const char *string, /* Like setlocale, but accept also locale names in the form ll or ll_CC, where ll is an ISO 639 language code and CC is an ISO 3166 country code. */ -static char * +static const char * setlocale_unixlike (int category, const char *locale) { int is_utf8 = (GetACP () == 65001); @@ -688,7 +688,7 @@ setlocale_unixlike (int category, const char *locale) locale = "English_United States.65001"; /* First, try setlocale with the original argument unchanged. */ - char *result = setlocale_mtsafe (category, locale); + const char *result = setlocale_mtsafe (category, locale); if (result != NULL) return result; @@ -845,10 +845,10 @@ setlocale_unixlike (int category, const char *locale) # elif defined __ANDROID__ /* Like setlocale, but accept also the locale names "C" and "POSIX". */ -static char * +static const char * setlocale_unixlike (int category, const char *locale) { - char *result = setlocale_fixed (category, locale); + const char *result = setlocale_fixed (category, locale); if (result == NULL) switch (category) { @@ -866,7 +866,7 @@ setlocale_unixlike (int category, const char *locale) case LC_MEASUREMENT: if (locale == NULL || streq (locale, "C") || streq (locale, "POSIX")) - result = (char *) "C"; + result = "C"; break; default: break; @@ -882,7 +882,7 @@ setlocale_unixlike (int category, const char *locale) # if LC_MESSAGES == 1729 /* Like setlocale, but support also LC_MESSAGES. */ -static char * +static const char * setlocale_single (int category, const char *locale) { if (category == LC_MESSAGES) @@ -1417,7 +1417,7 @@ get_main_locale_with_same_territory (const char *locale) # endif -char * +const char * setlocale_improved (int category, const char *locale) { if (locale != NULL && locale[0] == '\0') @@ -1437,10 +1437,10 @@ setlocale_improved (int category, const char *locale) }; /* Back up the old locale, in case one of the steps fails. */ - char *saved_locale = setlocale (LC_ALL, NULL); - if (saved_locale == NULL) + const char *old_locale = setlocale (LC_ALL, NULL); + if (old_locale == NULL) return NULL; - saved_locale = strdup (saved_locale); + char *saved_locale = strdup (old_locale); if (saved_locale == NULL) return NULL; @@ -1672,18 +1672,16 @@ setlocale_improved (int category, const char *locale) /* In the underlying implementation, LC_ALL does not contain LC_MESSAGES. Therefore we need to handle LC_MESSAGES separately. */ - char *result; + const char *result; # if defined _WIN32 && ! defined __CYGWIN__ if (strchr (native_locale, '.') != NULL) { - char *saved_locale; - /* Back up the old locale. */ - saved_locale = setlocale (LC_ALL, NULL); - if (saved_locale == NULL) + const char *old_locale = setlocale (LC_ALL, NULL); + if (old_locale == NULL) return NULL; - saved_locale = strdup (saved_locale); + char *saved_locale = strdup (old_locale); if (saved_locale == NULL) return NULL; @@ -1747,8 +1745,8 @@ setlocale_improved (int category, const char *locale) "LC_COLLATE=...;LC_CTYPE=...;LC_MONETARY=...;LC_NUMERIC=...;LC_TIME=..." If necessary, add ";LC_MESSAGES=..." at the end. */ { - char *name1 = setlocale (LC_ALL, NULL); - char *name2 = setlocale_single (LC_MESSAGES, NULL); + const char *name1 = setlocale (LC_ALL, NULL); + const char *name2 = setlocale_single (LC_MESSAGES, NULL); if (streq (name1, name2)) /* Not a mixed locale. */ return name1; diff --git a/m4/mbrtoc16.m4 b/m4/mbrtoc16.m4 index a14a7a093d..f8a8be460a 100644 --- a/m4/mbrtoc16.m4 +++ b/m4/mbrtoc16.m4 @@ -1,5 +1,5 @@ # mbrtoc16.m4 -# serial 4 +# serial 5 dnl Copyright (C) 2014-2026 Free Software Foundation, Inc. dnl This file is free software; the Free Software Foundation dnl gives unlimited permission to copy and/or distribute it, @@ -288,7 +288,7 @@ AC_DEFUN([gl_MBRTOC16_C_LOCALE], #include ]], [[ int i; - char *locale = setlocale (LC_ALL, "C"); + const char *locale = setlocale (LC_ALL, "C"); if (! locale) return 2; for (i = CHAR_MIN; i <= CHAR_MAX; i++) diff --git a/m4/mbrtoc32.m4 b/m4/mbrtoc32.m4 index 3eaa49ff41..9eb9a7e223 100644 --- a/m4/mbrtoc32.m4 +++ b/m4/mbrtoc32.m4 @@ -1,5 +1,5 @@ # mbrtoc32.m4 -# serial 24 +# serial 25 dnl Copyright (C) 2014-2026 Free Software Foundation, Inc. dnl This file is free software; the Free Software Foundation dnl gives unlimited permission to copy and/or distribute it, @@ -179,7 +179,7 @@ AC_DEFUN([gl_MBRTOC32_C_LOCALE], #include ]], [[ int i; - char *locale = setlocale (LC_ALL, "C"); + const char *locale = setlocale (LC_ALL, "C"); if (! locale) return 2; for (i = CHAR_MIN; i <= CHAR_MAX; i++) @@ -220,7 +220,7 @@ AC_DEFUN([gl_MBRTOC32_UTF8_LOCALE], #endif #include ]], [[ - char *locale = setlocale (LC_ALL, "en_US.UTF-8"); + const char *locale = setlocale (LC_ALL, "en_US.UTF-8"); if (locale) { /* This test fails on Cygwin 3.5.3. */ diff --git a/m4/mbrtowc.m4 b/m4/mbrtowc.m4 index fdc05da3a1..456f5e2147 100644 --- a/m4/mbrtowc.m4 +++ b/m4/mbrtowc.m4 @@ -1,5 +1,5 @@ # mbrtowc.m4 -# serial 50 +# serial 51 dnl Copyright (C) 2001-2002, 2004-2005, 2008-2026 Free Software Foundation, dnl Inc. dnl This file is free software; the Free Software Foundation @@ -683,7 +683,7 @@ AC_DEFUN([gl_MBRTOWC_C_LOCALE], #include ]], [[ int i; - char *locale = setlocale (LC_ALL, "C"); + const char *locale = setlocale (LC_ALL, "C"); if (! locale) return 2; for (i = CHAR_MIN; i <= CHAR_MAX; i++) diff --git a/tests/test-locale-h-c++.cc b/tests/test-locale-h-c++.cc index 80d9354eb1..25bd29d1af 100644 --- a/tests/test-locale-h-c++.cc +++ b/tests/test-locale-h-c++.cc @@ -29,7 +29,7 @@ SIGNATURE_CHECK (GNULIB_NAMESPACE::localeconv, struct lconv *, (void)); #endif #if GNULIB_TEST_SETLOCALE -SIGNATURE_CHECK (GNULIB_NAMESPACE::setlocale, char *, (int, const char *)); +SIGNATURE_CHECK (GNULIB_NAMESPACE::setlocale, const char *, (int, const char *)); #endif #if GNULIB_TEST_NEWLOCALE diff --git a/tests/test-setlocale-w32utf8.c b/tests/test-setlocale-w32utf8.c index e78f4ece9c..b4fc130610 100644 --- a/tests/test-setlocale-w32utf8.c +++ b/tests/test-setlocale-w32utf8.c @@ -30,7 +30,7 @@ main (void) { #ifdef _UCRT /* Test that setlocale() works as expected in a UTF-8 locale. */ - char *name; + const char *name; /* This looks at all LC_*, LANG environment variables, which are all unset at this point. */ diff --git a/tests/test-setlocale1.c b/tests/test-setlocale1.c index d622ea74d3..9bc56fe200 100644 --- a/tests/test-setlocale1.c +++ b/tests/test-setlocale1.c @@ -19,7 +19,11 @@ #include #include "signature.h" +#if GNULIB_defined_setlocale +SIGNATURE_CHECK (setlocale, const char *, (int, const char *)); +#else SIGNATURE_CHECK (setlocale, char *, (int, const char *)); +#endif #include #include