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