Hello,

This is updated version of assert() changes.

There are major changes in the third patch which updates _wassert() emulation:

- Remove incorrect reference to `wcstombs` as being thread-unsafe in commit 
message
- Call `WideCharToMultiByte` to calculate buffer size required to hold 
converted strings
- If converted string is being truncated, append "..." to end of the converted 
string

While testing truncation logic, I realized that `WideCharToMultiByte` does not 
store partial output in the buffer, but rather fails with 
`ERROR_INSUFFICIENT_BUFFER`. For this reason, when a string is being truncated, 
we convert wide characters one-by-one. Once we add `_set_error_mode` emulation 
for crtdll.dll and msvcrt{10,20}.dll, this loop can be extended to handle 
special case for "C" locale.

There is also one more patch with a small change for t_assert test. It now 
waits until child process terminates before reading from the pipe, which allows 
us to print and examine assertion message written by the child process. It was 
really helpful in testing truncation logic.

 - Kirill Makurin
From 6adc4d27b7a430bea7ce259b9196d6d7eb5dada2 Mon Sep 17 00:00:00 2001
From: Kirill Makurin <[email protected]>
Date: Mon, 22 Jun 2026 18:59:42 +0900
Subject: [PATCH 1/4] crt: always use _wassert() to implement assert() macro

Both `_assert` and `_wassert` are helper function used to implement `assert`
macro; they can be called directly, but it is highly discouraged.

Using `_assert` has multiple issues:

First, string literals passed to `_assert` are usually encoded using
"execution charset," which is usually UTF-8 with gcc and clang. Those strings
are either printed to `stderr` or displayed in a message box. In former case,
the string will be interpreted using code page used by active CRT locale, in
the latter case, using active ANSI code page. If either string contains
non-ASCII characters, there is a high chance that that string will be displayed
incorrectly (mojibake). Also note that only UCRT supports UTF-8 locales.

Second, since msvcr80.dll, and msvcrt.dll on Vista, it is possible to enable
Unicode translation modes on `stderr`. Doing so, makes `_assert` produce no
output at all; with debug version of CRT, it will trigger an internal assertion.

Using `_wassert` solves both issues. For CRTs where `_wassert` is not available,
we already provide a wrapper which redirects to `_assert`; for msvcrt.dll,
this wrapper also checks whether `_wassert` is available and can be used.

Signed-off-by: Kirill Makurin <[email protected]>
---
 mingw-w64-headers/crt/assert.h | 7 -------
 1 file changed, 7 deletions(-)

diff --git a/mingw-w64-headers/crt/assert.h b/mingw-w64-headers/crt/assert.h
index 51a223a0b..99c127e4f 100644
--- a/mingw-w64-headers/crt/assert.h
+++ b/mingw-w64-headers/crt/assert.h
@@ -41,15 +41,8 @@ void __cdecl _assert (const char *_Message, const char 
*_File, unsigned _Line);
 #ifdef NDEBUG
 #define assert(_Expression) ((void)0)
 #else /* !defined (NDEBUG) */
