// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// TODO(ananta/scottmg)
// Add test coverage for Crashpad.
#include "chrome/app/chrome_crash_reporter_client_win.h"

#include <windows.h>

#include <assert.h>

#include <iterator>
#include <memory>
#include <string>
#include <utility>
#include <vector>

#include "base/debug/leak_annotations.h"
#include "base/files/file_path.h"
#include "base/notreached.h"
#include "base/rand_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/win/current_module.h"
#include "chrome/chrome_elf/chrome_elf_constants.h"
#include "chrome/install_static/install_util.h"
#include "chrome/install_static/user_data_dir.h"
#include "components/crash/core/app/crashpad.h"
#include "components/metrics/system_profile_user_stream.h"
#include "components/version_info/channel.h"

ChromeCrashReporterClient::ChromeCrashReporterClient() = default;

ChromeCrashReporterClient::~ChromeCrashReporterClient() = default;

// static
void ChromeCrashReporterClient::InitializeCrashReportingForProcess() {
  static ChromeCrashReporterClient* instance = nullptr;
  if (instance) {
    return;
  }

  instance = new ChromeCrashReporterClient();
  ANNOTATE_LEAKING_OBJECT_PTR(instance);

  std::wstring process_type = install_static::GetCommandLineSwitchValue(
      ::GetCommandLine(), install_static::kProcessType);

  // Don't set up Crashpad crash reporting in the Crashpad handler itself, nor
  // in the fallback crash handler for the Crashpad handler process.
  if (process_type != install_static::kCrashpadHandler &&
      process_type != install_static::kFallbackHandler) {
    crash_reporter::SetCrashReporterClient(instance);

    std::wstring user_data_dir;
    if (process_type.empty()) {
      install_static::GetUserDataDirectory(&user_data_dir, nullptr);
    }

    // TODO(wfh): Add a DCHECK for success. See https://crbug.com/40226723.
    std::ignore = crash_reporter::InitializeCrashpadWithEmbeddedHandler(
        /*initial_client=*/process_type.empty(),
        install_static::WideToUTF8(process_type),
        install_static::WideToUTF8(user_data_dir), base::FilePath());
  }
}

bool ChromeCrashReporterClient::GetAlternativeCrashDumpLocation(
    std::wstring* crash_dir) {
  // By setting the BREAKPAD_DUMP_LOCATION environment variable, an alternate
  // location to write breakpad crash dumps can be set.
  *crash_dir = install_static::GetEnvironmentString(L"BREAKPAD_DUMP_LOCATION");
  return !crash_dir->empty();
}

void ChromeCrashReporterClient::GetProductNameAndVersion(
    const std::wstring& exe_path,
    std::wstring* product_name,
    std::wstring* version,
    std::wstring* special_build,
    std::wstring* channel_name) {
  assert(product_name);
  assert(version);
  assert(special_build);
  assert(channel_name);

  install_static::GetExecutableVersionDetails(exe_path, product_name, version,
                                              special_build, channel_name);
}

void ChromeCrashReporterClient::GetProductInfo(ProductInfo* product_info) {
  std::wstring product_name, version, special_build, channel_name;
  wchar_t exe_file[MAX_PATH] = {};
  CHECK(::GetModuleFileName(nullptr, exe_file, std::size(exe_file)));
  GetProductNameAndVersion(exe_file, &product_name, &version, &special_build,
                           &channel_name);
  product_info->product_name = base::WideToUTF8(product_name);
  product_info->version = base::WideToUTF8(version);
  product_info->channel = base::WideToUTF8(channel_name);
}

bool ChromeCrashReporterClient::GetShouldDumpLargerDumps() {
  // Capture larger dumps for Google Chrome beta, dev, and canary channels, and
  // Chromium builds. The Google Chrome stable channel uses smaller dumps.
  return install_static::GetChromeChannel() != version_info::Channel::STABLE;
}

bool ChromeCrashReporterClient::ReportingIsEnforcedByPolicy(
    bool* crashpad_enabled) {
  // Determine whether configuration management allows loading the crash
  // reporter.
  // Since the configuration management infrastructure is not initialized at
  // this point, we read the corresponding registry key directly. The return
  // status indicates whether policy data was successfully read. If it is true,
  // |breakpad_enabled| contains the value set by policy.
  return install_static::ReportingIsEnforcedByPolicy(crashpad_enabled);
}

