//===-- BreakpointLocation.cpp --------------------------------------------===//
//
// 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/Breakpoint/BreakpointLocation.h"
#include "lldb/Breakpoint/BreakpointID.h"
#include "lldb/Breakpoint/BreakpointResolver.h"
#include "lldb/Breakpoint/BreakpointResolverScripted.h"
#include "lldb/Breakpoint/StoppointCallbackContext.h"
#include "lldb/Core/Debugger.h"
#include "lldb/Core/Module.h"
#include "lldb/Expression/DiagnosticManager.h"
#include "lldb/Expression/ExpressionVariable.h"
#include "lldb/Expression/UserExpression.h"
#include "lldb/Symbol/CompileUnit.h"
#include "lldb/Symbol/Symbol.h"
#include "lldb/Symbol/TypeSystem.h"
#include "lldb/Target/Process.h"
#include "lldb/Target/Target.h"
#include "lldb/Target/Thread.h"
#include "lldb/Target/ThreadSpec.h"
#include "lldb/Utility/LLDBLog.h"
#include "lldb/Utility/Log.h"
#include "lldb/Utility/StreamString.h"
#include "lldb/ValueObject/ValueObject.h"

using namespace lldb;
using namespace lldb_private;

BreakpointLocation::BreakpointLocation(break_id_t loc_id, Breakpoint &owner,
                                       const Address &addr, lldb::tid_t tid,
                                       bool check_for_resolver)
    : m_should_resolve_indirect_functions(false), m_is_reexported(false),
      m_is_indirect(false), m_address(addr), m_owner(owner),
      m_condition_hash(0), m_loc_id(loc_id), m_hit_counter() {
  if (check_for_resolver) {
    Symbol *symbol = m_address.CalculateSymbolContextSymbol();
    if (symbol && symbol->IsIndirect()) {
      SetShouldResolveIndirectFunctions(true);
    }
  }

  SetThreadIDInternal(tid);
}

BreakpointLocation::BreakpointLocation(break_id_t loc_id, Breakpoint &owner)
    : m_should_resolve_indirect_functions(false), m_is_reexported(false),
      m_is_indirect(false), m_address(LLDB_INVALID_ADDRESS), m_owner(owner),
      m_condition_hash(0), m_loc_id(loc_id), m_hit_counter() {
  SetThreadIDInternal(LLDB_INVALID_THREAD_ID);
}

BreakpointLocation::~BreakpointLocation() {
  llvm::consumeError(ClearBreakpointSite());
}

lldb::addr_t BreakpointLocation::GetLoadAddress() const {
  return m_address.GetOpcodeLoadAddress(&m_owner.GetTarget());
}

const BreakpointOptions &BreakpointLocation::GetOptionsSpecifyingKind(
    BreakpointOptions::OptionKind kind) const {
  if (m_options_up && m_options_up->IsOptionSet(kind))
    return *m_options_up;
  return m_owner.GetOptions();
}

Address &BreakpointLocation::GetAddress() { return m_address; }

Breakpoint &BreakpointLocation::GetBreakpoint() { return m_owner; }

Target &BreakpointLocation::GetTarget() { return m_owner.GetTarget(); }

bool BreakpointLocation::IsEnabled() const {
  if (!m_owner.IsEnabled())
    return false;
  if (m_options_up != nullptr)
    return m_options_up->IsEnabled();
  return true;
}

llvm::Error BreakpointLocation::SetEnabled(bool enabled) {
  GetLocationOptions().SetEnabled(enabled);
  llvm::Error error = enabled ? ResolveBreakpointSite() : ClearBreakpointSite();
  SendBreakpointLocationChangedEvent(enabled ? eBreakpointEventTypeEnabled
                                             : eBreakpointEventTypeDisabled);
  return error;
}

bool BreakpointLocation::IsAutoContinue() const {
  if (m_options_up &&
      m_options_up->IsOptionSet(BreakpointOptions::eAutoContinue))
    return m_options_up->IsAutoContinue();
  return m_owner.IsAutoContinue();
}

void BreakpointLocation::SetAutoContinue(bool auto_continue) {
  GetLocationOptions().SetAutoContinue(auto_continue);
  SendBreakpointLocationChangedEvent(eBreakpointEventTypeAutoContinueChanged);
}

void BreakpointLocation::SetThreadID(lldb::tid_t thread_id) {
  SetThreadIDInternal(thread_id);
  SendBreakpointLocationChangedEvent(eBreakpointEventTypeThreadChanged);
}

