// Copyright 2010-2021, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//     * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#include "base/crash_report_handler.h"

#if defined(OS_WIN) && defined(GOOGLE_JAPANESE_INPUT_BUILD)

#include <ShellAPI.h>  // for CommandLineToArgvW
#include <Windows.h>

#include <cstdlib>
#include <string>

#include "base/const.h"
#include "base/file_util.h"
#include "base/logging.h"
#include "base/system_util.h"
#include "base/util.h"
#include "base/version.h"
#include "third_party/breakpad/src/client/windows/handler/exception_handler.h"

namespace {

// The prefix of the pipe name which GoogleCrashHandler.exe opens for clients
// to register them.
const wchar_t kGoogleCrashHandlerPipePrefix[] =
    L"\\\\.\\pipe\\GoogleCrashServices\\";

// This is the well known SID for the system principal.
const wchar_t kSystemPrincipalSid[] = L"S-1-5-18";

// The postfix of the pipe name which GoogleCrashHandler.exe opens for clients
// to register them.
// Covert: On x86 environment, both _M_X64 and _M_IX86 are defined. So we need
//     to check _M_X64 first.
#if defined(_M_X64)
// x64 crash handler expects the postfix "-64".
// See b/5166654 or http://crbug.com/89730 for the background info.
const wchar_t kGoogleCrashHandlerPipePostfix[] = L"-x64";
#elif defined(_M_IX86)
// No postfix for the x86 crash handler.
const wchar_t kGoogleCrashHandlerPipePostfix[] = L"";
#else  // other than _M_X64 or _M_IX86.
#error "unsupported platform"
#endif  // _M_X64 or _M_IX86

// The product name registered in the crash server.
const wchar_t kProductNameInCrash[] = L"Google_Japanese_IME";

// The reference count for ExceptionHandler.
int g_reference_count = 0;

// The CRITICAL_SECTION struct used for creating or deleting ExceptionHandler in
// a mutually exclusive manner.
CRITICAL_SECTION *g_critical_section = nullptr;

google_breakpad::ExceptionHandler *g_handler = nullptr;

// Returns the name of the build mode.
std::wstring GetBuildMode() {
#if defined(MOZC_NO_LOGGING)
  return L"rel";
#elif defined(DEBUG)
  return L"dbg";
#else   // Other than MOZC_NO_LOGGING or DEBUG
  return L"opt";
#endif  // MOZC_NO_LOGGING, DEBUG
}

// Reduces the size of the string |str| to a max of 64 chars (Extra 1 char is
// trimmed for nullptr-terminator so effective characters are 63 characters).
// Required because breakpad's CustomInfoEntry raises an invalid_parameter error
// if the string we want to set is longer than 64 characters, including
// nullptr-terminator.
std::wstring TrimToBreakpadMax(const std::wstring &str) {
  std::wstring shorter(str);
  return shorter.substr(0,
                        google_breakpad::CustomInfoEntry::kValueMaxLength - 1);
}

// Returns the custom info structure based on the dll in parameter and the
// process type.
google_breakpad::CustomClientInfo *GetCustomInfo() {
  // Common entries.
  google_breakpad::CustomInfoEntry ver_entry(
      L"ver", mozc::Version::GetMozcVersionW().c_str());
  google_breakpad::CustomInfoEntry prod_entry(L"prod", kProductNameInCrash);
  google_breakpad::CustomInfoEntry buildmode_entry(L"Build Mode",
                                                   GetBuildMode().c_str());

  // Get the first two command line switches if they exist.
  google_breakpad::CustomInfoEntry switch1(L"switch-1", L"");
  google_breakpad::CustomInfoEntry switch2(L"switch-2", L"");
  {
    int num_args = 0;
    wchar_t **args = ::CommandLineToArgvW(::GetCommandLineW(), &num_args);
    if (args != nullptr) {
      if (num_args > 1) {
        switch1.set_value(TrimToBreakpadMax(args[1]).c_str());
      }
      if (num_args > 2) {
        switch2.set_value(TrimToBreakpadMax(args[2]).c_str());
      }
      // Caller is responsible to free the returned value of CommandLineToArgv.
      ::LocalFree(args);
    }
  }

  static google_breakpad::CustomInfoEntry entries[] = {
      ver_entry, prod_entry, buildmode_entry, switch1, switch2};
  static google_breakpad::CustomClientInfo custom_info = {entries,
                                                          std::size(entries)};

  return &custom_info;
}

// Returns the pipe name of the GoogleCrashHandler.exe or
// GoogleCrashHandler64.exe running as a system user.
std::wstring GetCrashHandlerPipeName() {
  std::wstring pipe_name = kGoogleCrashHandlerPipePrefix;
  pipe_name.append(kSystemPrincipalSid);
  pipe_name.append(kGoogleCrashHandlerPipePostfix);
  return pipe_name;
}

class ScopedCriticalSection {
 public:
  explicit ScopedCriticalSection(CRITICAL_SECTION *critical_section)
      : critical_section_(critical_section) {
    if (critical_section_ != nullptr) {
      EnterCriticalSection(critical_section_);
    }
  }
  ~ScopedCriticalSection() {
    if (critical_section_ != nullptr) {
      LeaveCriticalSection(critical_section_);
    }
  }

