| /** |
| * This file has no copyright assigned and is placed in the Public Domain. |
| * This file is part of the mingw-w64 runtime package. |
| * No warranty is given; refer to the file DISCLAIMER.PD within this package. |
| */ |
| |
| #include <assert.h> |
| #include <limits.h> |
| #include <stdbool.h> |
| #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) { |
| 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); |
| } |
| |
| /* _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) |
| { |
| /** |
| * _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; |
| } |
| |
| 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); |
| } |
| |
| #define RETT void |
| #define FUNC _wassert |
| #define ARGS const wchar_t * message, const wchar_t * file, unsigned line |
| #define CALL message, file, line |
| #include "msvcrt_or_emu_glue.h" |