bool ChromeCrashReporterClient::GetCrashDumpLocation(std::wstring* crash_dir) {
  // By setting the BREAKPAD_DUMP_LOCATION environment variable, an alternate
  // location to write breakpad crash dumps can be set.
  // If this environment variable exists, then for the time being,
  // short-circuit how it's handled on Windows. Honoring this
  // variable is required in order to symbolize stack traces in
  // Telemetry based tests: http://crbug.com/40446521.
  if (GetAlternativeCrashDumpLocation(crash_dir)) {
    return true;
  }

  *crash_dir = install_static::GetCrashDumpLocation();
  return !crash_dir->empty();
}

bool ChromeCrashReporterClient::GetCrashMetricsLocation(
    std::wstring* metrics_dir) {
  if (!GetCollectStatsConsent()) {
    return false;
  }
  install_static::GetUserDataDirectory(metrics_dir, nullptr);
  return !metrics_dir->empty();
}

bool ChromeCrashReporterClient::IsRunningUnattended() {
  return install_static::HasEnvironmentVariable(install_static::kHeadless);
}

bool ChromeCrashReporterClient::GetCollectStatsConsent() {
  return install_static::GetCollectStatsConsent();
}

bool ChromeCrashReporterClient::GetCollectStatsInSample() {
  return install_static::GetCollectStatsInSample();
}

bool ChromeCrashReporterClient::ShouldMonitorCrashHandlerExpensively() {
  // The expensive mechanism dedicates a process to be crashpad_handler's own
  // crashpad_handler. In Google Chrome, scale back on this in the more stable
  // channels. There's a fallback crash handler that can catch crashes when this
  // expensive mechanism isn't used, although the fallback crash handler has
  // different characteristics so it's desirable to use the expensive mechanism
  // at least some of the time.
  double probability;
  switch (install_static::GetChromeChannel()) {
    case version_info::Channel::STABLE:
      return false;

    case version_info::Channel::BETA:
      probability = 0.1;
      break;

    case version_info::Channel::DEV:
      probability = 0.25;
      break;

    default:
      probability = 0.5;
      break;
  }

  return base::RandDouble() < probability;
}

bool ChromeCrashReporterClient::EnableBreakpadForProcess(
    const std::string& process_type) {
  // This is not used by Crashpad (at least on Windows).
  NOTREACHED();
}

std::vector<base::ReadOnlySharedMemoryRegion>
ChromeCrashReporterClient::GetUserStreamSharedMemoryRegions() {
  std::vector<base::ReadOnlySharedMemoryRegion> streams;

  // Early-initialize the singleton before Crashpad spawns.
  // This guarantees the memory region exists for Crashpad to inherit.
  metrics::SystemProfileUserStream& stream =
      metrics::SystemProfileUserStream::Get();
  stream.Initialize();

  base::ReadOnlySharedMemoryRegion region =
      stream.DuplicateSharedMemoryRegion();
  // An OOM or initial allocation failure inside Initialize() would have
  // already triggered a CHECK and crashed the browser. However,
  // DuplicateSharedMemoryRegion() can still fail if the OS exhausts its
  // handles. In that case, gracefully degrade by dropping the telemetry stream
  // rather than causing an unnecessary crash.
  if (region.IsValid()) {
    streams.push_back(std::move(region));
  }

  return streams;
}

std::wstring ChromeCrashReporterClient::GetWerRuntimeExceptionModule() {
  // We require that chrome_wer.dll is installed next to chrome_elf.dll. This
  // approach means we don't need to check for the dll's existence on disk early
  // in the process lifetime - we never load this dll ourselves - it is only
  // loaded by WerFault.exe after certain crashes. We do not use base::FilePath
  // and friends as chrome_elf will eventually not depend on //base.

  wchar_t elf_file[MAX_PATH];
  DWORD len = GetModuleFileName(CURRENT_MODULE(), elf_file, MAX_PATH);
  // On error return an empty path to indicate than a module is not to be
  // registered. This is harmless.
  if (len == 0 || len == MAX_PATH) {
    return std::wstring();
  }

  wchar_t elf_dir[MAX_PATH];
  wchar_t* file_start = nullptr;
  DWORD dir_len = GetFullPathName(elf_file, MAX_PATH, elf_dir, &file_start);
  if (dir_len == 0 || dir_len > len || !file_start) {
    return std::wstring();
  }

  // file_start points to the start of the filename in the elf_dir buffer.
  return std::wstring(elf_dir, file_start).append(kWerDll);
}
