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