lldb::tid_t BreakpointLocation::GetThreadID() {
  const ThreadSpec *thread_spec =
      GetOptionsSpecifyingKind(BreakpointOptions::eThreadSpec)
          .GetThreadSpecNoCreate();
  if (thread_spec)
    return thread_spec->GetTID();
  return LLDB_INVALID_THREAD_ID;
}

void BreakpointLocation::SetThreadIndex(uint32_t index) {
  if (index != 0)
    GetLocationOptions().GetThreadSpec()->SetIndex(index);
  else {
    // If we're resetting this to an invalid thread id, then don't make an
    // options pointer just to do that.
    if (m_options_up != nullptr)
      m_options_up->GetThreadSpec()->SetIndex(index);
  }
  SendBreakpointLocationChangedEvent(eBreakpointEventTypeThreadChanged);
}

uint32_t BreakpointLocation::GetThreadIndex() const {
  const ThreadSpec *thread_spec =
      GetOptionsSpecifyingKind(BreakpointOptions::eThreadSpec)
          .GetThreadSpecNoCreate();
  if (thread_spec)
    return thread_spec->GetIndex();
  return 0;
}

void BreakpointLocation::SetThreadName(const char *thread_name) {
  if (thread_name != nullptr)
    GetLocationOptions().GetThreadSpec()->SetName(thread_name);
  else {
    // If we're resetting this to an invalid thread id, then don't make an
    // options pointer just to do that.
    if (m_options_up != nullptr)
      m_options_up->GetThreadSpec()->SetName(thread_name);
  }
  SendBreakpointLocationChangedEvent(eBreakpointEventTypeThreadChanged);
}

const char *BreakpointLocation::GetThreadName() const {
  const ThreadSpec *thread_spec =
      GetOptionsSpecifyingKind(BreakpointOptions::eThreadSpec)
          .GetThreadSpecNoCreate();
  if (thread_spec)
    return thread_spec->GetName();
  return nullptr;
}

void BreakpointLocation::SetQueueName(const char *queue_name) {
  if (queue_name != nullptr)
    GetLocationOptions().GetThreadSpec()->SetQueueName(queue_name);
  else {
    // If we're resetting this to an invalid thread id, then don't make an
    // options pointer just to do that.
    if (m_options_up != nullptr)
      m_options_up->GetThreadSpec()->SetQueueName(queue_name);
  }
  SendBreakpointLocationChangedEvent(eBreakpointEventTypeThreadChanged);
}

const char *BreakpointLocation::GetQueueName() const {
  const ThreadSpec *thread_spec =
      GetOptionsSpecifyingKind(BreakpointOptions::eThreadSpec)
          .GetThreadSpecNoCreate();
  if (thread_spec)
    return thread_spec->GetQueueName();
  return nullptr;
}

bool BreakpointLocation::InvokeCallback(StoppointCallbackContext *context) {
  if (m_options_up != nullptr && m_options_up->HasCallback())
    return m_options_up->InvokeCallback(context, m_owner.GetID(), GetID());
  return m_owner.InvokeCallback(context, GetID());
}

bool BreakpointLocation::IsCallbackSynchronous() {
  if (m_options_up != nullptr && m_options_up->HasCallback())
    return m_options_up->IsCallbackSynchronous();
  return m_owner.GetOptions().IsCallbackSynchronous();
}

void BreakpointLocation::SetCallback(BreakpointHitCallback callback,
                                     void *baton, bool is_synchronous) {
  // The default "Baton" class will keep a copy of "baton" and won't free or
  // delete it when it goes out of scope.
  GetLocationOptions().SetCallback(
      callback, std::make_shared<UntypedBaton>(baton), is_synchronous);
  SendBreakpointLocationChangedEvent(eBreakpointEventTypeCommandChanged);
}

void BreakpointLocation::SetCallback(BreakpointHitCallback callback,
                                     const BatonSP &baton_sp,
                                     bool is_synchronous) {
  GetLocationOptions().SetCallback(callback, baton_sp, is_synchronous);
  SendBreakpointLocationChangedEvent(eBreakpointEventTypeCommandChanged);
}

void BreakpointLocation::ClearCallback() {
  GetLocationOptions().ClearCallback();
}

