xref: /reactos/sdk/lib/ucrt/startup/assert.cpp (revision 04e0dc4a)
1 /***
2 *assert.c - Display a message and abort
3 *
4 *       Copyright (c) Microsoft Corporation. All rights reserved.
5 *
6 *Purpose:
7 *
8 *******************************************************************************/
9 
10 #include <corecrt_internal.h>
11 #include <corecrt_internal_stdio.h>
12 #include <limits.h>
13 #include <signal.h>
14 #include <stdio.h>
15 #include <string.h>
16 
17 #undef NDEBUG
18 #define _ASSERT_OK
19 #include <assert.h>
20 
21 // Assertion string components:
22 #define MAXLINELEN  64 /* max length for line in message box */
23 #define ASSERTBUFSZ (MAXLINELEN * 9) /* 9 lines in message box */
24 
25 // Format of stderr for assertions:
26 //
27 //      Assertion failed: <expression>, file c:\test\mytest\bar.c, line 69
28 //
29 
30 
31 
32 _GENERATE_TCHAR_STRING_FUNCTIONS(assert_format, "Assertion failed: %Ts, file %Ts, line %d\n")
33 
34 // Enclaves only support assertions sent to the debugger.
35 // This mode could also be enabled for normal apps as well.
36 
37 #ifdef _UCRT_ENCLAVE_BUILD
38 
39 template <typename Character>
common_assert_to_debug(Character const * const expression,Character const * const file_name,unsigned const line_number)40 __declspec(noreturn) static void __cdecl common_assert_to_debug(
41     Character const* const expression,
42     Character const* const file_name,
43     unsigned         const line_number
44     ) throw()
45 {
46     using traits = __crt_char_traits<Character>;
47 
48     Character assert_buffer[ASSERTBUFSZ];
49     if (traits::sntprintf_s(assert_buffer, _countof(assert_buffer), _countof(assert_buffer), get_assert_format(Character()), expression, file_name, line_number) < 0)
50     {
51         abort();
52     }
53     traits::output_debug_string(assert_buffer);
54     abort();
55 }
56 
57 template <typename Character>
common_assert(Character const * const expression,Character const * const file_name,unsigned const line_number,void * const)58 static void __cdecl common_assert(
59     Character const* const expression,
60     Character const* const file_name,
61     unsigned         const line_number,
62     void*            const
63     ) throw()
64 {
65     common_assert_to_debug(expression, file_name, line_number);
66 }
67 
68 #else /* ^^^ _UCRT_ENCLAVE_BUILD ^^^ // vvv !_UCRT_ENCLAVE_BUILD vvv */
69 
70 // Format of MessageBox for assertions:
71 //
72 //      ================= Microsft Visual C++ Debug Library ================
73 //
74 //      Assertion Failed!
75 //
76 //      Program: c:\test\mytest\foo.exe
77 //      File: c:\test\mytest\bar.c
78 //      Line: 69
79 //
80 //      Expression: <expression>
81 //
82 //      For information on how your program can cause an assertion
83 //      failure, see the Visual C++ documentation on asserts
84 //
85 //      (Press Retry to debug the application - JIT must be enabled)
86 //
87 //      ===================================================================
88 
89 
90 
91 _GENERATE_TCHAR_STRING_FUNCTIONS(banner_text, "Microsoft Visual C++ Runtime Library")
92 
93 _GENERATE_TCHAR_STRING_FUNCTIONS(box_intro,        "Assertion failed!")
94 _GENERATE_TCHAR_STRING_FUNCTIONS(program_intro,    "Program: ")
95 _GENERATE_TCHAR_STRING_FUNCTIONS(file_intro,       "File: ")
96 _GENERATE_TCHAR_STRING_FUNCTIONS(line_intro,       "Line: ")
97 _GENERATE_TCHAR_STRING_FUNCTIONS(expression_intro, "Expression: ")
98 _GENERATE_TCHAR_STRING_FUNCTIONS(info_intro,       "For information on how your program can cause an assertion\nfailure, see the Visual C++ documentation on asserts")
99 _GENERATE_TCHAR_STRING_FUNCTIONS(help_intro,       "(Press Retry to debug the application - JIT must be enabled)")
100 
101 _GENERATE_TCHAR_STRING_FUNCTIONS(dot_dot_dot,      "...")
102 _GENERATE_TCHAR_STRING_FUNCTIONS(newline,          "\n")
103 _GENERATE_TCHAR_STRING_FUNCTIONS(double_newline,   "\n\n")
104 
105 _GENERATE_TCHAR_STRING_FUNCTIONS(program_name_unknown_text, "<program name unknown>")
106 
107 /***
108 *_assert() - Display a message and abort
109 *
110 *Purpose:
111 *       The assert macro calls this routine if the assert expression is
112 *       true.  By placing the assert code in a subroutine instead of within
113 *       the body of the macro, programs that call assert multiple times will
114 *       save space.
115 *
116 *Entry:
117 *
118 *Exit:
119 *
120 *Exceptions:
121 *
122 *******************************************************************************/
123 static void __cdecl common_assert_to_stderr_direct(char const*, char const*, unsigned) throw()
124 {
125     // No action for narrow strings
126 }
127 
128 static void __cdecl common_assert_to_stderr_direct(
129     wchar_t const* const expression,
130     wchar_t const* const file_name,
131     unsigned       const line_number
132     ) throw()
133 {
134     HANDLE const stderr_handle = GetStdHandle(STD_ERROR_HANDLE);
135 #pragma warning(suppress:__WARNING_REDUNDANT_POINTER_TEST) // 28922
136     if (stderr_handle == INVALID_HANDLE_VALUE || stderr_handle == nullptr)
137     {
138         return;
139     }
140 
141     if (GetFileType(stderr_handle) != FILE_TYPE_CHAR)
142     {
143         return;
144     }
145 
146     wchar_t assert_buffer[ASSERTBUFSZ];
147 #pragma warning(suppress:__WARNING_BANNED_API_USAGE) // 28719
148     if (swprintf(assert_buffer, _countof(assert_buffer), get_assert_format(wchar_t()), expression, file_name, line_number) < 0)
149     {
150         return;
151     }
152 
153     DWORD const assert_buffer_length = static_cast<DWORD>(wcslen(assert_buffer));
154     DWORD characters_written = 0;
155     if (WriteConsoleW(stderr_handle, assert_buffer, assert_buffer_length, &characters_written, nullptr) == 0)
156     {
157         return;
158     }
159 
160     abort();
161 }
162 
163 template <typename Character>
164 __declspec(noreturn) static void __cdecl common_assert_to_stderr(
165     Character const* const expression,
166     Character const* const file_name,
167     unsigned         const line_number
168     ) throw()
169 {
170     using traits = __crt_char_traits<Character>;
171 
172     // Try to write directly to the console.  This is only supported for wide
173     // character strings.  If we have a narrow character string or the write
174     // fails, we fall back to call through stdio.
175     common_assert_to_stderr_direct(expression, file_name, line_number);
176 
177     // If stderr does not yet have a buffer, set it to use single character
178     // buffering to avoid dynamic allocation of a stream buffer:
179     if (!__crt_stdio_stream(stderr).has_any_buffer())
180     {
181         setvbuf(stderr, nullptr, _IONBF, 0);
182     }
183 
184     traits::ftprintf(stderr, get_assert_format(Character()), expression, file_name, line_number);
185     fflush(stderr);
186     abort();
187 }
188 
189 template <typename Character>
190 static void __cdecl common_assert_to_message_box_build_string(
191     Character*       const assert_buffer,
192     size_t           const assert_buffer_count,
193     Character const* const expression,
194     Character const* const file_name,
195     unsigned         const line_number,
196     void*            const return_address
197     ) throw()
198 {
199     using traits = __crt_char_traits<Character>;
200 
201     // Line 1: Box introduction line:
202     _ERRCHECK(traits::tcscpy_s(assert_buffer, assert_buffer_count, get_box_intro(Character())));
203     _ERRCHECK(traits::tcscat_s(assert_buffer, assert_buffer_count, get_double_newline(Character())));
204 
205     // Line 2: Program line:
206     _ERRCHECK(traits::tcscat_s(assert_buffer, assert_buffer_count, get_program_intro(Character())));
207 
208     Character program_name[_MAX_PATH + 1]{};
209 
210     HMODULE asserting_module = nullptr;
211     if (!GetModuleHandleExW(
212             GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT | GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
213             static_cast<wchar_t const*>(return_address),
214             &asserting_module))
215     {
216         asserting_module = nullptr;
217     }
218 
219     #ifdef CRTDLL
220     // If the assert came from within the CRT DLL, report it as having come
221     // from the EXE instead:
222     if (asserting_module == reinterpret_cast<HMODULE>(&__ImageBase))
223     {
224         asserting_module = nullptr;
225     }
226     #endif
227 
228     if (!traits::get_module_file_name(asserting_module, program_name, static_cast<DWORD>(_countof(program_name))))
229     {
230         _ERRCHECK(traits::tcscpy_s(program_name, _countof(program_name), get_program_name_unknown_text(Character())));
231     }
232 
233     Character* pchProg = program_name;
234     if (program_intro_count + traits::tcslen(program_name) + newline_length > MAXLINELEN)
235     {
236         pchProg += (program_intro_count + traits::tcslen(program_name) + newline_length) - MAXLINELEN;
237         // Only replace first (sizeof(Character) * dot_dot_dot_length) bytes to ellipsis:
238         _ERRCHECK(memcpy_s(
239             pchProg,
240             sizeof(Character) * ((MAX_PATH + 1) - (pchProg - program_name)),
241             get_dot_dot_dot(Character()),
242             sizeof(Character) * dot_dot_dot_length
243             ));
244     }
245 
246     _ERRCHECK(traits::tcscat_s(assert_buffer, assert_buffer_count, pchProg));
247     _ERRCHECK(traits::tcscat_s(assert_buffer, assert_buffer_count, get_newline(Character())));
248 
249     // Line 3:  File line
250     _ERRCHECK(traits::tcscat_s(assert_buffer, assert_buffer_count, get_file_intro(Character())));
251 
252     if (file_intro_count + traits::tcslen(file_name) + newline_length > MAXLINELEN)
253     {
254         size_t const ffn = MAXLINELEN - file_intro_count - newline_length;
255 
256         size_t p   = 0;
257         size_t len = 0;
258         Character const* pch = file_name;
259         for (len = traits::tcslen(file_name), p = 1;
260              pch[len - p] != '\\' && pch[len - p] != '/' && p < len;
261              p++)
262         {
263         }
264 
265         // Trim the path and file name so that they fit, using up to 2/3 of
266         // the maximum number of characters for the path and the remaining
267         // 1/3 for the file name:
268         if ((ffn - ffn / 3) < (len - p) && ffn / 3 > p)
269         {
270             // The path is too long.  Use the first part of the path and the
271             // full file name:
272             _ERRCHECK(traits::tcsncat_s(assert_buffer, assert_buffer_count, pch, ffn - dot_dot_dot_length - p));
273             _ERRCHECK(traits::tcscat_s (assert_buffer, assert_buffer_count, get_dot_dot_dot(Character())));
274             _ERRCHECK(traits::tcscat_s (assert_buffer, assert_buffer_count, pch + len - p));
275         }
276         else if (ffn - ffn / 3 > len - p)
277         {
278             // The file name is too long.  Use the full path and the first
279             // and last part of the file name, with a ... in between:
280             p = p / 2;
281             _ERRCHECK(traits::tcsncat_s(assert_buffer, assert_buffer_count, pch, ffn - dot_dot_dot_length - p));
282             _ERRCHECK(traits::tcscat_s (assert_buffer, assert_buffer_count, get_dot_dot_dot(Character())));
283             _ERRCHECK(traits::tcscat_s (assert_buffer, assert_buffer_count, pch + len - p));
284         }
285         else
286         {
287             // Both are too long.  Use the first part of the path and the
288             // first and last part of the file name, with ...s in between:
289             _ERRCHECK(traits::tcsncat_s(assert_buffer, assert_buffer_count, pch, ffn - ffn / 3 - dot_dot_dot_length));
290             _ERRCHECK(traits::tcscat_s (assert_buffer, assert_buffer_count, get_dot_dot_dot(Character())));
291             _ERRCHECK(traits::tcsncat_s(assert_buffer, assert_buffer_count, pch + len - p, ffn / 6 - 1));
292             _ERRCHECK(traits::tcscat_s (assert_buffer, assert_buffer_count, get_dot_dot_dot(Character())));
293             _ERRCHECK(traits::tcscat_s (assert_buffer, assert_buffer_count, pch + len - (ffn / 3 - ffn / 6 - 2)));
294         }
295     }
296     else
297     {
298         // Plenty of room on the line; just append the full path and file name:
299         _ERRCHECK(traits::tcscat_s(assert_buffer, assert_buffer_count, file_name));
300     }
301 
302     _ERRCHECK(traits::tcscat_s(assert_buffer, assert_buffer_count, get_newline(Character())));
303 
304     // Line 4: Line Number line:
305     _ERRCHECK(traits::tcscat_s(assert_buffer, assert_buffer_count, get_line_intro(Character())));
306     _ERRCHECK(traits::itot_s(
307         line_number,
308         assert_buffer           + traits::tcslen(assert_buffer),
309         assert_buffer_count - traits::tcslen(assert_buffer),
310         10));
311     _ERRCHECK(traits::tcscat_s(assert_buffer, assert_buffer_count, get_double_newline(Character())));
312 
313     // Line 5: Message line:
314     _ERRCHECK(traits::tcscat_s(assert_buffer, assert_buffer_count, get_expression_intro(Character())));
315 
316     size_t const characters_used =
317         traits::tcslen(assert_buffer) +
318         2 * double_newline_length +
319         info_intro_length +
320         help_intro_count;
321 
322     if (characters_used + traits::tcslen(expression) > assert_buffer_count)
323     {
324         size_t const characters_to_write = assert_buffer_count - (characters_used + dot_dot_dot_length);
325         _ERRCHECK(traits::tcsncat_s(
326             assert_buffer,
327             assert_buffer_count,
328             expression,
329             characters_to_write));
330         _ERRCHECK(traits::tcscat_s(assert_buffer, assert_buffer_count, get_dot_dot_dot(Character())));
331     }
332     else
333     {
334         _ERRCHECK(traits::tcscat_s(assert_buffer, assert_buffer_count, expression));
335     }
336 
337     _ERRCHECK(traits::tcscat_s(assert_buffer, assert_buffer_count, get_double_newline(Character())));
338 
339     // Info line:
340     _ERRCHECK(traits::tcscat_s(assert_buffer, assert_buffer_count, get_info_intro(Character())));
341     _ERRCHECK(traits::tcscat_s(assert_buffer, assert_buffer_count, get_double_newline(Character())));
342 
343     // Help line:
344     _ERRCHECK(traits::tcscat_s(assert_buffer, assert_buffer_count, get_help_intro(Character())));
345 }
346 
347 
348 
349 template <typename Character>
350 static void __cdecl common_assert_to_message_box(
351     Character const* const expression,
352     Character const* const file_name,
353     unsigned         const line_number,
354     void*            const return_address
355     ) throw()
356 {
357     using traits = __crt_char_traits<Character>;
358 
359     Character assert_buffer[ASSERTBUFSZ]{};
360     common_assert_to_message_box_build_string(
361         assert_buffer,
362         _countof(assert_buffer),
363         expression,
364         file_name,
365         line_number,
366         return_address);
367 
368     int const action = traits::show_message_box(
369         assert_buffer,
370         get_banner_text(Character()),
371         MB_TASKMODAL | MB_ICONHAND | MB_ABORTRETRYIGNORE | MB_SETFOREGROUND);
372 
373     switch (action)
374     {
375     case IDABORT: // Abort the program:
376     {
377         raise(SIGABRT);
378 
379         // We won't usually get here, but it's possible that a user-registered
380         // abort handler returns, so exit the program immediately.  Note that
381         // even though we are "aborting," we do not call abort() because we do
382         // not want to invoke Watson (the user has already had an opportunity
383         // to debug the error and chose not to).
384         _exit(3);
385     }
386     case IDRETRY: // Break into the debugger then return control to caller
387     {
388         __debugbreak();
389         return;
390     }
391     case IDIGNORE: // Return control to caller
392     {
393         return;
394     }
395     default: // This should not happen; treat as fatal error:
396     {
397         abort();
398     }
399     }
400 }
401 
402 template <typename Character>
403 static void __cdecl common_assert(
404     Character const* const expression,
405     Character const* const file_name,
406     unsigned         const line_number,
407     void*            const return_address
408     ) throw()
409 {
410     using traits = __crt_char_traits<Character>;
411 
412     int const current_error_mode = _set_error_mode(_REPORT_ERRMODE);
413     if (current_error_mode == _OUT_TO_STDERR)
414     {
415         return common_assert_to_stderr(expression, file_name, line_number);
416     }
417 
418     if (current_error_mode == _OUT_TO_DEFAULT && _query_app_type() == _crt_console_app)
419     {
420         return common_assert_to_stderr(expression, file_name, line_number);
421     }
422 
423     return common_assert_to_message_box(expression, file_name, line_number, return_address);
424 }
425 
426 #endif /* _UCRT_ENCLAVE_BUILD */
427 
_assert(char const * const expression,char const * const file_name,unsigned const line_number)428 extern "C" void __cdecl _assert(
429     char const* const expression,
430     char const* const file_name,
431     unsigned    const line_number
432     ) throw()
433 {
434     return common_assert(expression, file_name, line_number, _ReturnAddress());
435 }
436 
_wassert(wchar_t const * const expression,wchar_t const * const file_name,unsigned const line_number)437 extern "C" void __cdecl _wassert(
438     wchar_t const* const expression,
439     wchar_t const* const file_name,
440     unsigned       const line_number
441     )
442 {
443     return common_assert(expression, file_name, line_number, _ReturnAddress());
444 }
445