I wrote:
> 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.

And similarly, we need consistency for the btowc and wctob functions.


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

        wctob: Add tests.
        * tests/test-wctob.c: New file.
        * tests/test-wctob.sh: New file.
        * modules/wctob-tests: New file.

        wctob: Make consistent with wcrtomb.
        * m4/wctob.m4 (gl_FUNC_WCTOB): Require gl_MBRTOWC_C_LOCALE and, if
        needed, set REPLACE_WCTOB.
        * modules/wctob (Files): Add mbrtowc.m4.

        wctob: Make thread-safe if possible.
        * m4/wctob.m4 (gl_PREREQ_WCTOB): Test for wcrtomb.
        * lib/wctob.c (wctob): Use wcrtomb instead of wctomb if possible.
        * modules/wctob (Depends-on): Add mbszero, wcrtomb.

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

        btowc: Enhance tests.
        * tests/test-btowc.c (main): Check that btowc is consistent with
        mbrtowc.
        * modules/btowc-tests (Depends-on): Add mbrtowc.

From 159d00520ecd01ebfc92397fd9b470a7627974f0 Mon Sep 17 00:00:00 2001
From: Bruno Haible <[email protected]>
Date: Sun, 20 Sep 2026 16:50:14 +0200
Subject: [PATCH 1/4] btowc: Enhance tests.

* tests/test-btowc.c (main): Check that btowc is consistent with
mbrtowc.
* modules/btowc-tests (Depends-on): Add mbrtowc.
---
 ChangeLog           |  7 +++++++
 modules/btowc-tests |  1 +
 tests/test-btowc.c  | 15 +++++++++++++++
 3 files changed, 23 insertions(+)

diff --git a/ChangeLog b/ChangeLog
index aca5a13e45..7f9e95abdd 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,10 @@
+2026-09-20  Bruno Haible  <[email protected]>
+
+	btowc: Enhance tests.
+	* tests/test-btowc.c (main): Check that btowc is consistent with
+	mbrtowc.
+	* modules/btowc-tests (Depends-on): Add mbrtowc.
+
 2026-09-20  Bruno Haible  <[email protected]>
 
 	wcrtomb: Make consistent with mbrtowc (regression 2016-04-09).
diff --git a/modules/btowc-tests b/modules/btowc-tests
index 5b5ce4aa42..2f9fbee441 100644
--- a/modules/btowc-tests
+++ b/modules/btowc-tests
@@ -11,6 +11,7 @@ m4/codeset.m4
 
 Depends-on:
 setlocale
+mbrtowc
 streq
 
 configure.ac:
diff --git a/tests/test-btowc.c b/tests/test-btowc.c
index e4ca8418ad..a3ab096638 100644
--- a/tests/test-btowc.c
+++ b/tests/test-btowc.c
@@ -37,6 +37,21 @@ main (int argc, char *argv[])
 
   ASSERT (btowc (EOF) == WEOF);
 
+  /* Verify that btowc is consistent with mbrtowc.  */
+  for (int c = 0; c < 0x100; c++)
+    if (c != 0)
+      {
+        mbstate_t state;
+        mbszero (&state);
+        char s[1];
+        s[0] = (unsigned char) c;
+        wchar_t wc;
+        if (mbrtowc (&wc, s, 1, &state) <= 1)
+          ASSERT (btowc (c) == (wint_t) wc);
+        else
+          ASSERT (btowc (c) == WEOF);
+      }
+
 #ifdef __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"
-- 
2.53.0

>From 4258ec3e8362b815c3da749e32fa4818bde7f306 Mon Sep 17 00:00:00 2001
From: Bruno Haible <[email protected]>
Date: Sun, 20 Sep 2026 17:13:27 +0200
Subject: [PATCH 2/4] wctob: Make thread-safe if possible.

* m4/wctob.m4 (gl_PREREQ_WCTOB): Test for wcrtomb.
* lib/wctob.c (wctob): Use wcrtomb instead of wctomb if possible.
* modules/wctob (Depends-on): Add mbszero, wcrtomb.
---
 ChangeLog     |  7 +++++++
 lib/wctob.c   | 12 ++++++++++--
 m4/wctob.m4   |  3 ++-
 modules/wctob |  2 ++
 4 files changed, 21 insertions(+), 3 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 7f9e95abdd..d8b7ff6559 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,10 @@
+2026-09-20  Bruno Haible  <[email protected]>
+
+	wctob: Make thread-safe if possible.
+	* m4/wctob.m4 (gl_PREREQ_WCTOB): Test for wcrtomb.
+	* lib/wctob.c (wctob): Use wcrtomb instead of wctomb if possible.
+	* modules/wctob (Depends-on): Add mbszero, wcrtomb.
+
 2026-09-20  Bruno Haible  <[email protected]>
 
 	btowc: Enhance tests.