void BreakpointLocation::SetCondition(StopCondition condition) {
  GetLocationOptions().SetCondition(std::move(condition));
  SendBreakpointLocationChangedEvent(eBreakpointEventTypeConditionChanged);
}

const StopCondition &BreakpointLocation::GetCondition() const {
  return GetOptionsSpecifyingKind(BreakpointOptions::eCondition).GetCondition();
}

bool BreakpointLocation::ConditionSaysStop(ExecutionContext &exe_ctx,
                                           Status &error) {
  Log *log = GetLog(LLDBLog::Breakpoints);

  std::lock_guard<std::mutex> guard(m_condition_mutex);

  StopCondition condition = GetCondition();

  if (!condition) {
    m_user_expression_sp.reset();
    return false;
  }

  error.Clear();

  DiagnosticManager diagnostics;

  if (condition.GetHash() != m_condition_hash || !m_user_expression_sp ||
      !m_user_expression_sp->IsParseCacheable() ||
      !m_user_expression_sp->MatchesContext(exe_ctx)) {
    LanguageType language = condition.GetLanguage();
    if (language == lldb::eLanguageTypeUnknown) {
      // See if we can figure out the language from the frame, otherwise use the
      // default language:
      if (CompileUnit *comp_unit =
              m_address.CalculateSymbolContextCompileUnit())
        language = comp_unit->GetLanguage();
    }

    m_user_expression_sp.reset(GetTarget().GetUserExpressionForLanguage(
        condition.GetText(), llvm::StringRef(), SourceLanguage{language},
        Expression::eResultTypeAny, EvaluateExpressionOptions(), nullptr,
        error));
    if (error.Fail()) {
      LLDB_LOGF(log, "Error getting condition expression: %s.",
                error.AsCString());
      m_user_expression_sp.reset();
      return true;
    }

    if (!m_user_expression_sp->Parse(diagnostics, exe_ctx,
                                     eExecutionPolicyOnlyWhenNeeded, true,
                                     false)) {
      error = Status::FromError(
          diagnostics.GetAsError(lldb::eExpressionParseError,
                                 "Couldn't parse conditional expression:"));

      m_user_expression_sp.reset();
      return true;
    }

    m_condition_hash = condition.GetHash();
  }

  // We need to make sure the user sees any parse errors in their condition, so
  // we'll hook the constructor errors up to the debugger's Async I/O.

  ValueObjectSP result_value_sp;

  EvaluateExpressionOptions options;
  options.SetUnwindOnError(true);
  options.SetIgnoreBreakpoints(true);
  options.SetTryAllThreads(true);
  options.SetSuppressPersistentResult(
      true); // Don't generate a user variable for condition expressions.

  Status expr_error;

  diagnostics.Clear();

  ExpressionVariableSP result_variable_sp;

  ExpressionResults result_code = m_user_expression_sp->Execute(
      diagnostics, exe_ctx, options, m_user_expression_sp, result_variable_sp);

  bool ret;

  if (result_code == eExpressionCompleted) {
    if (!result_variable_sp) {
      error = Status::FromErrorString("Expression did not return a result");
      return false;
    }

    result_value_sp = result_variable_sp->GetValueObject();

    if (result_value_sp) {
      ret = result_value_sp->IsLogicalTrue(error);
      if (log) {
        if (error.Success()) {
          LLDB_LOGF(log, "Condition successfully evaluated, result is %s.\n",
                    ret ? "true" : "false");
        } else {
          error = Status::FromErrorString(
              "Failed to get an integer result from the expression");
          ret = false;
        }
      }
    } else {
      ret = false;
      error = Status::FromErrorString(
          "Failed to get any result from the expression");
    }
  } else {
    ret = false;
    error = Status::FromError(diagnostics.GetAsError(
        lldb::eExpressionParseError, "Couldn't execute expression:"));
  }

  return ret;
}

uint32_t BreakpointLocation::GetIgnoreCount() const {
  return GetOptionsSpecifyingKind(BreakpointOptions::eIgnoreCount)
      .GetIgnoreCount();
}

void BreakpointLocation::SetIgnoreCount(uint32_t n) {
  GetLocationOptions().SetIgnoreCount(n);
  SendBreakpointLocationChangedEvent(eBreakpointEventTypeIgnoreChanged);
}

void BreakpointLocation::DecrementIgnoreCount() {
  if (m_options_up != nullptr) {
    uint32_t loc_ignore = m_options_up->GetIgnoreCount();
    if (loc_ignore != 0)
      m_options_up->SetIgnoreCount(loc_ignore - 1);
  }
}

