Hi Alejandro,

> I will mention within the committee that now that we've changed the
> prototypes of string APIs --thus accepting breaking changes--, we could
> embrace the const-correctness even further by changing the return types
> of those functions too.

Thanks! That would be useful.

> Of which, the following are in ISO C:
> 
>       localeconv
>       setlocale
>       strerror

So, let me do the same change also for localeconv and strerror (patches
attached).

With this, you can tell the committee that prior art exists, and they can
discuss all 3 functions at once.

> I've reviewed 8724b4400aac (2026-06-07; "setlocale: Detect invalid
> writes to the returned string in some cases."), and it LGTM.  Thanks!
> 
> Should I also have a look at anything else from this thread?

You are welcome to review our mails and patches as much as you want, of
course :) But I don't expect it from you.

Thanks for your WG14 liaison work!

Bruno
>From 87b9d1e0868811274ce3efbad5fe334e31a810d0 Mon Sep 17 00:00:00 2001
From: Bruno Haible <[email protected]>
Date: Mon, 8 Jun 2026 12:57:03 +0200
Subject: [PATCH 1/2] localeconv: Detect invalid writes to the returned struct
 in some cases.

* lib/locale.in.h (localeconv): Change the return type to
'const struct lconv *'.
(GNULIB_defined_localeconv): New macro.
* lib/localeconv.c (localeconv): Change the return type to
'const struct lconv *'. Change a variable from 'struct lconv *' to
'const struct lconv *'.
* doc/posix-functions/localeconv.texi: Document that Gnulib uses the
return type 'const struct lconv *'.
* NEWS: Mention the change.
* m4/localeconv.m4 (gl_FUNC_LOCALECONV): Change a variable from
'struct lconv *' to 'const struct lconv *'.
* tests/test-localeconv.c (localeconv): Update expected signature.
(main): Change a variable from 'struct lconv *' to
'const struct lconv *'.
* tests/test-locale-h-c++.cc (GNULIB_NAMESPACE::localeconv): Update
expected signature.
---
 ChangeLog                           | 20 ++++++++++++++++++++
 NEWS                                |  7 +++++++
 doc/posix-functions/localeconv.texi |  8 ++++++++
 lib/locale.in.h                     | 12 +++++++++---
 lib/localeconv.c                    |  6 +++---
 m4/localeconv.m4                    |  4 ++--
 tests/test-locale-h-c++.cc          |  2 +-
 tests/test-localeconv.c             |  6 +++++-
 8 files changed, 55 insertions(+), 10 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 0de8c029f9..0a779c4ce2 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,23 @@
+2026-06-08  Bruno Haible  <[email protected]>
+
+	localeconv: Detect invalid writes to the returned struct in some cases.
+	* lib/locale.in.h (localeconv): Change the return type to
+	'const struct lconv *'.
+	(GNULIB_defined_localeconv): New macro.
+	* lib/localeconv.c (localeconv): Change the return type to
+	'const struct lconv *'. Change a variable from 'struct lconv *' to
+	'const struct lconv *'.
+	* doc/posix-functions/localeconv.texi: Document that Gnulib uses the
+	return type 'const struct lconv *'.
+	* NEWS: Mention the change.
+	* m4/localeconv.m4 (gl_FUNC_LOCALECONV): Change a variable from
+	'struct lconv *' to 'const struct lconv *'.
+	* tests/test-localeconv.c (localeconv): Update expected signature.
+	(main): Change a variable from 'struct lconv *' to
+	'const struct lconv *'.
+	* tests/test-locale-h-c++.cc (GNULIB_NAMESPACE::localeconv): Update
+	expected signature.
+
 2026-06-08  Bruno Haible  <[email protected]>
 
 	setlocale: Avoid a portability pitfall through the last patch.
diff --git a/NEWS b/NEWS
index 260f2736c4..6f2a0a4b89 100644
--- a/NEWS
+++ b/NEWS
@@ -78,6 +78,13 @@ User visible incompatible changes
 
 Date        Modules         Changes
 
+2026-06-08  localeconv      The return type of the strerror function is now
+                            'const struct lconv *' on most platforms.  You may
+                            need to adjust assignments of the form
+                              struct lconv *l = localeconv ();
+                            to
+                              const struct lconv *l = localeconv ();
+
 2026-06-07  setlocale       The return type of the setlocale function is now
                             'const char *' on most platforms.  You may need to
                             adjust assignments of the form
