xref: /reactos/sdk/lib/ucrt/misc/dbgrptt.cpp (revision e98e9000)
1 /***
2 *dbgrptt.c - Debug CRT Reporting Functions
3 *
4 *       Copyright (c) Microsoft Corporation. All rights reserved.
5 *
6 *Purpose:
7 *
8 *******************************************************************************/
9 
10 #ifndef _DEBUG
11     #error This file is supported only in debug builds
12     #define _DEBUG // For design-time support, when editing/viewing CRT sources
13 #endif
14 
15 #include <corecrt_internal.h>
16 #include <errno.h>
17 #include <malloc.h>
18 #include <minmax.h>
19 #include <stdio.h>
20 #include <stdlib.h>
21 
22 
23 
24 extern "C" int __cdecl __acrt_MessageWindowA(
25     int         report_type,
26     void*       return_address,
27     char const* file_name,
28     char const* line_number,
29     char const* module_name,
30     char const* user_message
31     );
32 
33 extern "C" int __cdecl __acrt_MessageWindowW(
34     int            report_type,
35     void*          return_address,
36     wchar_t const* file_name,
37     wchar_t const* line_number,
38     wchar_t const* module_name,
39     wchar_t const* user_message
40     );
41 
42 extern "C" {
43 
44 _CRT_REPORT_HOOK _pfnReportHook;
45 
46 __crt_report_hook_node<char> *_pReportHookList;
47 __crt_report_hook_node<wchar_t> *_pReportHookListW;
48 
49 long _crtAssertBusy = -1;
50 
51 // Enclaves only support MODE_DEBUG for error output
52 #ifdef _UCRT_ENCLAVE_BUILD
53 
54 int const _CrtDbgMode[_CRT_ERRCNT]
55 {
56     _CRTDBG_MODE_DEBUG,
57     _CRTDBG_MODE_DEBUG,
58     _CRTDBG_MODE_DEBUG
59 };
60 
61 _HFILE const _CrtDbgFile[_CRT_ERRCNT]
62 {
63     _CRTDBG_INVALID_HFILE,
64     _CRTDBG_INVALID_HFILE,
65     _CRTDBG_INVALID_HFILE
66 };
67 
68 #else
69 
70 int _CrtDbgMode[_CRT_ERRCNT]
71 {
72     _CRTDBG_MODE_DEBUG,
73     _CRTDBG_MODE_WNDW,
74     _CRTDBG_MODE_WNDW
75 };
76 
77 _HFILE _CrtDbgFile[_CRT_ERRCNT]
78 {
79     _CRTDBG_INVALID_HFILE,
80     _CRTDBG_INVALID_HFILE,
81     _CRTDBG_INVALID_HFILE
82 };
83 
84 /***
85 *int _CrtSetReportMode - set the reporting mode for a given report type
86 *
87 *Purpose:
88 *       set the reporting mode for a given report type
89 *
90 *Entry:
91 *       int nRptType    - the report type
92 *       int fMode       - new mode for given report type
93 *
94 *Exit:
95 *       previous mode for given report type
96 *
97 *Exceptions:
98 *       Input parameters are validated. Refer to the validation section of the function.
99 *
100 *******************************************************************************/
_CrtSetReportMode(int nRptType,int fMode)101 int __cdecl _CrtSetReportMode(
102     int nRptType,
103     int fMode
104     )
105 {
106     int oldMode;
107 
108     /* validation section */
109     _VALIDATE_RETURN(nRptType >= 0 && nRptType < _CRT_ERRCNT, EINVAL, -1);
110     _VALIDATE_RETURN(
111         fMode == _CRTDBG_REPORT_MODE ||
112         (fMode & ~(_CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG | _CRTDBG_MODE_WNDW)) == 0,
113         EINVAL,
114         -1);
115 
116     if (fMode == _CRTDBG_REPORT_MODE)
117         return _CrtDbgMode[nRptType];
118 
119     oldMode = _CrtDbgMode[nRptType];
120 
121     _CrtDbgMode[nRptType] = fMode;
122 
123     return oldMode;
124 }
125 
126 /***
127 *int _CrtSetReportFile - set the reporting file for a given report type
128 *
129 *Purpose:
130 *       set the reporting file for a given report type
131 *
132 *Entry:
133 *       int nRptType    - the report type
134 *       _HFILE hFile    - new file for given report type
135 *
136 *Exit:
137 *       previous file for given report type
138 *
139 *Exceptions:
140 *       Input parameters are validated. Refer to the validation section of the function.
141 *
142 *******************************************************************************/
_CrtSetReportFile(int nRptType,_HFILE hFile)143 _HFILE __cdecl _CrtSetReportFile(
144     int nRptType,
145     _HFILE hFile
146     )
147 {
148     _HFILE oldFile;
149 
150     /* validation section */
151     _VALIDATE_RETURN(nRptType >= 0 && nRptType < _CRT_ERRCNT, EINVAL, _CRTDBG_HFILE_ERROR);
152 
153     if (hFile == _CRTDBG_REPORT_FILE)
154         return _CrtDbgFile[nRptType];
155 
156     oldFile = _CrtDbgFile[nRptType];
157 
158     if (_CRTDBG_FILE_STDOUT == hFile)
159         _CrtDbgFile[nRptType] = GetStdHandle(STD_OUTPUT_HANDLE);
160     else if (_CRTDBG_FILE_STDERR == hFile)
161         _CrtDbgFile[nRptType] = GetStdHandle(STD_ERROR_HANDLE);
162     else
163         _CrtDbgFile[nRptType] = hFile;
164 
165     return oldFile;
166 }
167 
168 #endif /* _UCRT_ENCLAVE_BUILD */
169 
170 /***
171 *_CRT_REPORT_HOOK _CrtSetReportHook() - set client report hook
172 *
173 *Purpose:
174 *       set client report hook. This function is provided only in ANSI
175 *       for backward compatibility. No Unicode Version of this function exists
176 *
177 *Entry:
178 *       _CRT_REPORT_HOOK pfnNewHook - new report hook
179 *
180 *Exit:
181 *       return previous hook
182 *
183 *Exceptions:
184 *
185 *******************************************************************************/
186 
_CrtSetReportHook(_CRT_REPORT_HOOK pfnNewHook)187 _CRT_REPORT_HOOK __cdecl _CrtSetReportHook(_CRT_REPORT_HOOK pfnNewHook)
188 {
189     _CRT_REPORT_HOOK pfnOldHook = _pfnReportHook;
190     _pfnReportHook = pfnNewHook;
191     return pfnOldHook;
192 }
193 
194 /***
195 *_CRT_REPORT_HOOK _CrtGetReportHook() - get client report hook
196 *
197 *Purpose:
198 *       get client report hook.
199 *
200 *Entry:
201 *
202 *Exit:
203 *       return current hook
204 *
205 *Exceptions:
206 *
207 *******************************************************************************/
208 
_CrtGetReportHook(void)209 _CRT_REPORT_HOOK __cdecl _CrtGetReportHook(void)
210 {
211     return _pfnReportHook;
212 }
213 
214 #define ASSERTINTRO1 "Assertion failed: "
215 #define ASSERTINTRO2 "Assertion failed!"
216 
217 /***
218 *int _VCrtDbgReportA() - _CrtDbgReport calls into this function
219 *
220 *Purpose:
221 *       See remarks for _CrtDbgReport.
222 *
223 *Entry:
224 *       int             nRptType    - report type
225 *       char const*    szFile      - file name
226 *       int             nLine       - line number
227 *       char const*    szModule    - module name
228 *       char const*    szFormat    - format string
229 *       va_list         arglist      - var args arglist
230 *
231 *Exit:
232 *       See remarks for _CrtDbgReport
233 *
234 *Exceptions:
235 *
236 *******************************************************************************/
237 
238 #pragma warning(push)
239 #pragma warning(disable:6262)
240 // prefast (6262): This func uses lots of stack because we want to tolerate very large reports, and we can't use malloc here.
_VCrtDbgReportA(int nRptType,void * returnAddress,char const * szFile,int nLine,char const * szModule,char const * szFormat,va_list arglist)241 int __cdecl _VCrtDbgReportA(
242     int nRptType,
243     void * returnAddress,
244     char const* szFile,
245     int nLine,
246     char const* szModule,
247     char const* szFormat,
248     va_list arglist
249     )
250 {
251     int retval=0;
252     int handled=FALSE;
253 
254     char    szLineMessage[DBGRPT_MAX_MSG]{0};
255     char    szOutMessage [DBGRPT_MAX_MSG]{0};
256     wchar_t szOutMessage2[DBGRPT_MAX_MSG]{0};
257     char    szUserMessage[DBGRPT_MAX_MSG]{0};
258 
259     if (nRptType < 0 || nRptType >= _CRT_ERRCNT)
260         return -1;
261 
262     /*
263      * handle the (hopefully rare) case of
264      *
265      * 1) ASSERT while already dealing with an ASSERT
266      *      or
267      * 2) two threads asserting at the same time
268      */
269 
270     __try
271     {
272         if (_CRT_ASSERT == nRptType && _InterlockedIncrement(&_crtAssertBusy) > 0)
273         {
274             /* use only 'safe' functions -- must not assert in here! */
275 
276             _ERRCHECK(_itoa_s(nLine, szLineMessage, DBGRPT_MAX_MSG, 10));
277 
278             __acrt_OutputDebugStringA("Second Chance Assertion Failed: File ");
279             __acrt_OutputDebugStringA(szFile ? szFile : "<file unknown>");
280             __acrt_OutputDebugStringA(", Line ");
281             __acrt_OutputDebugStringA(szLineMessage);
282             __acrt_OutputDebugStringA("\n");
283 
284             _CrtDbgBreak();
285             retval=-1;
286             __leave;
287         }
288 
289         // Leave space for ASSERTINTRO1 and "\r\n"
290         if (szFormat)
291         {
292             int szlen = 0;
293             _ERRCHECK_SPRINTF(szlen = _vsnprintf_s(szUserMessage, DBGRPT_MAX_MSG,
294                                                     DBGRPT_MAX_MSG - 2- max(sizeof(ASSERTINTRO1),sizeof(ASSERTINTRO2)),
295                                                     szFormat, arglist));
296             if (szlen < 0)
297             {
298                 _ERRCHECK(strcpy_s(szUserMessage, DBGRPT_MAX_MSG, DBGRPT_TOOLONGMSG));
299             }
300         }
301 
302         if (_CRT_ASSERT == nRptType)
303         {
304             _ERRCHECK(strcpy_s(szLineMessage, DBGRPT_MAX_MSG, szFormat ? ASSERTINTRO1 : ASSERTINTRO2));
305         }
306 
307         _ERRCHECK(strcat_s(szLineMessage, DBGRPT_MAX_MSG, szUserMessage));
308 
309         if (_CRT_ASSERT == nRptType)
310         {
311             if (_CrtDbgMode[nRptType] & _CRTDBG_MODE_FILE)
312             {
313                 _ERRCHECK(strcat_s(szLineMessage, DBGRPT_MAX_MSG, "\r"));
314             }
315 
316             _ERRCHECK(strcat_s(szLineMessage, DBGRPT_MAX_MSG, "\n"));
317         }
318 
319         if (szFile)
320         {
321             int szlen = 0;
322             _ERRCHECK_SPRINTF(szlen = _snprintf_s(szOutMessage, DBGRPT_MAX_MSG, DBGRPT_MAX_MSG - 1, "%s(%d) : %s",
323                                                     szFile, nLine, szLineMessage));
324             if (szlen < 0)
325             {
326                 _ERRCHECK(strcpy_s(szOutMessage, DBGRPT_MAX_MSG, DBGRPT_TOOLONGMSG));
327             }
328         }
329         else
330         {
331             _ERRCHECK(strcpy_s(szOutMessage, DBGRPT_MAX_MSG, szLineMessage));
332         }
333 
334         {
335             size_t ret = 0;
336             errno_t e = 0;
337             _ERRCHECK_EINVAL_ERANGE(e = mbstowcs_s(&ret, szOutMessage2, DBGRPT_MAX_MSG, szOutMessage, _TRUNCATE));
338             if(e != 0)
339             {
340                 _ERRCHECK(wcscpy_s(szOutMessage2, DBGRPT_MAX_MSG, _CRT_WIDE(DBGRPT_INVALIDMSG)));
341             }
342         }
343 
344         /* User hook may handle report.
345             We have to check the ANSI Hook2 List & then the UNICODE Hook2 List.
346             Then we have check any ANSI individual Hook set through
347             SetReportHook */
348 
349         if (_pReportHookList || _pReportHookListW)
350         {
351             __crt_report_hook_node<char> *pnode=nullptr;
352             __crt_report_hook_node<wchar_t> *pnodeW=nullptr;
353 
354             __acrt_lock(__acrt_debug_lock);
355             __try
356             {
357                 for (pnode = _pReportHookList; pnode; pnode = pnode->next)
358                 {
359                     int hook_retval=0;
360                     if (pnode->hook(nRptType, szOutMessage, &hook_retval))
361                     {
362                         handled=TRUE;
363                         retval=hook_retval;
364                         __leave;
365                     }
366                 }
367 
368                 for (pnodeW = _pReportHookListW; pnodeW; pnodeW = pnodeW->next)
369                 {
370                     int hook_retval=0;
371                     if (pnodeW->hook(nRptType, szOutMessage2, &hook_retval))
372                     {
373                         handled=TRUE;
374                         retval=hook_retval;
375                         __leave;
376                     }
377                 }
378             }
379             __finally
380             {
381                 __acrt_unlock(__acrt_debug_lock);
382             }
383             __endtry
384         }
385 
386         if (handled)
387             __leave;
388 
389         if (_pfnReportHook)
390         {
391             int hook_retval=0;
392             if (_pfnReportHook(nRptType, szOutMessage, &hook_retval))
393             {
394                 retval = hook_retval;
395                 __leave;
396             }
397         }
398 
399         if (_CrtDbgMode[nRptType] & _CRTDBG_MODE_FILE)
400         {
401             if (_CrtDbgFile[nRptType] != _CRTDBG_INVALID_HFILE)
402             {
403                 DWORD bytes_written = 0;
404                 WriteFile(_CrtDbgFile[nRptType], szOutMessage, static_cast<DWORD>(strlen(szOutMessage)), &bytes_written, nullptr);
405             }
406         }
407 
408         if (_CrtDbgMode[nRptType] & _CRTDBG_MODE_DEBUG)
409         {
410             __acrt_OutputDebugStringA(szOutMessage);
411         }
412 
413         if (_CrtDbgMode[nRptType] & _CRTDBG_MODE_WNDW)
414         {
415             szLineMessage[0] = 0;
416             if (nLine)
417             {
418                 _ERRCHECK(_itoa_s(nLine, szLineMessage, DBGRPT_MAX_MSG, 10));
419             }
420 
421             retval = __acrt_MessageWindowA(nRptType, returnAddress, szFile, (nLine ? szLineMessage : nullptr), szModule, szUserMessage);
422         }
423     }
424     __finally
425     {
426         if (_CRT_ASSERT == nRptType)
427         {
428             _InterlockedDecrement(&_crtAssertBusy);
429         }
430     }
431     __endtry
432 
433     return retval;
434 }
435 #pragma warning(pop)
436 
437 /***
438 *int _VCrtDbgReportW() - _CrtDbgReportW calls into this function
439 *
440 *Purpose:
441 *       See remarks for _CrtDbgReport.
442 *
443 *Entry:
444 *       int             nRptType    - report type
445 *       wchar_t const* szFile      - file name
446 *       int             nLine       - line number
447 *       wchar_t const* szModule    - module name
448 *       wchar_t const* szFormat    - format string
449 *       va_list         arglist      - var args arglist
450 *
451 *Exit:
452 *       See remarks for _CrtDbgReport
453 *
454 *Exceptions:
455 *
456 *******************************************************************************/
457 
458 #pragma warning(push)
459 #pragma warning(disable:6262)
460 // prefast(6262): This func uses lots of stack because we want to tolerate very large reports, and we can't use malloc here.
_VCrtDbgReportW(int nRptType,void * returnAddress,wchar_t const * szFile,int nLine,wchar_t const * szModule,wchar_t const * szFormat,va_list arglist)461 int __cdecl _VCrtDbgReportW
462 (
463     int nRptType,
464     void * returnAddress,
465     wchar_t const* szFile,
466     int nLine,
467     wchar_t const* szModule,
468     wchar_t const* szFormat,
469     va_list arglist
470     )
471 {
472     int retval=0;
473     int handled=FALSE;
474     wchar_t szLineMessage[DBGRPT_MAX_MSG] = {0};
475     wchar_t szOutMessage[DBGRPT_MAX_MSG] = {0};
476     char szOutMessage2[DBGRPT_MAX_MSG] = {0};
477     wchar_t szUserMessage[DBGRPT_MAX_MSG] = {0};
478 
479     if (nRptType < 0 || nRptType >= _CRT_ERRCNT)
480         return -1;
481 
482     /*
483      * handle the (hopefully rare) case of
484      *
485      * 1) ASSERT while already dealing with an ASSERT
486      *      or
487      * 2) two threads asserting at the same time
488      */
489 
490     __try
491     {
492         if (_CRT_ASSERT == nRptType && _InterlockedIncrement(&_crtAssertBusy) > 0)
493         {
494             /* use only 'safe' functions -- must not assert in here! */
495 
496             _ERRCHECK(_itow_s(nLine, szLineMessage, DBGRPT_MAX_MSG, 10));
497 
498             OutputDebugStringW(L"Second Chance Assertion Failed: File ");
499             OutputDebugStringW(szFile ? szFile : L"<file unknown>");
500             OutputDebugStringW(L", Line ");
501             OutputDebugStringW(szLineMessage);
502             OutputDebugStringW(L"\n");
503 
504             _CrtDbgBreak();
505             retval = -1;
506             __leave;
507         }
508 
509         if (szFormat)
510         {
511             // Leave space for ASSERTINTRO{1,2} and "\r\n"
512             size_t const max_assert_intro_count = __max(_countof(ASSERTINTRO1), _countof(ASSERTINTRO2));
513             size_t const max_user_message_count = _countof(szUserMessage) - 2 - max_assert_intro_count;
514 
515             // Force use of the legacy stdio wide character format specifiers
516             // mode for source compatibility.  If we ever revisit support for
517             // the standard format specifiers, we'll need to revisit this as
518             // well.
519             int szlen = 0;
520             _ERRCHECK_SPRINTF(szlen = __stdio_common_vsnwprintf_s(
521                 _CRT_INTERNAL_PRINTF_LEGACY_WIDE_SPECIFIERS,
522                 szUserMessage,
523                 _countof(szUserMessage),
524                 max_user_message_count,
525                 szFormat,
526                 nullptr,
527                 arglist));
528             if (szlen < 0)
529             {
530                 _ERRCHECK(wcscpy_s(szUserMessage, DBGRPT_MAX_MSG, _CRT_WIDE(DBGRPT_TOOLONGMSG)));
531             }
532         }
533 
534         if (_CRT_ASSERT == nRptType)
535             _ERRCHECK(wcscpy_s(szLineMessage, DBGRPT_MAX_MSG, szFormat ? _CRT_WIDE(ASSERTINTRO1) : _CRT_WIDE(ASSERTINTRO2)));
536 
537         _ERRCHECK(wcscat_s(szLineMessage, DBGRPT_MAX_MSG, szUserMessage));
538 
539         if (_CRT_ASSERT == nRptType)
540         {
541             if (_CrtDbgMode[nRptType] & _CRTDBG_MODE_FILE)
542                 _ERRCHECK(wcscat_s(szLineMessage, DBGRPT_MAX_MSG, L"\r"));
543             {
544                 _ERRCHECK(wcscat_s(szLineMessage, DBGRPT_MAX_MSG, L"\n"));
545             }
546         }
547 
548         if (szFile)
549         {
550             int szlen = 0;
551             _ERRCHECK_SPRINTF(szlen = _snwprintf_s(szOutMessage, DBGRPT_MAX_MSG, DBGRPT_MAX_MSG, L"%ls(%d) : %ls",
552                                                     szFile, nLine, szLineMessage));
553             if (szlen < 0)
554                 _ERRCHECK(wcscpy_s(szOutMessage, DBGRPT_MAX_MSG, _CRT_WIDE(DBGRPT_TOOLONGMSG)));
555         }
556         else
557         {
558             _ERRCHECK(wcscpy_s(szOutMessage, DBGRPT_MAX_MSG, szLineMessage));
559         }
560 
561         /* scope */
562         {
563             errno_t e = _ERRCHECK_EINVAL_ERANGE(wcstombs_s(nullptr, szOutMessage2, DBGRPT_MAX_MSG, szOutMessage, _TRUNCATE));
564             if(e != 0)
565                 _ERRCHECK(strcpy_s(szOutMessage2, DBGRPT_MAX_MSG, DBGRPT_INVALIDMSG));
566         }
567 
568         /* User hook may handle report.
569             We have to check the ANSI Hook2 List & then the UNICODE Hook2 List.
570             Then we have check any ANSI individual Hook set through
571             SetReportHook */
572 
573         if (_pReportHookList || _pReportHookListW)
574         {
575             __crt_report_hook_node<char> *pnode=nullptr;
576             __crt_report_hook_node<wchar_t> *pnodeW=nullptr;
577 
578             __acrt_lock(__acrt_debug_lock);
579             __try
580             {
581                 for (pnode = _pReportHookList; pnode; pnode = pnode->next)
582                 {
583                     int hook_retval=0;
584                     if (pnode->hook(nRptType, szOutMessage2, &hook_retval))
585                     {
586                         retval=hook_retval;
587                         handled=TRUE;
588                         __leave;
589                     }
590                 }
591 
592                 for (pnodeW = _pReportHookListW; pnodeW; pnodeW = pnodeW->next)
593                 {
594                     int hook_retval=0;
595                     if (pnodeW->hook(nRptType, szOutMessage, &hook_retval))
596                     {
597                         retval=hook_retval;
598                         handled=TRUE;
599                         __leave;
600                     }
601                 }
602             }
603             __finally
604             {
605                 __acrt_unlock(__acrt_debug_lock);
606             }
607             __endtry
608         }
609 
610         if (handled)
611             __leave;
612 
613         if(_pfnReportHook)
614         {
615             int hook_retval=0;
616             if (_pfnReportHook(nRptType, szOutMessage2, &hook_retval))
617             {
618                 retval = hook_retval;
619                 __leave;
620             }
621         }
622 
623         if ((_CrtDbgMode[nRptType] & _CRTDBG_MODE_FILE) && _CrtDbgFile[nRptType] != _CRTDBG_INVALID_HFILE)
624         {
625             /* Use WriteConsole for Consoles, WriteFile otherwise */
626             switch (GetFileType(_CrtDbgFile[nRptType]))
627             {
628             case FILE_TYPE_CHAR:
629             {
630                 DWORD characters_written = 0;
631                 if (WriteConsoleW(_CrtDbgFile[nRptType], szOutMessage, static_cast<DWORD>(wcslen(szOutMessage)), &characters_written, nullptr))
632                     break;
633 
634                 /* If WriteConsole fails & LastError is ERROR_INVALID_VALUE, then the console is redirected */
635                 if (GetLastError() != ERROR_INVALID_HANDLE)
636                     break;
637             }
638             default:
639             {
640                 char szaOutMessage[DBGRPT_MAX_MSG];
641                 size_t ret = 0;
642                 errno_t e = _ERRCHECK_EINVAL_ERANGE(wcstombs_s(&ret, szaOutMessage, DBGRPT_MAX_MSG, szOutMessage, _TRUNCATE));
643 
644                 if (e != 0 && e != STRUNCATE)
645                 {
646                     DWORD bytes_written = 0;
647                     WriteFile(_CrtDbgFile[nRptType], szOutMessage, static_cast<DWORD>(wcslen(szOutMessage)) * 2, &bytes_written, nullptr);
648                 }
649                 else
650                 {
651                     /* ret counts for the null terminator as well */
652                     if (ret > 0)
653                     {
654                         --ret;
655                     }
656 
657                     DWORD bytes_written = 0;
658                     WriteFile(_CrtDbgFile[nRptType], szaOutMessage, static_cast<DWORD>(ret), &bytes_written, nullptr);
659                 }
660             }
661             }
662         }
663 
664         if (_CrtDbgMode[nRptType] & _CRTDBG_MODE_DEBUG)
665         {
666             ::OutputDebugStringW(szOutMessage);
667         }
668 
669         if (_CrtDbgMode[nRptType] & _CRTDBG_MODE_WNDW)
670         {
671             szLineMessage[0] = 0;
672             if (nLine)
673             {
674                 _ERRCHECK(_itow_s(nLine, szLineMessage, DBGRPT_MAX_MSG, 10));
675             }
676             retval = __acrt_MessageWindowW(nRptType, returnAddress, szFile, (nLine ? szLineMessage : nullptr), szModule, szUserMessage);
677         }
678     }
679     __finally
680     {
681         if (_CRT_ASSERT == nRptType)
682         {
683             _InterlockedDecrement(&_crtAssertBusy);
684         }
685     }
686     __endtry
687 
688     return retval;
689 }
690 #pragma warning(pop)
691 
692 } // extern "C"
693