diff --git a/lib/wctob.c b/lib/wctob.c
index 1a569a88b9..3b3ab82b1a 100644
--- a/lib/wctob.c
+++ b/lib/wctob.c
@@ -32,7 +32,15 @@ wctob (wint_t wc)
     abort ();
   /* Handle the case where WEOF is a value that does not fit in a wchar_t.  */
   if (wc == (wchar_t)wc)
-    if (wctomb (buf, (wchar_t)wc) == 1)
-      return (unsigned char) buf[0];
+    {
+#if HAVE_WCRTOMB
+      mbstate_t state; mbszero (&state);
+      if (wcrtomb (buf, (wchar_t)wc, &state) == 1)
+        return (unsigned char) buf[0];
+#else
+      if (wctomb (buf, (wchar_t)wc) == 1)
+        return (unsigned char) buf[0];
+#endif
+    }
   return EOF;
 }
diff --git a/m4/wctob.m4 b/m4/wctob.m4
index 76acd28929..0fcb560aaf 100644
--- a/m4/wctob.m4
+++ b/m4/wctob.m4
@@ -1,5 +1,5 @@
 # wctob.m4
-# serial 15
+# serial 16
 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,
@@ -96,5 +96,6 @@ AC_DEFUN([gl_FUNC_WCTOB]
 
 # Prerequisites of lib/wctob.c.
 AC_DEFUN([gl_PREREQ_WCTOB], [
+  AC_CHECK_FUNCS_ONCE([wcrtomb])
   :
 ])
diff --git a/modules/wctob b/modules/wctob
index ef50bf5b5c..35e6e8be0d 100644
--- a/modules/wctob
+++ b/modules/wctob
@@ -10,6 +10,8 @@ m4/codeset.m4
 
 Depends-on:
 wchar-h
+mbszero         [test $HAVE_WCTOB = 0 || test $REPLACE_WCTOB = 1]
+wcrtomb         [test $HAVE_WCTOB = 0 || test $REPLACE_WCTOB = 1]
 wctomb          [test $HAVE_WCTOB = 0 || test $REPLACE_WCTOB = 1]
 
 configure.ac:
-- 
2.53.0

>From b09ea200306c5df2f897f032c3d0e9f76e00a913 Mon Sep 17 00:00:00 2001
From: Bruno Haible <[email protected]>
Date: Sun, 20 Sep 2026 17:21:26 +0200
Subject: [PATCH 3/4] wctob: Make consistent with wcrtomb.

* m4/wctob.m4 (gl_FUNC_WCTOB): Require gl_MBRTOWC_C_LOCALE and, if
needed, set REPLACE_WCTOB.
* modules/wctob (Files): Add mbrtowc.m4.
---
 ChangeLog     |  5 +++++
 m4/wctob.m4   | 10 +++++++++-
 modules/wctob |  1 +
 3 files changed, 15 insertions(+), 1 deletion(-)

diff --git a/ChangeLog b/ChangeLog
index d8b7ff6559..6145b67156 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,10 @@
 2026-09-20  Bruno Haible  <[email protected]>
 
+	wctob: Make consistent with wcrtomb.
+	* m4/wctob.m4 (gl_FUNC_WCTOB): Require gl_MBRTOWC_C_LOCALE and, if
+	needed, set REPLACE_WCTOB.
+	* modules/wctob (Files): Add mbrtowc.m4.
+
 	wctob: Make thread-safe if possible.
 	* m4/wctob.m4 (gl_PREREQ_WCTOB): Test for wcrtomb.
 	* lib/wctob.c (wctob): Use wcrtomb instead of wctomb if possible.
diff --git a/m4/wctob.m4 b/m4/wctob.m4
index 0fcb560aaf..127fd18dc5 100644
--- a/m4/wctob.m4
+++ b/m4/wctob.m4
@@ -1,5 +1,5 @@
 # wctob.m4
-# serial 16
+# serial 17
 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,
@@ -91,6 +91,14 @@ AC_DEFUN([gl_FUNC_WCTOB]
       *yes) ;;
       *) REPLACE_WCTOB=1 ;;
     esac
+    dnl When we adjust mbrtowc in the C locale, we need to adjust wctob as well,
+    dnl for consistency.
+    AC_REQUIRE([gl_MBRTOWC_C_LOCALE])
+    case "$gl_cv_func_mbrtowc_C_locale_sans_EILSEQ" in
+      *yes) ;;
+      *) REPLACE_WCTOB=1
+         ;;
+    esac
   fi
 ])
 
