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