910e62b5创建于 1月15日历史提交
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/allocation_recorder/testing/crash_verification.h"

#include <algorithm>
#include <iterator>
#include <memory>
#include <ostream>
#include <vector>

#include "base/debug/debugging_buildflags.h"
#include "base/files/file_path.h"
#include "base/functional/callback.h"
#include "base/threading/platform_thread.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "components/allocation_recorder/internal/internal.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/crashpad/crashpad/client/crash_report_database.h"
#include "third_party/crashpad/crashpad/snapshot/minidump/minidump_stream.h"
#include "third_party/crashpad/crashpad/snapshot/minidump/process_snapshot_minidump.h"
#include "third_party/crashpad/crashpad/util/misc/uuid.h"

#if BUILDFLAG(ENABLE_ALLOCATION_STACK_TRACE_RECORDER)
#include "components/allocation_recorder/crash_handler/memory_operation_report.pb.h"
#endif

using testing::AssertionFailure;
using testing::AssertionResult;
using testing::AssertionSuccess;

namespace crashpad {
void PrintTo(const CrashReportDatabase::OperationStatus status,
             std::ostream* os) {
  switch (status) {
    case CrashReportDatabase::kNoError:
      *os << "CrashReportDatabase::OperationStatus::kNoError";
      break;
    case CrashReportDatabase::kReportNotFound:
      *os << "CrashReportDatabase::OperationStatus::kReportNotFound";
      break;
    case CrashReportDatabase::kFileSystemError:
      *os << "CrashReportDatabase::OperationStatus::kFileSystemError";
      break;
    case CrashReportDatabase::kDatabaseError:
      *os << "CrashReportDatabase::OperationStatus::kDatabaseError";
      break;
    case CrashReportDatabase::kBusyError:
      *os << "CrashReportDatabase::OperationStatus::kBusyError";
      break;
    case CrashReportDatabase::kCannotRequestUpload:
      *os << "CrashReportDatabase::OperationStatus::kCannotRequestUpload";
      break;
  }
}
}  // namespace crashpad

namespace {

class CrashpadIntegration {
 public:
  CrashpadIntegration() = default;
  ~CrashpadIntegration() {
    if (crash_report_database_) {
      ShutDown();
    }
  }

  void SetUp(const base::FilePath& crashpad_database_path);
  void ShutDown();

  // Get all MinidumpStreams whose stream type equals
  // |allocation_recorder::internal::kStreamDataType|.
  std::vector<const crashpad::MinidumpStream*> GetAllocationRecorderStreams();

  void CheckHasNoAllocationRecorderStream();

#if BUILDFLAG(ENABLE_ALLOCATION_STACK_TRACE_RECORDER)
  void GetPayload(allocation_recorder::Payload& payload);
#endif

 private:
  void CrashDatabaseHasReport();
  void ReportHasProcessSnapshot();

#if BUILDFLAG(ENABLE_ALLOCATION_STACK_TRACE_RECORDER)
  void GetAllocationRecorderStream(
      const crashpad::MinidumpStream*& allocation_recorder_stream);
#endif

  // Try and read the created report from the database of crash reports. Errors
  // such as failure to read the list of pending reports will always result in a
  // fatal error.
  void TryReadCreatedReport();

  base::FilePath database_dir_;

  std::unique_ptr<crashpad::CrashReportDatabase> crash_report_database_;

