This patch series provides new implementation for C95 conversion functions:

- btowc
- mbrlen
- mbrtowc
- mbsrtowc
- wcrtomb
- wcsrtombs
- wctob

This patch series also adds tests for these functions. All tests except for 
btowc and wctob should be skipped when building for msvcr80.dll or later 
(including UCRT), however currently they will fail since 
`-D__MSVCRT_VERSION__=0x600` is always used when compiling tests. I believe 
Pali mentioned this issue recently, so I assume we're getting it fixed sometime 
soon.

Notable changes:

mb*towc* and wc*tomb* functions do not write to output buffer (if non-null) on 
failure (CRT's versions do). This means that output buffer for mbsrtowcs and 
wcsrtombs will not be null-terminated if conversion fails.

btowc and wctob now call mbrtowc and wcrtomb to perform conversion. Their 
behavior regarding best-fit conversion and handling "C" locale will be 
consistent with mb*towc* and wc*tomb* functions. This also fixes issue with 
sign-extended return value of CRT's `wctob`.

POSIX[1] specifies a possible error condition:

```
[EINVAL]
    [CX] ps points to an object that contains an invalid conversion state.
```

New implementation can fail with this error condition. This error condition 
should never occur if caller initialized `mbstate_t` object properly (e.g. 
`mbstate_t s = {0}`) before calling any function which uses it. Let me know if 
this error condition seems unnecessary for mingw-w64.

----

The return value of `mbrtowc` remains non-conforming for compatibility with CRT.

Also, I did not touch msvcp60.def files. I don't know if import library for 
msvcp60.dll is even used with mingw-w64. If they also need to be changed, I can 
write a follow-up patch.

Both implementation and tests are based on my own code from my project, I 
adopted them to be usable with mingw-w64. I could try rewrite misc/mbrtowc.c 
and misc/wcrtomb.c to preserve old contents for better diffs, but I find linear 
structure of code easier to reason about than nested ifs.

I wish we could get rid of CRT's behavior for "C" locale and do not use lossy 
conversion, but it is tricky to do with UCRT since we need to handle charsets 
like UTF-8 and its mbstate_t is different. Since I will look into it for my 
project anyway, I would like to hear opinion if such changes would be welcome 
in mingw-w64.

If we go that far, we can also fix return value of `mbrtowc`.

- Kirill Makurin

[1] https://pubs.opengroup.org/onlinepubs/9799919799/functions/mbrtowc.html
From c2ea3a177dc5795b5b765705d932b05b901abea4 Mon Sep 17 00:00:00 2001
From: Kirill Makurin <maiddais...@outlook.com>
Date: Mon, 30 Jun 2025 16:46:32 +0900
Subject: [PATCH 1/6] crt: new implementation of 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 | 325 ++++++++++++++++++++++-------------
 mingw-w64-crt/misc/wcrtomb.c | 200 ++++++++++++---------
 2 files changed, 324 insertions(+), 201 deletions(-)

diff --git a/mingw-w64-crt/misc/mbrtowc.c b/mingw-w64-crt/misc/mbrtowc.c
index 3c0115675..dd32eff66 100644
--- a/mingw-w64-crt/misc/mbrtowc.c
+++ b/mingw-w64-crt/misc/mbrtowc.c
@@ -12,148 +12,225 @@
 #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
+    );
+
+    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;
+
+    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..dd498f3cc 100644
--- a/mingw-w64-crt/misc/wcrtomb.c
+++ b/mingw-w64-crt/misc/wcrtomb.c
@@ -13,91 +13,137 @@
 #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 0;
