// 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 <algorithm>
#include <cstring>
#include <iterator>
#include <memory>
#include <numeric>
#include <utility>

#include "base/functional/bind.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/raw_ref.h"
#include "base/profiler/module_cache.h"
#include "base/profiler/profile_builder.h"
#include "base/profiler/stack_buffer.h"
#include "base/profiler/stack_copier.h"
#include "base/profiler/stack_sampler.h"
#include "base/profiler/stack_sampling_profiler_test_util.h"
#include "base/profiler/suspendable_thread_delegate.h"
#include "base/profiler/unwinder.h"
#include "base/test/metrics/histogram_tester.h"
#include "build/build_config.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace base {

namespace {

using ::testing::ElementsAre;

class TestProfileBuilder : public ProfileBuilder {
 public:
  TestProfileBuilder(ModuleCache* module_cache) : module_cache_(module_cache) {}

  TestProfileBuilder(const TestProfileBuilder&) = delete;
  TestProfileBuilder& operator=(const TestProfileBuilder&) = delete;

  // ProfileBuilder
  ModuleCache* GetModuleCache() override { return module_cache_; }
  void RecordMetadata(
      const MetadataRecorder::MetadataProvider& metadata_provider) override {}

  void OnSampleCompleted(std::vector<Frame> frames,
                         TimeTicks sample_timestamp) override {
    last_timestamp_ = sample_timestamp;
  }

  void OnProfileCompleted(TimeDelta profile_duration,
                          TimeDelta sampling_period) override {}

  TimeTicks last_timestamp() { return last_timestamp_; }

 private:
  raw_ptr<ModuleCache> module_cache_;
  TimeTicks last_timestamp_;
};

// A stack copier for use in tests that provides the expected behavior when
// operating on the supplied fake stack.
class TestStackCopier : public StackCopier {
 public:
  TestStackCopier(const std::vector<uintptr_t>& fake_stack,
                  TimeTicks timestamp = TimeTicks())
      : fake_stack_(fake_stack), timestamp_(timestamp) {}

  bool CopyStack(StackBuffer* stack_buffer,
                 uintptr_t* stack_top,
                 TimeTicks* timestamp,
                 RegisterContext* thread_context,
                 Delegate* delegate) override {
    std::memcpy(stack_buffer->buffer(), &(*fake_stack_)[0],
                fake_stack_->size() * sizeof((*fake_stack_)[0]));
    *stack_top = reinterpret_cast<uintptr_t>(stack_buffer->buffer() +
                                             fake_stack_->size());
    // Set the stack pointer to be consistent with the copied stack.
    *thread_context = {};
    RegisterContextStackPointer(thread_context) =
        reinterpret_cast<uintptr_t>(stack_buffer->buffer());

    *timestamp = timestamp_;

    return true;
  }

 private:
  // Must be a reference to retain the underlying allocation from the vector
  // passed to the constructor.
  const raw_ref<const std::vector<uintptr_t>> fake_stack_;

  const TimeTicks timestamp_;
};

// A StackCopier that just invokes the expected functions on the delegate.
class DelegateInvokingStackCopier : public StackCopier {
 public:
  bool CopyStack(StackBuffer* stack_buffer,
                 uintptr_t* stack_top,
                 TimeTicks* timestamp,
                 RegisterContext* thread_context,
                 Delegate* delegate) override {
    *stack_top = reinterpret_cast<uintptr_t>(stack_buffer->buffer()) +
                 10;  // Make msan happy.
    delegate->OnStackCopy();
    return true;
  }
};

// Trivial unwinder implementation for testing.
class TestUnwinder : public Unwinder {
 public:
  explicit TestUnwinder(std::vector<uintptr_t>* stack_copy)
      : stack_copy_(stack_copy) {}

  bool CanUnwindFrom(const Frame& current_frame) const override { return true; }

  UnwindResult TryUnwind(RegisterContext* thread_context,
                         uintptr_t stack_top,
                         std::vector<Frame>* stack) override {
    auto* bottom = reinterpret_cast<uintptr_t*>(
        RegisterContextStackPointer(thread_context));
    *stack_copy_ =
        std::vector<uintptr_t>(bottom, reinterpret_cast<uintptr_t*>(stack_top));
    return UnwindResult::kCompleted;
  }

 private:
  raw_ptr<std::vector<uintptr_t>> stack_copy_;
};

// Records invocations of calls to OnStackCapture()/UpdateModules().
class CallRecordingUnwinder : public Unwinder {
 public:
  void OnStackCapture() override { on_stack_capture_was_invoked_ = true; }

