Here's the updated patches. They passed all tests[1].

The reason was that when misc/btowc.c and misc/wctob.c were compiled for UCRT, 
they were using msvcr*.dll's mbstate_t which has different size. I added new 
misc/ucrt_btowc.c and misc/ucrt_wctob.c for UCRT compilation.

I also discovered one case when wcrtomb would return incorrect value. The fix 
is included.

Do I understand correctly that libc++ is only built and tested with UCRT? If 
this is the case, this means that only btowc and wctob were effectively tested, 
since replacements for the rest are only used for msvcrt.dll and older CRTs.

- Kirill Makurin

[1] https://github.com/maiddaisuki/mingw-w64/actions/runs/16165302800
From 88e0c9a47af9f5fe00d1426361cc80fb97a5bed2 Mon Sep 17 00:00:00 2001
From: Kirill Makurin <maiddais...@outlook.com>
Date: Wed, 9 Jul 2025 17:04:34 +0900
Subject: [PATCH 1/8] crt: new implementation for C95 conversion functions

New implementation writes to output buffer only if successful
conversion has occured. CRT's versions and old implementation may
write to out buffer even if conversion has failed.

POSIX specifies that C95 conversion functions *may* fail with errno
set to EINVAL if conversion state described in `mbstate_t` is invalid.
This error condition is now possible with mbrlen, mbrtowc, mbsrtowcs,
wcrtomb and wcsrtombs functions.

Signed-off-by: Kirill Makurin <maiddais...@outlook.com>
---
 mingw-w64-crt/misc/mbrtowc.c | 327 ++++++++++++++++++++++-------------
 mingw-w64-crt/misc/wcrtomb.c | 209 +++++++++++++---------
 2 files changed, 334 insertions(+), 202 deletions(-)

diff --git a/mingw-w64-crt/misc/mbrtowc.c b/mingw-w64-crt/misc/mbrtowc.c
index 3c0115675..f53286e91 100644
--- a/mingw-w64-crt/misc/mbrtowc.c
+++ b/mingw-w64-crt/misc/mbrtowc.c
@@ -12,148 +12,227 @@
 #include <errno.h>
 #include <windows.h>
 