bool BreakpointLocation::IgnoreCountShouldStop() {
  uint32_t owner_ignore = GetBreakpoint().GetIgnoreCount();
  uint32_t loc_ignore = 0;
  if (m_options_up != nullptr)
    loc_ignore = m_options_up->GetIgnoreCount();

  if (loc_ignore != 0 || owner_ignore != 0) {
    m_owner.DecrementIgnoreCount();
    DecrementIgnoreCount(); // Have to decrement our owners' ignore count,
                            // since it won't get a chance to.
    return false;
  }
  return true;
}

BreakpointOptions &BreakpointLocation::GetLocationOptions() {
  // If we make the copy we don't copy the callbacks because that is
  // potentially expensive and we don't want to do that for the simple case
  // where someone is just disabling the location.
  if (m_options_up == nullptr)
    m_options_up = std::make_unique<BreakpointOptions>(false);

  return *m_options_up;
}

bool BreakpointLocation::ValidForThisThread(Thread &thread) {
  return thread.MatchesSpec(
      GetOptionsSpecifyingKind(BreakpointOptions::eThreadSpec)
          .GetThreadSpecNoCreate());
}

BreakpointLocationSP
BreakpointLocation::WasHit(StoppointCallbackContext *context) {
  // Only the BreakpointResolverScripted provides WasHit.
  BreakpointResolverSP resolver_sp = GetBreakpoint().GetResolver();
  BreakpointResolverScripted *scripted =
      llvm::dyn_cast<BreakpointResolverScripted>(resolver_sp.get());
  if (!scripted)
    return shared_from_this();

  StackFrameSP frame_sp = context->exe_ctx_ref.GetFrameSP();
  if (!frame_sp)
    return shared_from_this();

  BreakpointLocationSP return_loc_sp =
      scripted->WasHit(frame_sp, shared_from_this());
  // If this is a facade location, then we won't have bumped its hit count
  // while processing the original location hit.  Do so here.  We don't need
  // to bump the breakpoint's hit count, however, since hitting the real
  // location would have already done that.
  // Also we have to check the enabled state here, since we would never have
  // gotten here with a real location...
  if (return_loc_sp && return_loc_sp->IsFacade()) {
    if (return_loc_sp->IsEnabled())
      return_loc_sp->m_hit_counter.Increment();
    else
      return {};
  }
  return return_loc_sp;
}

// RETURNS - true if we should stop at this breakpoint, false if we
// should continue.  Note, we don't check the thread spec for the breakpoint
// here, since if the breakpoint is not for this thread, then the event won't
// even get reported, so the check is redundant.

bool BreakpointLocation::ShouldStop(StoppointCallbackContext *context,
                                    lldb::BreakpointLocationSP &facade_loc_sp) {
  bool should_stop = true;
  Log *log = GetLog(LLDBLog::Breakpoints);

  // Do this first, if a location is disabled, it shouldn't increment its hit
  // count.
  if (!IsEnabled())
    return false;

  // Next check WasHit:
  BreakpointLocationSP loc_hit_sp = WasHit(context);

  if (!loc_hit_sp) {
    // We bump the hit counts in StopInfoBreakpoint::ShouldStopSynchronous,
    // before we call into each location's ShouldStop.  So we need to undo
    // that here.
    UndoBumpHitCount();
    return false;
  }

  // If the location hit was not us, it was a facade location, in which case
  // we should use the facade location's callbacks, etc.  Those will all be
  // run in the asynchronous phase, so for now we just have to record the fact
  // that we should treat this as a facade hit.  This is strictly an out
  // parameter, so clear it if this isn't a facade hit.
  if (loc_hit_sp.get() != this)
    facade_loc_sp = loc_hit_sp;
  else
    facade_loc_sp.reset();

  // We only run synchronous callbacks in ShouldStop:
  context->is_synchronous = true;
  should_stop = InvokeCallback(context);

  if (log) {
    StreamString s;
    GetDescription(&s, lldb::eDescriptionLevelVerbose);
    LLDB_LOGF(log, "Hit breakpoint location: %s, %s.\n", s.GetData(),
              should_stop ? "stopping" : "continuing");
    if (facade_loc_sp) {
      s.Clear();
      facade_loc_sp->GetDescription(&s, lldb::eDescriptionLevelVerbose);
      LLDB_LOGF(log, "Attributing to facade location: %s.\n", s.GetData());
    }
  }

  return should_stop;
}

