Functions that returns a pointer into a string passed argument, such as strchr,
ought to return
  - a 'char *' if the argument is a 'char *',
  - a 'const char *' if the argument is a 'const char *'.

This is now even mandated in ISO C 23 ยง 7.26.5.1.

As C functions, these take a 'const char *' as argument and return a
'char *' (because C doesn't have function overloading).

But the effect is that buggy code like this does not produce a warning
with "gcc -Wall":
=============================================
#include <string.h>

int main ()
{
  const char *s = "hello world";
  char *p = strchr (s, ' ');
  *p = 'x';
  return 0;
}
=============================================
And yet it crashes at runtime.

In order to make this code produce a warning, one needs to define
strchr as a macro:

#if __STDC_VERSION__ >= 202311
# define strchr(s,c) (typeof(s)) strchr ((s), (c))
#elif (__GNUC__ + (__GNUC_MINOR__ >= 9) > 4) || (__clang_major__ >= 3) || 
defined __ICC  || defined __TINYC__
# define strchr(s,c) _Generic ((s), char const *: (char const *) strchr ((s), 
(c)), default: strchr ((s), (c)))
#endif

This uses the 'typeof' keyword from ISO C 23, or the '_Generic' keyword
for older compilers.

For C++, such a macro approach

#if __cplusplus >= 201103L
# define strchr(s,c) const_cast<decltype(s)>(strchr ((s), (c)))
#endif

should better be avoided, because it would not work when the first or second
argument contains template parameters that include a comma outside of
parentheses. So, for C++, the solution should be something with templates.
If someone has spare time available for this, please go ahead.

For the beginning, let me install this for 3 <string.h> functions.
This could be generalized to more functions:
  strchr
  strrchr
  strchrnul
  mbschr
  mbsrchr
  memchr
  memrchr
  rawmemchr
  strstr
  strcasestr
  memmem
  strpbrk
  mbspbrk


2025-02-09  Bruno Haible  <br...@clisp.org>

        mbsstr, mbscasestr, mbspcasecmp: Use const-improved function macros.
        * lib/string.in.h (mbsstr, mbspcasecmp, mbscasestr): Define as macros
        that cast the result to 'const char *' when the first argument is a
        'const char *'.
        * lib/mbsstr.c: Define _GL_NO_CONST_GENERICS.
        * lib/mbscasestr.c: Likewise.
        * lib/mbspcasecmp.c: Likewise.

diff --git a/lib/mbscasestr.c b/lib/mbscasestr.c
index 7a186e0f98..843229666f 100644
--- a/lib/mbscasestr.c
+++ b/lib/mbscasestr.c
@@ -15,6 +15,9 @@
    You should have received a copy of the GNU Lesser General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
 
+/* Don't use the const-improved function macros in this compilation unit.  */
+#define _GL_NO_CONST_GENERICS
+
 #include <config.h>
 
 /* Specification.  */
diff --git a/lib/mbspcasecmp.c b/lib/mbspcasecmp.c
index f57337f01e..0717cd893a 100644
--- a/lib/mbspcasecmp.c
+++ b/lib/mbspcasecmp.c
@@ -15,6 +15,9 @@
    You should have received a copy of the GNU Lesser General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
 
+/* Don't use the const-improved function macros in this compilation unit.  */
+#define _GL_NO_CONST_GENERICS
+
 #include <config.h>
 
 /* Specification.  */
diff --git a/lib/mbsstr.c b/lib/mbsstr.c
index 579bcd054d..8e8eb73d55 100644
--- a/lib/mbsstr.c
+++ b/lib/mbsstr.c
@@ -15,6 +15,9 @@
    You should have received a copy of the GNU Lesser General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
 
+/* Don't use the const-improved function macros in this compilation unit.  */
+#define _GL_NO_CONST_GENERICS
+
 #include <config.h>
 
 /* Specification.  */
diff --git a/lib/string.in.h b/lib/string.in.h
index ce48829900..6705967c59 100644
--- a/lib/string.in.h
+++ b/lib/string.in.h
@@ -1178,6 +1178,19 @@ _GL_CXXALIASWARN (mbsrchr);
 _GL_EXTERN_C char * mbsstr (const char *haystack, const char *needle)
      _GL_ATTRIBUTE_PURE
      _GL_ARG_NONNULL ((1, 2));
+/* Don't silently convert a 'const char *' to a 'char *'.  Programmers want
+   compiler warnings for 'const' related mistakes.  */
+# if !(defined _GL_NO_CONST_GENERICS || defined __cplusplus)
+#  if __STDC_VERSION__ >= 202311
+#   define mbsstr(h,n) (typeof(h)) mbsstr ((h), (n))
+#  elif ((__GNUC__ + (__GNUC_MINOR__ >= 9) > 4) || (__clang_major__ >= 3) \
+         || defined __ICC  || defined __TINYC__)
+#   define mbsstr(h,n) \
+      _Generic ((h), \
+                char const *: (char const *) mbsstr ((h), (n)), \
+                default     :                mbsstr ((h), (n)))
+#  endif
+# endif
 #endif
 
 #if @GNULIB_MBSCASECMP@
@@ -1219,6 +1232,19 @@ _GL_EXTERN_C int mbsncasecmp (const char *s1, const char 
*s2, size_t n)
 _GL_EXTERN_C char * mbspcasecmp (const char *string, const char *prefix)
      _GL_ATTRIBUTE_PURE
      _GL_ARG_NONNULL ((1, 2));
+/* Don't silently convert a 'const char *' to a 'char *'.  Programmers want
+   compiler warnings for 'const' related mistakes.  */
+# if !(defined _GL_NO_CONST_GENERICS || defined __cplusplus)
+#  if __STDC_VERSION__ >= 202311
+#   define mbspcasecmp(s,p) (typeof(s)) mbspcasecmp ((s), (p))
+#  elif ((__GNUC__ + (__GNUC_MINOR__ >= 9) > 4) || (__clang_major__ >= 3) \
+         || defined __ICC  || defined __TINYC__)
+#   define mbspcasecmp(s,p) \
+      _Generic ((s), \
+                char const *: (char const *) mbspcasecmp ((s), (p)), \
+                default     :                mbspcasecmp ((s), (p)))
+#  endif
+# endif
 #endif
 
 #if @GNULIB_MBSCASESTR@
@@ -1230,6 +1256,19 @@ _GL_EXTERN_C char * mbspcasecmp (const char *string, 
const char *prefix)
 _GL_EXTERN_C char * mbscasestr (const char *haystack, const char *needle)
      _GL_ATTRIBUTE_PURE
      _GL_ARG_NONNULL ((1, 2));
+/* Don't silently convert a 'const char *' to a 'char *'.  Programmers want
+   compiler warnings for 'const' related mistakes.  */
+# if !(defined _GL_NO_CONST_GENERICS || defined __cplusplus)
+#  if __STDC_VERSION__ >= 202311
+#   define mbscasestr(h,n) (typeof(h)) mbscasestr ((h), (n))
+#  elif ((__GNUC__ + (__GNUC_MINOR__ >= 9) > 4) || (__clang_major__ >= 3) \
+         || defined __ICC  || defined __TINYC__)
+#   define mbscasestr(h,n) \
+      _Generic ((h), \
+                char const *: (char const *) mbscasestr ((h), (n)), \
+                default     :                mbscasestr ((h), (n)))
+#  endif
+# endif
 #endif
 
 #if @GNULIB_MBSCSPN@




Reply via email to