Hello,

A few days ago, I sent changes which removed noreturn attribute from `_assert` 
and `_wassert` functions. After that, I was thinking about wrappers we provide, 
and I think that they are really problematic. I think those wrappers were 
initially implemented under assumption that neither `_assert` nor `_wassert` 
can return.

Before I start, I want to mention that recent versions of Windows SDK always 
implement `assert` using `_wassert` and do not even expose `_assert` to the 
user. In contrast, mingw-w64 uses `_wassert` only when `UNICODE` or `_UNICODE` 
macros are defined; by default, it uses `_assert`.

Another thing to keep in mind about both `_[w]assert` functions is that they 
call `abort` to terminate the process. If application has set a signal handler 
for SIGABRT, `abort` will invoke that handler before terminating the process. 
An application can supply SIGABRT handler which calls `longjmp` to continue 
execution from a save point.

In wrapper for `_assert`, we change stderr translation mode to `_O_TEXT`, so 
that CRT `_assert` function can print assertion message using `fprintf` 
function; otherwise, it would produce no output, and in debug versions of CRT, 
it would trigger an internal assertion. In case when CRT `_assert` returns, we 
must restore translation mode used by stderr before the call. However, if 
application escapes from `_assert` through SIGABRT handler, we will not restore 
translation mode on stderr, leaving it in a state which may cause application 
to misbehave.

Another problem here is that restoration can only occur when `_assert` pops up 
a message box (so user can press the "Ignore" button), in which case 
translation mode juggling is not required at all, but we have to do it since we 
switch it to `_O_TEXT` unconditionally. Currently, we cannot call 
`_set_error_mode (_REPORT_ERRMODE)` to switch it conditionally because it is 
not available in crtdll.dll, msvcrt10.dll and msvcrt20.dll.

In wrapper for `_wassert` we use `malloc` to allocate memory to hold converted 
strings with fallback to static buffers. Currently, `_wassert` does not free 
allocated strings. Even if we free allocated memory in case `_assert` returns, 
application still can use SIGABRT handler to escape from `_assert`, in which 
case we will end up with a memory leak.

I propose the following changes to `assert` implementation:

1. Always use `_wassert`, which properly works with Unicode translation modes. 
This function is available in all CRTs which support Unicode translation modes; 
in case of msvcrt.dll, the wrapper already checks whether it is available in 
the DLL, and if available, uses it instead of emulation around `_assert`.

2. Remove wrapper for `_assert`. The purpose for this wrapper was to make 
`assert` usable if application set stderr to a Unicode translation mode. If we 
always use `_wassert`, there is no need for this `_assert` wrapper. Both 
`_[w]assert` functions are not really supposed to be called directly by 
applications, and if they do, they must be aware of this limitation.

3. Improve `_wassert` emulation by making it thread-safe and improving 
conversion of its arguments, which are then passed to `_assert`.

Proposed changes are in attachment. These changes passed CI: 
https://github.com/maiddaisuki/mingw-w64/actions/runs/27751004928.

There are, however, a few specific corner cases: crtdll.dll, msvcrt20.dll and 
msvcrt40.dll. Those CRTs may simply forward to OS msvcrt.dll (crtdll.dll and 
msvcrt20.dll started forwarding to msvcrt.dll in recent Windows 11 updates), 
which may allow an appliation to set a stream to a Unicode translation mode, 
while we keep using `_assert` through `_wassert` emulation.

What we can do is:

A. Ignore. Unicode translation modes are not supported by real, standalone 
versions of crtdll.dll and msvcrt{20,40}.dll.

B. Expose _O_{W,U16,U8}TEXT macros only for CRTs where there are actually 
usable, making it impossible to use them in crtdll.dll and msvcrt{20,40}.dll.

C. Handle it on per-CRT basis, providing a custom check whether msvcrt.dll is 
loaded and contains `_wassert`. This creates a window for errors, because it is 
possible that msvcrt.dll was loaded by another DLL, and its internal state has 
nothing to do with *current* CRT.

I am in favor of B.

- Kirill Makurin
From 25971c0b54976e4831f5bced8eabe3bba489adae Mon Sep 17 00:00:00 2001
From: Kirill Makurin <[email protected]>
Date: Thu, 18 Jun 2026 18:45:33 +0900
Subject: [PATCH 1/3] 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 afc0db0586bc69a2400e9656aad4b87a2911ece2 Mon Sep 17 00:00:00 2001
From: Kirill Makurin <[email protected]>
Date: Thu, 18 Jun 2026 18:45:33 +0900
Subject: [PATCH 2/3] 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 793be1639456576814cc5213d9a241eab665924d Mon Sep 17 00:00:00 2001
From: Kirill Makurin <[email protected]>
Date: Thu, 18 Jun 2026 18:45:33 +0900
Subject: [PATCH 3/3] 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`; function `wcstombs` is
thread-unsafe in its nature and should not be used.

Other than thread-safety, `wcstombs` has other 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 | 115 +++++++++++++++++++++++++++--------
 1 file changed, 90 insertions(+), 25 deletions(-)

diff --git a/mingw-w64-crt/misc/wassert.c b/mingw-w64-crt/misc/wassert.c
index 11b08cfd3..9faaa4ba4 100644
--- a/mingw-w64-crt/misc/wassert.c
+++ b/mingw-w64-crt/misc/wassert.c
@@ -5,37 +5,102 @@
  */
 
 #include <assert.h>
+#include <limits.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`.
+ *
+ * Written string is guaranteed to be NUL-terminated; coverted string will be
+ * truncated if its size exceeds `buffer_size - 1` bytes.
+ */
+static void conv (char *buffer, int buffer_size, const wchar_t *wcs, unsigned 
cp) {
+    /**
+     * 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 length = WideCharToMultiByte (cp, 0, wcs, -1, NULL, 0, NULL, NULL);
+
+    if (length == 0) {
+        return;
+    }
+
+    if (length > buffer_size) {
+        buffer_size -= 1;
+    }
+
+    WideCharToMultiByte (cp, 0, wcs, -1, buffer, buffer_size, NULL, NULL);
+}
 
 /* _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);
-    }
+    /**
+     * 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;
 
-    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);
-    }
+    /**
+     * Estimate maximum buffer size assuming that each wide character converts
+     * to at least 1 byte and at most `MB_LEN_MAX` bytes.
+     */
+    int message_length = wcsnlen (_Message, BUFSIZ) * MB_LEN_MAX;
+    int file_length = wcsnlen (_File, FILENAME_MAX) * MB_LEN_MAX;
+
+    message_length = __min (message_length, BUFSIZ);
+    file_length = __min (file_length, FILENAME_MAX);
+
+    message = _alloca (message_length);
+    file = _alloca (file_length);
+
+    memset (message, 0, message_length);
+    memset (file, 0, file_length);
+
+    /**
+     * _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 ();
+
+    conv (message, message_length, _Message, cp);
+    conv (file, file_length, _File, cp);
 
     _assert(message, file, _Line);
 }
-- 
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