Hello,
While working on crt testcases changes, I was playing around with
_set_error_mode, _set_abort_behavior, abort and assert to see how they behave
in practice. I have discovered one inconsistency with MSVC: _[w]assert function
must be declared without noreturn attribute.
I'm attaching a simple program (test.c) that I was using; you can use it to
reproduce the issue: call _set_error_mode with _OUT_TO_MSGBOX and press
"Ignore" when message box pops up.
What should happen is _[w]assert() returns and program continues until it hits
abort(). Instead:
A. when compiled without -D_UNICODE (using _assert() wrapper for CRT's
_assert()), or -O2 and higher, the program crashes.
B. otherwise, when compiled with -D_UNICODE, it prints "Great escape from
assert!" which must not be printed from this code path.
This issue comes from the fact that we declare _[w]assert function with
noreturn attribute, when in fact they can return; this behavior is
documented[1]. Note that Microsoft headers declare _wassert without noreturn
attribute (recent Microsoft headers do not expose _assert at all), so we should
do the same.
Proposed fix is attached.
- Kirill Makurin
[1]
https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/assert-macro-assert-wassert
From 9916f9c8df538eab97b3ec5df32868593ffd2c86 Mon Sep 17 00:00:00 2001
From: Kirill Makurin <[email protected]>
Date: Tue, 16 Jun 2026 10:41:37 +0900
Subject: [PATCH] crt: remove noreturn attribute for _assert and _wassert
Since msvcrt40.dll, applications can call _set_error_mode(_OUT_TO_MSGBOX)
to make assert() and abort() display messages in a message box instead of
printing them to stderr; when doing so, user can press "Ignore" button to
continue after an assertion has failed.
Both _assert and _wassert are declared with noreturn attribure, which would
result in undefined behavior in case when they return to the caller site.
---
mingw-w64-crt/misc/_assert.c | 40 ++++++++++++++++++++++++----------
mingw-w64-crt/misc/wassert.c | 2 --
mingw-w64-headers/crt/assert.h | 5 ++---
3 files changed, 30 insertions(+), 17 deletions(-)
diff --git a/mingw-w64-crt/misc/_assert.c b/mingw-w64-crt/misc/_assert.c
index c0476a29e..c8c854820 100644
--- a/mingw-w64-crt/misc/_assert.c
+++ b/mingw-w64-crt/misc/_assert.c
@@ -5,26 +5,42 @@
*/
#include <assert.h>
-#include <stdio.h>
-#include <io.h>
#include <fcntl.h>
+#include <io.h>
+#include <stdio.h>
+#include <stdlib.h>
/* This is import symbol name for "_assert" from CRT DLL library */
-__MINGW_ATTRIB_NORETURN
extern void (__cdecl *__MINGW_IMP_SYMBOL(__msvcrt_assert))(const char
*message, const char *file, unsigned line);
-__MINGW_ATTRIB_NORETURN
+/* 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)
{
- /* 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. */
- FILE *stream = stderr; /* stderr expands to function call */
+ /* 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);
- _setmode(fileno(stream), _O_TEXT);
+ 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 ();
+ }
}
-__MINGW_ATTRIB_NORETURN
+
void (__cdecl *__MINGW_IMP_SYMBOL(_assert))(const char *message, const char
*file, unsigned line) = _assert;
diff --git a/mingw-w64-crt/misc/wassert.c b/mingw-w64-crt/misc/wassert.c
index 104cbd003..11b08cfd3 100644
--- a/mingw-w64-crt/misc/wassert.c
+++ b/mingw-w64-crt/misc/wassert.c
@@ -8,7 +8,6 @@
#include <stdlib.h>
/* _wassert is not available on XP, so forward it to _assert if needed */
-__MINGW_ATTRIB_NORETURN
static void __cdecl emu__wassert(const wchar_t *_Message, const wchar_t
*_File, unsigned _Line)
{
static char static_message_buf[128]; /* thread unsafe */
@@ -41,7 +40,6 @@ static void __cdecl emu__wassert(const wchar_t *_Message,
const wchar_t *_File,
_assert(message, file, _Line);
}
-#define NORETURN
#define RETT void
#define FUNC _wassert
#define ARGS const wchar_t * message, const wchar_t * file, unsigned line
diff --git a/mingw-w64-headers/crt/assert.h b/mingw-w64-headers/crt/assert.h
index bce9a57cf..51a223a0b 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 __MINGW_ATTRIB_NORETURN _wassert(const wchar_t
*_Message,const wchar_t *_File,unsigned _Line);
-void __cdecl __MINGW_ATTRIB_NORETURN _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
}
@@ -53,4 +53,3 @@ void __cdecl __MINGW_ATTRIB_NORETURN _assert (const char
*_Message, const char *
(_assert(#_Expression,__FILE__,__LINE__),0))
#endif /* _UNICODE||UNICODE */
#endif /* !defined (NDEBUG) */
-
--
2.51.0.windows.1
#define __USE_MINGW_ANSI_STDIO 0
#include <assert.h>
#include <setjmp.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wchar.h>
/* call _assert/_wassert through a pointer */
#ifdef REDIRECT
#ifndef _MSC_VER
void (__cdecl *assert_ptr) (const char *, const char *, unsigned) = _assert;
#define _assert assert_ptr
#endif
#if defined _MSC_VER || defined _UNICODE
void (__cdecl *wassert_ptr) (const wchar_t *, const wchar_t *, unsigned) =
_wassert;
#define _wassert wassert_ptr
#endif
#endif /* REDIRECT */
static jmp_buf escape_route;
static void __cdecl sigabrt (int sig) {
/* CRT resets signal handler to default after signal was fired */
signal (sig, sigabrt);
fprintf (stderr, "In SIGABRT handler...\n");
longjmp (escape_route, 1);
}
int main (int argc, char **argv) {
// freopen ("stdout.log", "w", stdout);
// freopen ("stderr.log", "w", stderr);
#if defined _UCRT || __MSVCRT_VERSION__ >= 0x0400
_set_error_mode (_OUT_TO_DEFAULT);
#endif
#if defined _UCRT || __MSVCRT_VERSION__ >= 0x0800
_set_abort_behavior (_WRITE_ABORT_MSG | _CALL_REPORTFAULT, _WRITE_ABORT_MSG |
_CALL_REPORTFAULT);
#endif
/* abort() calls SIGABRT handler before terminating */
signal (SIGABRT, sigabrt);
if (setjmp (escape_route) == 0) {
/* If no args, trigger an assert */
assert (argc > 1);
} else {
fprintf (stderr, "Great escape from assert!\n");
}
if (setjmp (escape_route) == 0) {
fprintf (stderr, "Time to die...\n");
abort ();
} else {
fprintf (stderr, "Great escape from abort!\n");
}
return 0;
}
_______________________________________________
Mingw-w64-public mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/mingw-w64-public