/*
 * Cppcheck - A tool for static C/C++ code analysis
 * Copyright (C) 2007-2022 Cppcheck team.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "cppcheckexecutorseh.h"

#ifdef USE_WINDOWS_SEH

#include "cppcheckexecutor.h"
#include "utils.h"

#include <windows.h>
#include <dbghelp.h>
#include <tchar.h>

namespace {
    const ULONG maxnamelength = 512;
    struct IMAGEHLP_SYMBOL64_EXT : public IMAGEHLP_SYMBOL64 {
        TCHAR nameExt[maxnamelength]; // actually no need to worry about character encoding here
    };
    typedef BOOL (WINAPI *fpStackWalk64)(DWORD, HANDLE, HANDLE, LPSTACKFRAME64, PVOID, PREAD_PROCESS_MEMORY_ROUTINE64, PFUNCTION_TABLE_ACCESS_ROUTINE64, PGET_MODULE_BASE_ROUTINE64, PTRANSLATE_ADDRESS_ROUTINE64);
    fpStackWalk64 pStackWalk64;
    typedef DWORD64 (WINAPI *fpSymGetModuleBase64)(HANDLE, DWORD64);
    fpSymGetModuleBase64 pSymGetModuleBase64;
    typedef BOOL (WINAPI *fpSymGetSymFromAddr64)(HANDLE, DWORD64, PDWORD64, PIMAGEHLP_SYMBOL64);
    fpSymGetSymFromAddr64 pSymGetSymFromAddr64;
    typedef BOOL (WINAPI *fpSymGetLineFromAddr64)(HANDLE, DWORD64, PDWORD, PIMAGEHLP_LINE64);
    fpSymGetLineFromAddr64 pSymGetLineFromAddr64;
    typedef DWORD (WINAPI *fpUnDecorateSymbolName)(const TCHAR*, PTSTR, DWORD, DWORD);
    fpUnDecorateSymbolName pUnDecorateSymbolName;
    typedef PVOID (WINAPI *fpSymFunctionTableAccess64)(HANDLE, DWORD64);
    fpSymFunctionTableAccess64 pSymFunctionTableAccess64;
    typedef BOOL (WINAPI *fpSymInitialize)(HANDLE, PCSTR, BOOL);
    fpSymInitialize pSymInitialize;

    HMODULE hLibDbgHelp;
// avoid explicit dependency on Dbghelp.dll
    bool loadDbgHelp()
    {
        hLibDbgHelp = ::LoadLibraryW(L"Dbghelp.dll");
        if (!hLibDbgHelp)
            return false;
        pStackWalk64 = (fpStackWalk64) ::GetProcAddress(hLibDbgHelp, "StackWalk64");
        pSymGetModuleBase64 = (fpSymGetModuleBase64) ::GetProcAddress(hLibDbgHelp, "SymGetModuleBase64");
        pSymGetSymFromAddr64 = (fpSymGetSymFromAddr64) ::GetProcAddress(hLibDbgHelp, "SymGetSymFromAddr64");
        pSymGetLineFromAddr64 = (fpSymGetLineFromAddr64)::GetProcAddress(hLibDbgHelp, "SymGetLineFromAddr64");
        pSymFunctionTableAccess64 = (fpSymFunctionTableAccess64)::GetProcAddress(hLibDbgHelp, "SymFunctionTableAccess64");
        pSymInitialize = (fpSymInitialize) ::GetProcAddress(hLibDbgHelp, "SymInitialize");
        pUnDecorateSymbolName = (fpUnDecorateSymbolName)::GetProcAddress(hLibDbgHelp, "UnDecorateSymbolName");
        return true;
    }


    void printCallstack(FILE* outputFile, PEXCEPTION_POINTERS ex)
    {
        if (!loadDbgHelp())
            return;
        const HANDLE hProcess   = GetCurrentProcess();
        const HANDLE hThread    = GetCurrentThread();
        pSymInitialize(
            hProcess,
            nullptr,
            TRUE
            );
        CONTEXT context = *(ex->ContextRecord);
        STACKFRAME64 stack= {0};
#ifdef _M_IX86
        stack.AddrPC.Offset    = context.Eip;
        stack.AddrPC.Mode      = AddrModeFlat;
        stack.AddrStack.Offset = context.Esp;
        stack.AddrStack.Mode   = AddrModeFlat;
        stack.AddrFrame.Offset = context.Ebp;
        stack.AddrFrame.Mode   = AddrModeFlat;
#else
        stack.AddrPC.Offset    = context.Rip;
        stack.AddrPC.Mode      = AddrModeFlat;
        stack.AddrStack.Offset = context.Rsp;
        stack.AddrStack.Mode   = AddrModeFlat;
        stack.AddrFrame.Offset = context.Rsp;
        stack.AddrFrame.Mode   = AddrModeFlat;
#endif
        IMAGEHLP_SYMBOL64_EXT symbol;
        symbol.SizeOfStruct  = sizeof(IMAGEHLP_SYMBOL64);
        symbol.MaxNameLength = maxnamelength;
        DWORD64 displacement   = 0;
        int beyond_main=-1; // emergency exit, see below
        for (ULONG frame = 0; ; frame++) {
            BOOL result = pStackWalk64
                          (
#ifdef _M_IX86
                IMAGE_FILE_MACHINE_I386,
#else
                IMAGE_FILE_MACHINE_AMD64,
#endif
                hProcess,
                hThread,
                &stack,
                &context,
                nullptr,
                pSymFunctionTableAccess64,
                pSymGetModuleBase64,
                nullptr
                          );
            if (!result)  // official end...
                break;
            pSymGetSymFromAddr64(hProcess, (ULONG64)stack.AddrPC.Offset, &displacement, &symbol);
            TCHAR undname[maxnamelength]= {0};
            pUnDecorateSymbolName((const TCHAR*)symbol.Name, (PTSTR)undname, (DWORD)getArrayLength(undname), UNDNAME_COMPLETE);
            if (beyond_main>=0)
                ++beyond_main;
            if (_tcscmp(undname, _T("main"))==0)
                beyond_main=0;
            fprintf(outputFile,
                    "%lu. 0x%08I64X in ",
                    frame, (ULONG64)stack.AddrPC.Offset);
            fputs((const char *)undname, outputFile);
            fputc('\n', outputFile);
            if (0==stack.AddrReturn.Offset || beyond_main>2) // StackWalk64() sometimes doesn't reach any end...
                break;
        }

        FreeLibrary(hLibDbgHelp);
        hLibDbgHelp=nullptr;
    }

    void writeMemoryErrorDetails(FILE* outputFile, PEXCEPTION_POINTERS ex, const char* description)
    {
        fputs(description, outputFile);
        fprintf(outputFile, " (instruction: 0x%p) ", ex->ExceptionRecord->ExceptionAddress);
        // Using %p for ULONG_PTR later on, so it must have size identical to size of pointer
        // This is not the universally portable solution but good enough for Win32/64
        C_ASSERT(sizeof(void*) == sizeof(ex->ExceptionRecord->ExceptionInformation[1]));
        switch (ex->ExceptionRecord->ExceptionInformation[0]) {
        case 0:
            fprintf(outputFile, "reading from 0x%p",
                    reinterpret_cast<void*>(ex->ExceptionRecord->ExceptionInformation[1]));
            break;
        case 1:
            fprintf(outputFile, "writing to 0x%p",
                    reinterpret_cast<void*>(ex->ExceptionRecord->ExceptionInformation[1]));
            break;
        case 8:
            fprintf(outputFile, "data execution prevention at 0x%p",
                    reinterpret_cast<void*>(ex->ExceptionRecord->ExceptionInformation[1]));
            break;
        default:
            break;
        }
    }

    /*
     * Any evaluation of the exception needs to be done here!
     */
    int filterException(FILE *outputFile, int code, PEXCEPTION_POINTERS ex)
    {
        fputs("Internal error: ", outputFile);
        switch (ex->ExceptionRecord->ExceptionCode) {
        case EXCEPTION_ACCESS_VIOLATION:
            writeMemoryErrorDetails(outputFile, ex, "Access violation");
            break;
        case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
            fputs("Out of array bounds", outputFile);
            break;
        case EXCEPTION_BREAKPOINT:
            fputs("Breakpoint", outputFile);
            break;
        case EXCEPTION_DATATYPE_MISALIGNMENT:
            fputs("Misaligned data", outputFile);
            break;
        case EXCEPTION_FLT_DENORMAL_OPERAND:
            fputs("Denormalized floating-point value", outputFile);
            break;
        case EXCEPTION_FLT_DIVIDE_BY_ZERO:
            fputs("Floating-point divide-by-zero", outputFile);
            break;
        case EXCEPTION_FLT_INEXACT_RESULT:
            fputs("Inexact floating-point value", outputFile);
            break;
        case EXCEPTION_FLT_INVALID_OPERATION:
            fputs("Invalid floating-point operation", outputFile);
            break;
        case EXCEPTION_FLT_OVERFLOW:
            fputs("Floating-point overflow", outputFile);
            break;
        case EXCEPTION_FLT_STACK_CHECK:
            fputs("Floating-point stack overflow", outputFile);
            break;
        case EXCEPTION_FLT_UNDERFLOW:
            fputs("Floating-point underflow", outputFile);
            break;
        case EXCEPTION_GUARD_PAGE:
            fputs("Page-guard access", outputFile);
            break;
        case EXCEPTION_ILLEGAL_INSTRUCTION:
            fputs("Illegal instruction", outputFile);
            break;
        case EXCEPTION_IN_PAGE_ERROR:
            writeMemoryErrorDetails(outputFile, ex, "Invalid page access");
            break;
        case EXCEPTION_INT_DIVIDE_BY_ZERO:
            fputs("Integer divide-by-zero", outputFile);
            break;
        case EXCEPTION_INT_OVERFLOW:
            fputs("Integer overflow", outputFile);
            break;
        case EXCEPTION_INVALID_DISPOSITION:
            fputs("Invalid exception dispatcher", outputFile);
            break;
        case EXCEPTION_INVALID_HANDLE:
            fputs("Invalid handle", outputFile);
            break;
        case EXCEPTION_NONCONTINUABLE_EXCEPTION:
            fputs("Non-continuable exception", outputFile);
            break;
        case EXCEPTION_PRIV_INSTRUCTION:
            fputs("Invalid instruction", outputFile);
            break;
        case EXCEPTION_SINGLE_STEP:
            fputs("Single instruction step", outputFile);
            break;
        case EXCEPTION_STACK_OVERFLOW:
            fputs("Stack overflow", outputFile);
            break;
        default:
            fprintf(outputFile, "Unknown exception (%d)\n",
                    code);
            break;
        }
        fputc('\n', outputFile);
        printCallstack(outputFile, ex);
        fflush(outputFile);
        return EXCEPTION_EXECUTE_HANDLER;
    }
}

/**
 * Signal/SEH handling
 * Has to be clean for using with SEH on windows, i.e. no construction of C++ object instances is allowed!
 * TODO Check for multi-threading issues!
 *
 */
int check_wrapper_seh(CppCheckExecutor& executor, int (CppCheckExecutor::*f)(CppCheck&), CppCheck& cppcheck)
{
    FILE *outputFile = CppCheckExecutor::getExceptionOutput();
    __try {
        return (&executor->*f)(cppcheck);
    } __except (filterException(outputFile, GetExceptionCode(), GetExceptionInformation())) {
        fputs("Please report this to the cppcheck developers!\n", outputFile);
        return -1;
    }
}

#endif