Pali Rohár <[email protected]> wrote:

> On Thursday 18 June 2026 12:36:04 Kirill Makurin wrote:
>> Previous implementaion used `wcstombs` to convert its wide string arguments 
>> to
>> narrow strings, which are then passed to `_assert`; function `wcstombs` is
>> thread-unsafe in its nature and should not be used.
>
> It is really truth that wcstombs is thread-unsafe? I had an impression
> that wcstombs is using its own thread local buffer, and that it is safe
> to use it (I used it many times in this way). So I would like to know
> more information.

I made this assumption because C89 mb* functions (mblen, mbtowc and mbstowcs) 
are not threads-safe. What's interesting is that their _l versions seem to be 
thread-safe, at least based on my tests.

I wrote a test program (attached), and it seems like C89 wc* functions (wctomb 
and wcstombs) are really thread-safe on Windows. Even so, thread-safe code 
should use `wcrtomb` and `wcsrtombs` instead.

I noticed that pre-msvcr80.dll's `w*printf` functions do not handle multibyte 
narrow strings properly.

>> Other than thread-safety, `wcstombs` has other 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.
>
> Case 1. for sure when displaying to GUI MessageBoxA() it is needed to
> use ACP for conversion. But I still think that for fprintf output it
> should be used the CRT locale, not the ACP.

Absolutely; this is where we could use `_set_error_mode` to choose proper 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.
>
> This is also good point. WinAPI WideCharToMultiByte() can solve it. Or
> if we want to stick with CRT (which I think that it is a good idea for
> CRT functions) then loop over wcrtomb() could be used.

This is not an option when we convert to ACP, so I think using 
`WideCharToMultiByte` is the simplest solution.

- Kirill Makurin
#define __USE_MINGW_ANSI_STDIO 0
#define _UNICODE
#define UNICODE

#include <assert.h>
#include <fcntl.h>
#include <io.h>
#include <locale.h>
#include <process.h>
#include <stdio.h>
#include <stdlib.h>
#include <wchar.h>

#define WIN32_LEAN_AND_MEAN
#include <windows.h>

#ifdef _WIN64
#define ZU L"%I64u"
#else
#define ZU L"%lu"
#endif

#ifdef _UCRT
#define SETLOCALE(c, l)             _wsetlocale (c, l)
#define CREATE_LOCALE(c, l)         _wcreate_locale (c, l)
#define LOCALE_STRING(locale, cp)   TEXT (locale) TEXT (".65001")
#define EXPECTED_OUTPUT(input, ...) TEXT (input), input
#define EXPECTED_LENGTH(utf8, acp)  utf8
#else
#define SETLOCALE(c, l)             setlocale (c, l)
#define CREATE_LOCALE(c, l)         _create_locale (c, l)
#define LOCALE_STRING(locale, cp)   locale "." #cp
#define EXPECTED_OUTPUT(input, ...) TEXT (input), (const char[]) {__VA_ARGS__}
#define EXPECTED_LENGTH(utf8, acp)  acp
#endif

typedef struct TestCase {
#ifdef _UCRT
  const wchar_t *Locale;
#else
  const char    *Locale;
#endif
  const wchar_t *In;
  const char    *ExpectedOut;
  size_t         ExpectedOutLength;
} TestCase;

static TestCase TestCase1 = {
  LOCALE_STRING ("English_United States", 1252), 
  EXPECTED_OUTPUT ("Simple English text.", 'S', 'i', 'm', 'p', 'l', 'e', ' ', 
'E', 'n', 'g', 'l', 'i', 's', 'h', ' ', 't', 'e', 'x', 't', '.', '\0'),
  EXPECTED_LENGTH (20, 20)
};

static TestCase TestCase2 = {
  LOCALE_STRING ("Japanese_Japan", 932), 
  EXPECTED_OUTPUT ("日本語。", 0x93, 0xFA, 0x96, 0x7B, 0x8C, 0xEA, 0x81, 
0x42, '\0'),
  EXPECTED_LENGTH (12, 8)
};

