This ports Gnulib to strict C23 platforms that reject code
like ‘char *q = strchr (P, 'x');’ when P is a pointer to const,
because in C23 strchr is a qualifier-generic function so
strchr (P, 'x') returns char const *.
This patch does not attempt to do the following two things,
which might be useful in the future:
1. When compiling on non-C23 platforms, check user code for
portability to platforms that define qualifier-generic functions.
2. Port Gnulib to platforms that have qualifier-generic functions
not listed in the C23 standard, e.g., strchrnul.  I don’t know
of any such platforms.
* lib/argp-help.c (argp_doc):
* lib/c-strstr.c (c_strstr):
* lib/dfa.c (comsubs):
* lib/mbschr.c (mbschr):
* lib/mbspbrk.c (mbspbrk):
* lib/mbsrchr.c (mbsrchr):
* lib/memchr2.c (memchr2):
* lib/string-desc.c (_sd_index):
* tests/test-bsearch.c (lib_bsearch):
* tests/test-memchr.c (lib_memchr):
* tests/test-wmemchr.c (lib_wmemchr):
Port to C23, where functions like strchr are qualifier-generic.
* lib/c++defs.h (_GL_FUNCDECL_SYS_NAME): New macro.
* lib/c++defs.h (_GL_FUNCDECL_SYS):
* lib/stdlib.in.h (bsearch):
Use it, to prevent C23 names like strchr from acting like macros.
* lib/string.in.h (memchr, strchr, strpbrk, strrchr):
Do not #undef when GNULIB_POSIXCHECK is defined, as this could
cause conforming C23 code to fail to conform.  It’s not clear why
_GL_WARN_ON_USE_CXX; perhaps it was needed but isn’t any more?
But for now, limit the removal of #undef to these four functions
where #undeffing is clearly undesirable in C23.
* lib/wchar.in.h (wmemchr): Parenthesize function name in decl,
to prevent it from acting like a macro.
---
 ChangeLog            | 40 ++++++++++++++++++++++++++++++++++++++++
 lib/argp-help.c      |  2 +-
 lib/c++defs.h        | 12 +++++++++++-
 lib/c-strstr.c       |  2 +-
 lib/dfa.c            |  2 +-
 lib/mbschr.c         |  2 +-
 lib/mbspbrk.c        |  2 +-
 lib/mbsrchr.c        |  2 +-
 lib/memchr2.c        |  2 +-
 lib/stdlib.in.h      |  6 +++---
 lib/string-desc.c    |  4 ++--
 lib/string.in.h      |  4 ----
 lib/wchar.in.h       |  2 +-
 tests/test-bsearch.c |  2 +-
 tests/test-memchr.c  |  2 +-
 tests/test-wmemchr.c |  2 +-
 16 files changed, 67 insertions(+), 21 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 4eadd88f12..1cfaf9e528 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,43 @@
+2025-11-23  Paul Eggert  <[email protected]>
+
+       Port to C23 qualifier-generic fns like strchr
+       This ports Gnulib to strict C23 platforms that reject code
+       like ‘char *q = strchr (P, 'x');’ when P is a pointer to const,
+       because in C23 strchr is a qualifier-generic function so
+       strchr (P, 'x') returns char const *.
+       This patch does not attempt to do the following two things,
+       which might be useful in the future:
+       1. When compiling on non-C23 platforms, check user code for
+       portability to platforms that define qualifier-generic functions.
+       2. Port Gnulib to platforms that have qualifier-generic functions
+       not listed in the C23 standard, e.g., strchrnul.  I don’t know
+       of any such platforms.
+       * lib/argp-help.c (argp_doc):
+       * lib/c-strstr.c (c_strstr):
+       * lib/dfa.c (comsubs):
+       * lib/mbschr.c (mbschr):
+       * lib/mbspbrk.c (mbspbrk):
+       * lib/mbsrchr.c (mbsrchr):
+       * lib/memchr2.c (memchr2):
+       * lib/string-desc.c (_sd_index):
+       * tests/test-bsearch.c (lib_bsearch):
+       * tests/test-memchr.c (lib_memchr):
+       * tests/test-wmemchr.c (lib_wmemchr):
+       Port to C23, where functions like strchr are qualifier-generic.
+       * lib/c++defs.h (_GL_FUNCDECL_SYS_NAME): New macro.
+       * lib/c++defs.h (_GL_FUNCDECL_SYS):
+       * lib/stdlib.in.h (bsearch):
+       Use it, to prevent C23 names like strchr from acting like macros.
+       * lib/string.in.h (memchr, strchr, strpbrk, strrchr):
+       Do not #undef when GNULIB_POSIXCHECK is defined, as this could
+       cause conforming C23 code to fail to conform.  It’s not clear why
+       #undef is needed before others uses of _GL_WARN_ON_USE and
+       _GL_WARN_ON_USE_CXX; perhaps it was needed but isn’t any more?
+       But for now, limit the removal of #undef to these four functions
+       where #undeffing is clearly undesirable in C23.
+       * lib/wchar.in.h (wmemchr): Parenthesize function name in decl,
+       to prevent it from acting like a macro.
+
 2025-11-20  Bruno Haible  <[email protected]>
 
        strnlen: Fix compilation error (regression 2025-11-18).
