//===-- ThreadPlanRunToAddress.cpp ------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "lldb/Target/ThreadPlanRunToAddress.h"
#include "lldb/Target/Process.h"
#include "lldb/Target/RegisterContext.h"
#include "lldb/Target/Target.h"
#include "lldb/Target/Thread.h"
#include "lldb/Utility/Log.h"
#include "lldb/Utility/Stream.h"

using namespace lldb;
using namespace lldb_private;

// ThreadPlanRunToAddress: Continue plan

ThreadPlanRunToAddress::ThreadPlanRunToAddress(Thread &thread, Address &address,
                                               bool stop_others)
    : ThreadPlan(ThreadPlan::eKindRunToAddress, "Run to address plan", thread,
                 eVoteNoOpinion, eVoteNoOpinion),
      m_stop_others(stop_others), m_addresses(), m_break_ids() {
  m_addresses.push_back(
      address.GetOpcodeLoadAddress(m_thread.CalculateTarget().get()));
  SetInitialBreakpoints();
}

ThreadPlanRunToAddress::ThreadPlanRunToAddress(Thread &thread,
                                               lldb::addr_t address,
                                               bool stop_others)
    : ThreadPlan(ThreadPlan::eKindRunToAddress, "Run to address plan", thread,
                 eVoteNoOpinion, eVoteNoOpinion),
      m_stop_others(stop_others), m_addresses(), m_break_ids() {
  m_addresses.push_back(
      m_thread.CalculateTarget()->GetOpcodeLoadAddress(address));
  SetInitialBreakpoints();
}

ThreadPlanRunToAddress::ThreadPlanRunToAddress(
    Thread &thread, const std::vector<lldb::addr_t> &addresses,
    bool stop_others)
    : ThreadPlan(ThreadPlan::eKindRunToAddress, "Run to address plan", thread,
                 eVoteNoOpinion, eVoteNoOpinion),
      m_stop_others(stop_others), m_addresses(addresses), m_break_ids() {
  // Convert all addresses into opcode addresses to make sure we set
  // breakpoints at the correct address.
  Target &target = thread.GetProcess()->GetTarget();
  std::vector<lldb::addr_t>::iterator pos, end = m_addresses.end();
  for (pos = m_addresses.begin(); pos != end; ++pos)
    *pos = target.GetOpcodeLoadAddress(*pos);

  SetInitialBreakpoints();
}

void ThreadPlanRunToAddress::SetInitialBreakpoints() {
  size_t num_addresses = m_addresses.size();
  m_break_ids.resize(num_addresses);

  for (size_t i = 0; i < num_addresses; i++) {
    Breakpoint *breakpoint;
    breakpoint = m_thread.CalculateTarget()
                     ->CreateBreakpoint(m_addresses[i], true, false)
                     .get();
    if (breakpoint != nullptr) {
      if (breakpoint->IsHardware() && !breakpoint->HasResolvedLocations())
        m_could_not_resolve_hw_bp = true;
      m_break_ids[i] = breakpoint->GetID();
      breakpoint->SetThreadID(m_thread.GetID());
      breakpoint->SetBreakpointKind("run-to-address");
    }
  }
}

ThreadPlanRunToAddress::~ThreadPlanRunToAddress() {
  size_t num_break_ids = m_break_ids.size();
  for (size_t i = 0; i < num_break_ids; i++) {
    m_thread.CalculateTarget()->RemoveBreakpointByID(m_break_ids[i]);
  }
  m_could_not_resolve_hw_bp = false;
}

void ThreadPlanRunToAddress::GetDescription(Stream *s,
                                            lldb::DescriptionLevel level) {
  size_t num_addresses = m_addresses.size();

  if (level == lldb::eDescriptionLevelBrief) {
    if (num_addresses == 0) {
      s->Printf("run to address with no addresses given.");
      return;
    } else if (num_addresses == 1)
      s->Printf("run to address: ");
    else
      s->Printf("run to addresses: ");

    for (size_t i = 0; i < num_addresses; i++) {
      s->Address(m_addresses[i], sizeof(addr_t));
      s->Printf(" ");
    }
  } else {
    if (num_addresses == 0) {
      s->Printf("run to address with no addresses given.");
      return;
    } else if (num_addresses == 1)
      s->Printf("Run to address: ");
    else {
      s->Printf("Run to addresses: ");
    }

    for (size_t i = 0; i < num_addresses; i++) {
      if (num_addresses > 1) {
        s->Printf("\n");
        s->Indent();
      }

      s->Address(m_addresses[i], sizeof(addr_t));
      s->Printf(" using breakpoint: %d - ", m_break_ids[i]);
      Breakpoint *breakpoint =
          m_thread.CalculateTarget()->GetBreakpointByID(m_break_ids[i]).get();
      if (breakpoint)
        breakpoint->Dump(s);
      else
        s->Printf("but the breakpoint has been deleted.");
    }
  }
}

bool ThreadPlanRunToAddress::ValidatePlan(Stream *error) {
  if (m_could_not_resolve_hw_bp) {
    if (error)
      error->Printf("Could not set hardware breakpoint(s)");
    return false;
  }

  // If we couldn't set the breakpoint for some reason, then this won't work.
  bool all_bps_good = true;
  size_t num_break_ids = m_break_ids.size();
  for (size_t i = 0; i < num_break_ids; i++) {
    if (m_break_ids[i] == LLDB_INVALID_BREAK_ID) {
      all_bps_good = false;
      if (error) {
        error->Printf("Could not set breakpoint for address: ");
        error->Address(m_addresses[i], sizeof(addr_t));
        error->Printf("\n");
      }
    }
  }
  return all_bps_good;
}

bool ThreadPlanRunToAddress::DoPlanExplainsStop(Event *event_ptr) {
  return AtOurAddress();
}

bool ThreadPlanRunToAddress::ShouldStop(Event *event_ptr) {
  return AtOurAddress();
}

bool ThreadPlanRunToAddress::StopOthers() { return m_stop_others; }

void ThreadPlanRunToAddress::SetStopOthers(bool new_value) {
  m_stop_others = new_value;
}

StateType ThreadPlanRunToAddress::GetPlanRunState() { return eStateRunning; }

bool ThreadPlanRunToAddress::WillStop() { return true; }

bool ThreadPlanRunToAddress::MischiefManaged() {
  Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_STEP));

  if (AtOurAddress()) {
    // Remove the breakpoint
    size_t num_break_ids = m_break_ids.size();

    for (size_t i = 0; i < num_break_ids; i++) {
      if (m_break_ids[i] != LLDB_INVALID_BREAK_ID) {
        m_thread.CalculateTarget()->RemoveBreakpointByID(m_break_ids[i]);
        m_break_ids[i] = LLDB_INVALID_BREAK_ID;
      }
    }
    LLDB_LOGF(log, "Completed run to address plan.");
    ThreadPlan::MischiefManaged();
    return true;
  } else
    return false;
}

bool ThreadPlanRunToAddress::AtOurAddress() {
  lldb::addr_t current_address = m_thread.GetRegisterContext()->GetPC();
  bool found_it = false;
  size_t num_addresses = m_addresses.size();
  for (size_t i = 0; i < num_addresses; i++) {
    if (m_addresses[i] == current_address) {
      found_it = true;
      break;
    }
  }
  return found_it;
}