  std::unique_ptr<crashpad::CrashReportDatabase::Report> report_;
  std::unique_ptr<crashpad::ProcessSnapshotMinidump> minidump_process_snapshot_;
};

void CrashpadIntegration::SetUp(const base::FilePath& crashpad_database_path) {
  database_dir_ = crashpad_database_path;

  crash_report_database_ =
      crashpad::CrashReportDatabase::InitializeWithoutCreating(database_dir_);
  ASSERT_NE(crash_report_database_, nullptr)
      << "Failed to load crash database. database='" << database_dir_ << '\'';

  std::vector<crashpad::CrashReportDatabase::Report> reports;
  ASSERT_EQ(crashpad::CrashReportDatabase::kNoError,
            crash_report_database_->GetPendingReports(&reports))
      << "Failed to read list of old pending reports. database='"
      << database_dir_ << '\'';
  ASSERT_EQ(reports.size(), 0u)
      << "Expected no reports at setup time. "
      << "Please choose a unique temporary crashpad_database_path.";
}

void CrashpadIntegration::ShutDown() {
  if (report_) {
    const auto report_deletion_result =
        crash_report_database_->DeleteReport(report_->uuid);
    EXPECT_EQ(crashpad::CrashReportDatabase::kNoError, report_deletion_result);
  }

  minidump_process_snapshot_ = {};
  report_ = {};
  crash_report_database_ = {};
  database_dir_ = {};
}

void CrashpadIntegration::CrashDatabaseHasReport() {
  // The Crashpad report might not have been written yet. Try to read the report
  // multiple times without asserting success. Only in the very last try we
  // assert that a new report is present.
  constexpr auto maximum_total_retry_duration = base::Seconds(5);
  constexpr auto wait_time_between_retries = base::Milliseconds(200);

  const auto time_out_to_last_nonfatal_try =
      base::Time::Now() + maximum_total_retry_duration;

  while (base::Time::Now() <= time_out_to_last_nonfatal_try) {
    ASSERT_NO_FATAL_FAILURE(TryReadCreatedReport());

    if (report_) {
      return;
    }

    base::PlatformThreadBase::Sleep(wait_time_between_retries);
  }

  ASSERT_NO_FATAL_FAILURE(TryReadCreatedReport());
  ASSERT_NE(report_, nullptr)
      << "Found no new report. database='" << database_dir_ << '\'';
}

void CrashpadIntegration::ReportHasProcessSnapshot() {
  crashpad::FileReader file_reader;

  minidump_process_snapshot_ =
      std::make_unique<crashpad::ProcessSnapshotMinidump>();

  ASSERT_TRUE(file_reader.Open(report_->file_path))
      << "Failed to open dump file. path='" << report_->file_path << '\'';

  ASSERT_TRUE(minidump_process_snapshot_->Initialize(&file_reader))
      << "Failed to initialize process snapshot "
         "from report file. path='"
      << report_->file_path << '\'';
}

std::vector<const crashpad::MinidumpStream*>
CrashpadIntegration::GetAllocationRecorderStreams() {
  const auto stream_has_correct_type_predicate =
      [](const crashpad::MinidumpStream* const stream) {
        return stream && stream->stream_type() ==
                             allocation_recorder::internal::kStreamDataType;
      };

  const auto& custom_minidump_streams =
      minidump_process_snapshot_->CustomMinidumpStreams();

  std::vector<const crashpad::MinidumpStream*> allocation_recorder_streams;

  std::copy_if(std::begin(custom_minidump_streams),
               std::end(custom_minidump_streams),
               std::back_inserter(allocation_recorder_streams),
               stream_has_correct_type_predicate);

  return allocation_recorder_streams;
}

void CrashpadIntegration::TryReadCreatedReport() {
  std::vector<crashpad::CrashReportDatabase::Report> reports;
  ASSERT_EQ(crashpad::CrashReportDatabase::kNoError,
            crash_report_database_->GetPendingReports(&reports))
      << "Failed to read list of pending reports. database='" << database_dir_
      << '\'';
  ASSERT_EQ(reports.size(), 1u);
  report_ = std::make_unique<crashpad::CrashReportDatabase::Report>(reports[0]);
}

void CrashpadIntegration::CheckHasNoAllocationRecorderStream() {
  ASSERT_NO_FATAL_FAILURE(CrashDatabaseHasReport());
  ASSERT_NO_FATAL_FAILURE(ReportHasProcessSnapshot());

  const auto allocation_recorder_streams = GetAllocationRecorderStreams();

  EXPECT_EQ(std::size(allocation_recorder_streams), 0u)
      << "Found at least one allocation recorder stream.";
}

#if BUILDFLAG(ENABLE_ALLOCATION_STACK_TRACE_RECORDER)
void CrashpadIntegration::GetAllocationRecorderStream(
    const crashpad::MinidumpStream*& allocation_recorder_stream) {
  const auto allocation_recorder_streams = GetAllocationRecorderStreams();

  ASSERT_EQ(std::size(allocation_recorder_streams), 1u)
      << "Didn't find expected number of allocation recorder streams.";
  ASSERT_NE(allocation_recorder_streams.front(), nullptr)
      << "The only allocation recorder stream is nullptr.";

  allocation_recorder_stream = allocation_recorder_streams.front();
}

void CrashpadIntegration::GetPayload(allocation_recorder::Payload& payload) {
  ASSERT_NO_FATAL_FAILURE(CrashDatabaseHasReport());
  ASSERT_NO_FATAL_FAILURE(ReportHasProcessSnapshot());

  const crashpad::MinidumpStream* allocation_recorder_stream;
  ASSERT_NO_FATAL_FAILURE(
      GetAllocationRecorderStream(allocation_recorder_stream));
  const std::vector<uint8_t>& data = allocation_recorder_stream->data();

  ASSERT_TRUE(payload.ParseFromArray(std::data(data), std::size(data)))
      << "Failed to parse recorder information "
         "from recorder stream.";
}
#endif
}  // namespace

