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

Reply via email to