/***
*dbgrptt.c - Debug CRT Reporting Functions
*
*       Copyright (c) Microsoft Corporation. All rights reserved.
*
*Purpose:
*
*******************************************************************************/

#ifndef _DEBUG
    #error This file is supported only in debug builds
    #define _DEBUG // For design-time support, when editing/viewing CRT sources
#endif

#include <corecrt_internal.h>
#include <errno.h>
#include <malloc.h>
#include <minmax.h>
#include <stdio.h>
#include <stdlib.h>



extern "C" int __cdecl __acrt_MessageWindowA(
    int         report_type,
    void*       return_address,
    char const* file_name,
    char const* line_number,
    char const* module_name,
    char const* user_message
    );

extern "C" int __cdecl __acrt_MessageWindowW(
    int            report_type,
    void*          return_address,
    wchar_t const* file_name,
    wchar_t const* line_number,
    wchar_t const* module_name,
    wchar_t const* user_message
    );

extern "C" {

_CRT_REPORT_HOOK _pfnReportHook;

__crt_report_hook_node<char> *_pReportHookList;
__crt_report_hook_node<wchar_t> *_pReportHookListW;

long _crtAssertBusy = -1;

// Enclaves only support MODE_DEBUG for error output
#ifdef _UCRT_ENCLAVE_BUILD

int const _CrtDbgMode[_CRT_ERRCNT]
{
    _CRTDBG_MODE_DEBUG,
    _CRTDBG_MODE_DEBUG,
    _CRTDBG_MODE_DEBUG
};

_HFILE const _CrtDbgFile[_CRT_ERRCNT]
{
    _CRTDBG_INVALID_HFILE,
    _CRTDBG_INVALID_HFILE,
    _CRTDBG_INVALID_HFILE
};

#else

int _CrtDbgMode[_CRT_ERRCNT]
{
    _CRTDBG_MODE_DEBUG,
    _CRTDBG_MODE_WNDW,
    _CRTDBG_MODE_WNDW
};

_HFILE _CrtDbgFile[_CRT_ERRCNT]
{
    _CRTDBG_INVALID_HFILE,
    _CRTDBG_INVALID_HFILE,
    _CRTDBG_INVALID_HFILE
};

/***
*int _CrtSetReportMode - set the reporting mode for a given report type
*
*Purpose:
*       set the reporting mode for a given report type
*
*Entry:
*       int nRptType    - the report type
*       int fMode       - new mode for given report type
*
*Exit:
*       previous mode for given report type
*
*Exceptions:
*       Input parameters are validated. Refer to the validation section of the function.
*
*******************************************************************************/
int __cdecl _CrtSetReportMode(
    int nRptType,
    int fMode
    )
{
    int oldMode;

    /* validation section */
    _VALIDATE_RETURN(nRptType >= 0 && nRptType < _CRT_ERRCNT, EINVAL, -1);
    _VALIDATE_RETURN(
        fMode == _CRTDBG_REPORT_MODE ||
        (fMode & ~(_CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG | _CRTDBG_MODE_WNDW)) == 0,
        EINVAL,
        -1);

    if (fMode == _CRTDBG_REPORT_MODE)
        return _CrtDbgMode[nRptType];

    oldMode = _CrtDbgMode[nRptType];

    _CrtDbgMode[nRptType] = fMode;

    return oldMode;
}

/***
*int _CrtSetReportFile - set the reporting file for a given report type
*
*Purpose:
*       set the reporting file for a given report type
*
*Entry:
*       int nRptType    - the report type
*       _HFILE hFile    - new file for given report type
*
*Exit:
*       previous file for given report type
*
*Exceptions:
*       Input parameters are validated. Refer to the validation section of the function.
*
*******************************************************************************/
_HFILE __cdecl _CrtSetReportFile(
    int nRptType,
    _HFILE hFile
    )
{
    _HFILE oldFile;

    /* validation section */
    _VALIDATE_RETURN(nRptType >= 0 && nRptType < _CRT_ERRCNT, EINVAL, _CRTDBG_HFILE_ERROR);

    if (hFile == _CRTDBG_REPORT_FILE)
        return _CrtDbgFile[nRptType];

    oldFile = _CrtDbgFile[nRptType];

    if (_CRTDBG_FILE_STDOUT == hFile)
        _CrtDbgFile[nRptType] = GetStdHandle(STD_OUTPUT_HANDLE);
    else if (_CRTDBG_FILE_STDERR == hFile)
        _CrtDbgFile[nRptType] = GetStdHandle(STD_ERROR_HANDLE);
    else
        _CrtDbgFile[nRptType] = hFile;

    return oldFile;
}

#endif /* _UCRT_ENCLAVE_BUILD */

/***
*_CRT_REPORT_HOOK _CrtSetReportHook() - set client report hook
*
*Purpose:
*       set client report hook. This function is provided only in ANSI
*       for backward compatibility. No Unicode Version of this function exists
*
*Entry:
*       _CRT_REPORT_HOOK pfnNewHook - new report hook
*
*Exit:
*       return previous hook
*
*Exceptions:
*
*******************************************************************************/

_CRT_REPORT_HOOK __cdecl _CrtSetReportHook(_CRT_REPORT_HOOK pfnNewHook)
{
    _CRT_REPORT_HOOK pfnOldHook = _pfnReportHook;
    _pfnReportHook = pfnNewHook;
    return pfnOldHook;
}

/***
*_CRT_REPORT_HOOK _CrtGetReportHook() - get client report hook
*
*Purpose:
*       get client report hook.
*
*Entry:
*
*Exit:
*       return current hook
*
*Exceptions:
*
*******************************************************************************/

_CRT_REPORT_HOOK __cdecl _CrtGetReportHook(void)
{
    return _pfnReportHook;
}

#define ASSERTINTRO1 "Assertion failed: "
#define ASSERTINTRO2 "Assertion failed!"

/***
*int _VCrtDbgReportA() - _CrtDbgReport calls into this function
*
*Purpose:
*       See remarks for _CrtDbgReport.
*
*Entry:
*       int             nRptType    - report type
*       char const*    szFile      - file name
*       int             nLine       - line number
*       char const*    szModule    - module name
*       char const*    szFormat    - format string
*       va_list         arglist      - var args arglist
*
*Exit:
*       See remarks for _CrtDbgReport
*
*Exceptions:
*
*******************************************************************************/

#pragma warning(push)
#pragma warning(disable:6262)
// prefast (6262): This func uses lots of stack because we want to tolerate very large reports, and we can't use malloc here.
int __cdecl _VCrtDbgReportA(
    int nRptType,
    void * returnAddress,
    char const* szFile,
    int nLine,
    char const* szModule,
    char const* szFormat,
    va_list arglist
    )
{
    int retval=0;
    int handled=FALSE;

    char    szLineMessage[DBGRPT_MAX_MSG]{0};
    char    szOutMessage [DBGRPT_MAX_MSG]{0};
    wchar_t szOutMessage2[DBGRPT_MAX_MSG]{0};
    char    szUserMessage[DBGRPT_MAX_MSG]{0};

    if (nRptType < 0 || nRptType >= _CRT_ERRCNT)
        return -1;

    /*
     * handle the (hopefully rare) case of
     *
     * 1) ASSERT while already dealing with an ASSERT
     *      or
     * 2) two threads asserting at the same time
     */

    __try
    {
        if (_CRT_ASSERT == nRptType && _InterlockedIncrement(&_crtAssertBusy) > 0)
        {
            /* use only 'safe' functions -- must not assert in here! */

            _ERRCHECK(_itoa_s(nLine, szLineMessage, DBGRPT_MAX_MSG, 10));

            __acrt_OutputDebugStringA("Second Chance Assertion Failed: File ");
            __acrt_OutputDebugStringA(szFile ? szFile : "<file unknown>");
            __acrt_OutputDebugStringA(", Line ");
            __acrt_OutputDebugStringA(szLineMessage);
            __acrt_OutputDebugStringA("\n");

            _CrtDbgBreak();
            retval=-1;
            __leave;
        }

        // Leave space for ASSERTINTRO1 and "\r\n"
        if (szFormat)
        {
            int szlen = 0;
            _ERRCHECK_SPRINTF(szlen = _vsnprintf_s(szUserMessage, DBGRPT_MAX_MSG,
                                                    DBGRPT_MAX_MSG - 2- max(sizeof(ASSERTINTRO1),sizeof(ASSERTINTRO2)),
                                                    szFormat, arglist));
            if (szlen < 0)
            {
                _ERRCHECK(strcpy_s(szUserMessage, DBGRPT_MAX_MSG, DBGRPT_TOOLONGMSG));
            }
        }

        if (_CRT_ASSERT == nRptType)
        {
            _ERRCHECK(strcpy_s(szLineMessage, DBGRPT_MAX_MSG, szFormat ? ASSERTINTRO1 : ASSERTINTRO2));
        }

        _ERRCHECK(strcat_s(szLineMessage, DBGRPT_MAX_MSG, szUserMessage));

        if (_CRT_ASSERT == nRptType)
        {
            if (_CrtDbgMode[nRptType] & _CRTDBG_MODE_FILE)
            {
                _ERRCHECK(strcat_s(szLineMessage, DBGRPT_MAX_MSG, "\r"));
            }

            _ERRCHECK(strcat_s(szLineMessage, DBGRPT_MAX_MSG, "\n"));
        }

        if (szFile)
        {
            int szlen = 0;
            _ERRCHECK_SPRINTF(szlen = _snprintf_s(szOutMessage, DBGRPT_MAX_MSG, DBGRPT_MAX_MSG - 1, "%s(%d) : %s",
                                                    szFile, nLine, szLineMessage));
            if (szlen < 0)
            {
                _ERRCHECK(strcpy_s(szOutMessage, DBGRPT_MAX_MSG, DBGRPT_TOOLONGMSG));
            }
        }
        else
        {
            _ERRCHECK(strcpy_s(szOutMessage, DBGRPT_MAX_MSG, szLineMessage));
        }

        {
            size_t ret = 0;
            errno_t e = 0;
            _ERRCHECK_EINVAL_ERANGE(e = mbstowcs_s(&ret, szOutMessage2, DBGRPT_MAX_MSG, szOutMessage, _TRUNCATE));
            if(e != 0)
            {
                _ERRCHECK(wcscpy_s(szOutMessage2, DBGRPT_MAX_MSG, _CRT_WIDE(DBGRPT_INVALIDMSG)));
            }
        }

        /* User hook may handle report.
            We have to check the ANSI Hook2 List & then the UNICODE Hook2 List.
            Then we have check any ANSI individual Hook set through
            SetReportHook */

        if (_pReportHookList || _pReportHookListW)
        {
            __crt_report_hook_node<char> *pnode=nullptr;
            __crt_report_hook_node<wchar_t> *pnodeW=nullptr;

            __acrt_lock(__acrt_debug_lock);
            __try
            {
                for (pnode = _pReportHookList; pnode; pnode = pnode->next)
                {
                    int hook_retval=0;
                    if (pnode->hook(nRptType, szOutMessage, &hook_retval))
                    {
                        handled=TRUE;
                        retval=hook_retval;
                        __leave;
                    }
                }

                for (pnodeW = _pReportHookListW; pnodeW; pnodeW = pnodeW->next)
                {
                    int hook_retval=0;
                    if (pnodeW->hook(nRptType, szOutMessage2, &hook_retval))
                    {
                        handled=TRUE;
                        retval=hook_retval;
                        __leave;
                    }
                }
            }
            __finally
            {
                __acrt_unlock(__acrt_debug_lock);
            }
        }

        if (handled)
            __leave;

        if (_pfnReportHook)
        {
            int hook_retval=0;
            if (_pfnReportHook(nRptType, szOutMessage, &hook_retval))
            {
                retval = hook_retval;
                __leave;
            }
        }

        if (_CrtDbgMode[nRptType] & _CRTDBG_MODE_FILE)
        {
            if (_CrtDbgFile[nRptType] != _CRTDBG_INVALID_HFILE)
            {
                DWORD bytes_written = 0;
                WriteFile(_CrtDbgFile[nRptType], szOutMessage, static_cast<DWORD>(strlen(szOutMessage)), &bytes_written, nullptr);
            }
        }

        if (_CrtDbgMode[nRptType] & _CRTDBG_MODE_DEBUG)
        {
            __acrt_OutputDebugStringA(szOutMessage);
        }

        if (_CrtDbgMode[nRptType] & _CRTDBG_MODE_WNDW)
        {
            szLineMessage[0] = 0;
            if (nLine)
            {
                _ERRCHECK(_itoa_s(nLine, szLineMessage, DBGRPT_MAX_MSG, 10));
            }

            retval = __acrt_MessageWindowA(nRptType, returnAddress, szFile, (nLine ? szLineMessage : nullptr), szModule, szUserMessage);
        }
    }
    __finally
    {
        if (_CRT_ASSERT == nRptType)
        {
            _InterlockedDecrement(&_crtAssertBusy);
        }
    }

    return retval;
}
#pragma warning(pop)

/***
*int _VCrtDbgReportW() - _CrtDbgReportW calls into this function
*
*Purpose:
*       See remarks for _CrtDbgReport.
*
*Entry:
*       int             nRptType    - report type
*       wchar_t const* szFile      - file name
*       int             nLine       - line number
*       wchar_t const* szModule    - module name
*       wchar_t const* szFormat    - format string
*       va_list         arglist      - var args arglist
*
*Exit:
*       See remarks for _CrtDbgReport
*
*Exceptions:
*
*******************************************************************************/

#pragma warning(push)
#pragma warning(disable:6262)
// prefast(6262): This func uses lots of stack because we want to tolerate very large reports, and we can't use malloc here.
int __cdecl _VCrtDbgReportW
(
    int nRptType,
    void * returnAddress,
    wchar_t const* szFile,
    int nLine,
    wchar_t const* szModule,
    wchar_t const* szFormat,
    va_list arglist
    )
{
    int retval=0;
    int handled=FALSE;
    wchar_t szLineMessage[DBGRPT_MAX_MSG] = {0};
    wchar_t szOutMessage[DBGRPT_MAX_MSG] = {0};
    char szOutMessage2[DBGRPT_MAX_MSG] = {0};
    wchar_t szUserMessage[DBGRPT_MAX_MSG] = {0};

    if (nRptType < 0 || nRptType >= _CRT_ERRCNT)
        return -1;

    /*
     * handle the (hopefully rare) case of
     *
     * 1) ASSERT while already dealing with an ASSERT
     *      or
     * 2) two threads asserting at the same time
     */

    __try
    {
        if (_CRT_ASSERT == nRptType && _InterlockedIncrement(&_crtAssertBusy) > 0)
        {
            /* use only 'safe' functions -- must not assert in here! */

            _ERRCHECK(_itow_s(nLine, szLineMessage, DBGRPT_MAX_MSG, 10));

            OutputDebugStringW(L"Second Chance Assertion Failed: File ");
            OutputDebugStringW(szFile ? szFile : L"<file unknown>");
            OutputDebugStringW(L", Line ");
            OutputDebugStringW(szLineMessage);
            OutputDebugStringW(L"\n");

            _CrtDbgBreak();
            retval = -1;
            __leave;
        }

        if (szFormat)
        {
            // Leave space for ASSERTINTRO{1,2} and "\r\n"
            size_t const max_assert_intro_count = __max(_countof(ASSERTINTRO1), _countof(ASSERTINTRO2));
            size_t const max_user_message_count = _countof(szUserMessage) - 2 - max_assert_intro_count;

            // Force use of the legacy stdio wide character format specifiers
            // mode for source compatibility.  If we ever revisit support for
            // the standard format specifiers, we'll need to revisit this as
            // well.
            int szlen = 0;
            _ERRCHECK_SPRINTF(szlen = __stdio_common_vsnwprintf_s(
                _CRT_INTERNAL_PRINTF_LEGACY_WIDE_SPECIFIERS,
                szUserMessage,
                _countof(szUserMessage),
                max_user_message_count,
                szFormat,
                nullptr,
                arglist));
            if (szlen < 0)
            {
                _ERRCHECK(wcscpy_s(szUserMessage, DBGRPT_MAX_MSG, _CRT_WIDE(DBGRPT_TOOLONGMSG)));
            }
        }

        if (_CRT_ASSERT == nRptType)
            _ERRCHECK(wcscpy_s(szLineMessage, DBGRPT_MAX_MSG, szFormat ? _CRT_WIDE(ASSERTINTRO1) : _CRT_WIDE(ASSERTINTRO2)));

        _ERRCHECK(wcscat_s(szLineMessage, DBGRPT_MAX_MSG, szUserMessage));

        if (_CRT_ASSERT == nRptType)
        {
            if (_CrtDbgMode[nRptType] & _CRTDBG_MODE_FILE)
                _ERRCHECK(wcscat_s(szLineMessage, DBGRPT_MAX_MSG, L"\r"));
            {
                _ERRCHECK(wcscat_s(szLineMessage, DBGRPT_MAX_MSG, L"\n"));
            }
        }

        if (szFile)
        {
            int szlen = 0;
            _ERRCHECK_SPRINTF(szlen = _snwprintf_s(szOutMessage, DBGRPT_MAX_MSG, DBGRPT_MAX_MSG, L"%ls(%d) : %ls",
                                                    szFile, nLine, szLineMessage));
            if (szlen < 0)
                _ERRCHECK(wcscpy_s(szOutMessage, DBGRPT_MAX_MSG, _CRT_WIDE(DBGRPT_TOOLONGMSG)));
        }
        else
        {
            _ERRCHECK(wcscpy_s(szOutMessage, DBGRPT_MAX_MSG, szLineMessage));
        }

        /* scope */
        {
            errno_t e = _ERRCHECK_EINVAL_ERANGE(wcstombs_s(nullptr, szOutMessage2, DBGRPT_MAX_MSG, szOutMessage, _TRUNCATE));
            if(e != 0)
                _ERRCHECK(strcpy_s(szOutMessage2, DBGRPT_MAX_MSG, DBGRPT_INVALIDMSG));
        }

        /* User hook may handle report.
            We have to check the ANSI Hook2 List & then the UNICODE Hook2 List.
            Then we have check any ANSI individual Hook set through
            SetReportHook */

        if (_pReportHookList || _pReportHookListW)
        {
            __crt_report_hook_node<char> *pnode=nullptr;
            __crt_report_hook_node<wchar_t> *pnodeW=nullptr;

            __acrt_lock(__acrt_debug_lock);
            __try
            {
                for (pnode = _pReportHookList; pnode; pnode = pnode->next)
                {
                    int hook_retval=0;
                    if (pnode->hook(nRptType, szOutMessage2, &hook_retval))
                    {
                        retval=hook_retval;
                        handled=TRUE;
                        __leave;
                    }
                }

                for (pnodeW = _pReportHookListW; pnodeW; pnodeW = pnodeW->next)
                {
                    int hook_retval=0;
                    if (pnodeW->hook(nRptType, szOutMessage, &hook_retval))
                    {
                        retval=hook_retval;
                        handled=TRUE;
                        __leave;
                    }
                }
            }
            __finally
            {
                __acrt_unlock(__acrt_debug_lock);
            }
        }

        if (handled)
            __leave;

        if(_pfnReportHook)
        {
            int hook_retval=0;
            if (_pfnReportHook(nRptType, szOutMessage2, &hook_retval))
            {
                retval = hook_retval;
                __leave;
            }
        }

        if ((_CrtDbgMode[nRptType] & _CRTDBG_MODE_FILE) && _CrtDbgFile[nRptType] != _CRTDBG_INVALID_HFILE)
        {
            /* Use WriteConsole for Consoles, WriteFile otherwise */
            switch (GetFileType(_CrtDbgFile[nRptType]))
            {
            case FILE_TYPE_CHAR:
            {
                DWORD characters_written = 0;
                if (WriteConsoleW(_CrtDbgFile[nRptType], szOutMessage, static_cast<DWORD>(wcslen(szOutMessage)), &characters_written, nullptr))
                    break;

                /* If WriteConsole fails & LastError is ERROR_INVALID_VALUE, then the console is redirected */
                if (GetLastError() != ERROR_INVALID_HANDLE)
                    break;
            }
            default:
            {
                char szaOutMessage[DBGRPT_MAX_MSG];
                size_t ret = 0;
                errno_t e = _ERRCHECK_EINVAL_ERANGE(wcstombs_s(&ret, szaOutMessage, DBGRPT_MAX_MSG, szOutMessage, _TRUNCATE));

                if (e != 0 && e != STRUNCATE)
                {
                    DWORD bytes_written = 0;
                    WriteFile(_CrtDbgFile[nRptType], szOutMessage, static_cast<DWORD>(wcslen(szOutMessage)) * 2, &bytes_written, nullptr);
                }
                else
                {
                    /* ret counts for the null terminator as well */
                    if (ret > 0)
                    {
                        --ret;
                    }

                    DWORD bytes_written = 0;
                    WriteFile(_CrtDbgFile[nRptType], szaOutMessage, static_cast<DWORD>(ret), &bytes_written, nullptr);
                }
            }
            }
        }

        if (_CrtDbgMode[nRptType] & _CRTDBG_MODE_DEBUG)
        {
            ::OutputDebugStringW(szOutMessage);
        }

        if (_CrtDbgMode[nRptType] & _CRTDBG_MODE_WNDW)
        {
            szLineMessage[0] = 0;
            if (nLine)
            {
                _ERRCHECK(_itow_s(nLine, szLineMessage, DBGRPT_MAX_MSG, 10));
            }
            retval = __acrt_MessageWindowW(nRptType, returnAddress, szFile, (nLine ? szLineMessage : nullptr), szModule, szUserMessage);
        }
    }
    __finally
    {
        if (_CRT_ASSERT == nRptType)
        {
            _InterlockedDecrement(&_crtAssertBusy);
        }
    }

    return retval;
}
#pragma warning(pop)

} // extern "C"
