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':