void BreakpointLocation::BumpHitCount() {
  if (IsEnabled()) {
    // Step our hit count, and also step the hit count of the owner.
    m_hit_counter.Increment();
    m_owner.m_hit_counter.Increment();
  }
}

void BreakpointLocation::UndoBumpHitCount() {
  if (IsEnabled()) {
    // Step our hit count, and also step the hit count of the owner.
    m_hit_counter.Decrement();
    m_owner.m_hit_counter.Decrement();
  }
}

bool BreakpointLocation::IsResolved() const {

  bool has_site = m_bp_site_sp.get() != nullptr;
  // Facade locations are currently always considered resolved.
  return has_site || IsFacade();
}

lldb::BreakpointSiteSP BreakpointLocation::GetBreakpointSite() const {
  return m_bp_site_sp;
}

llvm::Error BreakpointLocation::ResolveBreakpointSite() {
  // This might be a facade location, which doesn't have an address.
  // In that case, don't attempt to make a site.
  if (m_bp_site_sp || IsFacade())
    return llvm::Error::success();

  Process *process = m_owner.GetTarget().GetProcessSP().get();
  if (process == nullptr)
    return llvm::createStringError("no process");

  lldb::break_id_t new_id =
      process->CreateBreakpointSite(shared_from_this(), m_owner.IsHardware());

  if (new_id == LLDB_INVALID_BREAK_ID)
    return llvm::createStringError(
        llvm::formatv("Failed to add breakpoint site at {0:x}",
                      m_address.GetOpcodeLoadAddress(&m_owner.GetTarget())));

  if (!IsResolved())
    return llvm::createStringError(
        "breakpoint site created but location is still unresolved");

  return llvm::Error::success();
}

bool BreakpointLocation::SetBreakpointSite(BreakpointSiteSP &bp_site_sp) {
  m_bp_site_sp = bp_site_sp;
  SendBreakpointLocationChangedEvent(eBreakpointEventTypeLocationsResolved);
  return true;
}

llvm::Error BreakpointLocation::ClearBreakpointSite() {
  if (!m_bp_site_sp) {
    // This might be a Facade Location, which don't have sites or addresses
    if (IsFacade())
      return llvm::Error::success();
    return llvm::createStringError("no breakpoint site to clear");
  }

  // If the process exists, get it to remove the owner, it will remove the
  // physical implementation of the breakpoint as well if there are no more
  // owners.  Otherwise just remove this owner.
  if (ProcessSP process_sp = m_owner.GetTarget().GetProcessSP())
    process_sp->RemoveConstituentFromBreakpointSite(GetBreakpoint().GetID(),
                                                    GetID(), m_bp_site_sp);
  else
    m_bp_site_sp->RemoveConstituent(GetBreakpoint().GetID(), GetID());

  m_bp_site_sp.reset();
  return llvm::Error::success();
}