  void UpdateModules() override { update_modules_was_invoked_ = true; }

  bool CanUnwindFrom(const Frame& current_frame) const override { return true; }

  UnwindResult TryUnwind(RegisterContext* thread_context,
                         uintptr_t stack_top,
                         std::vector<Frame>* stack) override {
    return UnwindResult::kUnrecognizedFrame;
  }

  bool on_stack_capture_was_invoked() const {
    return on_stack_capture_was_invoked_;
  }

  bool update_modules_was_invoked() const {
    return update_modules_was_invoked_;
  }

 private:
  bool on_stack_capture_was_invoked_ = false;
  bool update_modules_was_invoked_ = false;
};

// Utility function to form a vector from a single module.
std::vector<std::unique_ptr<const ModuleCache::Module>> ToModuleVector(
    std::unique_ptr<const ModuleCache::Module> module) {
  return std::vector<std::unique_ptr<const ModuleCache::Module>>(
      std::make_move_iterator(&module), std::make_move_iterator(&module + 1));
}

// Injects a fake module covering the initial instruction pointer value, to
// avoid asking the OS to look it up. Windows doesn't return a consistent error
// code when doing so, and we DCHECK_EQ the expected error code.
void InjectModuleForContextInstructionPointer(
    const std::vector<uintptr_t>& stack,
    ModuleCache* module_cache) {
  module_cache->AddCustomNativeModule(
      std::make_unique<TestModule>(stack[0], sizeof(uintptr_t)));
}

// Returns a plausible instruction pointer value for use in tests that don't
// care about the instruction pointer value in the context, and hence don't need
// InjectModuleForContextInstructionPointer().
uintptr_t GetTestInstructionPointer() {
  return reinterpret_cast<uintptr_t>(&GetTestInstructionPointer);
}

// An unwinder fake that replays the provided outputs.
class FakeTestUnwinder : public Unwinder {
 public:
  struct Result {
    Result(bool can_unwind)
        : can_unwind(can_unwind), result(UnwindResult::kUnrecognizedFrame) {}

    Result(UnwindResult result, std::vector<uintptr_t> instruction_pointers)
        : can_unwind(true),
          result(result),
          instruction_pointers(instruction_pointers) {}

    bool can_unwind;
    UnwindResult result;
    std::vector<uintptr_t> instruction_pointers;
  };

  // Construct the unwinder with the outputs. The relevant unwinder functions
  // are expected to be invoked at least as many times as the number of values
  // specified in the arrays (except for CanUnwindFrom() which will always
  // return true if provided an empty array.
  explicit FakeTestUnwinder(std::vector<Result> results)
      : results_(std::move(results)) {}

  FakeTestUnwinder(const FakeTestUnwinder&) = delete;
  FakeTestUnwinder& operator=(const FakeTestUnwinder&) = delete;

  bool CanUnwindFrom(const Frame& current_frame) const override {
    bool can_unwind = results_[current_unwind_].can_unwind;
    // NB: If CanUnwindFrom() returns false then TryUnwind() will not be
    // invoked, so current_unwind_ is guarantee to be incremented only once for
    // each result.
    if (!can_unwind)
      ++current_unwind_;
    return can_unwind;
  }

  UnwindResult TryUnwind(RegisterContext* thread_context,
                         uintptr_t stack_top,
                         std::vector<Frame>* stack) override {
    CHECK_LT(current_unwind_, results_.size());
    const Result& current_result = results_[current_unwind_];
    ++current_unwind_;
    CHECK(current_result.can_unwind);
    for (const auto instruction_pointer : current_result.instruction_pointers)
      stack->emplace_back(
          instruction_pointer,
          module_cache()->GetModuleForAddress(instruction_pointer));
    return current_result.result;
  }

 private:
  mutable size_t current_unwind_ = 0;
  std::vector<Result> results_;
};

StackSampler::UnwindersFactory MakeUnwindersFactory(
    std::unique_ptr<Unwinder> unwinder) {
  return BindOnce(
      [](std::unique_ptr<Unwinder> unwinder) {
        std::vector<std::unique_ptr<Unwinder>> unwinders;
        unwinders.push_back(std::move(unwinder));
        return unwinders;
      },
      std::move(unwinder));
}

base::circular_deque<std::unique_ptr<Unwinder>> MakeUnwinderCircularDeque(
    std::unique_ptr<Unwinder> native_unwinder,
    std::unique_ptr<Unwinder> aux_unwinder) {
  base::circular_deque<std::unique_ptr<Unwinder>> unwinders;
  if (native_unwinder)
    unwinders.push_front(std::move(native_unwinder));
  if (aux_unwinder)
    unwinders.push_front(std::move(aux_unwinder));
  return unwinders;
}

}  // namespace