diff --git a/lib/argp-help.c b/lib/argp-help.c
index f742e1870f..5bf4d3aacf 100644
--- a/lib/argp-help.c
+++ b/lib/argp-help.c
@@ -1601,7 +1601,7 @@ argp_doc (const struct argp *argp, const struct 
argp_state *state,
 
   if (doc)
     {
-      char *vt = strchr (doc, '\v');
+      char const *vt = strchr (doc, '\v');
       inp_text = post ? (vt ? vt + 1 : NULL) : doc;
       inp_text_limit = (!post && vt) ? (vt - doc) : 0;
     }
diff --git a/lib/c++defs.h b/lib/c++defs.h
index b77979a325..7384457432 100644
--- a/lib/c++defs.h
+++ b/lib/c++defs.h
@@ -127,6 +127,16 @@
 #define _GL_FUNCDECL_RPL_1(rpl_func,rettype,parameters,...) \
   _GL_EXTERN_C_FUNC __VA_ARGS__ rettype rpl_func parameters
 
+/* _GL_FUNCDECL_SYS_NAME (func) expands to plain func if C++, and to
+   parenthsized func otherwise.  Parenthesization is needed in C23 if
+   the function is like strchr and so is a qualifier-generic macro
+   that expands to something more complicated.  */
+#ifdef __cplusplus
+# define _GL_FUNCDECL_SYS_NAME(func) func
+#else
+# define _GL_FUNCDECL_SYS_NAME(func) (func)
+#endif
+
 /* _GL_FUNCDECL_SYS (func, rettype, parameters, [attributes]);
    declares the system function, named func, with the given prototype,
    consisting of return type, parameters, and attributes.
@@ -139,7 +149,7 @@
      _GL_FUNCDECL_SYS (posix_openpt, int, (int flags), 
_GL_ATTRIBUTE_NODISCARD);
  */
 #define _GL_FUNCDECL_SYS(func,rettype,parameters,...) \
-  _GL_EXTERN_C_FUNC __VA_ARGS__ rettype func parameters
+  _GL_EXTERN_C_FUNC __VA_ARGS__ rettype _GL_FUNCDECL_SYS_NAME (func) parameters
 
 /* _GL_CXXALIAS_RPL (func, rettype, parameters);
    declares a C++ alias called GNULIB_NAMESPACE::func
diff --git a/lib/c-strstr.c b/lib/c-strstr.c
index 36ee9d29ca..660da96acd 100644
--- a/lib/c-strstr.c
+++ b/lib/c-strstr.c
@@ -28,5 +28,5 @@ c_strstr (const char *haystack, const char *needle)
 {
   /* POSIX says that strstr() interprets the strings as byte sequences, not
      as character sequences in the current locale.  */
-  return strstr (haystack, needle);
+  return (char *) strstr (haystack, needle);
 }
diff --git a/lib/dfa.c b/lib/dfa.c
index 34b7f8bf2d..11309ab36b 100644
--- a/lib/dfa.c
+++ b/lib/dfa.c
@@ -4050,7 +4050,7 @@ comsubs (char *left, char const *right)
   for (char *lcp = left; *lcp != '\0'; lcp++)
     {
       idx_t len = 0;
-      char *rcp = strchr (right, *lcp);
+      char const *rcp = strchr (right, *lcp);
       while (rcp != NULL)
         {
           idx_t i;
diff --git a/lib/mbschr.c b/lib/mbschr.c
index c9e14b5baa..65821340df 100644
--- a/lib/mbschr.c
+++ b/lib/mbschr.c
@@ -65,5 +65,5 @@ mbschr (const char *string, int c)
       return NULL;
     }
   else
-    return strchr (string, c);
+    return (char *) strchr (string, c);
 }