diff --git a/doc/posix-functions/localeconv.texi b/doc/posix-functions/localeconv.texi
index c27fad0a19..5b6e8a6286 100644
--- a/doc/posix-functions/localeconv.texi
+++ b/doc/posix-functions/localeconv.texi
@@ -27,3 +27,11 @@
 Portability problems not fixed by Gnulib:
 @itemize
 @end itemize
+
+Note: The return type of the @code{localeconv} function
+is @code{struct lconv *} in POSIX, with the constraint that
+``The application shall not modify the structure to which the return value
+points.''
+The Gnulib-overridden @code{localeconv} function
+has a return type of @code{const struct lconv *}.
+This helps detecting code that attempts to write into that structure.
diff --git a/lib/locale.in.h b/lib/locale.in.h
index fdf8c065d4..cdc3aab14f 100644
--- a/lib/locale.in.h
+++ b/lib/locale.in.h
@@ -245,15 +245,21 @@ struct lconv
 #endif
 
 #if @GNULIB_LOCALECONV@
+/* The return type 'const struct lconv *' serves the purpose of producing
+   warnings for invalid uses of the value returned from this function.  */
 # if @REPLACE_LOCALECONV@
 #  if !(defined __cplusplus && defined GNULIB_NAMESPACE)
 #   undef localeconv
 #   define localeconv rpl_localeconv
+#   define GNULIB_defined_localeconv
 #  endif
-_GL_FUNCDECL_RPL (localeconv, struct lconv *, (void), );
-_GL_CXXALIAS_RPL (localeconv, struct lconv *, (void));
+_GL_FUNCDECL_RPL (localeconv, const struct lconv *, (void), );
+_GL_CXXALIAS_RPL (localeconv, const struct lconv *, (void));
 # else
-_GL_CXXALIAS_SYS (localeconv, struct lconv *, (void));
+_GL_CXXALIAS_SYS_CAST (localeconv, const struct lconv *, (void));
+#  if !defined localeconv && !defined __cplusplus
+#   define localeconv() ((const struct lconv *) localeconv ())
+#  endif
 # endif
 # if __GLIBC__ >= 2
 _GL_CXXALIASWARN (localeconv);
diff --git a/lib/localeconv.c b/lib/localeconv.c
index bf6271eed8..2d3a2251b1 100644
--- a/lib/localeconv.c
+++ b/lib/localeconv.c
@@ -28,13 +28,13 @@
 /* Override for platforms where 'struct lconv' lacks the int_p_*, int_n_*
    members or where fields of type 'char' are set to -1 instead of CHAR_MAX.  */
 
-struct lconv *
+const struct lconv *
 localeconv (void)
 {
   static struct lconv result;
 # undef lconv
 # undef localeconv
-  struct lconv *sys_result = localeconv ();
+  const struct lconv *sys_result = localeconv ();
 
   result.decimal_point = sys_result->decimal_point;
   result.thousands_sep = sys_result->thousands_sep;
@@ -77,7 +77,7 @@ localeconv (void)
 
 /* Override for platforms where 'struct lconv' is a dummy.  */
 
-struct lconv *
+const struct lconv *
 localeconv (void)
 {
   static /*const*/ struct lconv result =
diff --git a/m4/localeconv.m4 b/m4/localeconv.m4
index 9fa31de899..a230b644a5 100644
--- a/m4/localeconv.m4
+++ b/m4/localeconv.m4
@@ -1,5 +1,5 @@
 # localeconv.m4
-# serial 3
+# serial 4
 dnl Copyright (C) 2012-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,
@@ -26,7 +26,7 @@ AC_DEFUN([gl_FUNC_LOCALECONV]
             #include <limits.h>
             int main ()
             {
-              struct lconv *l = localeconv ();
+              const struct lconv *l = localeconv ();
               return l->frac_digits != CHAR_MAX && l->frac_digits < 0;
             }
          ]])],
diff --git a/tests/test-locale-h-c++.cc b/tests/test-locale-h-c++.cc
index 25bd29d1af..019916bfc3 100644
--- a/tests/test-locale-h-c++.cc
+++ b/tests/test-locale-h-c++.cc
@@ -25,7 +25,7 @@
 
 
 #if GNULIB_TEST_LOCALECONV