void BreakpointLocation::GetDescription(Stream *s,
                                        lldb::DescriptionLevel level) {
  SymbolContext sc;

  // If this is a scripted breakpoint, give it a chance to describe its
  // locations:
  std::optional<std::string> scripted_opt;
  BreakpointResolverSP resolver_sp = GetBreakpoint().GetResolver();
  BreakpointResolverScripted *scripted =
      llvm::dyn_cast<BreakpointResolverScripted>(resolver_sp.get());
  if (scripted)
    scripted_opt = scripted->GetLocationDescription(shared_from_this(), level);

  bool is_scripted_desc = scripted_opt.has_value();

  // If the description level is "initial" then the breakpoint is printing out
  // our initial state, and we should let it decide how it wants to print our
  // label.
  if (level != eDescriptionLevelInitial) {
    s->Indent();
    BreakpointID::GetCanonicalReference(s, m_owner.GetID(), GetID());
  }

  if (level == lldb::eDescriptionLevelBrief)
    return;

  if (level != eDescriptionLevelInitial)
    s->PutCString(": ");

  if (level == lldb::eDescriptionLevelVerbose)
    s->IndentMore();

  if (is_scripted_desc) {
    s->PutCString(scripted_opt->c_str());
  } else if (m_address.IsSectionOffset()) {
    m_address.CalculateSymbolContext(&sc);

    if (level == lldb::eDescriptionLevelFull ||
        level == eDescriptionLevelInitial) {
      if (IsReExported())
        s->PutCString("re-exported target = ");
      else
        s->PutCString("where = ");

      // If there's a preferred line entry for printing, use that.
      bool show_function_info = true;
      if (auto preferred = GetPreferredLineEntry()) {
        sc.line_entry = *preferred;
        // FIXME: We're going to get the function name wrong when the preferred
        // line entry is not the lowest one.  For now, just leave the function
        // out in this case, but we really should also figure out how to easily
        // fake the function name here.
        show_function_info = false;
      }
      sc.DumpStopContext(s, m_owner.GetTarget().GetProcessSP().get(), m_address,
                         false, true, false, show_function_info,
                         show_function_info, show_function_info);
    } else {
      if (sc.module_sp) {
        s->EOL();
        s->Indent("module = ");
        sc.module_sp->GetFileSpec().Dump(s->AsRawOstream());
      }

      if (sc.comp_unit != nullptr) {
        s->EOL();
        s->Indent("compile unit = ");
        sc.comp_unit->GetPrimaryFile().GetFilename().Dump(s);

        if (sc.function != nullptr) {
          s->EOL();
          s->Indent("function = ");
          s->PutCString(sc.function->GetName().AsCString("<unknown>"));
          if (ConstString mangled_name =
                  sc.function->GetMangled().GetMangledName()) {
            s->EOL();
            s->Indent("mangled function = ");
            s->PutCString(mangled_name.AsCString());
          }
        }

        if (sc.line_entry.line > 0) {
          s->EOL();
          s->Indent("location = ");
          if (auto preferred = GetPreferredLineEntry())
            preferred->DumpStopContext(s, true);
          else
            sc.line_entry.DumpStopContext(s, true);
        }

      } else {
        // If we don't have a comp unit, see if we have a symbol we can print.
        if (sc.symbol) {
          s->EOL();
          if (IsReExported())
            s->Indent("re-exported target = ");
          else
            s->Indent("symbol = ");
          s->PutCString(sc.symbol->GetName().AsCString("<unknown>"));
        }
      }
    }
  }

  if (level == lldb::eDescriptionLevelVerbose) {
    s->EOL();
    s->Indent();
  }

  if (!is_scripted_desc) {
    if (m_address.IsSectionOffset() &&
        (level == eDescriptionLevelFull || level == eDescriptionLevelInitial))
      s->Printf(", ");
    s->Printf("address = ");

    ExecutionContextScope *exe_scope = nullptr;
    Target *target = &m_owner.GetTarget();
    if (target)
      exe_scope = target->GetProcessSP().get();
    if (exe_scope == nullptr)
      exe_scope = target;

    if (level == eDescriptionLevelInitial)
      m_address.Dump(s, exe_scope, Address::DumpStyleLoadAddress,
                     Address::DumpStyleFileAddress);
    else
      m_address.Dump(s, exe_scope, Address::DumpStyleLoadAddress,
                     Address::DumpStyleModuleWithFileAddress);

    if (IsIndirect() && m_bp_site_sp) {
      Address resolved_address;
      resolved_address.SetLoadAddress(m_bp_site_sp->GetLoadAddress(), target);
      Symbol *resolved_symbol = resolved_address.CalculateSymbolContextSymbol();
      if (resolved_symbol) {
        if (level == eDescriptionLevelFull || level == eDescriptionLevelInitial)
          s->Printf(", ");
        else if (level == lldb::eDescriptionLevelVerbose) {
          s->EOL();
          s->Indent();
        }
        s->Printf("indirect target = %s",
                  resolved_symbol->GetName().GetCString());
      }
    }
  }

  // FIXME: scripted breakpoint are currently always resolved.  Does this seem
  // right? If they don't add any scripted locations, we shouldn't consider them
  // resolved.
  bool is_resolved = is_scripted_desc || IsResolved();
  // A scripted breakpoint might be resolved but not have a site.  Be sure to
  // check for that.
  bool is_hardware = !is_scripted_desc && IsResolved() && m_bp_site_sp &&
                     m_bp_site_sp->IsHardware();

  if (level == lldb::eDescriptionLevelVerbose) {
    s->EOL();
    s->Indent();
    s->Printf("resolved = %s\n", is_resolved ? "true" : "false");
    s->Indent();
    s->Printf("hardware = %s\n", is_hardware ? "true" : "false");
    s->Indent();
    s->Printf("hit count = %-4u\n", GetHitCount());

    if (m_options_up) {
      s->Indent();
      m_options_up->GetDescription(s, level);
      s->EOL();
    }
    s->IndentLess();
  } else if (level != eDescriptionLevelInitial) {
    s->Printf(", %sresolved, %shit count = %u ", (is_resolved ? "" : "un"),
              (is_hardware ? "hardware, " : ""), GetHitCount());
    if (m_options_up) {
      m_options_up->GetDescription(s, level);
    }
  }
}

