Paul Eggert wrote:
> > Is that what you need for GNU sed?
> 
> Yes, the idea is that if mbrtoc32 succeeds in the C locale, it should yield a 
> char32_t that can be fed back into c32rtomb and get the original byte.

Since the *c32* functions are built on the *wc* functions, it is worth
to assert the same consistency — mbrtowc and wcrtomb — in the C locale.


2026-09-20  Bruno Haible  <[email protected]>

        wcrtomb: Make consistent with mbrtowc (regression 2016-04-09).
        * m4/wcrtomb.m4 (gl_FUNC_WCRTOMB): Require gl_MBRTOWC_C_LOCALE and, if
        needed, set REPLACE_WCRTOMB and define MBRTOWC_IN_C_LOCALE_MAYBE_EILSEQ.
        * lib/mbrtowc.c (rpl_mbrtowc) [MBRTOWC_IN_C_LOCALE_MAYBE_EILSEQ]: In the
        C locale, map 0x80..0xFF to 0xDF80..0xDFFF instead of 0x0080..0x00FF.
        * lib/wcrtomb.c: Include hard-locale.h, <locale.h>.
        (wcrtomb) [MBRTOWC_IN_C_LOCALE_MAYBE_EILSEQ]: In the C locale, map
        0xDF80..0xDFFF to 0x80..0xFF.
        * modules/wcrtomb (Depends-on): Add hard-locale.
        * tests/test-wcrtomb.c (main): Check that wcrtomb does the inverse of
        mbrtowc in the C locale.

diff --git a/lib/mbrtowc.c b/lib/mbrtowc.c
index 8026734689..157321aa4b 100644
--- a/lib/mbrtowc.c
+++ b/lib/mbrtowc.c
@@ -78,7 +78,7 @@ mbrtowc (wchar_t *pwc, const char *s, size_t n, mbstate_t *ps)
 # include "localcharset.h"
 # include "streq-opt.h"
 
-# if MBRTOWC_IN_C_LOCALE_MAYBE_EILSEQ
+# if MBRTOWC_IN_C_LOCALE_MAYBE_EILSEQ   /* glibc, Cygwin < 3.5 */
 #  include "hard-locale.h"
 #  include <locale.h>
 # endif
@@ -342,11 +342,12 @@ rpl_mbrtowc (wchar_t *pwc, const char *s, size_t n, 
mbstate_t *ps)
     return 0;
 # endif
 
-# if MBRTOWC_IN_C_LOCALE_MAYBE_EILSEQ
+# if MBRTOWC_IN_C_LOCALE_MAYBE_EILSEQ   /* glibc, Cygwin < 3.5 */
+  /* In the C locale, map 0x80..0xFF to 0xDF80..0xDFFF.  */
   if ((size_t) -2 <= ret && n != 0 && ! hard_locale (LC_CTYPE))
     {
       unsigned char uc = *s;
-      *pwc = uc;
+      *pwc = 0xDF00 + uc;
       return 1;
     }
 # endif
diff --git a/lib/wcrtomb.c b/lib/wcrtomb.c
index e472731e59..5809f60130 100644
--- a/lib/wcrtomb.c
+++ b/lib/wcrtomb.c
@@ -23,6 +23,11 @@
 #include <errno.h>
 #include <stdlib.h>
 
+#if MBRTOWC_IN_C_LOCALE_MAYBE_EILSEQ
+# include "hard-locale.h"
+# include <locale.h>
+#endif
+
 
 size_t
 wcrtomb (char *s, wchar_t wc, mbstate_t *ps)
@@ -38,7 +43,8 @@ wcrtomb (char *s, wchar_t wc, mbstate_t *ps)
 
 #if !HAVE_WCRTOMB                       /* HP-UX 11.00, mingw */ \
     || WCRTOMB_RETVAL_BUG               /* Solaris 11.3, MSVC */ \
