Hi Collin, Collin Funk wrote: > On Windows (x86_64-w64-mingw32), I see the following warning: > > $ gnulib-tool --create-testdir --dir testdir1 > $ cd testdir1 > $ ./configure CFLAGS='-Wdiscarded-qualifiers' > $ cd glllib > $ make setlocale.o > depbase=`echo setlocale.o | sed 's|[^/]*$|.deps/&|;s|\.o$||'`;\ > gcc -DHAVE_CONFIG_H -DEXEEXT=\".exe\" -DEXEEXT=\".exe\" -DNO_XMALLOC > -DEXEEXT=\".exe\" -I. -I.. -DGNULIB_STRICT_CHECKING=1 -I/mingw64/include > -Wdiscarded-qualifiers -MT setlocale.o -MD -MP -MF $depbase.Tpo -c -o > setlocale.o setlocale.c &&\ > mv -f $depbase.Tpo $depbase.Po > setlocale.c: In function 'setlocale_single': > setlocale.c:889:12: warning: return discards 'const' qualifier from > pointer target type [-Wdiscarded-qualifiers] > 889 | return setlocale_messages (locale); > | ^~~~~~~~~~~~~~~~~~~~~~~~~~~ > > The attached patch fixes it by casting away the const.
I don't like this because, although in line with the previous code, it adds one more cast. > Another > reasonable solution is changing the definition of setlocale_messages to > return a plain "char *". Returning a 'char *' instead of 'const char *'? This goes in the backward direction. Look: ISO C has 'const char *' since 1990. It also has 'setlocale' since 1990. POSIX <https://pubs.opengroup.org/onlinepubs/9799919799/functions/setlocale.html> says: "The application shall not modify the string returned." Which means that the intended return type is 'const char *'. The return type 'char *' is a historical accident. (Probably the ANSI C89 committee simply lacked the courage to add 'const'.) But we can add the intended return type in Gnulib. Done as follows. This patch removes two casts, instead of adding a cast. 2026-06-07 Bruno Haible <[email protected]> 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/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 @@ @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 <uchar.h> ]], [[ 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 <uchar.h> ]], [[ 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 <uchar.h> ]], [[ - 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 <wchar.h> ]], [[ 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 <locale.h> #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 <stdlib.h> #include <string.h>