TEST(StackSamplerTest, CopyStack) {
  ModuleCache module_cache;
  const std::vector<uintptr_t> stack = {0, 1, 2, 3, 4};
  InjectModuleForContextInstructionPointer(stack, &module_cache);
  std::vector<uintptr_t> stack_copy;
  std::unique_ptr<StackSampler> stack_sampler = StackSampler::CreateForTesting(
      std::make_unique<TestStackCopier>(stack),
      MakeUnwindersFactory(std::make_unique<TestUnwinder>(&stack_copy)),
      &module_cache);

  stack_sampler->Initialize();

  std::unique_ptr<StackBuffer> stack_buffer =
      std::make_unique<StackBuffer>(stack.size() * sizeof(uintptr_t));
  TestProfileBuilder profile_builder(&module_cache);
  stack_sampler->RecordStackFrames(stack_buffer.get(), &profile_builder,
                                   PlatformThread::CurrentId());

  EXPECT_EQ(stack, stack_copy);
}

#if BUILDFLAG(IS_CHROMEOS)
TEST(StackSamplerTest, RecordStackFramesUMAMetric) {
  HistogramTester histogram_tester;
  ModuleCache module_cache;
  std::vector<uintptr_t> stack;
  constexpr size_t UIntPtrsPerKilobyte = 1024 / sizeof(uintptr_t);
  // kExpectedSizeKB needs to be a fairly large number of kilobytes. The buckets
  // in UmaHistogramMemoryKB are big enough that small values are in the same
  // bucket as zero and less than zero, and testing that we added a sample in
  // that bucket means that the test won't fail if, for example, the
  // |stack_top - stack_bottom| subtraction was reversed and got a negative
  // value.
  constexpr int kExpectedSizeKB = 2048;
  for (uintptr_t i = 0; i <= (kExpectedSizeKB * UIntPtrsPerKilobyte) + 1; i++) {
    stack.push_back(i);
  }
  InjectModuleForContextInstructionPointer(stack, &module_cache);
  std::vector<uintptr_t> stack_copy;
  std::unique_ptr<StackSampler> stack_sampler = StackSampler::CreateForTesting(
      std::make_unique<TestStackCopier>(stack),
      MakeUnwindersFactory(std::make_unique<TestUnwinder>(&stack_copy)),
      &module_cache);

  stack_sampler->Initialize();

  std::unique_ptr<StackBuffer> stack_buffer =
      std::make_unique<StackBuffer>(stack.size() * sizeof(uintptr_t));
  TestProfileBuilder profile_builder(&module_cache);

  for (uint32_t i = 0; i < StackSampler::kUMAHistogramDownsampleAmount - 1;
       i++) {
    stack_sampler->RecordStackFrames(stack_buffer.get(), &profile_builder,
                                     PlatformThread::CurrentId());

    // Should have no new samples in the
    // Memory.StackSamplingProfiler.StackSampleSize2 histogram.
    histogram_tester.ExpectUniqueSample(
        "Memory.StackSamplingProfiler.StackSampleSize2", kExpectedSizeKB, 0);
  }

  stack_sampler->RecordStackFrames(stack_buffer.get(), &profile_builder,
                                   PlatformThread::CurrentId());

  histogram_tester.ExpectUniqueSample(
      "Memory.StackSamplingProfiler.StackSampleSize2", kExpectedSizeKB, 1);
}
#endif  // #if BUILDFLAG(IS_CHROMEOS)

TEST(StackSamplerTest, CopyStackTimestamp) {
  ModuleCache module_cache;
  const std::vector<uintptr_t> stack = {0};
  InjectModuleForContextInstructionPointer(stack, &module_cache);
  std::vector<uintptr_t> stack_copy;
  TimeTicks timestamp = TimeTicks::UnixEpoch();
  std::unique_ptr<StackSampler> stack_sampler = StackSampler::CreateForTesting(
      std::make_unique<TestStackCopier>(stack, timestamp),
      MakeUnwindersFactory(std::make_unique<TestUnwinder>(&stack_copy)),
      &module_cache);

  stack_sampler->Initialize();

  std::unique_ptr<StackBuffer> stack_buffer =
      std::make_unique<StackBuffer>(stack.size() * sizeof(uintptr_t));
  TestProfileBuilder profile_builder(&module_cache);
  stack_sampler->RecordStackFrames(stack_buffer.get(), &profile_builder,
                                   PlatformThread::CurrentId());

  EXPECT_EQ(timestamp, profile_builder.last_timestamp());
}