diff --git a/lib/mbspbrk.c b/lib/mbspbrk.c
index 3331d70e5c..974efe3a29 100644
--- a/lib/mbspbrk.c
+++ b/lib/mbspbrk.c
@@ -90,5 +90,5 @@ mbspbrk (const char *string, const char *accept)
       return NULL;
     }
   else
-    return strpbrk (string, accept);
+    return (char *) strpbrk (string, accept);
 }
diff --git a/lib/mbsrchr.c b/lib/mbsrchr.c
index f1cd8dccaf..3defe676dd 100644
--- a/lib/mbsrchr.c
+++ b/lib/mbsrchr.c
@@ -64,5 +64,5 @@ mbsrchr (const char *string, int c)
       return (char *) result;
     }
   else
-    return strrchr (string, c);
+    return (char *) strrchr (string, c);
 }
diff --git a/lib/memchr2.c b/lib/memchr2.c
index c6e7f4e9dc..8ad96500bd 100644
--- a/lib/memchr2.c
+++ b/lib/memchr2.c
@@ -55,7 +55,7 @@ memchr2 (void const *s, int c1_in, int c2_in, size_t n)
   c2 = (unsigned char) c2_in;
 
   if (c1 == c2)
-    return memchr (s, c1, n);
+    return (void *) memchr (s, c1, n);
 
   /* Handle the first few bytes by reading one byte at a time.
      Do this until VOID_PTR is aligned on a longword boundary.  */
diff --git a/lib/stdlib.in.h b/lib/stdlib.in.h
index bef0aaaf92..fd0e1e0d29 100644
--- a/lib/stdlib.in.h
+++ b/lib/stdlib.in.h
@@ -224,9 +224,9 @@ _GL_INLINE_HEADER_BEGIN
 
 /* Declarations for ISO C N3322.  */
 #if defined __GNUC__ && __GNUC__ >= 15 && !defined __clang__
-_GL_EXTERN_C void *bsearch (const void *__key,
-                            const void *__base, size_t __nmemb, size_t __size,
-                            int (*__compare) (const void *, const void *))
+_GL_EXTERN_C void *_GL_FUNCDECL_SYS_NAME (bsearch)
+  (const void *__key, const void *__base, size_t __nmemb, size_t __size,
+   int (*__compare) (const void *, const void *))
   _GL_ATTRIBUTE_NONNULL_IF_NONZERO (2, 3) _GL_ARG_NONNULL ((5));
 _GL_EXTERN_C void qsort (void *__base, size_t __nmemb, size_t __size,
                          int (*__compare) (const void *, const void *))
diff --git a/lib/string-desc.c b/lib/string-desc.c
index 2d13a866b9..4113aba865 100644
--- a/lib/string-desc.c
+++ b/lib/string-desc.c
@@ -110,9 +110,9 @@ _sd_index (idx_t s_nbytes, const char *s_data, char c)
 {
   if (s_nbytes > 0)
     {
-      void *found = memchr (s_data, (unsigned char) c, s_nbytes);
+      char const *found = memchr (s_data, (unsigned char) c, s_nbytes);
       if (found != NULL)
-        return (char *) found - s_data;
+        return found - s_data;
     }
   return -1;
 }
