chromium/components/allocation_recorder/crash_handler/payload_unittests.cc

// 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/crash_handler/payload.h"

#include "base/allocator/dispatcher/notification_data.h"
#include "base/allocator/dispatcher/subsystem.h"
#include "base/bits.h"
#include "base/containers/span.h"
#include "base/debug/allocation_trace.h"
#include "base/ranges/algorithm.h"
#include "testing/gtest/include/gtest/gtest.h"

using base::allocator::dispatcher::AllocationNotificationData;
using base::allocator::dispatcher::AllocationSubsystem;
using base::allocator::dispatcher::FreeNotificationData;
using base::debug::tracer::AllocationTraceRecorder;
using base::debug::tracer::AllocationTraceRecorderStatistics;
using base::debug::tracer::OperationRecord;
using testing::TestWithParam;

namespace allocation_recorder::crash_handler {
namespace {

void CreateFakeAllocationData(AllocationTraceRecorder& recorder,
                              uint64_t number_of_entries) {
  for (uint64_t entry_counter = 0; entry_counter < number_of_entries;
       ++entry_counter) {
    if (entry_counter & 0x1) {
      recorder.OnFree(
          FreeNotificationData(reinterpret_cast<void*>(entry_counter),
                               AllocationSubsystem::kPartitionAllocator));
    } else {
      recorder.OnAllocation(
          AllocationNotificationData(&recorder, entry_counter, nullptr,
                                     AllocationSubsystem::kPartitionAllocator));
    }
  }
}

void VerifyAllocationEntriesAreEqual(
    const base::debug::tracer::OperationRecord& source_entry,
    const ::allocation_recorder::MemoryOperation& report_entry) {
  EXPECT_EQ(reinterpret_cast<uint64_t>(source_entry.GetAddress()),
            report_entry.address());

  if (source_entry.GetOperationType() ==
      base::debug::tracer::OperationType::kAllocation) {
    ASSERT_TRUE(report_entry.has_size());
    EXPECT_EQ(source_entry.GetSize(), report_entry.size());
  } else {
    ASSERT_FALSE(report_entry.has_size());
  }

  switch (source_entry.GetOperationType()) {
    case base::debug::tracer::OperationType::kAllocation:
      EXPECT_EQ(report_entry.operation_type(),
                ::allocation_recorder::OperationType::ALLOCATION);
      break;
    case base::debug::tracer::OperationType::kFree:
      EXPECT_EQ(report_entry.operation_type(),
                ::allocation_recorder::OperationType::FREE);
      break;
    case base::debug::tracer::OperationType::kNone:
      ASSERT_NE(source_entry.GetOperationType(),
                base::debug::tracer::OperationType::kNone);
  }

  ASSERT_TRUE(report_entry.has_stack_trace());
  ASSERT_LE(std::ssize(report_entry.stack_trace().frames()),
            std::ssize(source_entry.GetStackTrace()));

  const auto& report_frames = report_entry.stack_trace().frames();
  std::vector<const void*> converted_frames;
  base::ranges::transform(
      report_frames, std::back_inserter(converted_frames),
      [](const allocation_recorder::StackFrame& frame) {
        return reinterpret_cast<const void*>(frame.address());
      });

  const auto [converted_call_stack_mismatch, source_call_stack_mismatch] =
      std::mismatch(std::begin(converted_frames), std::end(converted_frames),
                    std::begin(source_entry.GetStackTrace()));

  ASSERT_EQ(converted_call_stack_mismatch, std::end(converted_frames));
  EXPECT_TRUE(std::all_of(source_call_stack_mismatch,
                          std::end(source_entry.GetStackTrace()),
                          [](const void* ptr) { return ptr == nullptr; }));
}

}  // namespace

class CreatePayloadWithMemoryOperationReportTest : public testing::Test {
 public:
  AllocationTraceRecorder& GetRecorder() { return *recorder_; }

 private:
  // The recorder under test. Depending on number and size of traces, it
  // requires quite a lot of space. Therefore, we create it on heap to avoid any
  // out-of-stack scenarios.
  std::unique_ptr<AllocationTraceRecorder> const recorder_ =
      std::make_unique<AllocationTraceRecorder>();
};

TEST_F(CreatePayloadWithMemoryOperationReportTest, Verify) {
  constexpr size_t number_of_entries = 1024;
  auto& recorder = GetRecorder();

  CreateFakeAllocationData(recorder, number_of_entries);

  ASSERT_EQ(recorder.size(), number_of_entries);

  const allocation_recorder::Payload payload =
      CreatePayloadWithMemoryOperationReport(recorder);

  ASSERT_TRUE(payload.operation_report().has_statistics());
  EXPECT_EQ(
      number_of_entries,
      payload.operation_report().statistics().total_number_of_operations());
#if BUILDFLAG(ENABLE_ALLOCATION_TRACE_RECORDER_FULL_REPORTING)
  ASSERT_TRUE(
      payload.operation_report().statistics().has_total_number_of_collisions());
  EXPECT_EQ(
      0ul,
      payload.operation_report().statistics().total_number_of_collisions());
#endif

  ASSERT_TRUE(payload.has_operation_report());
  ASSERT_EQ(static_cast<int>(number_of_entries),
            payload.operation_report().memory_operations().size());

  for (size_t entry_index = 0; entry_index < number_of_entries; ++entry_index) {
    const auto& source_entry = recorder[entry_index];
    const auto& converted_entry =
        payload.operation_report().memory_operations().at(entry_index);

    VerifyAllocationEntriesAreEqual(source_entry, converted_entry);
  }
}

TEST_F(CreatePayloadWithMemoryOperationReportTest, VerifyErrorDataIsNotSet) {
  auto& recorder = GetRecorder();

  CreateFakeAllocationData(recorder, 8);

  const allocation_recorder::Payload payload =
      CreatePayloadWithMemoryOperationReport(recorder);

  EXPECT_FALSE(payload.has_processing_failures());
}

TEST(CreatePayloadWithProcessingFailuresTest, VerifySingleMessage) {
  const std::string_view message = "This is a very important message.";

  const allocation_recorder::Payload payload =
      CreatePayloadWithProcessingFailures(message);

  ASSERT_TRUE(payload.has_processing_failures());
  EXPECT_EQ(1, payload.processing_failures().messages().size());
  EXPECT_EQ(message, payload.processing_failures().messages().at(0));
}

TEST(CreatePayloadWithProcessingFailuresTest, VerifyMultipleMessages) {
  const std::string_view messages[] = {"This is a very important message.",
                                       "You'd better not ignore it."};

  const allocation_recorder::Payload payload =
      CreatePayloadWithProcessingFailures(base::make_span(messages));

  ASSERT_TRUE(payload.has_processing_failures());
  EXPECT_EQ(std::ssize(messages),
            payload.processing_failures().messages().size());

  for (int message_index = 0; const auto& source_message : messages) {
    EXPECT_EQ(source_message,
              payload.processing_failures().messages().at(message_index))
        << " at index=" << message_index;
    ++message_index;
  }
}

TEST(CreatePayloadWithProcessingFailuresTest, VerifyRegularReportDataIsNotSet) {
  const std::string_view message = "This is a very important message.";

  const allocation_recorder::Payload payload =
      CreatePayloadWithProcessingFailures(message);

  EXPECT_FALSE(payload.has_operation_report());
}

}  // namespace allocation_recorder::crash_handler