-    || WCRTOMB_C_LOCALE_BUG             /* Android */
+    || WCRTOMB_C_LOCALE_BUG             /* Android */ \
+    || MBRTOWC_IN_C_LOCALE_MAYBE_EILSEQ /* glibc, Cygwin < 3.5 */
   if (s == NULL)
     /* We know the NUL wide character corresponds to the NUL character.  */
     return 1;
@@ -60,6 +66,15 @@ wcrtomb (char *s, wchar_t wc, mbstate_t *ps)
           return (size_t)(-1);
         }
 # else
+#  if MBRTOWC_IN_C_LOCALE_MAYBE_EILSEQ  /* glibc, Cygwin < 3.5 */
+      /* Implement consistently with mbrtowc(): through a 1:1 correspondence,
+         mapping 0xDF80..0xDFFF back to 0x80..0xFF.  */
+      if (wc >= 0xDF80 && wc <= 0xDFFF && ! hard_locale (LC_CTYPE))
+        {
+          *s = (unsigned char) (wc - 0xDF00);
+          return 1;
+        }
+#  endif
       return wcrtomb (s, wc, ps);
 # endif
 #else                                   /* HP-UX 11.00, mingw */
diff --git a/m4/wcrtomb.m4 b/m4/wcrtomb.m4
index 47f89bf307..9e5ffe4b67 100644
--- a/m4/wcrtomb.m4
+++ b/m4/wcrtomb.m4
@@ -1,5 +1,5 @@
 # wcrtomb.m4
-# serial 24
+# serial 25
 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,
@@ -145,6 +145,15 @@ AC_DEFUN([gl_FUNC_WCRTOMB]
            REPLACE_WCRTOMB=1 ;;
       esac
     fi
+    dnl When we adjust mbrtowc in the C locale, we need to adjust wcrtomb as 
well, for consistency.
+    AC_REQUIRE([gl_MBRTOWC_C_LOCALE])
+    case "$gl_cv_func_mbrtowc_C_locale_sans_EILSEQ" in
+      *yes) ;;
+      *) AC_DEFINE([MBRTOWC_IN_C_LOCALE_MAYBE_EILSEQ], [1],
+           [Define if the mbrtowc function may signal encoding errors in the C 
locale.])
+         REPLACE_WCRTOMB=1
+         ;;
+    esac
   fi
 ])
 
diff --git a/modules/wcrtomb b/modules/wcrtomb
index dc0a61adb6..f3bb04d6fb 100644
--- a/modules/wcrtomb
+++ b/modules/wcrtomb
@@ -15,6 +15,7 @@ m4/codeset.m4
 Depends-on:
 wchar-h
 extensions
+hard-locale     [test $HAVE_WCRTOMB = 0 || test $REPLACE_WCRTOMB = 1]
 mbsinit         [test $HAVE_WCRTOMB = 0 || test $REPLACE_WCRTOMB = 1]
 
 configure.ac:
diff --git a/tests/test-wcrtomb.c b/tests/test-wcrtomb.c
index 7fea9cb914..681d24eac5 100644
--- a/tests/test-wcrtomb.c
+++ b/tests/test-wcrtomb.c
@@ -114,6 +114,23 @@ main (int argc, char *argv[])
       {
       case '1':
         /* C locale; tested above.  */
+#if !defined __ANDROID__
+        /* On Android ≥ 5.0, the default locale is the "C.UTF-8" locale, not 
the
+           "C" locale.  Furthermore, when you attempt to set the "C" or "POSIX"
+           locale via setlocale(), what you get is a "C" locale with UTF-8
+           encoding, that is, effectively the "C.UTF-8" locale.  */
+        /* Check that wcrtomb does the inverse of mbrtowc, in the C locale.
+           Recall that POSIX:2024 says about mbrtowc:
+             "In the POSIX locale an [EILSEQ] error cannot occur since all
+              byte values are valid characters."
+           Above we have only tested the ISO C "basic character set".  */
+        for (int c = 0; c < 0x100; c++)
+          {
+            ret = wcrtomb (buf, btowc (c), NULL);
+            ASSERT (ret == 1);
+            ASSERT (buf[0] == (char) c);
+          }
+#endif
         return test_exit_status;
 
       case '2':




Reply via email to