TEST(StackSamplerTest, UnwinderInvokedWhileRecordingStackFrames) {
  std::unique_ptr<StackBuffer> stack_buffer = std::make_unique<StackBuffer>(10);
  auto owned_unwinder = std::make_unique<CallRecordingUnwinder>();
  CallRecordingUnwinder* unwinder = owned_unwinder.get();
  ModuleCache module_cache;
  TestProfileBuilder profile_builder(&module_cache);
  std::unique_ptr<StackSampler> stack_sampler = StackSampler::CreateForTesting(
      std::make_unique<DelegateInvokingStackCopier>(),
      MakeUnwindersFactory(std::move(owned_unwinder)), &module_cache);

  stack_sampler->Initialize();

  stack_sampler->RecordStackFrames(stack_buffer.get(), &profile_builder,
                                   PlatformThread::CurrentId());

  EXPECT_TRUE(unwinder->on_stack_capture_was_invoked());
  EXPECT_TRUE(unwinder->update_modules_was_invoked());
}

TEST(StackSamplerTest, AuxUnwinderInvokedWhileRecordingStackFrames) {
  std::unique_ptr<StackBuffer> stack_buffer = std::make_unique<StackBuffer>(10);
  ModuleCache module_cache;
  TestProfileBuilder profile_builder(&module_cache);
  std::unique_ptr<StackSampler> stack_sampler = StackSampler::CreateForTesting(
      std::make_unique<DelegateInvokingStackCopier>(),
      MakeUnwindersFactory(std::make_unique<CallRecordingUnwinder>()),
      &module_cache);

  stack_sampler->Initialize();

  auto owned_aux_unwinder = std::make_unique<CallRecordingUnwinder>();
  CallRecordingUnwinder* aux_unwinder = owned_aux_unwinder.get();
  stack_sampler->AddAuxUnwinder(std::move(owned_aux_unwinder));

  stack_sampler->RecordStackFrames(stack_buffer.get(), &profile_builder,
                                   PlatformThread::CurrentId());

  EXPECT_TRUE(aux_unwinder->on_stack_capture_was_invoked());
  EXPECT_TRUE(aux_unwinder->update_modules_was_invoked());
}

TEST(StackSamplerTest, WalkStack_Completed) {
  ModuleCache module_cache;
  RegisterContext thread_context;
  RegisterContextInstructionPointer(&thread_context) =
      GetTestInstructionPointer();
  module_cache.AddCustomNativeModule(std::make_unique<TestModule>(1u, 1u));
  auto native_unwinder =
      WrapUnique(new FakeTestUnwinder({{UnwindResult::kCompleted, {1u}}}));
  native_unwinder->Initialize(&module_cache);

  std::vector<Frame> stack = StackSampler::WalkStackForTesting(
      &module_cache, &thread_context, 0u,
      MakeUnwinderCircularDeque(std::move(native_unwinder), nullptr));

  ASSERT_EQ(2u, stack.size());
  EXPECT_EQ(1u, stack[1].instruction_pointer);
}

TEST(StackSamplerTest, WalkStack_Aborted) {
  ModuleCache module_cache;
  RegisterContext thread_context;
  RegisterContextInstructionPointer(&thread_context) =
      GetTestInstructionPointer();
  module_cache.AddCustomNativeModule(std::make_unique<TestModule>(1u, 1u));
  auto native_unwinder =
      WrapUnique(new FakeTestUnwinder({{UnwindResult::kAborted, {1u}}}));
  native_unwinder->Initialize(&module_cache);

  std::vector<Frame> stack = StackSampler::WalkStackForTesting(
      &module_cache, &thread_context, 0u,
      MakeUnwinderCircularDeque(std::move(native_unwinder), nullptr));

  ASSERT_EQ(2u, stack.size());
  EXPECT_EQ(1u, stack[1].instruction_pointer);
}

TEST(StackSamplerTest, WalkStack_NotUnwound) {
  ModuleCache module_cache;
  RegisterContext thread_context;
  RegisterContextInstructionPointer(&thread_context) =
      GetTestInstructionPointer();
  auto native_unwinder = WrapUnique(
      new FakeTestUnwinder({{UnwindResult::kUnrecognizedFrame, {}}}));
  native_unwinder->Initialize(&module_cache);

  std::vector<Frame> stack = StackSampler::WalkStackForTesting(
      &module_cache, &thread_context, 0u,
      MakeUnwinderCircularDeque(std::move(native_unwinder), nullptr));

  ASSERT_EQ(1u, stack.size());
}