diff --git a/lib/string.in.h b/lib/string.in.h
index fdcdd21bed..8b56acfb51 100644
--- a/lib/string.in.h
+++ b/lib/string.in.h
@@ -409,7 +409,6 @@ _GL_CXXALIASWARN1 (memchr, void const *,
 _GL_CXXALIASWARN (memchr);
 # endif
 #elif defined GNULIB_POSIXCHECK
-# undef memchr
 /* Assume memchr is always declared.  */
 _GL_WARN_ON_USE (memchr, "memchr has platform-specific bugs - "
                  "use gnulib module memchr for portability" );
@@ -674,7 +673,6 @@ _GL_WARN_ON_USE (stpncpy, "stpncpy is unportable - "
 #if defined GNULIB_POSIXCHECK
 /* strchr() does not work with multibyte strings if the locale encoding is
    GB18030 and the character to be searched is a digit.  */
-# undef strchr
 /* Assume strchr is always declared.  */
 _GL_WARN_ON_USE_CXX (strchr,
                      const char *, char *, (const char *, int),
@@ -981,7 +979,6 @@ _GL_CXXALIASWARN (strpbrk);
    Even in this simple case, it does not work with multibyte strings if the
    locale encoding is GB18030 and one of the characters to be searched is a
    digit.  */
-#  undef strpbrk
 _GL_WARN_ON_USE_CXX (strpbrk,
                      const char *, char *, (const char *, const char *),
                      "strpbrk cannot work correctly on character strings "
@@ -1011,7 +1008,6 @@ _GL_WARN_ON_USE (strspn, "strspn cannot work correctly on 
character strings "
 #if defined GNULIB_POSIXCHECK
 /* strrchr() does not work with multibyte strings if the locale encoding is
    GB18030 and the character to be searched is a digit.  */
-# undef strrchr
 /* Assume strrchr is always declared.  */
 _GL_WARN_ON_USE_CXX (strrchr,
                      const char *, char *, (const char *, int),
diff --git a/lib/wchar.in.h b/lib/wchar.in.h
index ab602a2811..6be45155f3 100644
--- a/lib/wchar.in.h
+++ b/lib/wchar.in.h
@@ -301,7 +301,7 @@ _GL_EXTERN_C int wcsncmp (const wchar_t *__s1, const 
wchar_t *__s2, size_t __n)
   _GL_ATTRIBUTE_NONNULL_IF_NONZERO (1, 3)
   _GL_ATTRIBUTE_NONNULL_IF_NONZERO (2, 3);
 # ifndef __cplusplus
-_GL_EXTERN_C wchar_t *wmemchr (const wchar_t *__s, wchar_t __wc, size_t __n)
+_GL_EXTERN_C wchar_t *(wmemchr) (const wchar_t *__s, wchar_t __wc, size_t __n)
   _GL_ATTRIBUTE_NONNULL_IF_NONZERO (1, 3);
 # endif
 _GL_EXTERN_C wchar_t *wmemset (wchar_t *__s, wchar_t __wc, size_t __n)
diff --git a/tests/test-bsearch.c b/tests/test-bsearch.c
index aad3a22807..2e9575cc41 100644
--- a/tests/test-bsearch.c
+++ b/tests/test-bsearch.c
@@ -26,7 +26,7 @@ static void *
 lib_bsearch (void const *key, void const *base, size_t nel, size_t width,
              int (*compar) (void const *, void const *))
 {
-  return bsearch (key, base, nel, width, compar);
+  return (void *) bsearch (key, base, nel, width, compar);
 }
 static void *(*volatile volatile_bsearch) (void const *, void const *, size_t,
                                            size_t,
diff --git a/tests/test-memchr.c b/tests/test-memchr.c
index 02b882a975..ef04790f90 100644
--- a/tests/test-memchr.c
+++ b/tests/test-memchr.c
@@ -31,7 +31,7 @@ SIGNATURE_CHECK (memchr, void *, (void const *, int, size_t));
 static void *
 lib_memchr (void const *s, int c, size_t n)
 {
-  return memchr (s, c, n);
+  return (void *) memchr (s, c, n);
 }
 static void *(*volatile volatile_memchr) (void const *, int, size_t)
   = lib_memchr;
diff --git a/tests/test-wmemchr.c b/tests/test-wmemchr.c
index c5e8ce541c..457ed76a9b 100644
--- a/tests/test-wmemchr.c
+++ b/tests/test-wmemchr.c
@@ -27,7 +27,7 @@
 static wchar_t *
 lib_wmemchr (wchar_t const *s, wchar_t wc, size_t n)
 {
-  return wmemchr (s, wc, n);
+  return (wchar_t *) wmemchr (s, wc, n);
 }
 static wchar_t *(*volatile volatile_wmemchr) (wchar_t const *, wchar_t, size_t)
   = lib_wmemchr;
-- 
2.51.0


Reply via email to