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

#include "content/public/test/no_renderer_crashes_assertion.h"

#include "base/no_destructor.h"
#include "content/public/browser/child_process_host.h"
#include "content/public/browser/child_process_termination_info.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace content {

// static
NoRendererCrashesAssertion::Suspensions&
NoRendererCrashesAssertion::Suspensions::GetInstance() {
  static base::NoDestructor<NoRendererCrashesAssertion::Suspensions>
      s_suspensions;
  return *s_suspensions;
}

NoRendererCrashesAssertion::Suspensions::Suspensions() = default;
NoRendererCrashesAssertion::Suspensions::~Suspensions() = default;

void NoRendererCrashesAssertion::Suspensions::AddSuspension(int process_id) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  process_id_to_suspension_count_[process_id]++;
}

void NoRendererCrashesAssertion::Suspensions::RemoveSuspension(int process_id) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  auto it = process_id_to_suspension_count_.find(process_id);
  CHECK(it != process_id_to_suspension_count_.end());
  DCHECK_LT(0, it->second);
  --it->second;
  if (0 == it->second)
    process_id_to_suspension_count_.erase(it);
}

bool NoRendererCrashesAssertion::Suspensions::IsSuspended(int process_id) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK_NE(ChildProcessHost::kInvalidUniqueID, process_id);

  auto it = process_id_to_suspension_count_.find(process_id);
  if (it != process_id_to_suspension_count_.end() && it->second > 0)
    return true;

  auto it2 =
      process_id_to_suspension_count_.find(ChildProcessHost::kInvalidUniqueID);
  if (it2 != process_id_to_suspension_count_.end() && it2->second > 0)
    return true;

  return false;
}

NoRendererCrashesAssertion::NoRendererCrashesAssertion() {
  for (auto iter = RenderProcessHost::AllHostsIterator(); !iter.IsAtEnd();
       iter.Advance()) {
    process_observations_.AddObservation(iter.GetCurrentValue());
  }
}

NoRendererCrashesAssertion::~NoRendererCrashesAssertion() = default;

void NoRendererCrashesAssertion::OnRenderProcessHostCreated(
    RenderProcessHost* host) {
  if (!process_observations_.IsObservingSource(host)) {
    process_observations_.AddObservation(host);
  }
}

void NoRendererCrashesAssertion::RenderProcessExited(
    RenderProcessHost* host,
    const ChildProcessTerminationInfo& info) {
  if (NoRendererCrashesAssertion::Suspensions::GetInstance().IsSuspended(
          host->GetDeprecatedID())) {
    return;
  }

  switch (info.status) {
    case base::TERMINATION_STATUS_LAUNCH_FAILED:
    case base::TERMINATION_STATUS_NORMAL_TERMINATION:
    case base::TERMINATION_STATUS_STILL_RUNNING:
      return;  // Not a crash.
    default:
      break;  // Crash - need to trigger a test failure below.
  }

  const auto exit_code = info.exit_code;
  // Windows error codes such as 0xC0000005 and 0xC0000409 are much easier
  // to recognize and differentiate in hex.
  if (static_cast<int>(exit_code) < -100) {
    FAIL() << "Unexpected termination of a renderer process"
           << "; status: " << info.status << ", exit_code: 0x" << std::hex
           << exit_code;
  } else {
    // Print other error codes as a signed integer so that small negative
    // numbers are also recognizable.
    FAIL() << "Unexpected termination of a renderer process"
           << "; status: " << info.status << ", exit_code: " << exit_code;
  }
}

void NoRendererCrashesAssertion::RenderProcessHostDestroyed(
    RenderProcessHost* host) {
  CHECK(process_observations_.IsObservingSource(host));
  process_observations_.RemoveObservation(host);
}

ScopedAllowRendererCrashes::ScopedAllowRendererCrashes()
    : ScopedAllowRendererCrashes(nullptr) {}

ScopedAllowRendererCrashes::ScopedAllowRendererCrashes(
    RenderProcessHost* process)
    : process_id_(process ? process->GetDeprecatedID()
                          : ChildProcessHost::kInvalidUniqueID) {
  NoRendererCrashesAssertion::Suspensions::GetInstance().AddSuspension(
      process_id_);
}

ScopedAllowRendererCrashes::ScopedAllowRendererCrashes(
    const ToRenderFrameHost& frame)
    : ScopedAllowRendererCrashes(frame.render_frame_host()->GetProcess()) {}

ScopedAllowRendererCrashes::~ScopedAllowRendererCrashes() {
  NoRendererCrashesAssertion::Suspensions::GetInstance().RemoveSuspension(
      process_id_);
}

}  // namespace content