-SIGNATURE_CHECK (GNULIB_NAMESPACE::localeconv, struct lconv *, (void));
+SIGNATURE_CHECK (GNULIB_NAMESPACE::localeconv, const struct lconv *, (void));
 #endif
 
 #if GNULIB_TEST_SETLOCALE
diff --git a/tests/test-localeconv.c b/tests/test-localeconv.c
index c5a0d12f6c..4509ec22c6 100644
--- a/tests/test-localeconv.c
+++ b/tests/test-localeconv.c
@@ -21,7 +21,11 @@
 #include <locale.h>
 
 #include "signature.h"
+#if GNULIB_defined_localeconv
+SIGNATURE_CHECK (localeconv, const struct lconv *, (void));
+#else
 SIGNATURE_CHECK (localeconv, struct lconv *, (void));
+#endif
 
 #include <limits.h>
 #include <string.h>
@@ -33,7 +37,7 @@ main ()
 {
   /* Test localeconv() result in the "C" locale.  */
   {
-    struct lconv *l = localeconv ();
+    const struct lconv *l = localeconv ();
 
     ASSERT (streq (l->decimal_point, "."));
     ASSERT (streq (l->thousands_sep, ""));
-- 
2.54.0

>From 88592a2880cf39a2f597cd0294a90d8dd7faa2df Mon Sep 17 00:00:00 2001
From: Bruno Haible <[email protected]>
Date: Mon, 8 Jun 2026 11:46:56 +0200
Subject: [PATCH 2/2] strerror: Detect invalid writes to the returned string in
 some cases.

* lib/string.in.h (strerror): Change the return type to 'const char *'.
(GNULIB_defined_strerror): New macro.
* lib/strerror.c (strerror): Change the return type to 'const char *'.
* lib/strerror_l.c (errno_string_callback): Change a variable from
'char *' to 'const char *'.
* lib/strerror_r.c (strerror_r): Likewise.
* doc/posix-functions/strerror.texi: Document that Gnulib uses the
return type 'const char *'.
* NEWS: Mention the change.
* m4/perror.m4 (gl_FUNC_PERROR): Change a variable from 'char *' to
'const char *'.
* m4/strerror.m4 (gl_FUNC_STRERROR_0): Likewise.
* m4/strerror_r.m4 (gl_FUNC_STRERROR_R_WORKS): Likewise.
* tests/test-strerror.c (strerror): Update expected signature.
(main): Change a variable from 'char *' to 'const char *'.
* tests/test-string-h-c++.cc (GNULIB_NAMESPACE::strerror): Update
expected signature.
---
 ChangeLog                         | 21 +++++++++++++++++++++
 NEWS                              |  7 +++++++
 doc/posix-functions/strerror.texi |  7 +++++++
 lib/strerror.c                    |  2 +-
 lib/strerror_l.c                  |  2 +-
 lib/strerror_r.c                  |  4 ++--
 lib/string.in.h                   | 12 +++++++++---
 m4/perror.m4                      |  4 ++--
 m4/strerror.m4                    |  4 ++--
 m4/strerror_r.m4                  |  4 ++--
 tests/test-strerror.c             |  6 +++++-
 tests/test-string-h-c++.cc        |  2 +-
 12 files changed, 60 insertions(+), 15 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 0a779c4ce2..63f2d8b50a 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,24 @@
+2026-06-08  Bruno Haible  <[email protected]>
+
+	strerror: Detect invalid writes to the returned string in some cases.
+	* lib/string.in.h (strerror): Change the return type to 'const char *'.
+	(GNULIB_defined_strerror): New macro.
+	* lib/strerror.c (strerror): Change the return type to 'const char *'.
+	* lib/strerror_l.c (errno_string_callback): Change a variable from
+	'char *' to 'const char *'.
+	* lib/strerror_r.c (strerror_r): Likewise.
+	* doc/posix-functions/strerror.texi: Document that Gnulib uses the
+	return type 'const char *'.
+	* NEWS: Mention the change.
+	* m4/perror.m4 (gl_FUNC_PERROR): Change a variable from 'char *' to
+	'const char *'.
+	* m4/strerror.m4 (gl_FUNC_STRERROR_0): Likewise.
+	* m4/strerror_r.m4 (gl_FUNC_STRERROR_R_WORKS): Likewise.
+	* tests/test-strerror.c (strerror): Update expected signature.
+	(main): Change a variable from 'char *' to 'const char *'.
+	* tests/test-string-h-c++.cc (GNULIB_NAMESPACE::strerror): Update
+	expected signature.
+
 2026-06-08  Bruno Haible  <[email protected]>
 
 	localeconv: Detect invalid writes to the returned struct in some cases.
diff --git a/NEWS b/NEWS
index 6f2a0a4b89..8456732c43 100644
--- a/NEWS
+++ b/NEWS
@@ -78,6 +78,13 @@ User visible incompatible changes
 
 Date        Modules         Changes
 
+2026-06-08  strerror        The return type of the strerror function is now
+                            'const char *' on most platforms.  You may need to
+                            adjust assignments of the form
+                              char *s = strerror (...);
+                            to
+                              const char *s = strerror (...);
+
 2026-06-08  localeconv      The return type of the strerror function is now
                             'const struct lconv *' on most platforms.  You may
                             need to adjust assignments of the form
diff --git a/doc/posix-functions/strerror.texi b/doc/posix-functions/strerror.texi
index 74c87c0af1..abb26de1ea 100644
--- a/doc/posix-functions/strerror.texi
+++ b/doc/posix-functions/strerror.texi
@@ -28,3 +28,10 @@
 Portability problems not fixed by Gnulib:
 @itemize
 @end itemize
+
+Note: The return type of the @code{strerror} function
+is @code{char *} in POSIX, with the constraint that
+``The application shall not modify the string returned.''
+The Gnulib-overridden @code{strerror} function
+has a return type of @code{const char *}.
+This helps detecting code that attempts to write into that string.
diff --git a/lib/strerror.c b/lib/strerror.c
index 9d8554f2cf..f92a7399c0 100644
--- a/lib/strerror.c
+++ b/lib/strerror.c
@@ -37,7 +37,7 @@
 # pragma GCC diagnostic ignored "-Wdeprecated-declarations"
 #endif
 
-char *
+const char *
 strerror (int n)
 #undef strerror
 {
diff --git a/lib/strerror_l.c b/lib/strerror_l.c
index a3578120c4..bb6906e79c 100644
--- a/lib/strerror_l.c
+++ b/lib/strerror_l.c
@@ -62,7 +62,7 @@ errno_minmax_callback (void *data, int err)
 static int
 errno_string_callback (void *data, int err)
 {
-  char *s = strerror (err);
+  const char *s = strerror (err);
   if (s != NULL && *s != '\0')
     c_error_strings[err - errno_min] = strdup (s);
   return 0;
diff --git a/lib/strerror_r.c b/lib/strerror_r.c
index ec7bfe6bf0..99bff174e5 100644
--- a/lib/strerror_r.c
+++ b/lib/strerror_r.c
@@ -296,7 +296,7 @@ strerror_r (int errnum, char *buf, size_t buflen)
        a pointer to a not copied string, not to a buffer.  */
     if (errnum >= 0 && errnum < sys_nerr)
       {
-        char *errmsg = strerror (errnum);
+        const char *errmsg = strerror (errnum);
 
         if (errmsg == NULL || *errmsg == '\0')
           ret = EINVAL;
@@ -311,7 +311,7 @@ strerror_r (int errnum, char *buf, size_t buflen)
     gl_lock_lock (strerror_lock);
 
     {
-      char *errmsg = strerror (errnum);
+      const char *errmsg = strerror (errnum);
 
       /* For invalid error numbers, strerror() on HP-UX 11 returns an empty
          string.  */
diff --git a/lib/string.in.h b/lib/string.in.h
index 1c46c65f60..eb22a912c3 100644
--- a/lib/string.in.h
+++ b/lib/string.in.h
@@ -1647,15 +1647,21 @@ _GL_EXTERN_C bool mbs_endswith (const char *string, const char *suffix)
 
 /* Map any int, typically from errno, into an error message.  */
 #if @GNULIB_STRERROR@
+/* The return type 'const char *' serves the purpose of producing warnings
+   for invalid uses of the value returned from this function.  */
 # if @REPLACE_STRERROR@
 #  if !(defined __cplusplus && defined GNULIB_NAMESPACE)
 #   undef strerror
 #   define strerror rpl_strerror
+#   define GNULIB_defined_strerror 1
 #  endif
-_GL_FUNCDECL_RPL (strerror, char *, (int), );
-_GL_CXXALIAS_RPL (strerror, char *, (int));
+_GL_FUNCDECL_RPL (strerror, const char *, (int), );
+_GL_CXXALIAS_RPL (strerror, const char *, (int));
 # else
-_GL_CXXALIAS_SYS (strerror, char *, (int));
+_GL_CXXALIAS_SYS_CAST (strerror, const char *, (int));
+#  if !defined strerror && !defined __cplusplus
+#   define strerror(...) ((const char *) strerror (__VA_ARGS__))
+#  endif
 # endif
 # if __GLIBC__ >= 2
 _GL_CXXALIASWARN (strerror);
diff --git a/m4/perror.m4 b/m4/perror.m4
index 4052ab4a9d..c333677dff 100644
--- a/m4/perror.m4
+++ b/m4/perror.m4
@@ -1,5 +1,5 @@
 # perror.m4
-# serial 13
+# serial 14
 dnl Copyright (C) 2008-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,
@@ -33,7 +33,7 @@ AC_DEFUN([gl_FUNC_PERROR]
                 #include <stdlib.h>
                 #include <string.h>
               ]],
-              [[char *str = strerror (-1);
+              [[const char *str = strerror (-1);
                 if (!getenv("CONFTEST_OUTPUT")) return 0;
                 if (!str) str = "";
                 puts (str);
diff --git a/m4/strerror.m4 b/m4/strerror.m4
index 9bb60c3fdf..0455df24ce 100644
--- a/m4/strerror.m4
+++ b/m4/strerror.m4
@@ -1,5 +1,5 @@
 # strerror.m4
-# serial 25
+# serial 26
 dnl Copyright (C) 2002, 2007-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,
@@ -71,7 +71,7 @@ AC_DEFUN([gl_FUNC_STRERROR_0]
            #include <errno.h>
          ]],
          [[int result = 0;
-           char *str;
+           const char *str;
            errno = 0;
            str = strerror (0);
            if (!*str) result |= 1;
diff --git a/m4/strerror_r.m4 b/m4/strerror_r.m4
index 9b056c0fd4..2250ff2fca 100644
--- a/m4/strerror_r.m4
+++ b/m4/strerror_r.m4
@@ -1,5 +1,5 @@
 # strerror_r.m4
-# serial 29
+# serial 30
 dnl Copyright (C) 2002, 2007-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,
@@ -153,7 +153,7 @@ AC_DEFUN([gl_FUNC_STRERROR_R_WORKS]
                   [[int result = 0;
                     char buf[256] = "^";
                     char copy[256];
-                    char *str = strerror (-1);
+                    const char *str = strerror (-1);
                     strcpy (copy, str);
                     if (__xpg_strerror_r (-2, buf, 1) == 0)
                       result |= 1;
diff --git a/tests/test-strerror.c b/tests/test-strerror.c
index 0b0fd8a85d..41cfe0bb6c 100644
--- a/tests/test-strerror.c
+++ b/tests/test-strerror.c
@@ -21,7 +21,11 @@
 #include <string.h>
 
 #include "signature.h"
+#if GNULIB_defined_strerror
+SIGNATURE_CHECK (strerror, const char *, (int));
+#else
 SIGNATURE_CHECK (strerror, char *, (int));
+#endif
 
 #include <errno.h>
 
@@ -30,7 +34,7 @@ SIGNATURE_CHECK (strerror, char *, (int));
 int
 main (void)
 {
-  char *str;
+  const char *str;
 
   errno = 0;
   str = strerror (EACCES);
diff --git a/tests/test-string-h-c++.cc b/tests/test-string-h-c++.cc
index bad8e7337e..54ba1870cf 100644
--- a/tests/test-string-h-c++.cc
+++ b/tests/test-string-h-c++.cc
@@ -136,7 +136,7 @@ SIGNATURE_CHECK (GNULIB_NAMESPACE::mbspbrk, char *,
 #endif
 
 #if GNULIB_TEST_STRERROR
-SIGNATURE_CHECK (GNULIB_NAMESPACE::strerror, char *, (int));
+SIGNATURE_CHECK (GNULIB_NAMESPACE::strerror, const char *, (int));
 #endif
 
 #if GNULIB_TEST_STRERROR_R
-- 
2.54.0

Reply via email to