#if defined _UCRT || __MSVCRT_VERSION__ >= 0x0800
#define USE_THREAD_LOCALE
#define TEST_CASE_PAIR(test1, test2) test1, test2
#else
#define TEST_CASE_PAIR(test1, test2) test2
#endif

static TestCase *TestCases[] = {
  TEST_CASE_PAIR(&TestCase1, &TestCase2),
  TEST_CASE_PAIR(&TestCase1, &TestCase2),
  TEST_CASE_PAIR(&TestCase1, &TestCase2),
  TEST_CASE_PAIR(&TestCase1, &TestCase2),
  TEST_CASE_PAIR(&TestCase1, &TestCase2),
  TEST_CASE_PAIR(&TestCase1, &TestCase2),
  TEST_CASE_PAIR(&TestCase1, &TestCase2),
  TEST_CASE_PAIR(&TestCase1, &TestCase2),
};

static LONG go = FALSE;

static DWORD __stdcall thread (void *arg) {
  TestCase *test = (TestCase *) arg;

  unsigned exit_code = 0;

#if defined USE_THREAD_LOCALE
  _configthreadlocale (_ENABLE_PER_THREAD_LOCALE);
  assert (SETLOCALE (LC_ALL, test->Locale) != NULL);
#elif defined USE_LOCALE_T
  _locale_t locale = CREATE_LOCALE (LC_ALL, test->Locale);
  assert (locale != NULL);
#endif

  char   buf[BUFSIZ];
  size_t length;

  while (InterlockedCompareExchange (&go, TRUE, TRUE) == FALSE) {
    ; // wait
  }

#if defined USE_LOCALE_T
  length = _wcstombs_l (buf, test->In, BUFSIZ, locale);
#else
  length = wcstombs (buf, test->In, BUFSIZ);
#endif

  if (length != test->ExpectedOutLength) {
    fwprintf (
      stderr, L"Call to wcstombs (buf, %s, BUFSIZ) returned " ZU L" when 
expected " ZU L"\n",
      test->In, (unsigned long long) length, (unsigned long long) 
test->ExpectedOutLength
    );

    exit_code = 1;
  }

  if (memcmp (buf, test->ExpectedOut, test->ExpectedOutLength) == 0) {
#ifdef _UCRT
  wprintf (L"%s: %S\n", test->Locale, buf);
#else
  wprintf (L"%S: %S\n", test->Locale, buf);
#endif
  } else {
#ifdef _UCRT
  wprintf (L"%s: unexpected output\n", test->Locale);
#else
  wprintf (L"%S: unexpected output\n", test->Locale);
#endif
  }

  return exit_code;
}

int main(void) {
  int exit_code = EXIT_SUCCESS;

#if defined _UCRT || __MSVCRT_VERSION__ >= 0x0800
  _setmode (_fileno (stdout), _O_U8TEXT);
  _setmode (_fileno (stderr), _O_U8TEXT);
#endif

#ifdef USE_THREAD_LOCALE
  _wsetlocale (LC_ALL, L"");
#else
  SETLOCALE (LC_ALL, TestCases[0]->Locale);
#endif

  HANDLE Threads[_countof (TestCases)];

  for (size_t i = 0; i < _countof (TestCases); ++i) {
    Threads[i] = CreateThread (NULL, 0, thread, TestCases[i], 0, NULL);
    assert (Threads[i] != NULL);
  }

  go = TRUE;
  WaitForMultipleObjects (_countof (TestCases), Threads, TRUE, INFINITE);

  for (size_t i = 0; i < _countof (TestCases); ++i) {
    DWORD thread_exit_code;

    GetExitCodeThread (Threads[i], &thread_exit_code);

    if (thread_exit_code != 0) {
      exit_code = EXIT_FAILURE;
    }

    CloseHandle (Threads[i]);
  }

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

Reply via email to