This WideCharToMultiByte() function and its API is insane. I did not
though about these new issues. Now I must admit that this code is too
complicated.

I was thinking how to simplify it, I wanted to write few ideas, but I
have figured out that nothing worked which I tried due to insane API.

Below are just few small comments.

I see that there is mix of style, sometimes there is space between
function name and '('. And sometimes is not there. Would be nice to have
style consistent across whole file.

I think that this one change "crt: improve _wassert() emulation" is fine
and could be merged.

On Monday 22 June 2026 10:13:30 Kirill Makurin wrote:
> 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>

nit: As you are already including windows.h you can use BOOL type and
then no need to include this stdbool.h (one inline file less).

> +#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) {

I would really suggest to check as mbLength <= 0.
Every such custom string conversion function can be a ticking bomb, so
checking that returned values are valid should be always used.

> +            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);

This is very unusual way for specifying const string. Why not just
classic way with? memcpy(buffer, "...", 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;
>      }

There is missing case if the message_length is negative. I would suggest
to use "if (message_length <= 0) {" to handle this edge case.

>  
> -    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
> 


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

Reply via email to