void BreakpointLocation::Dump(Stream *s) const {
  if (s == nullptr)
    return;

  bool is_resolved = IsResolved();
  bool is_hardware = is_resolved && m_bp_site_sp->IsHardware();

  lldb::tid_t tid = GetOptionsSpecifyingKind(BreakpointOptions::eThreadSpec)
                        .GetThreadSpecNoCreate()
                        ->GetTID();
  s->Printf("BreakpointLocation %u: tid = %4.4" PRIx64
            "  load addr = 0x%8.8" PRIx64 "  state = %s  type = %s breakpoint  "
            "hit_count = %-4u  ignore_count = %-4u",
            GetID(), tid,
            (uint64_t)m_address.GetOpcodeLoadAddress(&m_owner.GetTarget()),
            (m_options_up ? m_options_up->IsEnabled() : m_owner.IsEnabled())
                ? "enabled "
                : "disabled",
            is_hardware ? "hardware" : "software", GetHitCount(),
            GetOptionsSpecifyingKind(BreakpointOptions::eIgnoreCount)
                .GetIgnoreCount());
}

void BreakpointLocation::SendBreakpointLocationChangedEvent(
    lldb::BreakpointEventType eventKind) {
  if (!m_owner.IsInternal()) {
    auto data_sp = std::make_shared<Breakpoint::BreakpointEventData>(
        eventKind, m_owner.shared_from_this());
    data_sp->GetBreakpointLocationCollection().Add(shared_from_this());
    m_owner.GetTarget().NotifyBreakpointChanged(m_owner, data_sp);
  }
}

std::optional<uint32_t> BreakpointLocation::GetSuggestedStackFrameIndex() {
  auto preferred_opt = GetPreferredLineEntry();
  if (!preferred_opt)
    return {};
  LineEntry preferred = *preferred_opt;
  SymbolContext sc;
  if (!m_address.CalculateSymbolContext(&sc))
    return {};
  // Don't return anything special if frame 0 is the preferred line entry.
  // We not really telling the stack frame list to do anything special in that
  // case.
  if (!LineEntry::Compare(sc.line_entry, preferred))
    return {};

  if (!sc.block)
    return {};

  // Blocks have their line info in Declaration form, so make one here:
  Declaration preferred_decl(preferred.GetFile(), preferred.line,
                             preferred.column);

  uint32_t depth = 0;
  Block *inlined_block = sc.block->GetContainingInlinedBlock();
  while (inlined_block) {
    // If we've moved to a block that this isn't the start of, that's not
    // our inlining info or call site, so we can stop here.
    Address start_address;
    if (!inlined_block->GetStartAddress(start_address) ||
        start_address != m_address)
      return {};

    const InlineFunctionInfo *info = inlined_block->GetInlinedFunctionInfo();
    if (info) {
      if (preferred_decl == info->GetDeclaration())
        return depth;
      if (preferred_decl == info->GetCallSite())
        return depth + 1;
    }
    inlined_block = inlined_block->GetInlinedParent();
    depth++;
  }
  return {};
}

void BreakpointLocation::SwapLocation(BreakpointLocationSP swap_from) {
  m_address = swap_from->m_address;
  m_should_resolve_indirect_functions =
      swap_from->m_should_resolve_indirect_functions;
  m_is_reexported = swap_from->m_is_reexported;
  m_is_indirect = swap_from->m_is_indirect;
  m_user_expression_sp.reset();
}

void BreakpointLocation::SetThreadIDInternal(lldb::tid_t thread_id) {
  if (thread_id != LLDB_INVALID_THREAD_ID) {
    GetLocationOptions().SetThreadID(thread_id);
  } else {
    // If we're resetting this to an invalid thread id, then don't make an
    // options pointer just to do that.
    if (m_options_up != nullptr)
      m_options_up->SetThreadID(thread_id);
  }
}