diff --git a/modules/wctob b/modules/wctob
index 35e6e8be0d..cfd3b898d1 100644
--- a/modules/wctob
+++ b/modules/wctob
@@ -4,6 +4,7 @@ wctob() function: convert wide character to unibyte character.
 Files:
 lib/wctob.c
 m4/wctob.m4
+m4/mbrtowc.m4
 m4/locale-en.m4
 m4/locale-fr.m4
 m4/codeset.m4
-- 
2.53.0

From 1052c15fa387a35c9548dae5ac5c77f93773bd9e Mon Sep 17 00:00:00 2001
From: Bruno Haible <[email protected]>
Date: Sun, 20 Sep 2026 17:23:31 +0200
Subject: [PATCH 4/4] wctob: Add tests.

* tests/test-wctob.c: New file.
* tests/test-wctob.sh: New file.
* modules/wctob-tests: New file.
---
 ChangeLog           |   5 ++
 modules/wctob-tests |  33 ++++++++++++
 tests/test-wctob.c  | 121 ++++++++++++++++++++++++++++++++++++++++++++
 tests/test-wctob.sh |  49 ++++++++++++++++++
 4 files changed, 208 insertions(+)
 create mode 100644 modules/wctob-tests
 create mode 100644 tests/test-wctob.c
 create mode 100755 tests/test-wctob.sh

diff --git a/ChangeLog b/ChangeLog
index 6145b67156..5bc453e4c4 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,10 @@
 2026-09-20  Bruno Haible  <[email protected]>
 
+	wctob: Add tests.
+	* tests/test-wctob.c: New file.
+	* tests/test-wctob.sh: New file.
+	* modules/wctob-tests: New file.
+
 	wctob: Make consistent with wcrtomb.
 	* m4/wctob.m4 (gl_FUNC_WCTOB): Require gl_MBRTOWC_C_LOCALE and, if
 	needed, set REPLACE_WCTOB.