+  }
+
+  /* Detect invalid conversion state */
+  if (state != NULL && *state) {
+    _set_errno (EINVAL);
+    return (size_t) -1;
+  }
+
+  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};
+
+  /* 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;
+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] = {0};
+  /* Total number of bytes stored in `mbs` */
+  size_t mbcConverted = 0;
+
+  /* Next wide character to convert */
+  const wchar_t *wc = *wcs;
+
+  while (1) {
+    size_t length = wcrtomb_cp (mbc, *wc, state, cp, mb_cur_max);
+
+    if (length == (size_t) -1) {
+      if (mbs != NULL) {
+        *wcs = wc;
+      }
+      return (size_t) -1;
+    }
 
-  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;
+    /* 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.46.1.windows.1

From 6e53ae3b910ba08299f7e1fb53a8ec604bfe0041 Mon Sep 17 00:00:00 2001
From: Kirill Makurin <maiddais...@outlook.com>
Date: Mon, 30 Jun 2025 17:07:34 +0900
Subject: [PATCH 2/6] 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    | 147 +++++++
 mingw-w64-crt/testcases/t_mbrtowc.c   | 174 ++++++++
 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, 1472 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..2ac727de1
--- /dev/null
+++ b/mingw-w64-crt/testcases/t_mbrlen.c
@@ -0,0 +1,147 @@
+#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);
+
+  /**
+   * Verify that `state` is usable once again
+   */
+
+  assert (mbrlen ((char *) &Ascii, MB_CUR_MAX, &state) == 1);
+  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..67fa86d70
--- /dev/null
+++ b/mingw-w64-crt/testcases/t_mbrtowc.c
@@ -0,0 +1,174 @@
+#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);
+
+  /**
+   * Verify that conversion state is usable once again
+   */
+  wc = WEOF;
+
+  assert (mbrtowc (&wc, (char *) &Ascii, MB_CUR_MAX, &state) == 1);
+  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);
+
+  /**
+   * 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..ca02a28ab
--- /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) == 0);
+  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) || wc == WEOF) {
+      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 bytes 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) || wc == WEOF) {
+      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 bytes in range [0,127] are valid 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) || wc == WEOF) {
+      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..aebf538dc
--- /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` should 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` should 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` should 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` should 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` should 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` should 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` should 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` should 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` should 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` should not change
+   * - value of `errno` must be EILSEQ
+   * - `state` must be in initial state
+   * - nothing should 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 should 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 should 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.46.1.windows.1

From 04cdf265d5923a3b608e27dc71c7a03f8024cf28 Mon Sep 17 00:00:00 2001
From: Kirill Makurin <maiddais...@outlook.com>
Date: Mon, 30 Jun 2025 18:39:43 +0900
Subject: [PATCH 3/6] crt: msvcrt.dll: always 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.46.1.windows.1

From 17ce281ea190c9ae80339c61b9a32ceefe3bce9f Mon Sep 17 00:00:00 2001
From: Kirill Makurin <maiddais...@outlook.com>
Date: Mon, 30 Jun 2025 19:50:47 +0900
Subject: [PATCH 4/6] crt: new implementation of 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 | 26 +++++++++++++++-----------
 2 files changed, 27 insertions(+), 20 deletions(-)

diff --git a/mingw-w64-crt/misc/btowc.c b/mingw-w64-crt/misc/btowc.c
index c8fbd8e74..fa35fabc7 100644
--- a/mingw-w64-crt/misc/btowc.c
+++ b/mingw-w64-crt/misc/btowc.c
@@ -15,14 +15,17 @@ 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] = {0};
+  str[0] = (unsigned char) c;
+
+  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..d351dabae 100644
--- a/mingw-w64-crt/misc/wctob.c
+++ b/mingw-w64-crt/misc/wctob.c
@@ -13,17 +13,21 @@
 #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 we can */
+  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.46.1.windows.1

From a7f1c898e81602b35e80082c6b32c70778403aa6 Mon Sep 17 00:00:00 2001
From: Kirill Makurin <maiddais...@outlook.com>
Date: Mon, 30 Jun 2025 20:01:13 +0900
Subject: [PATCH 5/6] 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 ++--
 29 files changed, 60 insertions(+), 62 deletions(-)

diff --git a/mingw-w64-crt/Makefile.am b/mingw-w64-crt/Makefile.am
index a393bf772..895a1137b 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 \
@@ -413,8 +415,10 @@ src_ucrtbase=\
   misc/__initenv.c \
   misc/__winitenv.c \
   misc/_onexit.c \
+  misc/btowc.c \
   misc/output_format.c \
   misc/ucrt-access.c \
+  misc/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
-- 
2.46.1.windows.1

From 43b7abc4353a131033873004a579393a52da84bc Mon Sep 17 00:00:00 2001
From: Kirill Makurin <maiddais...@outlook.com>
Date: Mon, 30 Jun 2025 20:07:02 +0900
Subject: [PATCH 6/6] 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 | 102 ++++++++++++++++++++++++
 mingw-w64-crt/testcases/t_wctob.c | 127 ++++++++++++++++++++++++++++++
 3 files changed, 231 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 895a1137b..f9bb294ac 100644
--- a/mingw-w64-crt/Makefile.am
+++ b/mingw-w64-crt/Makefile.am
@@ -4392,6 +4392,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 \
@@ -4431,6 +4432,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..7adcedd08
--- /dev/null
+++ b/mingw-w64-crt/testcases/t_btowc.c
@@ -0,0 +1,102 @@
+#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);
+    }
+  }
+
+#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
new file mode 100644
index 000000000..6177223a7
--- /dev/null
+++ b/mingw-w64-crt/testcases/t_wctob.c
@@ -0,0 +1,127 @@
+#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;
+    }
+  }
+
+#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.46.1.windows.1

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

Reply via email to