On Mon, Jun 22, 2026 at 8:32 PM Pali Rohár <[email protected]> wrote:

> 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 don't think it needs to be that complicated.

If you have null terminated source buffer and fixed size destination, you'd
use:

WideCharToMultiByte(CP_ACP, 0, src, -1, dest, dest_size, NULL, NULL);

This will write up to "dest_size" to output, but it will write partial
output too.

If you want to allocate, you'll do:

dest_size = WideCharToMultiByte(CP_ACP, 0, src, -1, NULL, 0, NULL, NULL);
dest = malloc(dest_size);
WideCharToMultiByte(CP_ACP, 0, src, -1, dest, dest_size, NULL, NULL);



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

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

Reply via email to