diff --git a/modules/wctob-tests b/modules/wctob-tests
new file mode 100644
index 0000000000..2eaec88855
--- /dev/null
+++ b/modules/wctob-tests
@@ -0,0 +1,33 @@
+Files:
+tests/test-wctob.sh
+tests/test-wctob.c
+tests/signature.h
+tests/macros.h
+m4/locale-en.m4
+m4/locale-fr.m4
+m4/locale-ja.m4
+m4/locale-zh.m4
+m4/codeset.m4
+
+Depends-on:
+btowc
+setlocale
+
+configure.ac:
+gt_LOCALE_EN_UTF8
+gt_LOCALE_FR
+gt_LOCALE_FR_UTF8
+gt_LOCALE_JA
+gt_LOCALE_ZH_CN
+
+Makefile.am:
+TESTS += \
+  test-wctob.sh
+TESTS_ENVIRONMENT += \
+  LOCALE_EN_UTF8='@LOCALE_EN_UTF8@' \
+  LOCALE_FR='@LOCALE_FR@' \
+  LOCALE_FR_UTF8='@LOCALE_FR_UTF8@' \
+  LOCALE_JA='@LOCALE_JA@' \
+  LOCALE_ZH_CN='@LOCALE_ZH_CN@'
+check_PROGRAMS += test-wctob
+test_wctob_LDADD = $(LDADD) $(SETLOCALE_LIB)
diff --git a/tests/test-wctob.c b/tests/test-wctob.c
new file mode 100644
index 0000000000..a3acfb3212
--- /dev/null
+++ b/tests/test-wctob.c
@@ -0,0 +1,121 @@
+/* Test of conversion of wide character to unibyte character.
+   Copyright (C) 2008-2026 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* Written by Bruno Haible <[email protected]>, 2026.  */
+
+#include <config.h>
+
+#include <wchar.h>
+
+#include "signature.h"
+SIGNATURE_CHECK (wctob, int, (wint_t));
+
+#include <locale.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "macros.h"
+
+/* Check the unibyte character c.  */
+static void
+check_character (unsigned char c)
+{
+  wint_t wc = btowc (c);
+  ASSERT (wc != WEOF);
+  int cc = wctob (wc);
+  ASSERT (cc == c);
+}
+
+int
+main (int argc, char *argv[])
+{
+  /* configure should already have checked that the locale is supported.  */
+  if (setlocale (LC_ALL, "") == NULL)
+    return 1;
+
+  /* Test NUL character.  */
+  ASSERT (wctob (0) == 0);
+
+  /* Test single bytes.  */
+  for (int c = 0; c < 0x100; c++)
+    switch (c)
+      {
+      case '\t': case '\v': case '\f':
+      case ' ': case '!': case '"': case '#': case '%':
+      case '&': case '\'': case '(': case ')': case '*':
+      case '+': case ',': case '-': case '.': case '/':
+      case '0': case '1': case '2': case '3': case '4':
+      case '5': case '6': case '7': case '8': case '9':
+      case ':': case ';': case '<': case '=': case '>':
+      case '?':
+      case 'A': case 'B': case 'C': case 'D': case 'E':
+      case 'F': case 'G': case 'H': case 'I': case 'J':
+      case 'K': case 'L': case 'M': case 'N': case 'O':
+      case 'P': case 'Q': case 'R': case 'S': case 'T':
+      case 'U': case 'V': case 'W': case 'X': case 'Y':
+      case 'Z':
+      case '[': case '\\': case ']': case '^': case '_':
+      case 'a': case 'b': case 'c': case 'd': case 'e':
+      case 'f': case 'g': case 'h': case 'i': case 'j':
+      case 'k': case 'l': case 'm': case 'n': case 'o':
+      case 'p': case 'q': case 'r': case 's': case 't':
+      case 'u': case 'v': case 'w': case 'x': case 'y':
+      case 'z': case '{': case '|': case '}': case '~':
+        /* c is in the ISO C "basic character set".  */
+        check_character (c);
+        break;
+      }
+
+  if (argc > 1)
+    switch (argv[1][0])
+      {
+      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 wctob does the inverse of btowc, in the C locale.
+           Above we have only tested the ISO C "basic character set".  */
+        for (int c = 0; c < 0x100; c++)
+          check_character (c);
+#endif
+        return test_exit_status;
+
+      case '2':
+        /* Locale encoding is ISO-8859-1 or ISO-8859-15.  */
+        return test_exit_status;
+
+      case '3':
+        /* Locale encoding is UTF-8.  */
+        ASSERT (wctob (0x0091) == EOF);
+        ASSERT (wctob (0x00DF) == EOF);
+        ASSERT (wctob (0x00FC) == EOF);
+        ASSERT (wctob (0x1F60B) == EOF);
+        return test_exit_status;
+
+      case '4':
+        /* Locale encoding is EUC-JP.  */
+        return test_exit_status;
+
+      case '5':
+        /* Locale encoding is GB18030.  */
+        return test_exit_status;
+      }
+
+  return 1;
+}
diff --git a/tests/test-wctob.sh b/tests/test-wctob.sh
new file mode 100755
index 0000000000..3c897d5660
--- /dev/null
+++ b/tests/test-wctob.sh
@@ -0,0 +1,49 @@
+#!/bin/sh
+
+# Test in the POSIX locale.
+LC_ALL=C     ${CHECKER} ./test-wctob${EXEEXT} 1 || exit 1
+LC_ALL=POSIX ${CHECKER} ./test-wctob${EXEEXT} 1 || exit 1
+
+# Test in an ISO-8859-1 or ISO-8859-15 locale.
+: "${LOCALE_FR=fr_FR}"
+if test $LOCALE_FR != none; then
+  LC_ALL=$LOCALE_FR \
+  ${CHECKER} ./test-wctob${EXEEXT} 2 \
+  || exit 1
+fi
+
+# Test whether a specific UTF-8 locale is installed.
+: "${LOCALE_EN_UTF8=en_US.UTF-8}"
+: "${LOCALE_FR_UTF8=fr_FR.UTF-8}"
+if test "$LOCALE_EN_UTF8" != none || test $LOCALE_FR_UTF8 != none; then
+  # It's sufficient to test in one of the two locales.
+  if test $LOCALE_FR_UTF8 != none; then
+    testlocale=$LOCALE_FR_UTF8
+  else
+    testlocale="$LOCALE_EN_UTF8"
+  fi
+  LC_ALL="$testlocale" \
+  ${CHECKER} ./test-wctob${EXEEXT} 3 \
+  || exit 1
+fi
+
+# Test whether a specific EUC-JP locale is installed.
+: "${LOCALE_JA=ja_JP}"
+if test $LOCALE_JA != none; then
+  LC_ALL=$LOCALE_JA \
+  ${CHECKER} ./test-wctob${EXEEXT} 4 \
+  || exit 1
+fi
+
+# Test whether a specific GB18030 locale is installed.
+: "${LOCALE_ZH_CN=zh_CN.GB18030}"
+if test $LOCALE_ZH_CN != none; then
+  LC_ALL=$LOCALE_ZH_CN \
+  ${CHECKER} ./test-wctob${EXEEXT} 5
+  case $? in
+    0 | 77) ;;
+    *) exit 1 ;;
+  esac
+fi
+
+exit 0
-- 
2.53.0

Reply via email to