TEST(StackSamplerTest, WalkStack_AuxUnwind) {
  ModuleCache module_cache;
  RegisterContext thread_context;
  RegisterContextInstructionPointer(&thread_context) =
      GetTestInstructionPointer();

  // Treat the context instruction pointer as being in the aux unwinder's
  // non-native module.
  module_cache.UpdateNonNativeModules(
      {}, ToModuleVector(std::make_unique<TestModule>(
              GetTestInstructionPointer(), 1u, false)));

  auto aux_unwinder =
      WrapUnique(new FakeTestUnwinder({{UnwindResult::kAborted, {1u}}}));
  aux_unwinder->Initialize(&module_cache);
  std::vector<Frame> stack = StackSampler::WalkStackForTesting(
      &module_cache, &thread_context, 0u,
      MakeUnwinderCircularDeque(nullptr, std::move(aux_unwinder)));

  ASSERT_EQ(2u, stack.size());
  EXPECT_EQ(GetTestInstructionPointer(), stack[0].instruction_pointer);
  EXPECT_EQ(1u, stack[1].instruction_pointer);
}

TEST(StackSamplerTest, WalkStack_AuxThenNative) {
  ModuleCache module_cache;
  RegisterContext thread_context;
  RegisterContextInstructionPointer(&thread_context) = 0u;

  // Treat the context instruction pointer as being in the aux unwinder's
  // non-native module.
  module_cache.UpdateNonNativeModules(
      {}, ToModuleVector(std::make_unique<TestModule>(0u, 1u, false)));
  // Inject a fake native module for the second frame.
  module_cache.AddCustomNativeModule(std::make_unique<TestModule>(1u, 1u));

  auto aux_unwinder = WrapUnique(
      new FakeTestUnwinder({{UnwindResult::kUnrecognizedFrame, {1u}}, false}));
  aux_unwinder->Initialize(&module_cache);
  auto native_unwinder =
      WrapUnique(new FakeTestUnwinder({{UnwindResult::kCompleted, {2u}}}));
  native_unwinder->Initialize(&module_cache);

  std::vector<Frame> stack = StackSampler::WalkStackForTesting(
      &module_cache, &thread_context, 0u,
      MakeUnwinderCircularDeque(std::move(native_unwinder),
                                std::move(aux_unwinder)));

  ASSERT_EQ(3u, stack.size());
  EXPECT_EQ(0u, stack[0].instruction_pointer);
  EXPECT_EQ(1u, stack[1].instruction_pointer);
  EXPECT_EQ(2u, stack[2].instruction_pointer);
}

TEST(StackSamplerTest, WalkStack_NativeThenAux) {
  ModuleCache module_cache;
  RegisterContext thread_context;
  RegisterContextInstructionPointer(&thread_context) = 0u;

  // Inject fake native modules for the instruction pointer from the context and
  // the third frame.
  module_cache.AddCustomNativeModule(std::make_unique<TestModule>(0u, 1u));
  module_cache.AddCustomNativeModule(std::make_unique<TestModule>(2u, 1u));
  // Treat the second frame's pointer as being in the aux unwinder's non-native
  // module.
  module_cache.UpdateNonNativeModules(
      {}, ToModuleVector(std::make_unique<TestModule>(1u, 1u, false)));

  auto aux_unwinder = WrapUnique(new FakeTestUnwinder(
      {{false}, {UnwindResult::kUnrecognizedFrame, {2u}}, {false}}));
  aux_unwinder->Initialize(&module_cache);
  auto native_unwinder =
      WrapUnique(new FakeTestUnwinder({{UnwindResult::kUnrecognizedFrame, {1u}},
                                       {UnwindResult::kCompleted, {3u}}}));
  native_unwinder->Initialize(&module_cache);

  std::vector<Frame> stack = StackSampler::WalkStackForTesting(
      &module_cache, &thread_context, 0u,
      MakeUnwinderCircularDeque(std::move(native_unwinder),
                                std::move(aux_unwinder)));

  ASSERT_EQ(4u, stack.size());
  EXPECT_EQ(0u, stack[0].instruction_pointer);
  EXPECT_EQ(1u, stack[1].instruction_pointer);
  EXPECT_EQ(2u, stack[2].instruction_pointer);
  EXPECT_EQ(3u, stack[3].instruction_pointer);
}

}  // namespace base