-#if defined(_UNICODE) || defined(UNICODE)
 #define assert(_Expression) \
  (void) \
  ((!!(_Expression)) || \
   (_wassert(_CRT_WIDE(#_Expression),_CRT_WIDE(__FILE__),__LINE__),0))
-#else /* not unicode */
-#define assert(_Expression) \
- (void) \
- ((!!(_Expression)) || \
-  (_assert(#_Expression,__FILE__,__LINE__),0))
-#endif /* _UNICODE||UNICODE */
 #endif /* !defined (NDEBUG) */
-- 
2.51.0.windows.1

From b6910641a29a9aee674942a8c52825cf1ba3a59e Mon Sep 17 00:00:00 2001
From: Kirill Makurin <[email protected]>
Date: Mon, 22 Jun 2026 18:59:42 +0900
Subject: [PATCH 2/4] crt: remove mingw-w64 wrapper for _assert()

Now that we always use `_wassert` to implement `assert` macro, there is no need
to provide wrapper for `_assert`.

The purpose of `_assert` wrapper was to make `assert` usable when an Unicode
translation mode was enabled on stderr. At the time when this wrapper was added,
it was asumed that `_assert` does not return, when in fact it can.

It is important to keep in mind that after displaying the message, `_assert`
terminates the process by calling `abort`; it is possible to escape from `abort`
by setting signal handler for SIGABRT which calls `longjmp`.

If an application were to perform such escape, previous translation mode used on
stderr will not be restored and it will be left in `_O_TEXT` translation mode.

`_assert` is also called from mingw-w64 wrapper for `_wassert`, which allocates
memory using `malloc`; such escape from `_assert` would result in a memory leak.

To avoid issues descrived above, remove wrapper for `_assert`. Any application
that calls `_assert` directly must take its limitations into account.

This change effectively reverts b2c3c92b542df57ed8bfcd798928692e94a35308.

Signed-off-by: Kirill Makurin <[email protected]>
---
 mingw-w64-crt/Makefile.am                     |  1 -
 .../api-ms-win-crt-runtime-l1-1-0.def.in      |  2 +-
 mingw-w64-crt/lib-common/msvcr120_app.def.in  |  2 +-
 mingw-w64-crt/lib-common/msvcrt.def.in        |  2 +-
 .../lib-common/ucrtbase-common.def.in         |  2 +-
 mingw-w64-crt/lib32/crtdll.def.in             |  2 +-
 mingw-w64-crt/lib32/msvcr100.def.in           |  2 +-
 mingw-w64-crt/lib32/msvcr100d.def.in          |  2 +-
 mingw-w64-crt/lib32/msvcr110.def.in           |  2 +-
 mingw-w64-crt/lib32/msvcr110d.def.in          |  2 +-
 mingw-w64-crt/lib32/msvcr120.def.in           |  2 +-
 mingw-w64-crt/lib32/msvcr120d.def.in          |  2 +-
 mingw-w64-crt/lib32/msvcr40d.def.in           |  2 +-
 mingw-w64-crt/lib32/msvcr70.def.in            |  2 +-
 mingw-w64-crt/lib32/msvcr70d.def.in           |  2 +-
 mingw-w64-crt/lib32/msvcr71.def.in            |  2 +-
 mingw-w64-crt/lib32/msvcr71d.def.in           |  2 +-
 mingw-w64-crt/lib32/msvcr80.def.in            |  2 +-
 mingw-w64-crt/lib32/msvcr80d.def.in           |  2 +-
 mingw-w64-crt/lib32/msvcr90.def.in            |  2 +-
 mingw-w64-crt/lib32/msvcr90d.def.in           |  2 +-
 mingw-w64-crt/lib32/msvcrt10.def.in           |  2 +-
 mingw-w64-crt/lib32/msvcrt20.def.in           |  2 +-
 mingw-w64-crt/lib32/msvcrt40.def.in           |  2 +-
 mingw-w64-crt/lib32/msvcrtd.def.in            |  2 +-
 mingw-w64-crt/lib64/msvcr100.def.in           |  2 +-
 mingw-w64-crt/lib64/msvcr100d.def.in          |  2 +-
 mingw-w64-crt/lib64/msvcr110.def.in           |  2 +-
 mingw-w64-crt/lib64/msvcr110d.def.in          |  2 +-
 mingw-w64-crt/lib64/msvcr120.def.in           |  2 +-
 mingw-w64-crt/lib64/msvcr120d.def.in          |  2 +-
 mingw-w64-crt/lib64/msvcr80.def.in            |  2 +-
 mingw-w64-crt/lib64/msvcr80d.def.in           |  2 +-
 mingw-w64-crt/lib64/msvcr90.def.in            |  2 +-
 mingw-w64-crt/lib64/msvcr90d.def.in           |  2 +-
 mingw-w64-crt/libarm32/msvcr110.def.in        |  2 +-
 mingw-w64-crt/libarm32/msvcr110d.def.in       |  2 +-
 mingw-w64-crt/libarm32/msvcr120.def.in        |  2 +-
 mingw-w64-crt/libarm32/msvcr120d.def.in       |  2 +-
 mingw-w64-crt/misc/_assert.c                  | 46 -------------------
 mingw-w64-headers/crt/assert.h                |  2 +-
 41 files changed, 39 insertions(+), 86 deletions(-)
 delete mode 100644 mingw-w64-crt/misc/_assert.c

diff --git a/mingw-w64-crt/Makefile.am b/mingw-w64-crt/Makefile.am
index b651dce59..b122f84ce 100644
--- a/mingw-w64-crt/Makefile.am
+++ b/mingw-w64-crt/Makefile.am
@@ -1268,7 +1268,6 @@ src_libmingwex=\
   math/powi.def.h       math/sqrt.def.h    \
   math/cephes_mconf.h   math/fp_consts.h   math/pi_const.h \
   \
-  misc/_assert.c \
   misc/mingw_longjmp.S \
   misc/mingw_getsp.S \
   misc/alarm.c \
diff --git a/mingw-w64-crt/lib-common/api-ms-win-crt-runtime-l1-1-0.def.in 
b/mingw-w64-crt/lib-common/api-ms-win-crt-runtime-l1-1-0.def.in
index ef0aa03ba..79a7ac15c 100644
--- a/mingw-w64-crt/lib-common/api-ms-win-crt-runtime-l1-1-0.def.in
+++ b/mingw-w64-crt/lib-common/api-ms-win-crt-runtime-l1-1-0.def.in
@@ -24,7 +24,7 @@ __threadid
 __wcserror
 __wcserror_s
 ; DATA set manually
-__msvcrt_assert DATA == _assert ; mingw-w64 provides _assert() function as 
wrapper around renamed __msvcrt_assert symbol
+_assert
 _beginthread
 _beginthreadex
 _c_exit
diff --git a/mingw-w64-crt/lib-common/msvcr120_app.def.in 
b/mingw-w64-crt/lib-common/msvcr120_app.def.in
index 0963eea67..06260eee3 100644
--- a/mingw-w64-crt/lib-common/msvcr120_app.def.in
+++ b/mingw-w64-crt/lib-common/msvcr120_app.def.in
@@ -1179,7 +1179,7 @@ _aligned_offset_recalloc
 _aligned_realloc
 _aligned_recalloc
 _amsg_exit
-__msvcrt_assert DATA == _assert ; mingw-w64 provides _assert() function as 
wrapper around renamed __msvcrt_assert symbol
+_assert
 _atodbl
 _atodbl_l
 _atof_l
diff --git a/mingw-w64-crt/lib-common/msvcrt.def.in 
b/mingw-w64-crt/lib-common/msvcrt.def.in
index 790915920..39ece04fe 100644
--- a/mingw-w64-crt/lib-common/msvcrt.def.in
+++ b/mingw-w64-crt/lib-common/msvcrt.def.in
@@ -531,7 +531,7 @@ F_I386(_adj_fptan)
 F_I386(_adjust_fdiv DATA)
 _aexit_rtn DATA
 _amsg_exit
-__msvcrt_assert DATA == _assert ; mingw-w64 provides _assert() function as 
wrapper around renamed __msvcrt_assert symbol
+_assert
 _atodbl
 _atoi64
 _atoldbl
diff --git a/mingw-w64-crt/lib-common/ucrtbase-common.def.in 
b/mingw-w64-crt/lib-common/ucrtbase-common.def.in
index 377ceef2f..38c505072 100644
--- a/mingw-w64-crt/lib-common/ucrtbase-common.def.in
+++ b/mingw-w64-crt/lib-common/ucrtbase-common.def.in
@@ -246,7 +246,7 @@ _aligned_realloc
 F_DEBUG(_aligned_realloc_dbg)
 _aligned_recalloc
 F_DEBUG(_aligned_recalloc_dbg)
-__msvcrt_assert DATA == _assert ; mingw-w64 provides _assert() function as 
wrapper around renamed __msvcrt_assert symbol
+_assert
 _atodbl
 _atodbl_l
 _atof_l
diff --git a/mingw-w64-crt/lib32/crtdll.def.in 
b/mingw-w64-crt/lib32/crtdll.def.in
index 99ededa69..154d7add2 100644
--- a/mingw-w64-crt/lib32/crtdll.def.in
+++ b/mingw-w64-crt/lib32/crtdll.def.in
@@ -110,7 +110,7 @@ _acmdln DATA == _acmdln_dll
 _aexit_rtn_dll DATA
 _aexit_rtn DATA == _aexit_rtn_dll
 _amsg_exit
-__msvcrt_assert DATA == _assert ; mingw-w64 provides _assert() function as 
wrapper around renamed __msvcrt_assert symbol
+_assert
 _basemajor_dll DATA
 _baseminor_dll DATA
 _baseversion_dll DATA
diff --git a/mingw-w64-crt/lib32/msvcr100.def.in 
b/mingw-w64-crt/lib32/msvcr100.def.in
index 50d31699a..1db3e0720 100644
--- a/mingw-w64-crt/lib32/msvcr100.def.in
+++ b/mingw-w64-crt/lib32/msvcr100.def.in
@@ -730,7 +730,7 @@ _aligned_offset_recalloc
 _aligned_realloc
 _aligned_recalloc
 _amsg_exit
-__msvcrt_assert DATA == _assert ; mingw-w64 provides _assert() function as 
wrapper around renamed __msvcrt_assert symbol
+_assert
 _atodbl
 _atodbl_l
 _atof_l
diff --git a/mingw-w64-crt/lib32/msvcr100d.def.in 
b/mingw-w64-crt/lib32/msvcr100d.def.in
index ee5717d07..9899fe057 100644
--- a/mingw-w64-crt/lib32/msvcr100d.def.in
+++ b/mingw-w64-crt/lib32/msvcr100d.def.in
@@ -777,7 +777,7 @@ _aligned_realloc_dbg
 _aligned_recalloc
 _aligned_recalloc_dbg
 _amsg_exit
-__msvcrt_assert DATA == _assert ; mingw-w64 provides _assert() function as 
wrapper around renamed __msvcrt_assert symbol
+_assert
 _atodbl
 _atodbl_l
 _atof_l
diff --git a/mingw-w64-crt/lib32/msvcr110.def.in 
b/mingw-w64-crt/lib32/msvcr110.def.in
index 09a7e68bf..1a4db8110 100644
--- a/mingw-w64-crt/lib32/msvcr110.def.in
+++ b/mingw-w64-crt/lib32/msvcr110.def.in
@@ -854,7 +854,7 @@ _aligned_offset_recalloc
 _aligned_realloc
 _aligned_recalloc
 _amsg_exit
-__msvcrt_assert DATA == _assert ; mingw-w64 provides _assert() function as 
wrapper around renamed __msvcrt_assert symbol
+_assert
 _atodbl
 _atodbl_l
 _atof_l
diff --git a/mingw-w64-crt/lib32/msvcr110d.def.in 
b/mingw-w64-crt/lib32/msvcr110d.def.in
index 3448d40e7..0b5269046 100644
--- a/mingw-w64-crt/lib32/msvcr110d.def.in
+++ b/mingw-w64-crt/lib32/msvcr110d.def.in
@@ -901,7 +901,7 @@ _aligned_realloc_dbg
 _aligned_recalloc
 _aligned_recalloc_dbg
 _amsg_exit
-__msvcrt_assert DATA == _assert ; mingw-w64 provides _assert() function as 
wrapper around renamed __msvcrt_assert symbol
+_assert
 _atodbl
 _atodbl_l
 _atof_l
diff --git a/mingw-w64-crt/lib32/msvcr120.def.in 
b/mingw-w64-crt/lib32/msvcr120.def.in
index 25933f156..af88bbbd5 100644
--- a/mingw-w64-crt/lib32/msvcr120.def.in
+++ b/mingw-w64-crt/lib32/msvcr120.def.in
@@ -865,7 +865,7 @@ _aligned_offset_recalloc
 _aligned_realloc
 _aligned_recalloc
 _amsg_exit
-__msvcrt_assert DATA == _assert ; mingw-w64 provides _assert() function as 
wrapper around renamed __msvcrt_assert symbol
+_assert
 _atodbl
 _atodbl_l
 _atof_l
diff --git a/mingw-w64-crt/lib32/msvcr120d.def.in 
b/mingw-w64-crt/lib32/msvcr120d.def.in
index e362dcd87..b8d4c9bb1 100644
--- a/mingw-w64-crt/lib32/msvcr120d.def.in
+++ b/mingw-w64-crt/lib32/msvcr120d.def.in
@@ -912,7 +912,7 @@ _aligned_realloc_dbg
 _aligned_recalloc
 _aligned_recalloc_dbg
 _amsg_exit
-__msvcrt_assert DATA == _assert ; mingw-w64 provides _assert() function as 
wrapper around renamed __msvcrt_assert symbol
+_assert
 _atodbl
 _atodbl_l
 _atof_l
diff --git a/mingw-w64-crt/lib32/msvcr40d.def.in 
b/mingw-w64-crt/lib32/msvcr40d.def.in
index 5f5f75409..aced5b3c5 100644
--- a/mingw-w64-crt/lib32/msvcr40d.def.in
+++ b/mingw-w64-crt/lib32/msvcr40d.def.in
@@ -1081,7 +1081,7 @@ _adj_fprem1
 _adj_fptan
 _adjust_fdiv DATA
 _amsg_exit
-__msvcrt_assert DATA == _assert ; mingw-w64 provides _assert() function as 
wrapper around renamed __msvcrt_assert symbol
+_assert
 _atodbl
 _atoldbl
 _beep
diff --git a/mingw-w64-crt/lib32/msvcr70.def.in 
b/mingw-w64-crt/lib32/msvcr70.def.in
index 29a8aa95e..53e2bfe2d 100644
--- a/mingw-w64-crt/lib32/msvcr70.def.in
+++ b/mingw-w64-crt/lib32/msvcr70.def.in
@@ -258,7 +258,7 @@ _aligned_offset_malloc
 _aligned_offset_realloc
 _aligned_realloc
 _amsg_exit
-__msvcrt_assert DATA == _assert ; mingw-w64 provides _assert() function as 
wrapper around renamed __msvcrt_assert symbol
+_assert
 _atodbl
 _atoi64
 _atoldbl
diff --git a/mingw-w64-crt/lib32/msvcr70d.def.in 
b/mingw-w64-crt/lib32/msvcr70d.def.in
index 3bbf5c542..7784d76f2 100644
--- a/mingw-w64-crt/lib32/msvcr70d.def.in
+++ b/mingw-w64-crt/lib32/msvcr70d.def.in
@@ -292,7 +292,7 @@ _aligned_offset_realloc_dbg
 _aligned_realloc
 _aligned_realloc_dbg
 _amsg_exit
-__msvcrt_assert DATA == _assert ; mingw-w64 provides _assert() function as 
wrapper around renamed __msvcrt_assert symbol
+_assert
 _atodbl
 _atoi64
 _atoldbl
diff --git a/mingw-w64-crt/lib32/msvcr71.def.in 
b/mingw-w64-crt/lib32/msvcr71.def.in
index 6248aa604..aa33cd3d0 100644
--- a/mingw-w64-crt/lib32/msvcr71.def.in
+++ b/mingw-w64-crt/lib32/msvcr71.def.in
@@ -251,7 +251,7 @@ _aligned_offset_malloc
 _aligned_offset_realloc
 _aligned_realloc
 _amsg_exit
-__msvcrt_assert DATA == _assert ; mingw-w64 provides _assert() function as 
wrapper around renamed __msvcrt_assert symbol
+_assert
 _atodbl
 _atoi64
 _atoldbl
diff --git a/mingw-w64-crt/lib32/msvcr71d.def.in 
b/mingw-w64-crt/lib32/msvcr71d.def.in
index 71fee41ac..a317d1ef2 100644
--- a/mingw-w64-crt/lib32/msvcr71d.def.in
+++ b/mingw-w64-crt/lib32/msvcr71d.def.in
@@ -285,7 +285,7 @@ _aligned_offset_realloc_dbg
 _aligned_realloc
 _aligned_realloc_dbg
 _amsg_exit
-__msvcrt_assert DATA == _assert ; mingw-w64 provides _assert() function as 
wrapper around renamed __msvcrt_assert symbol
+_assert
 _atodbl
 _atoi64
 _atoldbl
diff --git a/mingw-w64-crt/lib32/msvcr80.def.in 
b/mingw-w64-crt/lib32/msvcr80.def.in
index d84908cc5..7d579983e 100644
--- a/mingw-w64-crt/lib32/msvcr80.def.in
+++ b/mingw-w64-crt/lib32/msvcr80.def.in
@@ -355,7 +355,7 @@ _aligned_offset_malloc
 _aligned_offset_realloc
 _aligned_realloc
 _amsg_exit
-__msvcrt_assert DATA == _assert ; mingw-w64 provides _assert() function as 
wrapper around renamed __msvcrt_assert symbol
+_assert
 _atodbl
 _atodbl_l
 _atof_l
diff --git a/mingw-w64-crt/lib32/msvcr80d.def.in 
b/mingw-w64-crt/lib32/msvcr80d.def.in
index 30302a197..d4deb170d 100644
--- a/mingw-w64-crt/lib32/msvcr80d.def.in
+++ b/mingw-w64-crt/lib32/msvcr80d.def.in
@@ -416,7 +416,7 @@ _aligned_realloc_dbg
 _aligned_recalloc
 _aligned_recalloc_dbg
 _amsg_exit
-__msvcrt_assert DATA == _assert ; mingw-w64 provides _assert() function as 
wrapper around renamed __msvcrt_assert symbol
+_assert
 _atodbl
 _atodbl_l
 _atof_l
diff --git a/mingw-w64-crt/lib32/msvcr90.def.in 
b/mingw-w64-crt/lib32/msvcr90.def.in
index 5c3d6a48c..a6f575212 100644
--- a/mingw-w64-crt/lib32/msvcr90.def.in
+++ b/mingw-w64-crt/lib32/msvcr90.def.in
@@ -355,7 +355,7 @@ _aligned_offset_recalloc
 _aligned_realloc
 _aligned_recalloc
 _amsg_exit
-__msvcrt_assert DATA == _assert ; mingw-w64 provides _assert() function as 
wrapper around renamed __msvcrt_assert symbol
+_assert
 _atodbl
 _atodbl_l
 _atof_l
diff --git a/mingw-w64-crt/lib32/msvcr90d.def.in 
b/mingw-w64-crt/lib32/msvcr90d.def.in
index 294cb57f8..eb4f68ec0 100644
--- a/mingw-w64-crt/lib32/msvcr90d.def.in
+++ b/mingw-w64-crt/lib32/msvcr90d.def.in
@@ -406,7 +406,7 @@ _aligned_realloc_dbg
 _aligned_recalloc
 _aligned_recalloc_dbg
 _amsg_exit
-__msvcrt_assert DATA == _assert ; mingw-w64 provides _assert() function as 
wrapper around renamed __msvcrt_assert symbol
+_assert
 _atodbl
 _atodbl_l
 _atof_l
diff --git a/mingw-w64-crt/lib32/msvcrt10.def.in 
b/mingw-w64-crt/lib32/msvcrt10.def.in
index ab6773406..8a7d40842 100644
--- a/mingw-w64-crt/lib32/msvcrt10.def.in
+++ b/mingw-w64-crt/lib32/msvcrt10.def.in
@@ -902,7 +902,7 @@ _access
 _acmdln DATA
 _aexit_rtn DATA
 _amsg_exit
-__msvcrt_assert DATA == _assert ; mingw-w64 provides _assert() function as 
wrapper around renamed __msvcrt_assert symbol
+_assert
 __msvcrt_beginthread == _beginthread ; rename _beginthread as it does not 
handle SSE floating point exceptions, real _beginthread provided by emu
 _c_exit
 _cabs
diff --git a/mingw-w64-crt/lib32/msvcrt20.def.in 
b/mingw-w64-crt/lib32/msvcrt20.def.in
index 0b13fc424..8a87561df 100644
--- a/mingw-w64-crt/lib32/msvcrt20.def.in
+++ b/mingw-w64-crt/lib32/msvcrt20.def.in
@@ -937,7 +937,7 @@ __msvcrt20_wgetmainargs == __wgetmainargs ; msvcrt20.dll's 
__wgetmainargs is inc
 _abnormal_termination
 _access
 _amsg_exit
-__msvcrt_assert DATA == _assert ; mingw-w64 provides _assert() function as 
wrapper around renamed __msvcrt_assert symbol
+_assert
 _atodbl
 _atoldbl
 _beep
diff --git a/mingw-w64-crt/lib32/msvcrt40.def.in 
b/mingw-w64-crt/lib32/msvcrt40.def.in
index 95654f5f8..4c698c153 100644
--- a/mingw-w64-crt/lib32/msvcrt40.def.in
+++ b/mingw-w64-crt/lib32/msvcrt40.def.in
@@ -1055,7 +1055,7 @@ _adj_fprem1
 _adj_fptan
 _adjust_fdiv DATA
 _amsg_exit
-__msvcrt_assert DATA == _assert ; mingw-w64 provides _assert() function as 
wrapper around renamed __msvcrt_assert symbol
+_assert
 _atodbl
 _atoldbl
 _beep
diff --git a/mingw-w64-crt/lib32/msvcrtd.def.in 
b/mingw-w64-crt/lib32/msvcrtd.def.in
index f7d5dce9f..ea0c17c3c 100644
--- a/mingw-w64-crt/lib32/msvcrtd.def.in
+++ b/mingw-w64-crt/lib32/msvcrtd.def.in
@@ -237,7 +237,7 @@ _adj_fptan
 _adjust_fdiv DATA
 _aexit_rtn DATA
 _amsg_exit
-__msvcrt_assert DATA == _assert ; mingw-w64 provides _assert() function as 
wrapper around renamed __msvcrt_assert symbol
+_assert
 _atodbl
 _atoi64
 _atoldbl
diff --git a/mingw-w64-crt/lib64/msvcr100.def.in 
b/mingw-w64-crt/lib64/msvcr100.def.in
index 7e70caa86..2e7991c6d 100644
--- a/mingw-w64-crt/lib64/msvcr100.def.in
+++ b/mingw-w64-crt/lib64/msvcr100.def.in
@@ -687,7 +687,7 @@ _aligned_offset_recalloc
 _aligned_realloc
 _aligned_recalloc
 _amsg_exit
-__msvcrt_assert DATA == _assert ; mingw-w64 provides _assert() function as 
wrapper around renamed __msvcrt_assert symbol
+_assert
 _atodbl
 _atodbl_l
 _atof_l
diff --git a/mingw-w64-crt/lib64/msvcr100d.def.in 
b/mingw-w64-crt/lib64/msvcr100d.def.in
index 65ed8c2ca..26c568dc4 100644
--- a/mingw-w64-crt/lib64/msvcr100d.def.in
+++ b/mingw-w64-crt/lib64/msvcr100d.def.in
@@ -732,7 +732,7 @@ _aligned_realloc_dbg
 _aligned_recalloc
 _aligned_recalloc_dbg
 _amsg_exit
-__msvcrt_assert DATA == _assert ; mingw-w64 provides _assert() function as 
wrapper around renamed __msvcrt_assert symbol
+_assert
 _atodbl
 _atodbl_l
 _atof_l
diff --git a/mingw-w64-crt/lib64/msvcr110.def.in 
b/mingw-w64-crt/lib64/msvcr110.def.in
index aea6fabe6..83362bce9 100644
--- a/mingw-w64-crt/lib64/msvcr110.def.in
+++ b/mingw-w64-crt/lib64/msvcr110.def.in
@@ -813,7 +813,7 @@ _aligned_offset_recalloc
 _aligned_realloc
 _aligned_recalloc
 _amsg_exit
-__msvcrt_assert DATA == _assert ; mingw-w64 provides _assert() function as 
wrapper around renamed __msvcrt_assert symbol
+_assert
 _atodbl
 _atodbl_l
 _atof_l
diff --git a/mingw-w64-crt/lib64/msvcr110d.def.in 
b/mingw-w64-crt/lib64/msvcr110d.def.in
index ee51330b8..621768782 100644
--- a/mingw-w64-crt/lib64/msvcr110d.def.in
+++ b/mingw-w64-crt/lib64/msvcr110d.def.in
@@ -858,7 +858,7 @@ _aligned_realloc_dbg
 _aligned_recalloc
 _aligned_recalloc_dbg
 _amsg_exit
-__msvcrt_assert DATA == _assert ; mingw-w64 provides _assert() function as 
wrapper around renamed __msvcrt_assert symbol
+_assert
 _atodbl
 _atodbl_l
 _atof_l
diff --git a/mingw-w64-crt/lib64/msvcr120.def.in 
b/mingw-w64-crt/lib64/msvcr120.def.in
index 6442bb755..9095011ac 100644
--- a/mingw-w64-crt/lib64/msvcr120.def.in
+++ b/mingw-w64-crt/lib64/msvcr120.def.in
@@ -825,7 +825,7 @@ _aligned_offset_recalloc
 _aligned_realloc
 _aligned_recalloc
 _amsg_exit
-__msvcrt_assert DATA == _assert ; mingw-w64 provides _assert() function as 
wrapper around renamed __msvcrt_assert symbol
+_assert
 _atodbl
 _atodbl_l
 _atof_l
diff --git a/mingw-w64-crt/lib64/msvcr120d.def.in 
b/mingw-w64-crt/lib64/msvcr120d.def.in
index 51156e5d7..31c325805 100644
--- a/mingw-w64-crt/lib64/msvcr120d.def.in
+++ b/mingw-w64-crt/lib64/msvcr120d.def.in
@@ -870,7 +870,7 @@ _aligned_realloc_dbg
 _aligned_recalloc
 _aligned_recalloc_dbg
 _amsg_exit
-__msvcrt_assert DATA == _assert ; mingw-w64 provides _assert() function as 
wrapper around renamed __msvcrt_assert symbol
+_assert
 _atodbl
 _atodbl_l
 _atof_l
diff --git a/mingw-w64-crt/lib64/msvcr80.def.in 
b/mingw-w64-crt/lib64/msvcr80.def.in
index db1710d34..f81f10e29 100644
--- a/mingw-w64-crt/lib64/msvcr80.def.in
+++ b/mingw-w64-crt/lib64/msvcr80.def.in
@@ -298,7 +298,7 @@ _aligned_offset_malloc
 _aligned_offset_realloc
 _aligned_realloc
 _amsg_exit
-__msvcrt_assert DATA == _assert ; mingw-w64 provides _assert() function as 
wrapper around renamed __msvcrt_assert symbol
+_assert
 _atodbl
 _atodbl_l
 _atof_l
diff --git a/mingw-w64-crt/lib64/msvcr80d.def.in 
b/mingw-w64-crt/lib64/msvcr80d.def.in
index a28217c94..3fbac9478 100644
--- a/mingw-w64-crt/lib64/msvcr80d.def.in
+++ b/mingw-w64-crt/lib64/msvcr80d.def.in
@@ -353,7 +353,7 @@ _aligned_realloc_dbg
 _aligned_recalloc
 _aligned_recalloc_dbg
 _amsg_exit
-__msvcrt_assert DATA == _assert ; mingw-w64 provides _assert() function as 
wrapper around renamed __msvcrt_assert symbol
+_assert
 _atodbl
 _atodbl_l
 _atof_l
diff --git a/mingw-w64-crt/lib64/msvcr90.def.in 
b/mingw-w64-crt/lib64/msvcr90.def.in
index 1bc30455d..3efea2608 100644
--- a/mingw-w64-crt/lib64/msvcr90.def.in
+++ b/mingw-w64-crt/lib64/msvcr90.def.in
@@ -302,7 +302,7 @@ _aligned_offset_recalloc
 _aligned_realloc
 _aligned_recalloc
 _amsg_exit
-__msvcrt_assert DATA == _assert ; mingw-w64 provides _assert() function as 
wrapper around renamed __msvcrt_assert symbol
+_assert
 _atodbl
 _atodbl_l
 _atof_l
diff --git a/mingw-w64-crt/lib64/msvcr90d.def.in 
b/mingw-w64-crt/lib64/msvcr90d.def.in
index 864e0e10d..c95a741a8 100644
--- a/mingw-w64-crt/lib64/msvcr90d.def.in
+++ b/mingw-w64-crt/lib64/msvcr90d.def.in
@@ -347,7 +347,7 @@ _aligned_realloc_dbg
 _aligned_recalloc
 _aligned_recalloc_dbg
 _amsg_exit
-__msvcrt_assert DATA == _assert ; mingw-w64 provides _assert() function as 
wrapper around renamed __msvcrt_assert symbol
+_assert
 _atodbl
 _atodbl_l
 _atof_l
diff --git a/mingw-w64-crt/libarm32/msvcr110.def.in 
b/mingw-w64-crt/libarm32/msvcr110.def.in
index d70854050..3c067e002 100644
--- a/mingw-w64-crt/libarm32/msvcr110.def.in
+++ b/mingw-w64-crt/libarm32/msvcr110.def.in
@@ -807,7 +807,7 @@ _aligned_offset_recalloc
 _aligned_realloc
 _aligned_recalloc
 _amsg_exit
-__msvcrt_assert DATA == _assert ; mingw-w64 provides _assert() function as 
wrapper around renamed __msvcrt_assert symbol
+_assert
 _atodbl
 _atodbl_l
 _atof_l
diff --git a/mingw-w64-crt/libarm32/msvcr110d.def.in 
b/mingw-w64-crt/libarm32/msvcr110d.def.in
index 80a3e3029..7164dbb88 100644
--- a/mingw-w64-crt/libarm32/msvcr110d.def.in
+++ b/mingw-w64-crt/libarm32/msvcr110d.def.in
@@ -852,7 +852,7 @@ _aligned_realloc_dbg
 _aligned_recalloc
 _aligned_recalloc_dbg
 _amsg_exit
-__msvcrt_assert DATA == _assert ; mingw-w64 provides _assert() function as 
wrapper around renamed __msvcrt_assert symbol
+_assert
 _atodbl
 _atodbl_l
 _atof_l
diff --git a/mingw-w64-crt/libarm32/msvcr120.def.in 
b/mingw-w64-crt/libarm32/msvcr120.def.in
index 3a2f9267e..8e3b65f26 100644
--- a/mingw-w64-crt/libarm32/msvcr120.def.in
+++ b/mingw-w64-crt/libarm32/msvcr120.def.in
@@ -799,7 +799,7 @@ _aligned_offset_recalloc
 _aligned_realloc
 _aligned_recalloc
 _amsg_exit
-__msvcrt_assert DATA == _assert ; mingw-w64 provides _assert() function as 
wrapper around renamed __msvcrt_assert symbol
+_assert
 _atodbl
 _atodbl_l
 _atof_l
diff --git a/mingw-w64-crt/libarm32/msvcr120d.def.in 
b/mingw-w64-crt/libarm32/msvcr120d.def.in
index 4380106fa..ef278913e 100644
--- a/mingw-w64-crt/libarm32/msvcr120d.def.in
+++ b/mingw-w64-crt/libarm32/msvcr120d.def.in
@@ -844,7 +844,7 @@ _aligned_realloc_dbg
 _aligned_recalloc
 _aligned_recalloc_dbg
 _amsg_exit
-__msvcrt_assert DATA == _assert ; mingw-w64 provides _assert() function as 
wrapper around renamed __msvcrt_assert symbol
+_assert
 _atodbl
 _atodbl_l
 _atof_l
diff --git a/mingw-w64-crt/misc/_assert.c b/mingw-w64-crt/misc/_assert.c
deleted file mode 100644
index c8c854820..000000000
--- a/mingw-w64-crt/misc/_assert.c
+++ /dev/null
@@ -1,46 +0,0 @@
-/**
- * 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.
- */
-
-#include <assert.h>
-#include <fcntl.h>
-#include <io.h>
-#include <stdio.h>
-#include <stdlib.h>
-
-/* This is import symbol name for "_assert" from CRT DLL library */
-extern void (__cdecl *__MINGW_IMP_SYMBOL(__msvcrt_assert))(const char 
*message, const char *file, unsigned line);
-
-/* Turn off _O_WTEXT, _O_U16TEXT or _O_U8TEXT mode on stderr stream
- * by changing mode to _O_TEXT, because fprintf (called by __msvcrt_assert)
- * does not work (and does nothing) on FILE* stream in some of those modes.
- * Only fwprintf works with those modes, but _assert uses fprintf.
- * Before changing the FILE* stream mode, it is required to flush buffers. */
-void __cdecl _assert(const char *message, const char *file, unsigned line)
-{
-  /* stderr expands to function call */
-  FILE *stream = stderr;
-  /* Cache fd used by `stderr` */
-  int fd = _fileno (stream);
-  /* We need to restore previous mode in case `_assert` returns; it can happen
-   * if program has called _set_error_mode(_OUT_TO_MSGBOX) and user pressed
-   * "Ignore" button in popped up message box. */
-  int oldmode;
-
-  /* Change `stderr` mode to `_O_TEXT` */
-  fflush(stream);
-  oldmode = _setmode(fd, _O_TEXT);
-
-  /* Call CRT `_assert` */
-  __MINGW_IMP_SYMBOL(__msvcrt_assert)(message, file, line);
-
-  /* Restore `stderr` mode to `oldmode` */
-  fflush (stream);
-  if (_setmode (fd, oldmode) != _O_TEXT) {
-    abort ();
-  }
-}
-
-void (__cdecl *__MINGW_IMP_SYMBOL(_assert))(const char *message, const char 
*file, unsigned line) = _assert;
diff --git a/mingw-w64-headers/crt/assert.h b/mingw-w64-headers/crt/assert.h
index 99c127e4f..ad522f667 100644
--- a/mingw-w64-headers/crt/assert.h
+++ b/mingw-w64-headers/crt/assert.h
@@ -21,8 +21,8 @@
 extern "C" {
 #endif
 
+_CRTIMP void __cdecl _assert(const char *_Message,const char *_File,unsigned 
_Line);
 _CRTIMP void __cdecl _wassert(const wchar_t *_Message,const wchar_t 
*_File,unsigned _Line);
-void __cdecl _assert (const char *_Message, const char *_File, unsigned _Line);
 
 #ifdef __cplusplus
 }
-- 
2.51.0.windows.1

From 17cc1d34a1b3dea9b11f1d4f62b3fd35f9fb3553 Mon Sep 17 00:00:00 2001
From: Kirill Makurin <[email protected]>
Date: Mon, 22 Jun 2026 18:59:43 +0900
Subject: [PATCH 3/4] crt: improve _wassert() emulation

Previous implementaion used `malloc` to allocate memory for converted string,
falling back to static buffer if memory allocation fails. This approach was
reasonable assuming `_wassert` cannot return and always terminates the process.

There are two cases when `_wassert` does not terminate the process:

A. Application sets signal handler for SIGABRT which calls `longjmp`;
  `abort` calls this handler, which gives application an opportunity to escape.

B. Application calls `_set_error_mode (_OUT_TO_MSGBOX)` and user presses
  "Ingore" button in popped up message box; in this case `_wassert` returns
  normally.

Case A makes it impossible to free allocated memory, and so dynamic memory
allocation in `_wassert` must be avoided. Using fallback to static buffer makes
`_wassert` thread-unsafe, which may be a serious issue in case B.

Instead, use `_alloca` to allocate buffers on stack; add an arbitrary size
limit for buffers to minimize possibility of stack overflow.

Previous implementaion used `wcstombs` to convert its wide string arguments to
narrow strings, which are then passed to `_assert`. Using `wcstombs` has a few
issues:

1. It uses code page used by active CRT locale; this may be an issue when assert
  message is displayed in message box, which assumes string to be encoded using
  active ANSI code page.

2. In case when entire string cannot be converted to target code page, it does
  not allow to convert only part of the string.

To make `_wassert` more reliable on older systems, use `WideCharToMultiByte` to
perform conversion; it allows to replace characters which cannot be converted
with '?', while preserving the rest of information.

Signed-off-by: Kirill Makurin <[email protected]>
---
 mingw-w64-crt/misc/wassert.c | 201 +++++++++++++++++++++++++++++++----
 1 file changed, 178 insertions(+), 23 deletions(-)

diff --git a/mingw-w64-crt/misc/wassert.c b/mingw-w64-crt/misc/wassert.c
index 11b08cfd3..26ff7a0a9 100644
--- a/mingw-w64-crt/misc/wassert.c
+++ b/mingw-w64-crt/misc/wassert.c
@@ -5,38 +5,193 @@
  */
 
 #include <assert.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stdio.h>
 #include <stdlib.h>
+#include <string.h>
+#include <wchar.h>
+
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+
+/**
+ * Convert wide character string `wcs` to code page `cp`.
+ *
+ * The converted string is written to `buffer` with size `buffer_size`.
+ *
+ * If `buffer_size` is less than buffer size required to store converted `wcs`,
+ * set `truncated` to `true`; in this case, this function will convert as much
+ * wide characters in `wcs` as fits into `buffer`.
+ */
+static void conv (char *buffer, int buffer_size, const wchar_t *wcs, unsigned 
cp, bool truncated) {
+    if (!truncated) {
+        /**
+         * Calling `WideCharToMultiByte` with zero flags allows best-fit
+         * conversion and makes it replace characters which cannot be converted
+         * to `cp` with '?'.
+         *
+         * This allows us to preserve and display as much information as 
possible.
+         */
+        int written = WideCharToMultiByte (cp, 0, wcs, -1, buffer, 
buffer_size, NULL, NULL);
+
+        if (written == 0) {
+            buffer[0] = '\0';
+        }
+
+        return;
+    }
+
+    /**
+     * `WideCharToMultiByte` fails with `ERROR_INSUFFICIENT_BUFFER` when called
+     * with insufficient buffer size. We have to convert wide characters in
+     * `wcs` one-by-one.
+     */
+
+    /**
+     * If `wcs` is being truncated, reserve space in `buffer` to add "...".
+     */
+    buffer_size -= 4;
+
+    /**
+     * Total number of bytes written to `buffer`.
+     */
+    int written = 0;
+
+    while (1) {
+        if (wcs[0] == L'\0') {
+            break;
+        }
+
+        /**
+         * Buffer to store next converted character from `wcs`.
+         */
+        char buf[MB_LEN_MAX] = {'?', '\0', '\0', '\0', '\0'};
+
+        int wcLength = 1;
+        int mbLength = 1;
+
+        if (IS_LOW_SURROGATE (wcs[0])) {
+            goto invalid_unicode;
+        }
+
+        if (IS_HIGH_SURROGATE (wcs[0])) {
+            if (!IS_LOW_SURROGATE (wcs[1])) {
+                goto invalid_unicode;
+            }
+
+            wcLength = 2;
+        }
+
+        mbLength = WideCharToMultiByte (cp, 0, wcs, wcLength, buf, MB_LEN_MAX, 
NULL, NULL);
+
+        if (mbLength == 0) {
+            break;
+        }
+
+        if (written + mbLength > buffer_size) {
+            break;
+        }
+
+invalid_unicode:
+        memcpy (buffer, buf, mbLength);
+
+        buffer += mbLength;
+        written += mbLength;
+        wcs += wcLength;
+
+        if (written == buffer_size) {
+            break;
+        }
+    }
+
+    /**
+     * Append "..." to the end of `buffer`.
+     */
+    memcpy (buffer, (const char[]) {'.', '.', '.', '\0'}, 4);
+}
 
 /* _wassert is not available on XP, so forward it to _assert if needed */
 static void __cdecl emu__wassert(const wchar_t *_Message, const wchar_t 
*_File, unsigned _Line)
 {
-    static char static_message_buf[128]; /* thread unsafe */
-    static char static_file_buf[_MAX_PATH]; /* thread unsafe */
-    char *message = NULL, *file = NULL;
-    size_t len;
-
-    if ((len = wcstombs(NULL, _Message, 0)) != (size_t)-1)
-    {
-        message = malloc(len + 1);
-        if (!message)
-        {
-            message = static_message_buf;
-            len = sizeof(static_message_buf) - 2; /* -1 to not touch the last 
nul byte */
-        }
-        wcstombs(message, _Message, len + 1);
+    /**
+     * _assert() prints `_Message` to stderr or displays a message box,
+     * depending on `_set_error_mode` setting.
+     *
+     * When printing message to stderr, we want to convert `_Message` to
+     * code page used by active CRT locale.
+     *
+     * When displaying a message box, we want to convert `_Message` to active
+     * ANSI code page; most ANSI APIs interpret strings using that code page.
+     *
+     * Perfectly, we would call `_set_error_mode (_REPORT_ERRMODE)` to query
+     * current setting and decide which code page to use; we cannot do this
+     * because `_set_error_mode` is not available in crtdll.dll, msvcrt10.dll
+     * and msvcrt20.dll.
+     *
+     * Use active ANSI code page as the conservative option.
+     */
+    unsigned cp = GetACP ();
+
+    /**
+     * After _assert() displays the message, it calls abort() to terminate
+     * the process, which makes it possible to escape from _assert() by
+     * setting signal handler for SIGABRT which calls longjmp().
+     *
+     * As the result, we should avoid dynamic memory allocations to avoid
+     * possible memory leaks.
+     *
+     * Also, since msvcrt40.dll, _assert() can return if application has
+     * called `_set_error_mode (_OUT_TO_MSGBOX)` and user pressed "Ignore"
+     * button in the popped up message box.
+     *
+     * As the result, we should avoid using static buffer to avoid race
+     * condition when multiple threads call _wassert() at the same time.
+     */
+    char *message = NULL;
+    char *file = NULL;
+
+    /**
+     * We use `_alloca` to allocate buffers to hold converted `_Message` and
+     * `_File`. In order to reduce possibility of stack overflow, we limit
+     * allocation size for each buffer.
+     *
+     * In case when buffer size is not enough to store full converted string,
+     * we will truncate the string.
+     */
+    bool truncate_message = false;
+    bool truncate_file = false;
+
+    /**
+     * Call `WideCharToMultiByte` to calculate buffer size required to hold
+     * converted `_Message` and `_File`.
+     *
+     * On failure, it returns zero; we must be careful not to allocate
+     * zero-length buffer.
+     */
+    int message_length = WideCharToMultiByte (cp, 0, _Message, -1, NULL, 0, 
NULL, NULL);
+    int file_length = WideCharToMultiByte (cp, 0, _File, -1, NULL, 0, NULL, 
NULL);
+
+    if (message_length == 0) {
+        message_length = 1;
+    } else if (message_length > BUFSIZ) {
+        truncate_message = true;
+        message_length = BUFSIZ;
     }
 
-    if ((len = wcstombs(NULL, _File, 0)) != (size_t)-1)
-    {
-        file = malloc(len + 1);
-        if (!file)
-        {
-            file = static_file_buf;
-            len = sizeof(static_file_buf) - 2; /* -1 to not touch the last nul 
byte */
-        }
-        wcstombs(file, _File, len + 1);
+    if (file_length == 0) {
+        file_length = 1;
+    } else if (file_length > FILENAME_MAX) {
+        truncate_file = true;
+        file_length = FILENAME_MAX;
     }
 
+    message = _alloca (message_length);
+    file = _alloca (file_length);
+
+    conv (message, message_length, _Message, cp, truncate_message);
+    conv (file, file_length, _File, cp, truncate_file);
+
     _assert(message, file, _Line);
 }
 
-- 
2.51.0.windows.1

From 3fb6aa08f1cdc6a62c4a553391220c6740c0cf69 Mon Sep 17 00:00:00 2001
From: Kirill Makurin <[email protected]>
Date: Mon, 22 Jun 2026 18:59:43 +0900
Subject: [PATCH 4/4] crt: tests: update t_assert

Wait until the child process termintaes before reading from the pipe;
this allows to read complete output produces by the child process.

Print data read from the pipe to stdout; this allows to examine assertion
message printed by the child process.

Signed-off-by: Kirill Makurin <[email protected]>
---
 mingw-w64-crt/testcases/t_assert.c | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

diff --git a/mingw-w64-crt/testcases/t_assert.c 
b/mingw-w64-crt/testcases/t_assert.c
index e66c406e8..6b603ad35 100644
--- a/mingw-w64-crt/testcases/t_assert.c
+++ b/mingw-w64-crt/testcases/t_assert.c
@@ -35,14 +35,15 @@ int main(int argc, char *argv[]) {
 
         assert(process != -1);
 
+        /* wait until child process exits */
+        assert(_cwait(&exit_code, process, _WAIT_CHILD) == process);
+        assert(exit_code != 0);
+
         size = read(pipefd[0], buf, sizeof(buf));
         close(pipefd[0]);
         assert(size > 0); /* some data were written by child process */
         assert(strnlen(buf, sizeof(buf)) > 0);
-
-        /* wait until child process exits */
-        assert(_cwait(&exit_code, process, _WAIT_CHILD) == process);
-        assert(exit_code != 0);
+        write (STDOUT_FILENO, buf, size);
 
         return 0;
     }
-- 
2.51.0.windows.1

_______________________________________________
Mingw-w64-public mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/mingw-w64-public

Reply via email to