// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "src/developer/debug/zxdb/client/frame_impl.h"

#include "gtest/gtest.h"
#include "llvm/BinaryFormat/Dwarf.h"
#include "src/developer/debug/shared/platform_message_loop.h"
#include "src/developer/debug/zxdb/client/mock_remote_api.h"
#include "src/developer/debug/zxdb/client/process.h"
#include "src/developer/debug/zxdb/client/register.h"
#include "src/developer/debug/zxdb/client/remote_api_test.h"
#include "src/developer/debug/zxdb/client/thread.h"
#include "src/developer/debug/zxdb/symbols/function.h"
#include "src/developer/debug/zxdb/symbols/location.h"

namespace zxdb {

using debug_ipc::RegisterID;

class FrameImplTest : public RemoteAPITest {
 public:
  std::unique_ptr<RemoteAPI> GetRemoteAPIImpl() {
    auto remote_api = std::make_unique<MockRemoteAPI>();
    mock_remote_api_ = remote_api.get();
    return remote_api;
  }

  MockRemoteAPI* mock_remote_api() const { return mock_remote_api_; }

 private:
  MockRemoteAPI* mock_remote_api_ = nullptr;  // Owned by System.
};

class MockThread : public Thread, public Stack::Delegate {
 public:
  // The process and frame pointers must outlive this class.
  explicit MockThread(Process* process)
      : Thread(process->session()), process_(process), stack_(this) {}

  RegisterSet& register_contents() { return register_contents_; }

  // Thread implementation:
  Process* GetProcess() const override { return process_; }
  uint64_t GetKoid() const override { return 1234; }
  const std::string& GetName() const override { return thread_name_; }
  debug_ipc::ThreadRecord::State GetState() const override {
    return debug_ipc::ThreadRecord::State::kSuspended;
  }
  debug_ipc::ThreadRecord::BlockedReason GetBlockedReason() const override {
    return debug_ipc::ThreadRecord::BlockedReason::kNotBlocked;
  }
  void Pause(std::function<void()> on_paused) override {
    debug_ipc::MessageLoop::Current()->PostTask(FROM_HERE, [on_paused]() { on_paused(); });
  }
  void Continue() override {}
  void ContinueWith(std::unique_ptr<ThreadController> controller,
                    std::function<void(const Err&)> on_continue) override {}
  void JumpTo(uint64_t new_address, std::function<void(const Err&)> cb) override {}
  void NotifyControllerDone(ThreadController* controller) override {}
  void StepInstruction() override {}
  const Stack& GetStack() const override { return stack_; }
  Stack& GetStack() override { return stack_; }
  void ReadRegisters(std::vector<debug_ipc::RegisterCategory::Type> cats_to_get,
                     std::function<void(const Err&, const RegisterSet&)> cb) override {
    debug_ipc::MessageLoop::Current()->PostTask(
        FROM_HERE, [registers = register_contents_, cb]() { cb(Err(), registers); });
  }

 private:
  // Stack::Delegate implementation.
  void SyncFramesForStack(std::function<void(const Err&)> callback) override {
    FXL_NOTREACHED();  // All frames are available.
  }
  std::unique_ptr<Frame> MakeFrameForStack(const debug_ipc::StackFrame& input,
                                           Location location) override {
    FXL_NOTREACHED();  // Should not get called since we provide stack frames.
    return std::unique_ptr<Frame>();
  }
  Location GetSymbolizedLocationForStackFrame(const debug_ipc::StackFrame& input) override {
    return Location(Location::State::kSymbolized, input.ip);
  }

  std::string thread_name_ = "test thread";
  Process* process_;

  Stack stack_;

  RegisterSet register_contents_;
};

// Tests asynchronous evaluation and callbacks for evaluating the base pointer.
//
// This test uses the RemoteAPITest harness which normally creates ThreadImpls.
// But to get the stack frames the way they're needed, it creates its own
// thread implementation rather than relying on the ThreadImpl.
TEST_F(FrameImplTest, AsyncBasePointer) {
  // Make a process for notifying about.
  constexpr uint64_t kProcessKoid = 1234;
  Process* process = InjectProcess(kProcessKoid);

  // Provide a value for rax (DWARF reg 0) which will be used below.
  constexpr uint64_t kAddress = 0x86124309723;
  std::vector<debug_ipc::Register> frame_regs;
  frame_regs.emplace_back(RegisterID::kX64_rax, kAddress);

  const debug_ipc::StackFrame stack(0x12345678, 0x7890, 0, frame_regs);
  SymbolContext symbol_context = SymbolContext::ForRelativeAddresses();

  // Set the memory pointed to by the register.
  constexpr uint64_t kMemoryValue = 0x78362419047;
  std::vector<uint8_t> mem_value;
  mem_value.resize(sizeof(kMemoryValue));
  memcpy(&mem_value[0], &kMemoryValue, sizeof(kMemoryValue));
  mock_remote_api()->AddMemory(kAddress, mem_value);

  // This describes the frame base location for the function. This encodes
  // the memory pointed to by register 0.
  const uint8_t kSelectRegRef[2] = {llvm::dwarf::DW_OP_reg0, llvm::dwarf::DW_OP_deref};
  VariableLocation frame_base(kSelectRegRef, 2);

  auto function = fxl::MakeRefCounted<Function>(DwarfTag::kSubprogram);
  function->set_frame_base(frame_base);

  Location location(stack.ip, FileLine("file.cc", 12), 0, symbol_context, LazySymbol(function));

  MockThread thread(process);
  thread.register_contents().set_arch(debug_ipc::Arch::kX64);

  std::vector<std::unique_ptr<Frame>> frames;
  frames.push_back(std::make_unique<FrameImpl>(&thread, stack, location));
  Frame* frame = frames[0].get();
  thread.GetStack().SetFramesForTest(std::move(frames), true);

  // This should not be able to complete synchronously because the memory isn't
  // available synchronously.
  auto optional_base = frame->GetBasePointer();
  EXPECT_FALSE(optional_base);

  uint64_t result_base = 0;
  frame->GetBasePointerAsync([&result_base](uint64_t value) {
    result_base = value;
    debug_ipc::MessageLoop::Current()->QuitNow();
  });

  // The base pointer should have picked up our register0 value for the base
  // pointer.
  debug_ipc::MessageLoop::Current()->Run();
  EXPECT_EQ(kMemoryValue, result_base);
}

}  // namespace zxdb