 private:
  CRITICAL_SECTION *critical_section_;
};

// get the handle to the module containing the given executing address
HMODULE GetModuleHandleFromAddress(void *address) {
  // address may be nullptr
  MEMORY_BASIC_INFORMATION mbi;
  SIZE_T result = VirtualQuery(address, &mbi, sizeof(mbi));
  if (0 == result) {
    return nullptr;
  }
  return static_cast<HMODULE>(mbi.AllocationBase);
}

// get the handle to the currently executing module
HMODULE GetCurrentModuleHandle() {
  return GetModuleHandleFromAddress(GetCurrentModuleHandle);
}

// Check to see if an address is in the current module.
bool IsAddressInCurrentModule(void *address) {
  // address may be nullptr
  return GetCurrentModuleHandle() == GetModuleHandleFromAddress(address);
}

bool IsCurrentModuleInStack(PCONTEXT context) {
  STACKFRAME64 stack;
  ZeroMemory(&stack, sizeof(stack));
// This macro is defined for x86 processors.
// See http://msdn.microsoft.com/ja-jp/library/b0084kay(VS.80).aspx for details.
#if defined(_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;
// This macro is defined for x64 processors.
#elif defined(_M_X64)
  stack.AddrPC.Offset = context->Rip;
  stack.AddrPC.Mode = AddrModeFlat;
  stack.AddrStack.Offset = context->Rsp;
  stack.AddrStack.Mode = AddrModeFlat;
  stack.AddrFrame.Offset = context->Rbp;
  stack.AddrFrame.Mode = AddrModeFlat;
#else  // Other than  _M_IX86 or _M_X64
#error "unsupported platform"
#endif  // _M_IX86, _M_X64, and others.

  while (StackWalk64(IMAGE_FILE_MACHINE_I386, GetCurrentProcess(),
                     GetCurrentThread(), &stack, context, 0,
                     SymFunctionTableAccess64, SymGetModuleBase64, 0)) {
    if (IsAddressInCurrentModule(
            reinterpret_cast<void *>(stack.AddrPC.Offset))) {
      return true;
    }
  }
  return false;
}

bool FilterHandler(void *context, EXCEPTION_POINTERS *exinfo,
                   MDRawAssertionInfo *assertion) {
  if (exinfo == nullptr) {
    // We do not catch CRT error in release build.
#ifdef MOZC_NO_LOGGING
    return false;
#else   // MOZC_NO_LOGGING
    return true;
#endif  // MOZC_NO_LOGGING
  }

  // Make sure it's our module which cause the crash.
  if (IsAddressInCurrentModule(exinfo->ExceptionRecord->ExceptionAddress)) {
    return true;
  }

  if (IsCurrentModuleInStack(exinfo->ContextRecord)) {
    return true;
  }

  return false;
}

}  // namespace

namespace mozc {

bool CrashReportHandler::Initialize(bool check_address) {
  ScopedCriticalSection critical_section(g_critical_section);
  DCHECK_GE(g_reference_count, 0);
  ++g_reference_count;
  if (g_reference_count == 1 && g_handler == nullptr) {
    const std::string acrashdump_directory =
        SystemUtil::GetCrashReportDirectory();
    // create a crash dump directory if not exist.
    if (absl::Status s = FileUtil::FileExists(acrashdump_directory); !s.ok()) {
      FileUtil::CreateDirectory(acrashdump_directory);
    }

    std::wstring crashdump_directory;
    Util::Utf8ToWide(acrashdump_directory, &crashdump_directory);

    google_breakpad::ExceptionHandler::FilterCallback filter_callback =
        check_address ? FilterHandler : nullptr;
    const auto kCrashDumpType = static_cast<MINIDUMP_TYPE>(
        MiniDumpWithUnloadedModules | MiniDumpWithProcessThreadData);
    g_handler = new google_breakpad::ExceptionHandler(
        crashdump_directory.c_str(), filter_callback,
        nullptr,  // MinidumpCallback
        nullptr,  // callback_context
        google_breakpad::ExceptionHandler::HANDLER_ALL, kCrashDumpType,
        GetCrashHandlerPipeName().c_str(), GetCustomInfo());

#ifdef DEBUG
    g_handler->set_handle_debug_exceptions(true);
#endif  // DEBUG
    return true;
  }
  return false;
}

bool CrashReportHandler::IsInitialized() {
  ScopedCriticalSection critical_section(g_critical_section);
  return g_reference_count > 0;
}

bool CrashReportHandler::Uninitialize() {
  ScopedCriticalSection critical_section(g_critical_section);
  --g_reference_count;
  DCHECK_GE(g_reference_count, 0);
  if (g_reference_count == 0 && g_handler != nullptr) {
    delete g_handler;
    g_handler = nullptr;
    return true;
  }
  return false;
}

void CrashReportHandler::SetCriticalSection(
    CRITICAL_SECTION *critical_section) {
  g_critical_section = critical_section;
}

}  // namespace mozc

#else  // OS_WIN && GOOGLE_JAPANESE_INPUT_BUILD

namespace mozc {

// Null implementation for platforms where we do not want to enable breakpad.

bool CrashReportHandler::Initialize(bool check_address) { return false; }

bool CrashReportHandler::IsInitialized() { return false; }

bool CrashReportHandler::Uninitialize() { return false; }

#ifdef OS_WIN
void CrashReportHandler::SetCriticalSection(
    CRITICAL_SECTION *critical_section) {}
#endif  // OS_WIN

}  // namespace mozc

#endif  // OS_WIN && GOOGLE_JAPANESE_INPUT_BUILD