namespace allocation_recorder::testing {

#if BUILDFLAG(ENABLE_ALLOCATION_STACK_TRACE_RECORDER)
void VerifyCrashCreatesCrashpadReportWithAllocationRecorderStream(
    const base::FilePath& crashpad_database_path,
    base::OnceClosure crash_function,
    base::OnceCallback<void(const allocation_recorder::Payload& payload)>
        payload_verification) {
  ASSERT_TRUE(crash_function);
  CrashpadIntegration crashpad_integration;
  ASSERT_NO_FATAL_FAILURE(crashpad_integration.SetUp(crashpad_database_path));
  ASSERT_NO_FATAL_FAILURE(std::move(crash_function).Run());

  allocation_recorder::Payload payload;
  ASSERT_NO_FATAL_FAILURE(crashpad_integration.GetPayload(payload));

  if (payload_verification) {
    ASSERT_NO_FATAL_FAILURE(std::move(payload_verification).Run(payload));
  }
  ASSERT_NO_FATAL_FAILURE(crashpad_integration.ShutDown());
}

void VerifyPayload(const bool expect_report_with_content,
                   const allocation_recorder::Payload& payload) {
  if (expect_report_with_content) {
    if (payload.has_processing_failures()) {
      const auto& failures = payload.processing_failures();
      const auto& messages = failures.messages();
      ASSERT_GT(messages.size(), 0);
      FAIL() << "Payload has unexpected processing failure:\n" << messages[0];
    }
    ASSERT_TRUE(payload.has_operation_report());
    const auto& operation_report = payload.operation_report();

    ASSERT_TRUE(operation_report.has_statistics());
    const auto& statistics = operation_report.statistics();

    EXPECT_GT(operation_report.memory_operations_size(), 0);
    EXPECT_GT(statistics.total_number_of_operations(), 0u);

#if BUILDFLAG(ENABLE_ALLOCATION_TRACE_RECORDER_FULL_REPORTING)
    EXPECT_TRUE(statistics.has_total_number_of_collisions());
#endif
  } else {
    ASSERT_TRUE(payload.has_processing_failures());
    const auto& failures = payload.processing_failures();
    const auto& messages = failures.messages();
    ASSERT_EQ(messages.size(), 1);
    ASSERT_EQ(
        messages[0],
        "No annotation found! required-name=allocation-recorder-crash-info");
  }
}
#else
void VerifyCrashCreatesCrashpadReportWithoutAllocationRecorderStream(
    const base::FilePath& crashpad_database_path,
    base::OnceClosure crash_function) {
  ASSERT_TRUE(crash_function);
  CrashpadIntegration crashpad_integration;
  ASSERT_NO_FATAL_FAILURE(crashpad_integration.SetUp(crashpad_database_path));
  ASSERT_NO_FATAL_FAILURE(std::move(crash_function).Run());
  ASSERT_NO_FATAL_FAILURE(crashpad_integration.ShutDown());
}
#endif  // BUILDFLAG(ENABLE_ALLOCATION_STACK_TRACE_RECORDER)

}  // namespace allocation_recorder::testing