-static int __MINGW_ATTRIB_NONNULL(1) __MINGW_ATTRIB_NONNULL(4)
-__mbrtowc_cp (wchar_t * __restrict__ pwc, const char * __restrict__ s,
-             size_t n, mbstate_t* __restrict__ ps,
-             const unsigned int cp, const unsigned int mb_max) 
-{
-  union {
-    mbstate_t val;
-    char mbcs[4];
-  } shift_state;
-
-  /* Do the prelim checks */
-  if (s == NULL)
+/**
+ * Private `mbstate_t` to use if caller did not supply one.
+ */
+static mbstate_t state_mbrlen = {0};
+static mbstate_t state_mbrtowc = {0};
+static mbstate_t state_mbsrtowcs = {0};
+
+static size_t mbrtowc_cp (
+  wchar_t *__restrict__ wc,
+  const char *__restrict__ mbs,
+  size_t count,
+  mbstate_t *__restrict__ state,
+  unsigned cp,
+  int mb_cur_max
+) {
+  /* Set `state` to initial state */
+  if (mbs == NULL) {
+    *state = 0;
     return 0;
+  }
 
-  if (n == 0)
-    /* The standard doesn't mention this case explicitly. Tell
-       caller that the conversion from a non-null s is incomplete. */
-    return -2;
+  /* Detect invalid conversion state */
+  if ((unsigned) *state > 0xFF) {
+    goto einval;
+  }
 
-  /* Save the current shift state, in case we need it in DBCS case.  */
-  shift_state.val = *ps;
-  *ps = 0;
+  /* Both ISO C and POSIX do not mention this case */
+  if (count == 0) {
+    return (size_t) -2;
+  }
 
-  if (!*s)
-    {
-      *pwc = 0;
-      return 0;
+  /* Treat `state` as an array of bytes */
+  union {
+    mbstate_t state;
+    char bytes[4];
+  } conversion_state = {.state = *state};
+
+  /* For SBCS code pages `state` must always be in initial state */
+  if (mb_cur_max == 1 && conversion_state.bytes[0]) {
+    goto einval;
+  }
+
+  /* Handle "C" locale */
+  if (cp == 0) {
+    if (wc != NULL) {
+      *wc = (unsigned char) mbs[0];
+    }
+    return !!mbs[0];
+  }
+
+  /* Length of potential multibyte character */
+  int length = 1;
+
+  if (conversion_state.bytes[0]) {
+    conversion_state.bytes[1] = mbs[0];
+    length = 2;
+  } else if (mb_cur_max == 2 && IsDBCSLeadByteEx (cp, (BYTE) mbs[0])) {
+    conversion_state.bytes[0] = mbs[0];
+
+    /* We need to examine mbs[1] */
+    if (count < 2) {
+      *state = conversion_state.state;
+      return (size_t) -2;
     }
 
-  if (mb_max > 1)
-    {
-      if (shift_state.mbcs[0] != 0)
-       {
-         /* Complete the mb char with the trailing byte.  */
-         shift_state.mbcs[1] = *s;  /* the second byte */
-         if (MultiByteToWideChar(cp, MB_ERR_INVALID_CHARS,
-                                 shift_state.mbcs, 2, pwc, 1)
-                == 0)
-           {
-             /* An invalid trailing byte */
-             errno = EILSEQ;
-             return -1;
-           }
-         return 2;
-       }
-      else if (IsDBCSLeadByteEx (cp, *s))
-       {
-         /* If told to translate one byte, just save the leadbyte
-            in *ps.  */
-         if (n < 2)
-           {
-             ((char*) ps)[0] = *s;
-             return -2;
-           }
-         /* Else translate the first two bytes  */  
-         else if (MultiByteToWideChar (cp, MB_ERR_INVALID_CHARS,
-                                       s, 2, pwc, 1)
-                   == 0)
-           {
-             errno = EILSEQ;
-             return -1;
-           }
-         return 2;
-       }
+    conversion_state.bytes[1] = mbs[1];
+    length = 2;
+  } else {
+    conversion_state.bytes[0] = mbs[0];
+  }
+
+  /* Store terminating '\0' */
+  if (conversion_state.bytes[0] == '\0') {
+    if (wc != NULL) {
+      *wc = L'\0';
+      *state = 0;
     }
+    return 0;
+  }
 
-  /* Fall through to single byte char  */
-  if (cp == 0)
-      *pwc = (wchar_t)(unsigned char)*s;
+  /* Truncated multibyte character */
+  if (length == 2 && conversion_state.bytes[1] == '\0') {
+    goto eilseq;
+  }
 
-  else if (MultiByteToWideChar (cp, MB_ERR_INVALID_CHARS, s, 1, pwc, 1)
-           == 0)
-    {
-      errno = EILSEQ;
-      return  -1;
-    }
+  /* Converted wide character */
+  wchar_t wcOut = WEOF;
+
+  int ret = MultiByteToWideChar (
+    cp, MB_ERR_INVALID_CHARS, conversion_state.bytes, length, &wcOut, 1
+  );
+
+  if (ret != 1) {
+    goto eilseq;
+  }
+
+  if (wc != NULL) {
+    *wc = wcOut;
+    *state = 0;
+  }
+
+  return length;
 
-  return 1;
+eilseq:
+  _set_errno (EILSEQ);
+  return (size_t) -1;
+
+einval:
+  _set_errno (EINVAL);
+  return (size_t) -1;
 }
 
-size_t
-__cdecl
-mbrtowc (wchar_t * __restrict__ pwc, const char * __restrict__ s,
-        size_t n, mbstate_t* __restrict__ ps)
-{
-  static mbstate_t internal_mbstate = 0;
-  wchar_t  byte_bucket = 0;
-  wchar_t* dst = pwc ? pwc : &byte_bucket;
-
-  return (size_t) __mbrtowc_cp (dst, s, n, ps ? ps : &internal_mbstate,
-                               ___lc_codepage_func(), MB_CUR_MAX);
+size_t mbrlen (
+  const char *__restrict__ mbs,
+  size_t count,
+  mbstate_t *__restrict__ state
+) {
+  /* Use private `mbstate_t` if caller did not supply one */
+  if (state == NULL) {
+    state = &state_mbrlen;
+  }
+  wchar_t wc = WEOF;
+  return mbrtowc (&wc, mbs, count, state);
 }
 
+size_t mbrtowc (
+  wchar_t *__restrict__ wc,
+  const char *__restrict__ mbs,
+  size_t count,
+  mbstate_t *__restrict__ state
+) {
+  /* Use private `mbstate_t` if caller did not supply one */
+  if (state == NULL) {
+    state = &state_mbrtowc;
+  }
+
+  /* Code page used by current locale */
+  unsigned cp = ___lc_codepage_func ();
+  /* Maximum character length in `cp` */
+  int mb_cur_max = MB_CUR_MAX;
+
+  return mbrtowc_cp (wc, mbs, count, state, cp, mb_cur_max);
+}
 
-size_t
-__cdecl
-mbsrtowcs (wchar_t* __restrict__ dst,  const char ** __restrict__ src,
-          size_t len, mbstate_t* __restrict__ ps)
-{
-  int ret =0 ;
-  size_t n = 0;
-  static mbstate_t internal_mbstate = 0;
-  mbstate_t* internal_ps = ps ? ps : &internal_mbstate;
-  const unsigned int cp = ___lc_codepage_func();
-  const unsigned int mb_max = MB_CUR_MAX;
+size_t mbsrtowcs (
+  wchar_t *wcs,
+  const char **__restrict__ mbs,
+  size_t count,
+  mbstate_t *__restrict__ state
+) {
+  /* Use private `mbstate_t` if caller did not supply one */
+  if (state == NULL) {
+    state = &state_mbsrtowcs;
+  }
+
+  /* Code page used by current locale */
+  unsigned cp = ___lc_codepage_func ();
+  /* Maximum character length in `cp` */
+  int mb_cur_max = MB_CUR_MAX;
+
+  /* Treat `state` as array of bytes */
+  union {
+    mbstate_t state;
+    char bytes[4];
+  } conversion_state = {.state = *state};
+
+  /* Total number of wide character written to `wcs` */
+  size_t  wcConverted = 0;
+  /* Converted wide character */
+  wchar_t wc = 0;
+
+  /* Next multibyte character to convert */
+  const char *mbc = *mbs;
+
+  while (1) {
+    const size_t length = mbrtowc_cp (
+      &wc, mbc, mb_cur_max, &conversion_state.state, cp, mb_cur_max
+    );
+
+    /* Conversion failed */
+    if (length == (size_t) -1) {
+      if (wcs != NULL) {
+        *mbs = mbc;
+        *state = conversion_state.state;
+      }
+      return (size_t) -1;
+    }
 
-  if (src == NULL || *src == NULL)     /* undefined behavior */
-    return 0;
+    /* POSIX and ISO C are silent about this */
+    if (wcs != NULL && count == 0) {
+      return 0;
+    }
 
-  if (dst != NULL)
-    {
-      while (n < len
-            && (ret = __mbrtowc_cp(dst, *src, len - n,
-                                   internal_ps, cp, mb_max))
-                 > 0)
-       {
-         ++dst;
-         *src += ret;
-         n += ret;
-       }
-
-      if (n < len && ret == 0)
-       *src = (char *)NULL;
+    /* Store terminating L'\0' and stop */
+    if (length == 0) {
+      if (wcs != NULL) {
+        *wcs = L'\0';
+        *mbs = NULL;
+      }
+      break;
     }
-  else
-    {
-      wchar_t byte_bucket = 0;
-      while ((ret = __mbrtowc_cp (&byte_bucket, *src + n, mb_max,
-                                    internal_ps, cp, mb_max))
-                 > 0)
-       n += ret;
+
+    /* Store converted `wc` in `wcs` */
+    if (wcs != NULL) {
+      *wcs = wc;
+      wcs += 1;
     }
-  return n;
-}
 
-size_t
-__cdecl
-mbrlen (const char * __restrict__ s, size_t n,
-       mbstate_t * __restrict__ ps)
-{
-  static mbstate_t s_mbstate = 0;
-  wchar_t byte_bucket = 0;
-  return __mbrtowc_cp (&byte_bucket, s, n, (ps) ? ps : &s_mbstate,
-                      ___lc_codepage_func(), MB_CUR_MAX);
+    wcConverted += 1;
+    mbc += length;
+
+    /* `count` wide characters have been stored in `wcs`, stop */
+    if (wcs != NULL && wcConverted == count) {
+      *mbs = mbc;
+      break;
+    }
+  }
+
+  if (wcs != NULL) {
+    *state = conversion_state.state;
+  }
+
+  return wcConverted;
 }
diff --git a/mingw-w64-crt/misc/wcrtomb.c b/mingw-w64-crt/misc/wcrtomb.c
index c50932853..9f869e8f6 100644
--- a/mingw-w64-crt/misc/wcrtomb.c
+++ b/mingw-w64-crt/misc/wcrtomb.c
@@ -13,91 +13,144 @@
 #include <limits.h>
 #include <windows.h>
 
-__attribute__((noinline))
-static int __MINGW_ATTRIB_NONNULL(1)
- __wcrtomb_cp (char *dst, wchar_t wc, const unsigned int cp,
-              const unsigned int mb_max)
-{
-  if (cp == 0)
-    {
-      if (wc > 255)
-       {
-         errno = EILSEQ;
-         return -1;
-       }
-      *dst = (char) wc;
-      return 1;
+static size_t wcrtomb_cp (
+  char *__restrict__ mbc,
+  wchar_t wc,
+  mbstate_t *__restrict__ state,
+  unsigned cp,
+  int mb_cur_max
+) {
+  /* Set `state` to initial state */
+  if (mbc == NULL) {
+    if (state != NULL) {
+      *state = 0;
     }
-  else
-    {
-      int invalid_char = 0;
-
-      int size = WideCharToMultiByte (cp, 0 /* Is this correct flag? */,
-                                     &wc, 1, dst, mb_max,
-                                     NULL, &invalid_char);
-      if (size == 0 || invalid_char)  
-       {
-         errno = EILSEQ;
-         return -1;
-       }
-      return size;
+    return 1;
+  }
+
+  /* Detect invalid conversion state */
+  if (state != NULL && *state) {
+    _set_errno (EINVAL);
+    return (size_t) -1;
+  }
+
+  /* Store terminating L'\0' */
+  if (wc == L'\0') {
+    if (mbc != NULL) {
+      mbc[0] = '\0';
+    }
+    return 1;
+  }
+
+  /* Handle "C" locale */
+  if (cp == 0) {
+    if (wc > 0xFF) {
+      goto eilseq;
     }
+    if (mbc != NULL) {
+      mbc[0] = (char) wc;
+    }
+    return 1;
+  }
+
+  BOOL defaultCharacterUsed = FALSE;
+  char buffer[2] = {0, 0};
+
+  /* For consistency with CRT, we do not use WC_NO_BEST_FIT_CHARS */
+  int ret = WideCharToMultiByte (
+    cp, 0, &wc, 1, buffer, mb_cur_max, NULL, &defaultCharacterUsed
+  );
+
+  if (ret == 0 || ret > mb_cur_max || defaultCharacterUsed) {
+    goto eilseq;
+  }
+
+  memcpy (mbc, buffer, ret);
+  return ret;
+
+eilseq:
+  _set_errno (EILSEQ);
+  return (size_t) -1;
 }
 
-size_t
-__cdecl
-wcrtomb (char *dst, wchar_t wc, mbstate_t * __UNUSED_PARAM (ps))
-{
-  char byte_bucket [MB_LEN_MAX];
-  char* tmp_dst = dst ? dst : &byte_bucket[0];
-  return (size_t)__wcrtomb_cp (tmp_dst, wc, ___lc_codepage_func(),
-                              MB_CUR_MAX);
+size_t wcrtomb (
+  char *__restrict__ mbc,
+  wchar_t wc,
+  mbstate_t *__restrict__ state
+) {
+  /* Code page used by current locale */
+  unsigned cp = ___lc_codepage_func ();
+  /* Maximum character length in `cp` */
+  int mb_cur_max = MB_CUR_MAX;
+
+  return wcrtomb_cp (mbc, wc, state, cp, mb_cur_max);
 }
 
-size_t
-__cdecl
-wcsrtombs (char *dst, const wchar_t **src, size_t len,
-                 mbstate_t * __UNUSED_PARAM (ps))
-{
-  int ret = 0;
-  size_t n = 0;
-  const unsigned int cp = ___lc_codepage_func();
-  const unsigned int mb_max = MB_CUR_MAX;
-  const wchar_t *pwc = *src;
-
-  if (src == NULL || *src == NULL) /* undefined behavior */
-    return 0;
-
-  if (dst != NULL)
-    {
-      while (n < len)
-       {
-         if ((ret = __wcrtomb_cp (dst, *pwc, cp, mb_max)) <= 0)
-           return (size_t) -1;
-         n += ret;
-         dst += ret;
-         if (*(dst - 1) == '\0')
-           {
-             *src = (wchar_t *) NULL;
-             return (n  - 1);
-           }
-         pwc++;
-       }
-      *src = pwc;
+size_t wcsrtombs (
+  char *__restrict__ mbs,
+  const wchar_t **__restrict__ wcs,
+  size_t count,
+  mbstate_t *__restrict__ state
+) {
+  /* Code page used by current locale */
+  unsigned cp = ___lc_codepage_func ();
+  /* Maximum character length in `cp` */
+  int mb_cur_max = MB_CUR_MAX;
+
+  /* Buffer to store single converted character */
+  char mbc[2];
+  /* Total number of bytes stored in `mbs` */
+  size_t mbcConverted = 0;
+
+  /* Next wide character to convert */
+  const wchar_t *wc = *wcs;
+
+  while (1) {
+    const size_t length = wcrtomb_cp (mbc, *wc, state, cp, mb_cur_max);
+
+    /* Conversion failed */
+    if (length == (size_t) -1) {
+      if (mbs != NULL) {
+        *wcs = wc;
+      }
+      return (size_t) -1;
+    }
+
+    /* POSIX and ISO C are silent about this */
+    if (mbs != NULL && count == 0) {
+      return 0;
+    }
+
+    /* Terminating '\0' has been converted, stop */
+    if (mbc[0] == '\0') {
+      if (mbs != NULL) {
+        *mbs = '\0';
+        *wcs = NULL;
+      }
+      break;
     }
-  else
-    {
-      char byte_bucket [MB_LEN_MAX];
-      while (1)
-       {
-         if ((ret = __wcrtomb_cp (&byte_bucket[0], *pwc, cp, mb_max)) <= 0)
-           return (size_t) -1;
-         n += ret;
-         if (byte_bucket [ret - 1] == '\0')
-           return (n - 1);
-         pwc++;
-       }
+
+    /* Storing `mbc` in `mbs` would exceed `count` */
+    if (mbs != NULL && mbcConverted + length > count) {
+      *wcs = wc;
+      break;
+    }
+
+    /* Write `mbc` to `mbs` */
+    if (mbs != NULL) {
+      memcpy (mbs, mbc, length);
+      mbs += length;
+    }
+
+    mbcConverted += length;
+    wc += 1;
+
+    /* `count` bytes have been written to `mbs`, stop */
+    if (mbs != NULL && mbcConverted == count) {
+      *wcs = wc;
+      break;
     }
+  }
 
-  return n;
+  return mbcConverted;
 }
-- 
2.50.0.windows.2

From fe97ada28b2ef68686a57337e23c59cacb4c29d9 Mon Sep 17 00:00:00 2001
From: Kirill Makurin <maiddais...@outlook.com>
Date: Wed, 9 Jul 2025 17:32:56 +0900
Subject: [PATCH 2/8] crt: add tests for C95 conversion functions

Signed-off-by: Kirill Makurin <maiddais...@outlook.com>
---
 mingw-w64-crt/Makefile.am             |   5 +
 mingw-w64-crt/testcases/t_mbrlen.c    | 139 +++++++
 mingw-w64-crt/testcases/t_mbrtowc.c   | 164 ++++++++
 mingw-w64-crt/testcases/t_mbsrtowcs.c | 377 +++++++++++++++++
 mingw-w64-crt/testcases/t_wcrtomb.c   | 199 +++++++++
 mingw-w64-crt/testcases/t_wcsrtombs.c | 570 ++++++++++++++++++++++++++
 6 files changed, 1454 insertions(+)
 create mode 100644 mingw-w64-crt/testcases/t_mbrlen.c
 create mode 100644 mingw-w64-crt/testcases/t_mbrtowc.c
 create mode 100644 mingw-w64-crt/testcases/t_mbsrtowcs.c
 create mode 100644 mingw-w64-crt/testcases/t_wcrtomb.c
 create mode 100644 mingw-w64-crt/testcases/t_wcsrtombs.c

diff --git a/mingw-w64-crt/Makefile.am b/mingw-w64-crt/Makefile.am
index 7e7fde57f..97da616dd 100644
--- a/mingw-w64-crt/Makefile.am
+++ b/mingw-w64-crt/Makefile.am
@@ -4403,6 +4403,9 @@ testcase_progs = \
   testcases/t_imagebase \
   testcases/t_lfs \
   testcases/t_matherr \
+  testcases/t_mbrlen \
+  testcases/t_mbrtowc \
+  testcases/t_mbsrtowcs \
   testcases/t_nullptrexception \
   testcases/t_readdir \
   testcases/t_snprintf \
@@ -4430,6 +4433,8 @@ testcase_progs = \
   testcases/t_trycatch \
   testcases/t_stat_slash \
   testcases/t_vsscanf \
+  testcases/t_wcrtomb \
+  testcases/t_wcsrtombs \
   testcases/t_wreaddir \
   testcases/t_fseeko64
 
diff --git a/mingw-w64-crt/testcases/t_mbrlen.c 
b/mingw-w64-crt/testcases/t_mbrlen.c
new file mode 100644
index 000000000..0cf6b1836
--- /dev/null
+++ b/mingw-w64-crt/testcases/t_mbrlen.c
@@ -0,0 +1,139 @@
+#ifdef NDEBUG
+#undef NDEBUG
+#endif
+
+#include <assert.h>
+#include <errno.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <wchar.h>
+
+char Ascii[] = {'a'};
+char NonAscii[] = {(char) 0x80};
+char Multibyte[] = {(char) 0x81, (char) 0x81};
+char InvalidMultibyte[] = {(char) 0x81, 0};
+
+int main (void) {
+#if __MSVCRT_VERSION__ >= 0x0800
+  return 77;
+#endif
+  mbstate_t state = {0};
+
+  /**
+   * Test "C" locale
+   */
+  assert (setlocale (LC_ALL, "C") != NULL);
+  assert (MB_CUR_MAX == 1);
+
+  /**
+   * All bytes in range [0,255] are valid
+   */
+  for (unsigned char c = 0;; ++c) {
+    assert (mbrlen ((char *) &c, MB_CUR_MAX, &state) == !!c);
+    assert (mbsinit (&state));
+    assert (errno == 0);
+
+    if (c == 0xFF) {
+      break;
+    }
+  }
+
+  /**
+   * Detect invalid conversion state
+   *
+   * NOTE: this is optional error condition specified in POSIX.
+   * This check fails with CRT's mbrlen.
+   */
+  state = Ascii[0];
+
+  assert (mbrlen ((char *) &Ascii, MB_CUR_MAX, &state) == (size_t) -1);
+  assert (!mbsinit (&state));
+  assert (errno == EINVAL);
+
+  // reset errno
+  _set_errno (0);
+
+  /**
+   * Set conversion state to initial state
+   */
+
+  assert (mbrlen (NULL, 0, &state) == 0);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+
+  /**
+   * Test SBCS code page
+   */
+  assert (setlocale (LC_ALL, "English_United States.ACP") != NULL);
+  assert (MB_CUR_MAX == 1);
+
+  /**
+   * All bytes must be valid
+   */
+  for (unsigned char c = 0;; ++c) {
+    assert (mbrlen ((char *) &c, MB_CUR_MAX, &state) == !!c);
+    assert (mbsinit (&state));
+    assert (errno == 0);
+
+    if (c == 0xFF) {
+      break;
+    }
+  }
+
+  /**
+   * Test DBCS code page
+   */
+  assert (setlocale (LC_ALL, "Japanese_Japan.ACP") != NULL);
+  assert (MB_CUR_MAX == 2);
+
+  /**
+   * Make sure ASCII characters are handled correctly
+   */
+  for (char c = 0;; ++c) {
+    assert (mbrlen (&c, 1, &state) == !!c);
+    assert (mbsinit (&state));
+    assert (errno == 0);
+
+    if (c == 0x7F) {
+      break;
+    }
+  }
+
+  /**
+   * Try convert incomplete multibyte character
+   */
+
+  assert (mbrlen ((char *) Multibyte, 1, &state) == (size_t) -2);
+  assert (!mbsinit (&state));
+  assert (errno == 0);
+
+  /**
+   * Complete multibyte character
+   *
+   * NOTE: return value does not conform to ISO C and POSIX.
+   * This behavior is implemented for consistency with CRT.
+   */
+
+  assert (mbrlen ((char *) Multibyte + 1, 1, &state) == 2);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+
+  /**
+   * Convert multibyte character
+   */
+
+  assert (mbrlen ((char *) Multibyte, MB_CUR_MAX, &state) == 2);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+
+  /**
+   * Invalid multibyte character
+   */
+
+  assert (mbrlen ((char *) InvalidMultibyte, MB_CUR_MAX, &state) == (size_t) 
-1);
+  assert (mbsinit (&state));
+  assert (errno == EILSEQ);
+
+  return 0;
+}
diff --git a/mingw-w64-crt/testcases/t_mbrtowc.c 
b/mingw-w64-crt/testcases/t_mbrtowc.c
new file mode 100644
index 000000000..fd2fcb7de
--- /dev/null
+++ b/mingw-w64-crt/testcases/t_mbrtowc.c
@@ -0,0 +1,164 @@
+#ifdef NDEBUG
+#undef NDEBUG
+#endif
+
+#include <assert.h>
+#include <errno.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <wchar.h>
+
+char Ascii[] = {'a'};
+char NonAscii[] = {(char) 0x80};
+char Multibyte[] = {(char) 0x81, (char) 0x81};
+char InvalidMultibyte[] = {(char) 0x81, 0};
+
+int main (void) {
+#if __MSVCRT_VERSION__ >= 0x0800
+  return 77;
+#endif
+  mbstate_t state = {0};
+  wchar_t   wc = WEOF;
+
+  /**
+   * Test "C" locale
+   */
+  assert (setlocale (LC_ALL, "C") != NULL);
+  assert (MB_CUR_MAX == 1);
+
+  /**
+   * All bytes in range [0,255] are valid and must convert to themselves
+   */
+  for (unsigned char c = 0;; ++c) {
+    assert (mbrtowc (&wc, (char *) &c, MB_CUR_MAX, &state) == !!c);
+    assert (wc == c);
+    assert (mbsinit (&state));
+    assert (errno == 0);
+
+    if (c == 0xFF) {
+      break;
+    }
+  }
+
+  /**
+   * Detect invalid conversion state
+   *
+   * NOTE: this is optional error condition specified in POSIX.
+   * This check fails with CRT's mbrtowc.
+   */
+  state = Ascii[0];
+  wc = WEOF;
+
+  assert (mbrtowc (&wc, (char *) &Ascii, MB_CUR_MAX, &state) == (size_t) -1);
+  assert (wc == WEOF);
+  assert (!mbsinit (&state));
+  assert (errno == EINVAL);
+
+  // reset errno
+  _set_errno (0);
+
+  /**
+   * Set conversion state to initial state
+   */
+  wc = WEOF;
+
+  assert (mbrtowc (&wc, NULL, 0, &state) == 0);
+  assert (wc == WEOF);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+
+  /**
+   * Test SBCS code page
+   * NOTE: Code page 28951 is ISO-8859-1
+   */
+  assert (setlocale (LC_ALL, "English_United States.28591") != NULL);
+  assert (MB_CUR_MAX == 1);
+
+  /**
+   * All bytes must be valid
+   *
+   * We test ISO-8859-1 so that all bytes must convert to themselves
+   */
+  for (unsigned char c = 0;; ++c) {
+    wc = WEOF;
+
+    assert (mbrtowc (&wc, (char *) &c, MB_CUR_MAX, &state) == !!c);
+    assert (wc == c);
+    assert (mbsinit (&state));
+    assert (errno == 0);
+
+    if (c == 0xFF) {
+      break;
+    }
+  }
+
+  /**
+   * Test DBCS code page
+   */
+  assert (setlocale (LC_ALL, "Japanese_Japan.ACP") != NULL);
+  assert (MB_CUR_MAX == 2);
+
+  /**
+   * Make sure ASCII characters are handled correctly
+   */
+  for (char c = 0;; ++c) {
+    wc = WEOF;
+
+    assert (mbrtowc (&wc, &c, 1, &state) == !!c);
+    assert (wc == c);
+    assert (mbsinit (&state));
+    assert (errno == 0);
+
+    if (c == 0x7F) {
+      break;
+    }
+  }
+
+  /**
+   * Try convert incomplete multibyte character
+   */
+  wc = WEOF;
+
+  assert (mbrtowc (&wc, (char *) Multibyte, 1, &state) == (size_t) -2);
+  /* This assertion fails with CRT's version */
+  assert (wc == WEOF);
+  assert (!mbsinit (&state));
+  assert (errno == 0);
+
+  /**
+   * Complete multibyte character
+   *
+   * NOTE: return value does not conform to ISO C and POSIX.
+   * This behavior is implemented for consistency with CRT.
+   */
+  wc = WEOF;
+
+  assert (mbrtowc (&wc, (char *) Multibyte + 1, 1, &state) == 2);
+  assert (wc != WEOF);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+
+  /**
+   * Convert complete multibyte character
+   */
+  wc = WEOF;
+
+  assert (mbrtowc (&wc, (char *) Multibyte, MB_CUR_MAX, &state) == 2);
+  assert (wc != WEOF);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+
+  /**
+   * Try convert invalid multibyte character
+   */
+  wc = WEOF;
+
+  assert (mbrtowc (&wc, (char *) InvalidMultibyte, MB_CUR_MAX, &state) == 
(size_t) -1);
+  /* This assertion fails with CRT's version */
+  assert (wc == WEOF);
+  assert (mbsinit (&state));
+  assert (errno == EILSEQ);
+
+  return 0;
+}
diff --git a/mingw-w64-crt/testcases/t_mbsrtowcs.c 
b/mingw-w64-crt/testcases/t_mbsrtowcs.c
new file mode 100644
index 000000000..63eb1bde9
--- /dev/null
+++ b/mingw-w64-crt/testcases/t_mbsrtowcs.c
@@ -0,0 +1,377 @@
+#ifdef NDEBUG
+#undef NDEBUG
+#endif
+
+#include <assert.h>
+#include <errno.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <wchar.h>
+
+/* ASCII text */
+char          AsciiText[] = "Simple English string.";
+/* SBCS text (code page 1252) */
+unsigned char SBCSText[] = {'a', 'A', 0xC0, 0xE0, 'e', 'E', 0xC8, 0xE8, 0xCB, 
0xEB, 0x0};
+/* DBCS text (code page 932) */
+unsigned char DBCSText[] = {0x93, 0xFA, 0x96, 0x7B, 0x8C, 0xEA, 0x83, 0x65, 
0x83, 0x4E, 0x83, 0x58, 0x83, 0x67, 0x0};
+/* Mix of single-byte and double-byte characters */
+unsigned char MixedText[] = {0x93, 0xFA, 'n', 'i', 0x96, 0x7B, 'h', 'o', 'n', 
0x8C, 0xEA, 'g', 'o', 0x0};
+/* DBCS text with truncated multibyte character */
+unsigned char BadText[] = {0x93, 0xFA, 0x96, 0x7B, 0x8C, 0x0};
+
+int main (void) {
+#if __MSVCRT_VERSION__ >= 0x0800
+  return 77;
+#endif
+  mbstate_t state = {0};
+  wchar_t   buffer[BUFSIZ];
+
+  const char *original_text = NULL;
+  const char *text = NULL;
+  size_t      text_length = 0;
+
+  /**
+   * Test "C" locale
+   */
+  assert (setlocale (LC_ALL, "C") != NULL);
+  assert (MB_CUR_MAX == 1);
+
+  /* Test ASCII input */
+
+  original_text = AsciiText;
+  text_length = sizeof AsciiText - 1;
+
+  /**
+   * Get length of converted AsciiString
+   *
+   * - return value must be `text_length`
+   * - value of `text` must not change
+   * - `state` must be in the initial state
+   * - valie of `errno` must not change
+   */
+  text = original_text;
+
+  assert (mbsrtowcs (NULL, &text, 0, &state) == text_length);
+  assert (text == original_text);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+
+  /**
+   * Convert AsciiString
+   *
+   * - return value must be `text_length`
+   * - value of `text` must be NULL
+   * - `state` must be in the initial state
+   * - valie of `errno` must not change
+   * - converted string must be terminated with '\0'
+   */
+  wmemset (buffer, WEOF, BUFSIZ);
+  text = original_text;
+
+  assert (mbsrtowcs (buffer, &text, BUFSIZ, &state) == text_length);
+  assert (text == NULL);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+  assert (buffer[text_length] == L'\0');
+
+  /**
+   * Convert 10 characters of AsciiString
+   *
+   * - return value must be 10
+   * - value of `text` must be `original_text + 10`
+   * - `state` must be in the initial state
+   * - valie of `errno` must not change
+   * - converted string must not be terminated with '\0'
+   */
+  wmemset (buffer, WEOF, BUFSIZ);
+  text = original_text;
+
+  assert (mbsrtowcs (buffer, &text, 10, &state) == 10);
+  assert (text == original_text + 10);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+  assert (buffer[10] == WEOF);
+
+  /* Test SBCS input */
+
+  original_text = (char *) SBCSText;
+  text_length = sizeof SBCSText - 1;
+
+  /**
+   * Get length of converted SBCSText
+   *
+   * - return value must be `text_length`
+   * - value of `text` must not change
+   * - `state` must be in the initial state
+   * - valie of `errno` must not change
+   */
+  text = original_text;
+
+  assert (mbsrtowcs (NULL, &text, 0, &state) == text_length);
+  assert (text == original_text);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+
+  /**
+   * Convert SBCSText
+   *
+   * - return value must be `text_length`
+   * - value of `text` must be NULL
+   * - `state` must be in the initial state
+   * - valie of `errno` must not change
+   * - converted string must be terminated with '\0'
+   */
+  wmemset (buffer, WEOF, BUFSIZ);
+  text = original_text;
+
+  assert (mbsrtowcs (buffer, &text, BUFSIZ, &state) == text_length);
+  assert (text == NULL);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+  assert (buffer[text_length] == L'\0');
+
+  /**
+   * Test SBCS code page
+   */
+  assert (setlocale (LC_ALL, "English_United States.ACP") != NULL);
+  assert (MB_CUR_MAX == 1);
+
+  /* Test SBCS input */
+
+  original_text = (char *) SBCSText;
+  text_length = sizeof SBCSText - 1;
+
+  /**
+   * Get length of converted SBCSText
+   *
+   * - return value must be length of `text_length`
+   * - value of `text` must not change
+   * - `state` must be in the initial state
+   * - valie of `errno` must not change
+   */
+  text = original_text;
+
+  assert (mbsrtowcs (NULL, &text, 0, &state) == text_length);
+  assert (text == original_text);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+
+  /**
+   * Convert SBCSText
+   *
+   * - return value must be `text_length`
+   * - value of `text` must be NULL
+   * - `state` must be in the initial state
+   * - valie of `errno` must not change
+   * - converted string must be terminated with '\0'
+   */
+  wmemset (buffer, WEOF, BUFSIZ);
+  text = original_text;
+
+  assert (mbsrtowcs (buffer, &text, BUFSIZ, &state) == text_length);
+  assert (text == NULL);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+  assert (buffer[text_length] == L'\0');
+
+  /**
+   * Convert 8 characters in SBCSText
+   *
+   * - return value must be 8
+   * - value of `text` must be `original_text + 8`
+   * - `state` must be in the initial state
+   * - valie of `errno` must not change
+   * - converted string must not be terminated with '\0'
+   */
+  wmemset (buffer, WEOF, BUFSIZ);
+  text = original_text;
+
+  assert (mbsrtowcs (buffer, &text, 8, &state) == 8);
+  assert (text == original_text + 8);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+  assert (buffer[8] == WEOF);
+
+  /**
+   * Test DBCS code page
+   */
+  assert (setlocale (LC_ALL, "Japanese_Japan.ACP") != NULL);
+  assert (MB_CUR_MAX == 2);
+
+  /* Test ASCII input */
+
+  original_text = AsciiText;
+  text_length = sizeof AsciiText - 1;
+
+  /**
+   * Convert AsciiString
+   *
+   * - return value must be `text_length`
+   * - value of `text` must be NULL
+   * - `state` must be in the initial state
+   * - valie of `errno` must not change
+   * - converted string must be terminated with '\0'
+   */
+  wmemset (buffer, WEOF, BUFSIZ);
+  text = original_text;
+
+  assert (mbsrtowcs (buffer, &text, BUFSIZ, &state) == text_length);
+  assert (text == NULL);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+  assert (buffer[text_length] == L'\0');
+
+  /* Test DBCS input */
+
+  original_text = (char *) DBCSText;
+  text_length = sizeof DBCSText - 1;
+
+  /**
+   * Get length of converted DBCSText
+   *
+   * - return value must be 7
+   * - value of `text` must not change
+   * - `state` must be in the initial state
+   * - valie of `errno` must not change
+   */
+  text = original_text;
+
+  assert (mbsrtowcs (NULL, &text, 0, &state) == 7);
+  assert (text == original_text);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+
+  /**
+   * Convert 3 multibyte characters in DBCSText
+   *
+   * - return value must be 3
+   * - value of `text` must point to `original_text + 6`
+   * - `state` must be in the initial state
+   * - valie of `errno` must not change
+   * - converted string must not be terminated with '\0'
+   */
+  wmemset (buffer, WEOF, BUFSIZ);
+  text = original_text;
+
+  assert (mbsrtowcs (buffer, &text, 3, &state) == 3);
+  assert (text == original_text + 6);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+  assert (buffer[0] != WEOF && buffer[1] != WEOF && buffer[2] != WEOF && 
buffer[3] == WEOF);
+
+  /**
+   * Convert DBCSText
+   *
+   * - return value must be 7
+   * - value of `text` must be NULL
+   * - `state` must be in the initial state
+   * - valie of `errno` must not change
+   * - converted string must be terminated with '\0'
+   */
+  wmemset (buffer, WEOF, BUFSIZ);
+  text = original_text;
+
+  assert (mbsrtowcs (buffer, &text, BUFSIZ, &state) == 7);
+  assert (text == NULL);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+  assert (buffer[7] == L'\0');
+
+  /* Test mixed input */
+
+  original_text = (char *) MixedText;
+
+  /**
+   * Get length of converted MixedText
+   *
+   * - return value must be 10
+   * - value of `text` must not change
+   * - `state` must be in the initial state
+   * - valie of `errno` must not change
+   */
+  text = original_text;
+
+  assert (mbsrtowcs (NULL, &text, 0, &state) == 10);
+  assert (text == original_text);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+
+  /**
+   * Converted MixedText
+   *
+   * - return value must be 10
+   * - value of `text` must be NULL
+   * - `state` must be in the initial state
+   * - valie of `errno` must not change
+   * - converted string must be terminated with '\0'
+   */
+  wmemset (buffer, WEOF, BUFSIZ);
+  text = original_text;
+
+  assert (mbsrtowcs (buffer, &text, BUFSIZ, &state) == 10);
+  assert (text == NULL);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+  assert (buffer[10] == L'\0');
+
+  /**
+   * Converted 7 multibyte characters in MixedText
+   *
+   * - return value must be 7
+   * - value of `text` must be `original_text + 9`
+   * - `state` must be in the initial state
+   * - valie of `errno` must not change
+   * - converted string must not be terminated with '\0'
+   */
+  wmemset (buffer, WEOF, BUFSIZ);
+  text = original_text;
+
+  assert (mbsrtowcs (buffer, &text, 7, &state) == 7);
+  assert (text == original_text + 9);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+  assert (buffer[7] == WEOF);
+
+  /* Test bad DBCS input */
+
+  original_text = (char *) BadText;
+
+  /**
+   * Try get length of converted BadText
+   *
+   * - return value must be (size_t)-1
+   * - value of `text` must not change
+   * - `state` must be in the initial state
+   * - value of `errno` must be EILSEQ
+   */
+  text = original_text;
+
+  assert (mbsrtowcs (NULL, &text, 0, &state) == (size_t) -1);
+  assert (text == original_text);
+  assert (mbsinit (&state));
+  assert (errno == EILSEQ);
+
+  // reset errno
+  _set_errno (0);
+
+  /**
+   * Try convert BadText
+   *
+   * - return value must be (size_t)-1
+   * - value of `text` must be `original_text + 4`
+   * - `state` must be in the initial state
+   * - value of `errno` must be EILSEQ
+   */
+  wmemset (buffer, WEOF, BUFSIZ);
+  text = original_text;
+
+  assert (mbsrtowcs (buffer, &text, BUFSIZ, &state) == (size_t) -1);
+  assert (text == original_text + 4);
+  assert (mbsinit (&state));
+  assert (errno == EILSEQ);
+  /* This assertion fails with CRT's version */
+  assert (buffer[0] != WEOF && buffer[1] != WEOF && buffer[2] == WEOF);
+
+  return 0;
+}
diff --git a/mingw-w64-crt/testcases/t_wcrtomb.c 
b/mingw-w64-crt/testcases/t_wcrtomb.c
new file mode 100644
index 000000000..76d8ca02f
--- /dev/null
+++ b/mingw-w64-crt/testcases/t_wcrtomb.c
@@ -0,0 +1,199 @@
+#ifdef NDEBUG
+#undef NDEBUG
+#endif
+
+#include <assert.h>
+#include <errno.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <wchar.h>
+
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+
+int main (void) {
+#if __MSVCRT_VERSION__ >= 0x0800
+  return 77;
+#endif
+  mbstate_t state = {0};
+
+  /**
+   * Test "C" locale
+   */
+  assert (setlocale (LC_ALL, "C") != NULL);
+  assert (MB_CUR_MAX == 1);
+
+  /**
+   * All values in range [0,255] are valid and must convert to themselves
+   */
+  for (wchar_t wc = 0; wc < 0x100; ++wc) {
+    char c = EOF;
+
+    assert (wcrtomb (&c, wc, &state) == 1);
+    assert ((unsigned char) c == wc);
+    assert (errno == 0);
+    assert (mbsinit (&state));
+  }
+
+  /**
+   * Detect invalid conversion state
+   *
+   * NOTE: this is optional error condition specified in POSIX.
+   * This check fails with CRT's wcrtomb.
+   */
+  state = 1;
+
+  if (1) {
+    char c = EOF;
+
+    assert (wcrtomb (&c, L'\0', &state) == (size_t) -1);
+    assert (c == EOF);
+    assert (errno == EINVAL);
+    assert (!mbsinit (&state));
+
+    // reset errno
+    _set_errno (0);
+  }
+
+  /**
+   * Set conversion state to initial state
+   */
+
+  assert (wcrtomb (NULL, WEOF, &state) == 1);
+  assert (errno == 0);
+  assert (mbsinit (&state));
+
+  /**
+   * Try convert character which cannot be repesented by a single byte
+   */
+  if (1) {
+    char buffer = EOF;
+
+    assert (wcrtomb (&buffer, L'語', &state) == (size_t) -1);
+    assert (buffer == EOF);
+    assert (errno == EILSEQ);
+    assert (mbsinit (&state));
+
+    // reset errno
+    _set_errno (0);
+  }
+
+  /**
+   * Try to convert low and high surrogates
+   */
+  for (wchar_t wc = 0;; ++wc) {
+    if (IS_LOW_SURROGATE (wc) || IS_HIGH_SURROGATE (wc)) {
+      char c = EOF;
+
+      assert (wcrtomb (&c, wc, &state) == (size_t) -1);
+      assert (c == EOF);
+      assert (errno = EILSEQ);
+      assert (mbsinit (&state));
+
+      // reset errno
+      _set_errno (0);
+    }
+
+    if (wc == WEOF) {
+      break;
+    }
+  }
+
+  /**
+   * Test SBCS code page
+   * NOTE: Code page 28951 is ISO-8859-1
+   */
+  assert (setlocale (LC_ALL, "English_United States.28591") != NULL);
+  assert (MB_CUR_MAX == 1);
+
+  /**
+   * All values in range [0,255] must convert to themselves
+   */
+  for (wchar_t wc = 0; wc < 0x100; ++wc) {
+    char c = EOF;
+
+    assert (wcrtomb (&c, wc, &state) == 1);
+    assert ((unsigned char) c == wc);
+    assert (errno == 0);
+    assert (mbsinit (&state));
+  }
+
+  /**
+   * Try to convert low and high surrogates
+   */
+  for (wchar_t wc = 0;; ++wc) {
+    if (IS_LOW_SURROGATE (wc) || IS_HIGH_SURROGATE (wc)) {
+      char c = EOF;
+
+      assert (wcrtomb (&c, wc, &state) == (size_t) -1);
+      /* This assertion fails with CRT's version */
+      assert (c == EOF);
+      assert (errno = EILSEQ);
+      assert (mbsinit (&state));
+
+      // reset errno
+      _set_errno (0);
+    }
+
+    if (wc == WEOF) {
+      break;
+    }
+  }
+
+  /**
+   * Test DBCS code page
+   */
+  assert (setlocale (LC_ALL, "Japanese_Japan.ACP") != NULL);
+  assert (MB_CUR_MAX == 2);
+
+  /**
+   * All values in range [0,127] are valid ASCII characters
+   */
+  for (wchar_t wc = 0; wc < 0x80; ++wc) {
+    char c = EOF;
+
+    assert (wcrtomb (&c, wc, &state) == 1);
+    assert ((unsigned char) c == wc);
+    assert (errno == 0);
+    assert (mbsinit (&state));
+  }
+
+  /**
+   * Try convert multibyte characters
+   */
+  wchar_t DBCS[] = {L'日', L'本', L'語', L'。'};
+
+  for (size_t i = 0; i < _countof (DBCS); ++i) {
+    char buffer[2] = {0};
+
+    assert (wcrtomb (buffer, DBCS[i], &state) == 2);
+    assert (buffer[0] != 0 && buffer[1] != 0);
+    assert (errno == 0);
+    assert (mbsinit (&state));
+  }
+
+  /**
+   * Try to convert low and high surrogates
+   */
+  for (wchar_t wc = 0;; ++wc) {
+    if (IS_LOW_SURROGATE (wc) || IS_HIGH_SURROGATE (wc)) {
+      char c = EOF;
+
+      assert (wcrtomb (&c, wc, &state) == (size_t) -1);
+      /* This assertion fails with CRT's version */
+      assert (c == EOF);
+      assert (errno = EILSEQ);
+      assert (mbsinit (&state));
+
+      // reset errno
+      _set_errno (0);
+    }
+
+    if (wc == WEOF) {
+      break;
+    }
+  }
+
+  return 0;
+}
diff --git a/mingw-w64-crt/testcases/t_wcsrtombs.c 
b/mingw-w64-crt/testcases/t_wcsrtombs.c
new file mode 100644
index 000000000..f2c660daa
--- /dev/null
+++ b/mingw-w64-crt/testcases/t_wcsrtombs.c
@@ -0,0 +1,570 @@
+#ifdef NDEBUG
+#undef NDEBUG
+#endif
+
+#include <assert.h>
+#include <errno.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <wchar.h>
+
+wchar_t AsciiText[] = L"Simple English text.";
+wchar_t SBCSText[] = L"Sömè fÛnnÿ têxt";
+wchar_t DBCSText[] = L"日本語テクスト";
+wchar_t MixedText[] = L"日NI本HON語GO";
+wchar_t BadText[] = {L'テ', L'く', WEOF, L'ト'};
+
+int main (void) {
+#if __MSVCRT_VERSION__ >= 0x0800
+  return 77;
+#endif
+  const wchar_t *original_text = NULL;
+  const wchar_t *text = NULL;
+  size_t         text_length = 0;
+
+  char      buffer[BUFSIZ];
+  mbstate_t state = {0};
+
+  /**
+   * Test "C" locale
+   */
+  assert (setlocale (LC_ALL, "C") != NULL);
+  assert (MB_CUR_MAX == 1);
+
+  /* Test ASCII input */
+
+  original_text = AsciiText;
+  text_length = _countof (AsciiText) - 1;
+
+  /**
+   * Get length of converted AsciiText
+   *
+   * - return value must be `text_length`
+   * - value of `test` must not change
+   * - value of `errno` must not change
+   * - `state` must be in initial state
+   */
+  text = original_text;
+
+  assert (wcsrtombs (NULL, &text, 0, &state) == text_length);
+  assert (text == original_text);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+
+  /**
+   * Convert AsciiText
+   *
+   * - return value must be `text_length`
+   * - value of `test` must be NULL
+   * - value of `errno` must not change
+   * - `state` must be in initial state
+   * - converted string must be terminated with '\0'
+   */
+  memset (buffer, EOF, BUFSIZ);
+  text = original_text;
+
+  assert (wcsrtombs (buffer, &text, BUFSIZ, &state) == text_length);
+  assert (text == NULL);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+  assert (buffer[text_length] == '\0');
+
+  /**
+   * Convert 10 wide characters in AsciiText
+   *
+   * - return value must be 10
+   * - value of `test` must be `original_text + 10`
+   * - value of `errno` must not change
+   * - `state` must be in initial state
+   * - converted string must not be terminated with '\0'
+   */
+  memset (buffer, EOF, BUFSIZ);
+  text = original_text;
+
+  assert (wcsrtombs (buffer, &text, 10, &state) == 10);
+  assert (text == original_text + 10);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+  assert (buffer[10] == EOF);
+
+  /* Test SBCS input */
+
+  original_text = SBCSText;
+  text_length = _countof (SBCSText) - 1;
+
+  /**
+   * Get length of converted SBCSText
+   *
+   * - return value must be 15
+   * - value of `text` must not change
+   * - value of `errno` must not change
+   * - `state` must be in initial state
+   */
+  text = original_text;
+
+  assert (wcsrtombs (NULL, &text, 0, &state) == text_length);
+  assert (text == original_text);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+
+  /**
+   * Convert SBCSText
+   *
+   * - return value must be 15
+   * - value of `text` must be NULL
+   * - value of `errno` must not change
+   * - `state` must be in initial state
+   * - converted string must be terminated with '\0'
+   */
+  memset (buffer, EOF, BUFSIZ);
+  text = original_text;
+
+  assert (wcsrtombs (buffer, &text, BUFSIZ, &state) == text_length);
+  assert (text == NULL);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+  assert (buffer[text_length] == '\0');
+
+  /**
+   * Convert 10 wide characters in SBCSText
+   *
+   * - return value must be 10
+   * - value of `text` must be `original_text + 10`
+   * - value of `errno` must not change
+   * - `state` must be in initial state
+   * - converted string must not be terminated with '\0'
+   */
+  memset (buffer, EOF, BUFSIZ);
+  text = original_text;
+
+  assert (wcsrtombs (buffer, &text, 10, &state) == 10);
+  assert (text == original_text + 10);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+  assert (buffer[10] == EOF);
+
+  /* Test DBCS input */
+
+  original_text = DBCSText;
+  text_length = _countof (DBCSText) - 1;
+
+  /**
+   * Try get length of converted DBCSText
+   *
+   * - return value must be (size_t)-1
+   * - value of `text` must not change
+   * - value of `errno` must be EILSEQ
+   * - `state` must be in initial state
+   */
+  memset (buffer, EOF, BUFSIZ);
+  text = original_text;
+
+  assert (wcsrtombs (buffer, &text, BUFSIZ, &state) == (size_t) -1);
+  assert (text == original_text);
+  assert (mbsinit (&state));
+  assert (errno == EILSEQ);
+  assert (buffer[0] == EOF);
+
+  // reset errno
+  _set_errno (0);
+
+  /**
+   * Try convert DBCSText
+   *
+   * - return value must be (size_t)-1
+   * - value of `text` must not change
+   * - value of `errno` must be EILSEQ
+   * - `state` must be in initial state
+   */
+  text = original_text;
+
+  assert (wcsrtombs (NULL, &text, 0, &state) == (size_t) -1);
+  assert (text == original_text);
+  assert (mbsinit (&state));
+  assert (errno == EILSEQ);
+
+  // reset errno
+  _set_errno (0);
+
+  /**
+   * Test SBCS code page
+   */
+  assert (setlocale (LC_ALL, "English_United States.ACP") != NULL);
+  assert (MB_CUR_MAX == 1);
+
+  /* Test ASCII input */
+
+  original_text = AsciiText;
+  text_length = _countof (AsciiText) - 1;
+
+  /**
+   * Get length of converted AsciiText
+   *
+   * - return value must be `text_length`
+   * - value of `test` must not change
+   * - value of `errno` must not change
+   * - `state` must be in initial state
+   */
+  text = original_text;
+
+  assert (wcsrtombs (NULL, &text, 0, &state) == text_length);
+  assert (text == original_text);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+
+  /**
+   * Convert AsciiText
+   *
+   * - return value must be `text_length`
+   * - value of `test` must be NULL
+   * - value of `errno` must not change
+   * - `state` must be in initial state
+   * - converted string must be terminated with '\0'
+   */
+  memset (buffer, EOF, BUFSIZ);
+  text = original_text;
+
+  assert (wcsrtombs (buffer, &text, BUFSIZ, &state) == text_length);
+  assert (text == NULL);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+  assert (buffer[text_length] == '\0');
+
+  /**
+   * Convert 10 wide characters in AsciiText
+   *
+   * - return value must be 10
+   * - value of `test` must be `original_text + 10`
+   * - value of `errno` must not change
+   * - `state` must be in initial state
+   * - converted string must not be terminated with '\0'
+   */
+  memset (buffer, EOF, BUFSIZ);
+  text = original_text;
+
+  assert (wcsrtombs (buffer, &text, 10, &state) == 10);
+  assert (text == original_text + 10);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+  assert (buffer[10] == EOF);
+
+  /* Test SBCS input */
+
+  original_text = SBCSText;
+  text_length = _countof (SBCSText) - 1;
+
+  /**
+   * Get length of converted SBCSText
+   *
+   * - return value must be 15
+   * - value of `text` must not change
+   * - value of `errno` must not change
+   * - `state` must be in initial state
+   */
+  text = original_text;
+
+  assert (wcsrtombs (NULL, &text, 0, &state) == text_length);
+  assert (text == original_text);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+
+  /**
+   * Convert SBCSText
+   *
+   * - return value must be 15
+   * - value of `text` must be NULL
+   * - value of `errno` must not change
+   * - `state` must be in initial state
+   * - converted string must be terminated with '\0'
+   */
+  memset (buffer, EOF, BUFSIZ);
+  text = original_text;
+
+  assert (wcsrtombs (buffer, &text, BUFSIZ, &state) == text_length);
+  assert (text == NULL);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+  assert (buffer[text_length] == '\0');
+
+  /**
+   * Convert 10 wide characters in SBCSText
+   *
+   * - return value must be 10
+   * - value of `text` must be `original_text + 10`
+   * - value of `errno` must not change
+   * - `state` must be in initial state
+   * - converted string must not be terminated with '\0'
+   */
+  memset (buffer, EOF, BUFSIZ);
+  text = original_text;
+
+  assert (wcsrtombs (buffer, &text, 10, &state) == 10);
+  assert (text == original_text + 10);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+  assert (buffer[10] == EOF);
+
+  /* Test DBCS input */
+
+  original_text = DBCSText;
+  text_length = _countof (DBCSText) - 1;
+
+  /**
+   * Try get length of converted DBCSText
+   *
+   * - return value must be (size_t)-1
+   * - value of `text` must not change
+   * - value of `errno` must be EILSEQ
+   * - `state` must be in initial state
+   */
+  text = original_text;
+
+  assert (wcsrtombs (NULL, &text, 0, &state) == (size_t) -1);
+  assert (text == original_text);
+  assert (mbsinit (&state));
+  assert (errno == EILSEQ);
+
+  // reset errno
+  _set_errno (0);
+
+  /**
+   * Try convert DBCSText
+   *
+   * - return value must be (size_t)-1
+   * - value of `text` must not change
+   * - value of `errno` must be EILSEQ
+   * - `state` must be in initial state
+   * - nothing must be written to `buffer`
+   */
+  memset (buffer, EOF, BUFSIZ);
+  text = original_text;
+
+  assert (wcsrtombs (buffer, &text, BUFSIZ, &state) == (size_t) -1);
+  assert (text == original_text);
+  assert (mbsinit (&state));
+  assert (errno == EILSEQ);
+  /* This assertion fails with CRT's version */
+  assert (buffer[0] == EOF);
+
+  // reset errno
+  _set_errno (0);
+
+  /**
+   * Test DBCS code page
+   */
+  assert (setlocale (LC_ALL, "Japanese_Japan.ACP") != NULL);
+  assert (MB_CUR_MAX == 2);
+
+  /* Test ASCII input */
+
+  original_text = AsciiText;
+  text_length = _countof (AsciiText) - 1;
+
+  /**
+   * Get length of converted AsciiText
+   *
+   * - return value must be `text_length`
+   * - value of `test` must not change
+   * - value of `errno` must not change
+   * - `state` must be in initial state
+   */
+  text = original_text;
+
+  assert (wcsrtombs (NULL, &text, 0, &state) == text_length);
+  assert (text == original_text);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+
+  /**
+   * Convert AsciiText
+   *
+   * - return value must be `text_length`
+   * - value of `test` must be NULL
+   * - value of `errno` must not change
+   * - `state` must be in initial state
+   * - converted string must be terminated with '\0'
+   */
+  memset (buffer, EOF, BUFSIZ);
+  text = original_text;
+
+  assert (wcsrtombs (buffer, &text, BUFSIZ, &state) == text_length);
+  assert (text == NULL);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+  assert (buffer[text_length] == '\0');
+
+  /**
+   * Convert 10 wide characters in AsciiText
+   *
+   * - return value must be 10
+   * - value of `test` must be `original_text + 10`
+   * - value of `errno` must not change
+   * - `state` must be in initial state
+   * - converted string must not be terminated with '\0'
+   */
+  memset (buffer, EOF, BUFSIZ);
+  text = original_text;
+
+  assert (wcsrtombs (buffer, &text, 10, &state) == 10);
+  assert (text == original_text + 10);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+  assert (buffer[10] == EOF);
+
+  /* Test DBCS input */
+
+  original_text = DBCSText;
+  text_length = _countof (DBCSText) - 1;
+
+  /**
+   * Get length of converted DBCSText
+   *
+   * - return value must be 14
+   * - value of `text` must not change
+   * - value of `errno` must not change
+   * - `state` must be in initial state
+   */
+  text = original_text;
+
+  assert (wcsrtombs (NULL, &text, 0, &state) == 14);
+  assert (text == original_text);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+
+  /**
+   * Convert DBCSText
+   *
+   * - return value must be 14
+   * - value of `text` must be NULL
+   * - value of `errno` must not change
+   * - `state` must be in initial state
+   * - converted string must be terminated with '\0'
+   */
+  memset (buffer, EOF, BUFSIZ);
+  text = original_text;
+
+  assert (wcsrtombs (buffer, &text, BUFSIZ, &state) == 14);
+  assert (text == NULL);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+  assert (buffer[14] == '\0');
+
+  /**
+   * Convert 5 wide characters in DBCSText
+   *
+   * - return value must be 10
+   * - value of `text` must be `original_text + 5`
+   * - value of `errno` must not change
+   * - `state` must be in initial state
+   * - converted string must not be terminated with '\0'
+   */
+  memset (buffer, EOF, BUFSIZ);
+  text = original_text;
+
+  assert (wcsrtombs (buffer, &text, 11, &state) == 10);
+  assert (text == original_text + 5);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+  assert (buffer[10] == EOF);
+
+  /* Test mixed input */
+
+  original_text = MixedText;
+  text_length = _countof (MixedText) - 1;
+
+  /**
+   * Get length of converted MixedText
+   *
+   * - return value must be 13
+   * - value of `test` must not change
+   * - value of `errno` must not change
+   * - `state` must be in initial state
+   */
+  text = original_text;
+
+  assert (wcsrtombs (NULL, &text, 0, &state) == 13);
+  assert (text == original_text);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+
+  /**
+   * Convert MixedText
+   *
+   * - return value must be 13
+   * - value of `test` must be NULL
+   * - value of `errno` must not change
+   * - `state` must be in initial state
+   * - converted string must be teminated with '\0'
+   */
+  memset (buffer, EOF, BUFSIZ);
+  text = original_text;
+
+  assert (wcsrtombs (buffer, &text, BUFSIZ, &state) == 13);
+  assert (text == NULL);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+  assert (buffer[13] == '\0');
+
+  /**
+   * Convert 7 wide characters in MixedText
+   *
+   * - return value must be 9
+   * - value of `test` must be `original_text + 7`
+   * - value of `errno` must not change
+   * - `state` must be in initial state
+   * - converted string must not be teminated with '\0'
+   */
+  memset (buffer, EOF, BUFSIZ);
+  text = original_text;
+
+  assert (wcsrtombs (buffer, &text, 10, &state) == 9);
+  assert (text == original_text + 7);
+  assert (mbsinit (&state));
+  assert (errno == 0);
+  assert (buffer[9] == EOF);
+
+  /* Test bad input */
+
+  original_text = BadText;
+  text_length = _countof (BadText) - 1;
+
+  /**
+   * Try get length of converted BadText
+   *
+   * - return value must be (size_t)-1
+   * - value of `text` must not change
+   * - value of `errno` must be EILSEQ
+   * - `state` must be in initial state
+   */
+  text = original_text;
+
+  assert (wcsrtombs (NULL, &text, 0, &state) == (size_t) -1);
+  assert (text == original_text);
+  assert (mbsinit (&state));
+  assert (errno == EILSEQ);
+
+  /**
+   * Try convert BadText
+   *
+   * - return value must be (size_t)-1
+   * - value of `text` must be `original_text + 2`
+   * - value of `errno` must be EILSEQ
+   * - `state` must be in initial state
+   */
+  memset (buffer, EOF, BUFSIZ);
+  text = original_text;
+
+  assert (wcsrtombs (buffer, &text, BUFSIZ, &state) == (size_t) -1);
+  assert (text == original_text + 2);
+  assert (mbsinit (&state));
+  assert (errno == EILSEQ);
+  /* This assertion fails with CRT's version */
+  assert (buffer[3] != EOF && buffer[4] == EOF);
+
+  // reset errno
+  _set_errno (0);
+
+  return 0;
+}
-- 
2.50.0.windows.2

From d7b10b287520e17c02fc6f0443ccb8167f0a223d Mon Sep 17 00:00:00 2001
From: Kirill Makurin <maiddais...@outlook.com>
Date: Wed, 9 Jul 2025 17:33:50 +0900
Subject: [PATCH 3/8] crt: msvcrt.dll: use replacements for C95 conversion
 functions

Previously, replacements were used only for i386 and x86_64 versions
of msvcrt.dll.

Signed-off-by: Kirill Makurin <maiddais...@outlook.com>
---
 mingw-w64-crt/Makefile.am              |  6 ++----
 mingw-w64-crt/lib-common/msvcrt.def.in | 10 +++++-----
 2 files changed, 7 insertions(+), 9 deletions(-)

diff --git a/mingw-w64-crt/Makefile.am b/mingw-w64-crt/Makefile.am
index 97da616dd..a393bf772 100644
--- a/mingw-w64-crt/Makefile.am
+++ b/mingw-w64-crt/Makefile.am
@@ -338,6 +338,8 @@ src_msvcrt=\
   misc/iswblank.c \
   misc/_isblank_l.c \
   misc/_iswblank_l.c \
+  misc/mbrtowc.c \
+  misc/wcrtomb.c \
   misc/wctrans.c \
   misc/wctype.c \
   secapi/_vscprintf_p.c \
@@ -554,7 +556,6 @@ src_msvcrt32=\
   misc/btowc.c \
   misc/imaxabs.c \
   misc/lc_locale_func.c \
-  misc/mbrtowc.c \
   misc/output_format.c \
   misc/_get_errno.c \
   misc/_set_errno.c \
@@ -562,7 +563,6 @@ src_msvcrt32=\
   misc/strtoimax.c \
   misc/strtoumax.c \
   misc/wassert.c \
-  misc/wcrtomb.c \
   misc/wcsnlen.c \
   misc/wcstoimax.c \
   misc/wcstoumax.c \
@@ -616,13 +616,11 @@ src_msvcrt64=\
   misc/_get_current_locale.c \
   misc/_initterm_e.c \
   misc/btowc.c \
-  misc/mbrtowc.c \
   misc/output_format.c \
   misc/_get_errno.c \
   misc/_set_errno.c \
   misc/strnlen.c \
   misc/wassert.c \
-  misc/wcrtomb.c \
   misc/wcsnlen.c \
   misc/wctob.c \
   stdio/_fseeki64.c \
diff --git a/mingw-w64-crt/lib-common/msvcrt.def.in 
b/mingw-w64-crt/lib-common/msvcrt.def.in
index 7b903360a..47582c1aa 100644
--- a/mingw-w64-crt/lib-common/msvcrt.def.in
+++ b/mingw-w64-crt/lib-common/msvcrt.def.in
@@ -1804,10 +1804,10 @@ fscanf_s
 fwprintf_s
 fwscanf_s
 F_ARM_ANY(getenv_s) ; i386 and x64 getenv_s replaced by emu
-F_ARM_ANY(mbrlen) ; i386 and x64 mbrlen replaced by emu
-F_ARM_ANY(mbrtowc) ; i386 and x64 mbrtowc replaced by emu
+; F_ARM_ANY(mbrlen) ; always use replacement
+; F_ARM_ANY(mbrtowc) ; always use replacement
 mbsdup_dbg
-F_ARM_ANY(mbsrtowcs) ; i386 and x64 mbsrtowcs replaced by emu
+; F_ARM_ANY(mbsrtowcs) ; always use replacement
 mbsrtowcs_s
 mbstowcs_s
 F_ARM_ANY(memcpy_s) ; i386 and x64 memcpy_s replaced by emu
@@ -1838,14 +1838,14 @@ vprintf_s
 F_ARM_ANY(vsprintf_s) ; i386 and x64 vsprintf_s replaced by emu
 vswprintf_s
 vwprintf_s
-F_ARM_ANY(wcrtomb) ; i386 and x64 wcrtomb replaced by emu
+; F_ARM_ANY(wcrtomb) ; always use replacement
 wcrtomb_s
 wcscat_s
 wcscpy_s
 wcsncat_s
 wcsncpy_s
 F_ARM_ANY(wcsnlen) ; i386 and x64 wcsnlen replaced by emu
-F_ARM_ANY(wcsrtombs) ; i386 and x64 wcsrtombs replaced by emu
+; F_ARM_ANY(wcsrtombs) ; always use replacement
 wcsrtombs_s
 F_ARM_ANY(wcstok_s) ; i386 and x64 wcstok_s replaced by emu
 wcstombs_s
-- 
2.50.0.windows.2

From 010c46e55f7118df64e557fd05038239850dcfe1 Mon Sep 17 00:00:00 2001
From: Kirill Makurin <maiddais...@outlook.com>
Date: Wed, 9 Jul 2025 17:38:40 +0900
Subject: [PATCH 4/8] crt: new implementation for btowc and wctob

Replacementes for C95 functions btowc and wctob now use mbrtowc
and wcrtomb respectively to perform conversion. This ensures that
their behavior is consistent with mbrtowc and wcrtomb functions.

Signed-off-by: Kirill Makurin <maiddais...@outlook.com>
---
 mingw-w64-crt/misc/btowc.c | 21 ++++++++++++---------
 mingw-w64-crt/misc/wctob.c | 27 ++++++++++++++++-----------
 2 files changed, 28 insertions(+), 20 deletions(-)

diff --git a/mingw-w64-crt/misc/btowc.c b/mingw-w64-crt/misc/btowc.c
index c8fbd8e74..caf2d95ba 100644
--- a/mingw-w64-crt/misc/btowc.c
+++ b/mingw-w64-crt/misc/btowc.c
@@ -7,6 +7,7 @@
 #define WIN32_LEAN_AND_MEAN
 #endif
 #include "mb_wc_common.h"
+#include <limits.h>
 #include <wchar.h>
 #include <stdio.h>
 #include <windows.h>
@@ -15,14 +16,16 @@ wint_t btowc (int c)
 {
   if (c == EOF)
     return (WEOF);
-  else
-    {
-      unsigned char ch = c;
-      wchar_t wc = WEOF;
-      if (!MultiByteToWideChar (___lc_codepage_func(), MB_ERR_INVALID_CHARS,
-                                (char*)&ch, 1, &wc, 1))
-        return WEOF;
 
-      return wc;
-    }
+  /* Use dummy string so that mbrtowc will never return (size_t)-2 */
+  char str[MB_LEN_MAX] = {(unsigned char) c, 0, 0, 0, 0};
+
+  wint_t    wc = WEOF;
+  mbstate_t state = {0};
+
+  if (mbrtowc (&wc, (char *) str, MB_CUR_MAX, &state) == (size_t) -1) {
+    return WEOF;
+  }
+
+  return wc;
 }
diff --git a/mingw-w64-crt/misc/wctob.c b/mingw-w64-crt/misc/wctob.c
index 995f6db6e..2fe66618d 100644
--- a/mingw-w64-crt/misc/wctob.c
+++ b/mingw-w64-crt/misc/wctob.c
@@ -7,23 +7,28 @@
 #define WIN32_LEAN_AND_MEAN
 #endif
 #include "mb_wc_common.h"
+#include <limits.h>
 #include <wchar.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <errno.h>
 #include <windows.h>
 
-/* Return just the first byte after translating to multibyte.  */
-int wctob (wint_t wc )
+int wctob (wint_t wc)
 {
-    wchar_t w = wc;
-    char c;
-    int invalid_char = 0;
-    if (!WideCharToMultiByte (___lc_codepage_func(),
-                             0 /* Is this correct flag? */,
-                             &w, 1, &c, 1, NULL, &invalid_char)
-        || invalid_char)
-      return EOF;
+  /* Return early */
+  if (IS_LOW_SURROGATE (wc) || IS_HIGH_SURROGATE (wc) || wc == WEOF) {
+    return EOF;
+  }
 
-    return (unsigned char) c;
+  mbstate_t state = {0};
+  /* Buffer large enough to hold any multibyte character */
+  char mbc[MB_LEN_MAX];
+
+  size_t length = wcrtomb (mbc, wc, &state);
+  if (length > 1) {
+    return EOF;
+  }
+
+  return (unsigned char) mbc[0];
 }
-- 
2.50.0.windows.2

From 3567c074221d025196ff48e6d4fa790e9962c198 Mon Sep 17 00:00:00 2001
From: Kirill Makurin <maiddais...@outlook.com>
Date: Wed, 9 Jul 2025 17:41:43 +0900
Subject: [PATCH 5/8] crt: add tests for wctob and btowc functions

Signed-off-by: Kirill Makurin <maiddais...@outlook.com>
---
 mingw-w64-crt/Makefile.am         |   2 +
 mingw-w64-crt/testcases/t_btowc.c |  79 +++++++++++++++++++++++
 mingw-w64-crt/testcases/t_wctob.c | 101 ++++++++++++++++++++++++++++++
 3 files changed, 182 insertions(+)
 create mode 100644 mingw-w64-crt/testcases/t_btowc.c
 create mode 100644 mingw-w64-crt/testcases/t_wctob.c

diff --git a/mingw-w64-crt/Makefile.am b/mingw-w64-crt/Makefile.am
index a393bf772..30b1374c2 100644
--- a/mingw-w64-crt/Makefile.am
+++ b/mingw-w64-crt/Makefile.am
@@ -4394,6 +4394,7 @@ testcase_progs = \
   testcases/t__stat_all \
   testcases/t_aligned_alloc \
   testcases/t_ansi_io \
+  testcases/t_btowc \
   testcases/t_findfirst \
   testcases/t_float  \
   testcases/t_fstat \
@@ -4433,6 +4434,7 @@ testcase_progs = \
   testcases/t_vsscanf \
   testcases/t_wcrtomb \
   testcases/t_wcsrtombs \
+  testcases/t_wctob \
   testcases/t_wreaddir \
   testcases/t_fseeko64
 
diff --git a/mingw-w64-crt/testcases/t_btowc.c 
b/mingw-w64-crt/testcases/t_btowc.c
new file mode 100644
index 000000000..be35bf6f7
--- /dev/null
+++ b/mingw-w64-crt/testcases/t_btowc.c
@@ -0,0 +1,79 @@
+#ifdef NDEBUG
+#undef NDEBUG
+#endif
+
+#include <assert.h>
+#include <errno.h>
+#include <limits.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <wchar.h>
+
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+
+int main (void) {
+  /**
+   * Test "C" locale
+   */
+  assert (setlocale (LC_ALL, "C") != NULL);
+
+  /**
+   * All bytes in range [0,255] are valid and must convert to themselves
+   */
+  for (int c = 0; c < 0x100; ++c) {
+    assert (btowc (c) == c);
+  }
+
+  /**
+   * Try convert sign-extended input
+   */
+  for (char c = CHAR_MIN; c < 0; ++c) {
+    assert (btowc (c) == (c == EOF ? WEOF : (BYTE) c));
+  }
+
+  /**
+   * Test SBCS code page
+   */
+  assert (setlocale (LC_ALL, "English_United States.ACP") != NULL);
+  assert (MB_CUR_MAX == 1);
+
+  /**
+   * All bytes in range [0,127] are valid and must convert to themselves
+   */
+  for (int c = 0; c < 0x80; ++c) {
+    assert (btowc (c) == c);
+  }
+
+  /**
+   * All bytes in range [0,255] are valid
+   */
+  for (int c = 0x80; c < 0x100; ++c) {
+    assert (btowc (c) != WEOF);
+  }
+
+  /**
+   * Test DBCS code page
+   */
+  assert (setlocale (LC_ALL, "Japanese_Japan.ACP") != NULL);
+  assert (MB_CUR_MAX == 2);
+
+  /**
+   * All bytes in range [0,127] are valid and must convert to themselves
+   */
+  for (int c = 0; c < 0x80; ++c) {
+    assert (btowc (c) == c);
+  }
+
+  /**
+   * Try to convert lead bytes
+   */
+  for (int c = 0x80; c < 0x100; ++c) {
+    if (IsDBCSLeadByteEx (932, (BYTE) c)) {
+      assert (btowc (c) == WEOF);
+    }
+  }
+
+  return 0;
+}
diff --git a/mingw-w64-crt/testcases/t_wctob.c 
b/mingw-w64-crt/testcases/t_wctob.c
new file mode 100644
index 000000000..19ebd4ab7
--- /dev/null
+++ b/mingw-w64-crt/testcases/t_wctob.c
@@ -0,0 +1,101 @@
+#ifdef NDEBUG
+#undef NDEBUG
+#endif
+
+#include <assert.h>
+#include <errno.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <wchar.h>
+
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+
+int main (void) {
+  /**
+   * Test "C" locale
+   */
+  assert (setlocale (LC_ALL, "C") != NULL);
+
+  /**
+   * All values in range [0,255] are valid and must convert to themselves
+   */
+  for (wchar_t wc = 0; wc < 0x100; ++wc) {
+    assert (wctob (wc) == wc);
+  }
+
+  /**
+   * All values in range [256,WEOF] are invalid
+   */
+  for (wchar_t wc = 0x100;; ++wc) {
+    assert (wctob (wc) == EOF);
+    if (wc == WEOF) {
+      break;
+    }
+  }
+
+  /**
+   * Test SBCS code page
+   * NOTE: code page 28591 is ISO-8859-1
+   */
+  assert (setlocale (LC_ALL, "English_United States.28591") != NULL);
+  assert (MB_CUR_MAX == 1);
+
+  /**
+   * All values in range [0,255] are valid and must convert to themselves
+   */
+  for (wchar_t wc = 0; wc < 0x100; ++wc) {
+    assert (wctob (wc) == wc);
+  }
+
+  /**
+   * Try convert low and high surrogates
+   */
+  for (wchar_t wc = 0; wc < 0x100; ++wc) {
+    if (IS_LOW_SURROGATE (wc) || IS_HIGH_SURROGATE (wc) || wc == WEOF) {
+      assert (wctob (wc) == EOF);
+    }
+
+    if (wc == WEOF) {
+      break;
+    }
+  }
+
+  /**
+   * Test DBCS code page
+   */
+  assert (setlocale (LC_ALL, "Japanese_Japan.ACP") != NULL);
+  assert (MB_CUR_MAX == 2);
+
+  /**
+   * All values in range [0,127] are valid and must convert to themselves
+   */
+  for (wchar_t wc = 0; wc < 0x80; ++wc) {
+    assert (wctob (wc) == wc);
+  }
+
+  /**
+   * Try convert multibyte characters
+   */
+  wchar_t DBCS[] = {L'日', L'本', L'語', L'。'};
+
+  for (size_t i = 0; i < _countof (DBCS); ++i) {
+    assert (wctob (DBCS[i]) == EOF);
+  }
+
+  /**
+   * Try convert low and high surrogates
+   */
+  for (wchar_t wc = 0; wc < 0x100; ++wc) {
+    if (IS_LOW_SURROGATE (wc) || IS_HIGH_SURROGATE (wc) || wc == WEOF) {
+      assert (wctob (wc) == EOF);
+    }
+
+    if (wc == WEOF) {
+      break;
+    }
+  }
+
+  return 0;
+}
-- 
2.50.0.windows.2

From 79eaeca6af63e072ef3d7a122994f40e8bdad604 Mon Sep 17 00:00:00 2001
From: Kirill Makurin <maiddais...@outlook.com>
Date: Wed, 9 Jul 2025 17:58:19 +0900
Subject: [PATCH 6/8] crt: always use replacements for btowc and wctob

CRT's wctob sign-extends its return value if value of converted
single-byte character is outside of range [0,127].

This behavior makes it impossible to distinguish failure from success if
value of converted character is 255 (when sign-extended, same as EOF).

Signed-off-by: Kirill Makurin <maiddais...@outlook.com>
---
 mingw-w64-crt/Makefile.am                              | 10 ++++------
 .../lib-common/api-ms-win-crt-convert-l1-1-0.def.in    |  4 ++--
 mingw-w64-crt/lib-common/msvcr120_app.def.in           |  4 ++--
 mingw-w64-crt/lib-common/msvcrt.def.in                 |  4 ++--
 mingw-w64-crt/lib-common/ucrtbase-common.def.in        |  4 ++--
 mingw-w64-crt/lib32/msvcr100.def.in                    |  4 ++--
 mingw-w64-crt/lib32/msvcr100d.def.in                   |  4 ++--
 mingw-w64-crt/lib32/msvcr110.def.in                    |  4 ++--
 mingw-w64-crt/lib32/msvcr110d.def.in                   |  4 ++--
 mingw-w64-crt/lib32/msvcr120.def.in                    |  4 ++--
 mingw-w64-crt/lib32/msvcr120d.def.in                   |  4 ++--
 mingw-w64-crt/lib32/msvcr80.def.in                     |  4 ++--
 mingw-w64-crt/lib32/msvcr80d.def.in                    |  4 ++--
 mingw-w64-crt/lib32/msvcr90.def.in                     |  4 ++--
 mingw-w64-crt/lib32/msvcr90d.def.in                    |  4 ++--
 mingw-w64-crt/lib64/msvcr100.def.in                    |  4 ++--
 mingw-w64-crt/lib64/msvcr100d.def.in                   |  4 ++--
 mingw-w64-crt/lib64/msvcr110.def.in                    |  4 ++--
 mingw-w64-crt/lib64/msvcr110d.def.in                   |  4 ++--
 mingw-w64-crt/lib64/msvcr120.def.in                    |  4 ++--
 mingw-w64-crt/lib64/msvcr120d.def.in                   |  4 ++--
 mingw-w64-crt/lib64/msvcr80.def.in                     |  4 ++--
 mingw-w64-crt/lib64/msvcr80d.def.in                    |  4 ++--
 mingw-w64-crt/lib64/msvcr90.def.in                     |  4 ++--
 mingw-w64-crt/lib64/msvcr90d.def.in                    |  4 ++--
 mingw-w64-crt/libarm32/msvcr110.def.in                 |  4 ++--
 mingw-w64-crt/libarm32/msvcr110d.def.in                |  4 ++--
 mingw-w64-crt/libarm32/msvcr120.def.in                 |  4 ++--
 mingw-w64-crt/libarm32/msvcr120d.def.in                |  4 ++--
 mingw-w64-crt/misc/ucrt_btowc.c                        |  9 +++++++++
 mingw-w64-crt/misc/ucrt_wctob.c                        |  9 +++++++++
 31 files changed, 78 insertions(+), 62 deletions(-)
 create mode 100644 mingw-w64-crt/misc/ucrt_btowc.c
 create mode 100644 mingw-w64-crt/misc/ucrt_wctob.c

diff --git a/mingw-w64-crt/Makefile.am b/mingw-w64-crt/Makefile.am
index 30b1374c2..021b92c41 100644
--- a/mingw-w64-crt/Makefile.am
+++ b/mingw-w64-crt/Makefile.am
@@ -167,9 +167,11 @@ src_libws2_32=libsrc/ws2_32.c \
 # Files included in all libmsvcr*.a
 src_msvcrt_common=\
   misc/_onexit.c \
+  misc/btowc.c \
   misc/mbsinit.c \
   misc/onexit_table.c \
   misc/register_tls_atexit.c \
+  misc/wctob.c \
   stdio/_getc_nolock.c \
   stdio/_getwc_nolock.c \
   stdio/_putc_nolock.c \
@@ -415,6 +417,8 @@ src_ucrtbase=\
   misc/_onexit.c \
   misc/output_format.c \
   misc/ucrt-access.c \
+  misc/ucrt_btowc.c \
+  misc/ucrt_wctob.c \
   stdio/ucrt___local_stdio_printf_options.c \
   stdio/ucrt___local_stdio_scanf_options.c \
   stdio/ucrt__scprintf.c \
@@ -553,7 +557,6 @@ src_msvcrt32=\
   misc/_get_current_locale.c \
   misc/_initterm_e.c \
   misc/_time64.c \
-  misc/btowc.c \
   misc/imaxabs.c \
   misc/lc_locale_func.c \
   misc/output_format.c \
@@ -566,7 +569,6 @@ src_msvcrt32=\
   misc/wcsnlen.c \
   misc/wcstoimax.c \
   misc/wcstoumax.c \
-  misc/wctob.c \
   stdio/_fseeki64.c \
   stdio/_fstat64.c \
   stdio/_fstat64i32.c \
@@ -615,14 +617,12 @@ src_msvcrt64=\
   misc/_free_locale.c \
   misc/_get_current_locale.c \
   misc/_initterm_e.c \
-  misc/btowc.c \
   misc/output_format.c \
   misc/_get_errno.c \
   misc/_set_errno.c \
   misc/strnlen.c \
   misc/wassert.c \
   misc/wcsnlen.c \
-  misc/wctob.c \
   stdio/_fseeki64.c \
   stdio/_fstat32.c \
   stdio/_fstat32i64.c \
@@ -881,7 +881,6 @@ src_pre_msvcr80=\
   misc/_initterm_e.c \
   misc/_recalloc.c \
   misc/_set_errno.c \
-  misc/btowc.c \
   misc/imaxabs.c \
   misc/invalid_parameter_handler.c \
   misc/mbrtowc.c \
@@ -890,7 +889,6 @@ src_pre_msvcr80=\
   misc/wassert.c \
   misc/wcrtomb.c \
   misc/wcsnlen.c \
-  misc/wctob.c \
   secapi/getenv_s.c \
   stdio/_fseeki64.c \
   stdio/_fstat64i32.c \
diff --git a/mingw-w64-crt/lib-common/api-ms-win-crt-convert-l1-1-0.def.in 
b/mingw-w64-crt/lib-common/api-ms-win-crt-convert-l1-1-0.def.in
index d5e4f259c..fd49dc01d 100644
--- a/mingw-w64-crt/lib-common/api-ms-win-crt-convert-l1-1-0.def.in
+++ b/mingw-w64-crt/lib-common/api-ms-win-crt-convert-l1-1-0.def.in
@@ -93,7 +93,7 @@ atof
 atoi
 atol
 atoll
-btowc
+; btowc ; use replacement
 c16rtomb
 c32rtomb
 mbrtoc16
@@ -128,7 +128,7 @@ wcstombs_s
 wcstoul
 wcstoull
 wcstoumax
-wctob
+; wctob ; use replacement
 wctomb
 wctomb_s
 wctrans
diff --git a/mingw-w64-crt/lib-common/msvcr120_app.def.in 
b/mingw-w64-crt/lib-common/msvcr120_app.def.in
index 42c64240b..55d91279e 100644
--- a/mingw-w64-crt/lib-common/msvcr120_app.def.in
+++ b/mingw-w64-crt/lib-common/msvcr120_app.def.in
@@ -1877,7 +1877,7 @@ atol
 atoll
 bsearch
 bsearch_s
-btowc
+; btowc ; use replacement
 cabs
 cabsf
 F_ARM32(cabsl) ; Can't use long double functions from the CRT on x86
@@ -2321,7 +2321,7 @@ wcstoul
 wcstoull
 wcstoumax
 wcsxfrm
-wctob
+; wctob ; use replacement
 wctomb
 wctomb_s
 wctrans
diff --git a/mingw-w64-crt/lib-common/msvcrt.def.in 
b/mingw-w64-crt/lib-common/msvcrt.def.in
index 47582c1aa..3f1b1a8da 100644
--- a/mingw-w64-crt/lib-common/msvcrt.def.in
+++ b/mingw-w64-crt/lib-common/msvcrt.def.in
@@ -1795,7 +1795,7 @@ _wtol_l
 _wutime32 F_I386(== _wutime) ; i386 _wutime32 replaced by alias
 F_ARM_ANY(asctime_s) ; i386 and x64 asctime_s replaced by emu
 bsearch_s
-F_ARM_ANY(btowc) ; i386 and x64 btowc replaced by emu
+; F_ARM_ANY(btowc) ; always use replacement
 clearerr_s
 fopen_s
 fprintf_s
@@ -1849,7 +1849,7 @@ F_ARM_ANY(wcsnlen) ; i386 and x64 wcsnlen replaced by emu
 wcsrtombs_s
 F_ARM_ANY(wcstok_s) ; i386 and x64 wcstok_s replaced by emu
 wcstombs_s
-F_ARM_ANY(wctob) ; i386 and x64 wctob replaced by emu
+; F_ARM_ANY(wctob) ; always use replacement
 wctomb_s
 wprintf_s
 wscanf_s
diff --git a/mingw-w64-crt/lib-common/ucrtbase-common.def.in 
b/mingw-w64-crt/lib-common/ucrtbase-common.def.in
index 90a099306..e121cedc2 100644
--- a/mingw-w64-crt/lib-common/ucrtbase-common.def.in
+++ b/mingw-w64-crt/lib-common/ucrtbase-common.def.in
@@ -2233,7 +2233,7 @@ atol
 atoll
 bsearch
 bsearch_s
-btowc
+; btowc ; use replacement
 c16rtomb
 c32rtomb
 cabs
@@ -2656,7 +2656,7 @@ wcstoul
 wcstoull
 wcstoumax
 wcsxfrm
-wctob
+; wctob ; use replacement
 wctomb
 wctomb_s
 wctrans
diff --git a/mingw-w64-crt/lib32/msvcr100.def.in 
b/mingw-w64-crt/lib32/msvcr100.def.in
index b1d73dfb8..038b6b713 100644
--- a/mingw-w64-crt/lib32/msvcr100.def.in
+++ b/mingw-w64-crt/lib32/msvcr100.def.in
@@ -1662,7 +1662,7 @@ atoi
 atol
 bsearch
 bsearch_s
-btowc
+; btowc ; use replacement
 calloc
 ceil DATA
 clearerr
@@ -1892,7 +1892,7 @@ wcstombs
 wcstombs_s
 wcstoul
 wcsxfrm
-wctob
+; wctob ; use replacement
 wctomb
 wctomb_s
 wmemcpy_s
diff --git a/mingw-w64-crt/lib32/msvcr100d.def.in 
b/mingw-w64-crt/lib32/msvcr100d.def.in
index 0ec23175a..df0e0f67d 100644
--- a/mingw-w64-crt/lib32/msvcr100d.def.in
+++ b/mingw-w64-crt/lib32/msvcr100d.def.in
@@ -1729,7 +1729,7 @@ atoi
 atol
 bsearch
 bsearch_s
-btowc
+; btowc ; use replacement
 calloc
 ceil DATA ; overwritten
 clearerr
@@ -1955,7 +1955,7 @@ wcstombs
 wcstombs_s
 wcstoul
 wcsxfrm
-wctob
+; wctob ; use replacement
 wctomb
 wctomb_s
 wmemcpy_s
diff --git a/mingw-w64-crt/lib32/msvcr110.def.in 
b/mingw-w64-crt/lib32/msvcr110.def.in
index dee3662b1..ce8c9362f 100644
--- a/mingw-w64-crt/lib32/msvcr110.def.in
+++ b/mingw-w64-crt/lib32/msvcr110.def.in
@@ -1795,7 +1795,7 @@ atoi
 atol
 bsearch
 bsearch_s
-btowc
+; btowc ; use replacement
 calloc
 ceil DATA
 clearerr
@@ -2021,7 +2021,7 @@ wcstombs
 wcstombs_s
 wcstoul
 wcsxfrm
-wctob
+; wctob ; use replacement
 wctomb
 wctomb_s
 wmemcpy_s
diff --git a/mingw-w64-crt/lib32/msvcr110d.def.in 
b/mingw-w64-crt/lib32/msvcr110d.def.in
index 7719fbd32..89d6ef5ca 100644
--- a/mingw-w64-crt/lib32/msvcr110d.def.in
+++ b/mingw-w64-crt/lib32/msvcr110d.def.in
@@ -1862,7 +1862,7 @@ atoi
 atol
 bsearch
 bsearch_s
-btowc
+; btowc ; use replacement
 calloc
 ceil DATA ; overwritten
 clearerr
@@ -2088,7 +2088,7 @@ wcstombs
 wcstombs_s
 wcstoul
 wcsxfrm
-wctob
+; wctob ; use replacement
 wctomb
 wctomb_s
 wmemcpy_s
diff --git a/mingw-w64-crt/lib32/msvcr120.def.in 
b/mingw-w64-crt/lib32/msvcr120.def.in
index 2432346e8..49723bbfe 100644
--- a/mingw-w64-crt/lib32/msvcr120.def.in
+++ b/mingw-w64-crt/lib32/msvcr120.def.in
@@ -1853,7 +1853,7 @@ atol
 atoll
 bsearch
 bsearch_s
-btowc
+; btowc ; use replacement
 cabs
 cabsf
 ; cabsl ; Can't use long double functions from the CRT on x86
@@ -2284,7 +2284,7 @@ wcstoul
 wcstoull
 wcstoumax
 wcsxfrm
-wctob
+; wctob ; use replacement
 wctomb
 wctomb_s
 wctrans
diff --git a/mingw-w64-crt/lib32/msvcr120d.def.in 
b/mingw-w64-crt/lib32/msvcr120d.def.in
index a5db51612..ed503ba72 100644
--- a/mingw-w64-crt/lib32/msvcr120d.def.in
+++ b/mingw-w64-crt/lib32/msvcr120d.def.in
@@ -1920,7 +1920,7 @@ atol
 atoll
 bsearch
 bsearch_s
-btowc
+; btowc ; use replacement
 cabs
 cabsf
 ; cabsl ; Can't use long double functions from the CRT on x86
@@ -2351,7 +2351,7 @@ wcstoul
 wcstoull
 wcstoumax
 wcsxfrm
-wctob
+; wctob ; use replacement
 wctomb
 wctomb_s
 wctrans
diff --git a/mingw-w64-crt/lib32/msvcr80.def.in 
b/mingw-w64-crt/lib32/msvcr80.def.in
index 9b1b5d1a0..0f3b0f5b3 100644
--- a/mingw-w64-crt/lib32/msvcr80.def.in
+++ b/mingw-w64-crt/lib32/msvcr80.def.in
@@ -1303,7 +1303,7 @@ atoi
 atol
 bsearch
 bsearch_s
-btowc
+; btowc ; use replacement
 calloc
 ceil DATA
 clearerr
@@ -1526,7 +1526,7 @@ wcstombs
 wcstombs_s
 wcstoul
 wcsxfrm
-wctob
+; wctob ; use replacement
 wctomb
 wctomb_s
 wprintf
diff --git a/mingw-w64-crt/lib32/msvcr80d.def.in 
b/mingw-w64-crt/lib32/msvcr80d.def.in
index 1931dc3ae..153dfc4b4 100644
--- a/mingw-w64-crt/lib32/msvcr80d.def.in
+++ b/mingw-w64-crt/lib32/msvcr80d.def.in
@@ -1386,7 +1386,7 @@ atoi
 atol
 bsearch
 bsearch_s
-btowc
+; btowc ; use replacement
 calloc
 ceil DATA ; overwritten
 clearerr
@@ -1609,7 +1609,7 @@ wcstombs
 wcstombs_s
 wcstoul
 wcsxfrm
-wctob
+; wctob ; use replacement
 wctomb
 wctomb_s
 wprintf
diff --git a/mingw-w64-crt/lib32/msvcr90.def.in 
b/mingw-w64-crt/lib32/msvcr90.def.in
index 697b92818..6df2f0f33 100644
--- a/mingw-w64-crt/lib32/msvcr90.def.in
+++ b/mingw-w64-crt/lib32/msvcr90.def.in
@@ -1296,7 +1296,7 @@ atoi
 atol
 bsearch
 bsearch_s
-btowc
+; btowc ; use replacement
 calloc
 ceil DATA
 clearerr
@@ -1524,7 +1524,7 @@ wcstombs
 wcstombs_s
 wcstoul
 wcsxfrm
-wctob
+; wctob ; use replacement
 wctomb
 wctomb_s
 wprintf
diff --git a/mingw-w64-crt/lib32/msvcr90d.def.in 
b/mingw-w64-crt/lib32/msvcr90d.def.in
index f1da7b688..a311af181 100644
--- a/mingw-w64-crt/lib32/msvcr90d.def.in
+++ b/mingw-w64-crt/lib32/msvcr90d.def.in
@@ -1368,7 +1368,7 @@ atoi
 atol
 bsearch
 bsearch_s
-btowc
+; btowc ; use replacement
 calloc
 ceil DATA
 clearerr
@@ -1596,7 +1596,7 @@ wcstombs
 wcstombs_s
 wcstoul
 wcsxfrm
-wctob
+; wctob ; use replacement
 wctomb
 wctomb_s
 wprintf
diff --git a/mingw-w64-crt/lib64/msvcr100.def.in 
b/mingw-w64-crt/lib64/msvcr100.def.in
index 9b6bc4853..24698cc8d 100644
--- a/mingw-w64-crt/lib64/msvcr100.def.in
+++ b/mingw-w64-crt/lib64/msvcr100.def.in
@@ -1612,7 +1612,7 @@ atoi
 atol
 bsearch
 bsearch_s
-btowc
+; btowc ; use replacement
 calloc
 ceil DATA
 ceilf DATA
@@ -1854,7 +1854,7 @@ wcstombs
 wcstombs_s
 wcstoul
 wcsxfrm
-wctob
+; wctob ; use replacement
 wctomb
 wctomb_s
 wmemcpy_s
diff --git a/mingw-w64-crt/lib64/msvcr100d.def.in 
b/mingw-w64-crt/lib64/msvcr100d.def.in
index 70793d26a..e3c7e1b71 100644
--- a/mingw-w64-crt/lib64/msvcr100d.def.in
+++ b/mingw-w64-crt/lib64/msvcr100d.def.in
@@ -1677,7 +1677,7 @@ atoi
 atol
 bsearch
 bsearch_s
-btowc
+; btowc ; use replacement
 calloc
 ceil DATA ; overwritten
 ceilf DATA ; overwritten
@@ -1919,7 +1919,7 @@ wcstombs
 wcstombs_s
 wcstoul
 wcsxfrm
-wctob
+; wctob ; use replacement
 wctomb
 wctomb_s
 wmemcpy_s
diff --git a/mingw-w64-crt/lib64/msvcr110.def.in 
b/mingw-w64-crt/lib64/msvcr110.def.in
index 777943609..8ef9f860f 100644
--- a/mingw-w64-crt/lib64/msvcr110.def.in
+++ b/mingw-w64-crt/lib64/msvcr110.def.in
@@ -1736,7 +1736,7 @@ atoi
 atol
 bsearch
 bsearch_s
-btowc
+; btowc ; use replacement
 calloc
 ceil
 ceilf
@@ -1978,7 +1978,7 @@ wcstombs
 wcstombs_s
 wcstoul
 wcsxfrm
-wctob
+; wctob ; use replacement
 wctomb
 wctomb_s
 wmemcpy_s
diff --git a/mingw-w64-crt/lib64/msvcr110d.def.in 
b/mingw-w64-crt/lib64/msvcr110d.def.in
index b86002efb..ad13f3aad 100644
--- a/mingw-w64-crt/lib64/msvcr110d.def.in
+++ b/mingw-w64-crt/lib64/msvcr110d.def.in
@@ -1801,7 +1801,7 @@ atoi
 atol
 bsearch
 bsearch_s
-btowc
+; btowc ; use replacement
 calloc
 ceil
 ceilf
@@ -2043,7 +2043,7 @@ wcstombs
 wcstombs_s
 wcstoul
 wcsxfrm
-wctob
+; wctob ; use replacement
 wctomb
 wctomb_s
 wmemcpy_s
diff --git a/mingw-w64-crt/lib64/msvcr120.def.in 
b/mingw-w64-crt/lib64/msvcr120.def.in
index 3a6bc1a11..0401dbf20 100644
--- a/mingw-w64-crt/lib64/msvcr120.def.in
+++ b/mingw-w64-crt/lib64/msvcr120.def.in
@@ -1793,7 +1793,7 @@ atol
 atoll
 bsearch
 bsearch_s
-btowc
+; btowc ; use replacement
 cabs
 cabsf
 ; cabsl ; Can't use long double functions from the CRT on x86
@@ -2240,7 +2240,7 @@ wcstoul
 wcstoull
 wcstoumax
 wcsxfrm
-wctob
+; wctob ; use replacement
 wctomb
 wctomb_s
 wctrans
diff --git a/mingw-w64-crt/lib64/msvcr120d.def.in 
b/mingw-w64-crt/lib64/msvcr120d.def.in
index f0d89961d..bf914c744 100644
--- a/mingw-w64-crt/lib64/msvcr120d.def.in
+++ b/mingw-w64-crt/lib64/msvcr120d.def.in
@@ -1858,7 +1858,7 @@ atol
 atoll
 bsearch
 bsearch_s
-btowc
+; btowc ; use replacement
 cabs
 cabsf
 ; cabsl ; Can't use long double functions from the CRT on x86
@@ -2305,7 +2305,7 @@ wcstoul
 wcstoull
 wcstoumax
 wcsxfrm
-wctob
+; wctob ; use replacement
 wctomb
 wctomb_s
 wctrans
diff --git a/mingw-w64-crt/lib64/msvcr80.def.in 
b/mingw-w64-crt/lib64/msvcr80.def.in
index 08d972368..aaefaa677 100644
--- a/mingw-w64-crt/lib64/msvcr80.def.in
+++ b/mingw-w64-crt/lib64/msvcr80.def.in
@@ -1235,7 +1235,7 @@ atoi
 atol
 bsearch
 bsearch_s
-btowc
+; btowc ; use replacement
 calloc
 ceil DATA
 ceilf DATA
@@ -1474,7 +1474,7 @@ wcstombs
 wcstombs_s
 wcstoul
 wcsxfrm
-wctob
+; wctob ; use replacement
 wctomb
 wctomb_s
 wprintf
diff --git a/mingw-w64-crt/lib64/msvcr80d.def.in 
b/mingw-w64-crt/lib64/msvcr80d.def.in
index caeb5cec4..5888e92f9 100644
--- a/mingw-w64-crt/lib64/msvcr80d.def.in
+++ b/mingw-w64-crt/lib64/msvcr80d.def.in
@@ -1312,7 +1312,7 @@ atoi
 atol
 bsearch
 bsearch_s
-btowc
+; btowc ; use replacement
 calloc
 ceil DATA ; overwritten
 ceilf DATA ; overwritten
@@ -1551,7 +1551,7 @@ wcstombs
 wcstombs_s
 wcstoul
 wcsxfrm
-wctob
+; wctob ; use replacement
 wctomb
 wctomb_s
 wprintf
diff --git a/mingw-w64-crt/lib64/msvcr90.def.in 
b/mingw-w64-crt/lib64/msvcr90.def.in
index ddf7bfb86..c3524fff2 100644
--- a/mingw-w64-crt/lib64/msvcr90.def.in
+++ b/mingw-w64-crt/lib64/msvcr90.def.in
@@ -1232,7 +1232,7 @@ atoi
 atol
 bsearch
 bsearch_s
-btowc
+; btowc ; use replacement
 calloc
 ceil DATA
 ceilf DATA
@@ -1472,7 +1472,7 @@ wcstombs
 wcstombs_s
 wcstoul
 wcsxfrm
-wctob
+; wctob ; use replacement
 wctomb
 wctomb_s
 wprintf
diff --git a/mingw-w64-crt/lib64/msvcr90d.def.in 
b/mingw-w64-crt/lib64/msvcr90d.def.in
index c26790c9b..e88cffa39 100644
--- a/mingw-w64-crt/lib64/msvcr90d.def.in
+++ b/mingw-w64-crt/lib64/msvcr90d.def.in
@@ -1298,7 +1298,7 @@ atoi
 atol
 bsearch
 bsearch_s
-btowc
+; btowc ; use replacement
 calloc
 ceil DATA
 ceilf DATA
@@ -1538,7 +1538,7 @@ wcstombs
 wcstombs_s
 wcstoul
 wcsxfrm
-wctob
+; wctob ; use replacement
 wctomb
 wctomb_s
 wprintf
diff --git a/mingw-w64-crt/libarm32/msvcr110.def.in 
b/mingw-w64-crt/libarm32/msvcr110.def.in
index f7363708e..bba35f0be 100644
--- a/mingw-w64-crt/libarm32/msvcr110.def.in
+++ b/mingw-w64-crt/libarm32/msvcr110.def.in
@@ -1722,7 +1722,7 @@ atoi
 atol
 bsearch
 bsearch_s
-btowc
+; btowc ; use replacement
 calloc
 ceil
 ceilf
@@ -1965,7 +1965,7 @@ wcstombs
 wcstombs_s
 wcstoul
 wcsxfrm
-wctob
+; wctob ; use replacement
 wctomb
 wctomb_s
 wmemcpy_s
diff --git a/mingw-w64-crt/libarm32/msvcr110d.def.in 
b/mingw-w64-crt/libarm32/msvcr110d.def.in
index 66a344ce8..4ef8581d8 100644
--- a/mingw-w64-crt/libarm32/msvcr110d.def.in
+++ b/mingw-w64-crt/libarm32/msvcr110d.def.in
@@ -1787,7 +1787,7 @@ atoi
 atol
 bsearch
 bsearch_s
-btowc
+; btowc ; use replacement
 calloc
 ceil
 ceilf
@@ -2030,7 +2030,7 @@ wcstombs
 wcstombs_s
 wcstoul
 wcsxfrm
-wctob
+; wctob ; use replacement
 wctomb
 wctomb_s
 wmemcpy_s
diff --git a/mingw-w64-crt/libarm32/msvcr120.def.in 
b/mingw-w64-crt/libarm32/msvcr120.def.in
index e43e29da9..32c423fa8 100644
--- a/mingw-w64-crt/libarm32/msvcr120.def.in
+++ b/mingw-w64-crt/libarm32/msvcr120.def.in
@@ -1760,7 +1760,7 @@ atol
 atoll
 bsearch
 bsearch_s
-btowc
+; btowc ; use replacement
 cabs
 cabsf
 cabsl
@@ -2208,7 +2208,7 @@ wcstoul
 wcstoull
 wcstoumax
 wcsxfrm
-wctob
+; wctob ; use replacement
 wctomb
 wctomb_s
 wctrans
diff --git a/mingw-w64-crt/libarm32/msvcr120d.def.in 
b/mingw-w64-crt/libarm32/msvcr120d.def.in
index 7241a9e7b..84faadae1 100644
--- a/mingw-w64-crt/libarm32/msvcr120d.def.in
+++ b/mingw-w64-crt/libarm32/msvcr120d.def.in
@@ -1825,7 +1825,7 @@ atol
 atoll
 bsearch
 bsearch_s
-btowc
+; btowc ; use replacement
 cabs
 cabsf
 cabsl
@@ -2273,7 +2273,7 @@ wcstoul
 wcstoull
 wcstoumax
 wcsxfrm
-wctob
+; wctob ; use replacement
 wctomb
 wctomb_s
 wctrans
diff --git a/mingw-w64-crt/misc/ucrt_btowc.c b/mingw-w64-crt/misc/ucrt_btowc.c
new file mode 100644
index 000000000..2424f6e90
--- /dev/null
+++ b/mingw-w64-crt/misc/ucrt_btowc.c
@@ -0,0 +1,9 @@
+/**
+ * This file has no copyright assigned and is placed in the Public Domain.
+ * This file is part of the mingw-w64 runtime package.
+ * No warranty is given; refer to the file DISCLAIMER.PD within this package.
+ */
+
+#undef __MSVCRT_VERSION__
+#define _UCRT
+#include "btowc.c"
diff --git a/mingw-w64-crt/misc/ucrt_wctob.c b/mingw-w64-crt/misc/ucrt_wctob.c
new file mode 100644
index 000000000..4e7a1ac9b
--- /dev/null
+++ b/mingw-w64-crt/misc/ucrt_wctob.c
@@ -0,0 +1,9 @@
+/**
+ * This file has no copyright assigned and is placed in the Public Domain.
+ * This file is part of the mingw-w64 runtime package.
+ * No warranty is given; refer to the file DISCLAIMER.PD within this package.
+ */
+
+#undef __MSVCRT_VERSION__
+#define _UCRT
+#include "wctob.c"
-- 
2.50.0.windows.2

From 007b5edd6d3ceab6fdbd0956a30f1ee3b06b32b0 Mon Sep 17 00:00:00 2001
From: Kirill Makurin <maiddais...@outlook.com>
Date: Wed, 9 Jul 2025 17:59:02 +0900
Subject: [PATCH 7/8] crt: add ucrt-specific test cases for btowc and wctob

Signed-off-by: Kirill Makurin <maiddais...@outlook.com>
---
 mingw-w64-crt/testcases/t_btowc.c | 23 +++++++++++++++++++++++
 mingw-w64-crt/testcases/t_wctob.c | 26 ++++++++++++++++++++++++++
 2 files changed, 49 insertions(+)

diff --git a/mingw-w64-crt/testcases/t_btowc.c 
b/mingw-w64-crt/testcases/t_btowc.c
index be35bf6f7..7adcedd08 100644
--- a/mingw-w64-crt/testcases/t_btowc.c
+++ b/mingw-w64-crt/testcases/t_btowc.c
@@ -75,5 +75,28 @@ int main (void) {
     }
   }
 
+#ifdef _UCRT
+  /**
+   * Test UTF-8
+   */
+  if (setlocale (LC_ALL, ".UTF-8") != NULL) {
+    assert (MB_CUR_MAX == 4);
+
+    /**
+     * All bytes in range [0,127] are valid and must convert to themselves
+     */
+    for (int c = 0; c < 0x80; ++c) {
+      assert (btowc (c) == c);
+    }
+
+    /**
+     * All bytes in range [128,255] are invalid
+     */
+    for (int c = 0x80; c < 0x100; ++c) {
+      assert (btowc (c) == WEOF);
+    }
+  }
+#endif
+
   return 0;
 }
diff --git a/mingw-w64-crt/testcases/t_wctob.c 
b/mingw-w64-crt/testcases/t_wctob.c
index 19ebd4ab7..6177223a7 100644
--- a/mingw-w64-crt/testcases/t_wctob.c
+++ b/mingw-w64-crt/testcases/t_wctob.c
@@ -97,5 +97,31 @@ int main (void) {
     }
   }
 
+#ifdef _UCRT
+  /**
+   * Test UTF-8
+   */
+  if (setlocale (LC_ALL, ".UTF-8") != NULL) {
+    assert (MB_CUR_MAX == 4);
+
+    /**
+     * All values in range [0,127] are valid and must convert to themselves
+     */
+    for (wchar_t wc = 0; wc < 0x80; ++wc) {
+      assert (wctob (wc) == wc);
+    }
+
+    /**
+     * All values in range [128,WEOF] are invalid
+     */
+    for (int wc = 0x80;; ++wc) {
+      assert (wctob (wc) == EOF);
+      if (wc == WEOF) {
+        break;
+      }
+    }
+  }
+#endif
+
   return 0;
 }
-- 
2.50.0.windows.2

_______________________________________________
Mingw-w64-public mailing list
Mingw-w64-public@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/mingw-w64-public

Reply via email to