//===-- BreakpointLocation.h ------------------------------------*- 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
//
//===----------------------------------------------------------------------===//

#ifndef LLDB_BREAKPOINT_BREAKPOINTLOCATION_H
#define LLDB_BREAKPOINT_BREAKPOINTLOCATION_H

#include <memory>
#include <mutex>
#include <optional>

#include "lldb/Breakpoint/BreakpointOptions.h"
#include "lldb/Breakpoint/StoppointHitCounter.h"
#include "lldb/Core/Address.h"
#include "lldb/Symbol/LineEntry.h"
#include "lldb/Utility/UserID.h"
#include "lldb/lldb-private.h"

namespace lldb_private {

/// \class BreakpointLocation BreakpointLocation.h
/// "lldb/Breakpoint/BreakpointLocation.h" Class that manages one unique (by
/// address) instance of a logical breakpoint.

/// General Outline:
/// A breakpoint location is defined by the breakpoint that produces it,
/// and the address that resulted in this particular instantiation. Each
/// breakpoint location also may have a breakpoint site if its address has
/// been loaded into the program. Finally it has a settable options object.
///
/// FIXME: Should we also store some fingerprint for the location, so
/// we can map one location to the "equivalent location" on rerun?  This would
/// be useful if you've set options on the locations.

class BreakpointLocation
    : public std::enable_shared_from_this<BreakpointLocation> {
  friend class BreakpointSite;
  friend class BreakpointLocationList;
  friend class Breakpoint;
  friend class Process;
  friend class StopInfoBreakpoint;

public:
  ~BreakpointLocation();

  /// Gets the load address for this breakpoint location \return
  ///     Returns breakpoint location load address, \b
  ///     LLDB_INVALID_ADDRESS if not yet set.
  lldb::addr_t GetLoadAddress() const;

  /// Gets the Address for this breakpoint location \return
  ///     Returns breakpoint location Address.
  Address &GetAddress();
  /// Gets the Breakpoint that created this breakpoint location \return
  ///     Returns the owning breakpoint.
  Breakpoint &GetBreakpoint();

  Target &GetTarget();

  /// This is a programmatic version of a breakpoint "condition".  When a
  /// breakpoint is hit, WasHit will get called before the synchronous
  /// ShouldStop callback is run, and if it returns an empty
  /// BreakpointLocationSP, lldb will act as if that breakpoint wasn't hit.
  ///
  /// \param[in] context
  ///   The context at the stop point
  ///
  /// \return
  ///    This will return the breakpoint location that was hit on this stop.
  ///    If there was no facade location this will be the original location.
  ///    If the shared pointer is empty, then we'll treat it as if the
  ///    breakpoint was not hit.
  lldb::BreakpointLocationSP WasHit(StoppointCallbackContext *context);

  /// Determines whether we should stop due to a hit at this breakpoint
  /// location.
  ///
  /// Side Effects: This may evaluate the breakpoint condition, and run the
  /// callback.  So this command may do a considerable amount of work.
  ///
  /// \param[in] context
  ///   The context at the stop point
  ///
  /// \param[out] facade_loc_sp
  ///   If this stop should be attributed not to the location that was hit, but
  ///   to a facade location, it will be returned in this facade_loc_sp.
  ///
  /// \return
  ///     \b true if this breakpoint location thinks we should stop,
  ///     \b false otherwise.
  bool ShouldStop(StoppointCallbackContext *context,
                  lldb::BreakpointLocationSP &facade_loc_sp);

  // The next section deals with various breakpoint options.

  /// If \a enabled is \b true, enable the breakpoint, if \b false disable it.
  llvm::Error SetEnabled(bool enabled);

  /// Check the Enable/Disable state.
  ///
  /// \return
  ///     \b true if the breakpoint is enabled, \b false if disabled.
  bool IsEnabled() const;

  /// If \a auto_continue is \b true, set the breakpoint to continue when hit.
  void SetAutoContinue(bool auto_continue);

  /// Check the AutoContinue state.
  ///
  /// \return
  ///     \b true if the breakpoint is set to auto-continue, \b false if not.
  bool IsAutoContinue() const;

  /// Return the current Hit Count.
  uint32_t GetHitCount() const { return m_hit_counter.GetValue(); }

  /// Resets the current Hit Count.
  void ResetHitCount() { m_hit_counter.Reset(); }

  /// Return the current Ignore Count.
  ///
  /// \return
  ///     The number of breakpoint hits to be ignored.
  uint32_t GetIgnoreCount() const;

  /// Set the breakpoint to ignore the next \a count breakpoint hits.
  ///
  /// \param[in] n
  ///    The number of breakpoint hits to ignore.
  void SetIgnoreCount(uint32_t n);

  /// Set the callback action invoked when the breakpoint is hit.
  ///
  /// The callback will return a bool indicating whether the target should
  /// stop at this breakpoint or not.
  ///
  /// \param[in] callback
  ///     The method that will get called when the breakpoint is hit.
  ///
  /// \param[in] callback_baton_sp
  ///     A shared pointer to a Baton that provides the void * needed
  ///     for the callback.
  ///
  /// \see lldb_private::Baton
  void SetCallback(BreakpointHitCallback callback,
                   const lldb::BatonSP &callback_baton_sp, bool is_synchronous);

  void SetCallback(BreakpointHitCallback callback, void *baton,
                   bool is_synchronous);

  void ClearCallback();

  /// Set the breakpoint location's condition.
  ///
  /// \param[in] condition
  ///    The condition to evaluate when the breakpoint is hit.
  void SetCondition(StopCondition condition);

  /// Return the breakpoint condition.
  const StopCondition &GetCondition() const;

  bool ConditionSaysStop(ExecutionContext &exe_ctx, Status &error);

  /// Set the valid thread to be checked when the breakpoint is hit.
  ///
  /// \param[in] thread_id
  ///    If this thread hits the breakpoint, we stop, otherwise not.
  void SetThreadID(lldb::tid_t thread_id);

  lldb::tid_t GetThreadID();

  void SetThreadIndex(uint32_t index);

  uint32_t GetThreadIndex() const;

  void SetThreadName(const char *thread_name);

  const char *GetThreadName() const;

  void SetQueueName(const char *queue_name);

  const char *GetQueueName() const;

  // The next section deals with this location's breakpoint sites.

  /// Try to resolve the breakpoint site for this location.
  llvm::Error ResolveBreakpointSite();

  /// Clear this breakpoint location's breakpoint site - for instance when
  /// disabling the breakpoint.
  llvm::Error ClearBreakpointSite();

  /// Return whether this breakpoint location has a breakpoint site. \return
  ///     \b true if there was a breakpoint site for this breakpoint
  ///     location, \b false otherwise.
  bool IsResolved() const;

  lldb::BreakpointSiteSP GetBreakpointSite() const;

  // The next section are generic report functions.

  /// Print a description of this breakpoint location to the stream \a s.
  ///
  /// \param[in] s
  ///     The stream to which to print the description.
  ///
  /// \param[in] level
  ///     The description level that indicates the detail level to
  ///     provide.
  ///
  /// \see lldb::DescriptionLevel
  void GetDescription(Stream *s, lldb::DescriptionLevel level);

  /// Standard "Dump" method.  At present it does nothing.
  void Dump(Stream *s) const;

  /// Use this to set location specific breakpoint options.
  ///
  /// It will create a copy of the containing breakpoint's options if that
  /// hasn't been done already
  ///
  /// \return
  ///    A reference to the breakpoint options.
  BreakpointOptions &GetLocationOptions();

  /// Use this to access breakpoint options from this breakpoint location.
  /// This will return the options that have a setting for the specified
  /// BreakpointOptions kind.
  ///
  /// \param[in] kind
  ///     The particular option you are looking up.
  /// \return
  ///     A pointer to the containing breakpoint's options if this
  ///     location doesn't have its own copy.
  const BreakpointOptions &
  GetOptionsSpecifyingKind(BreakpointOptions::OptionKind kind) const;

  bool ValidForThisThread(Thread &thread);

  /// Invoke the callback action when the breakpoint is hit.
  ///
  /// Meant to be used by the BreakpointLocation class.
  ///
  /// \param[in] context
  ///    Described the breakpoint event.
  ///
  /// \return
  ///     \b true if the target should stop at this breakpoint and \b
  ///     false not.
  bool InvokeCallback(StoppointCallbackContext *context);

  /// Report whether the callback for this location is synchronous or not.
  ///
  /// \return
  ///     \b true if the callback is synchronous and \b false if not.
  bool IsCallbackSynchronous();

  /// Returns whether we should resolve Indirect functions in setting the
  /// breakpoint site for this location.
  ///
  /// \return
  ///     \b true if the breakpoint SITE for this location should be set on the
  ///     resolved location for Indirect functions.
  bool ShouldResolveIndirectFunctions() {
    return m_should_resolve_indirect_functions;
  }

  /// Returns whether the address set in the breakpoint site for this location
  /// was found by resolving an indirect symbol.
  ///
  /// \return
  ///     \b true or \b false as given in the description above.
  bool IsIndirect() { return m_is_indirect; }

  void SetIsIndirect(bool is_indirect) { m_is_indirect = is_indirect; }

  /// Returns whether the address set in the breakpoint location was re-routed
  /// to the target of a re-exported symbol.
  ///
  /// \return
  ///     \b true or \b false as given in the description above.
  bool IsReExported() { return m_is_reexported; }

  void SetIsReExported(bool is_reexported) { m_is_reexported = is_reexported; }

  /// Returns whether the two breakpoint locations might represent "equivalent
  /// locations". This is used when modules changed to determine if a Location
  /// in the old module might be the "same as" the input location.
  ///
  /// \param[in] location
  ///    The location to compare against.
  ///
  /// \return
  ///     \b true or \b false as given in the description above.
  bool EquivalentToLocation(BreakpointLocation &location);

  /// Returns the breakpoint location ID.
  lldb::break_id_t GetID() const { return m_loc_id; }

  /// Set the line entry that should be shown to users for this location.
  /// It is up to the caller to verify that this is a valid entry to show.
  /// The current use of this is to distinguish among line entries from a
  /// virtual inlined call stack that all share the same address.
  /// The line entry must have the same start address as the address for this
  /// location.
  bool SetPreferredLineEntry(const LineEntry &line_entry) {
    if (m_address == line_entry.range.GetBaseAddress()) {
      m_preferred_line_entry = line_entry;
      return true;
    }
    assert(0 && "Tried to set a preferred line entry with a different address");
    return false;
  }

  const std::optional<LineEntry> GetPreferredLineEntry() {
    return m_preferred_line_entry;
  }

protected:
  /// Set the breakpoint site for this location to \a bp_site_sp.
  ///
  /// \param[in] bp_site_sp
  ///      The breakpoint site we are setting for this location.
  ///
  /// \return
  ///     \b true if we were successful at setting the breakpoint site,
  ///     \b false otherwise.
  bool SetBreakpointSite(lldb::BreakpointSiteSP &bp_site_sp);

  void DecrementIgnoreCount();

  /// BreakpointLocation::IgnoreCountShouldStop  can only be called once
  /// per stop.  This method checks first against the loc and then the owner.
  /// It also takes care of decrementing the ignore counters.
  /// If it returns false we should continue, otherwise stop.
  bool IgnoreCountShouldStop();

  /// If this location knows that the virtual stack frame it represents is
  /// not frame 0, return the suggested stack frame instead.  This will happen
  /// when the location's address contains a "virtual inlined call stack" and
  /// the breakpoint was set on a file & line that are not at the bottom of that
  /// stack.  For now we key off the "preferred line entry" - looking for that
  /// in the blocks that start with the stop PC.
  /// This version of the API doesn't take an "inlined" parameter because it
  /// only changes frames in the inline stack.
  std::optional<uint32_t> GetSuggestedStackFrameIndex();

private:
  void SwapLocation(lldb::BreakpointLocationSP swap_from);

  void BumpHitCount();

  void UndoBumpHitCount();

  /// Updates the thread ID internally.
  ///
  /// This method was created to handle actually mutating the thread ID
  /// internally because SetThreadID broadcasts an event in addition to mutating
  /// state. The constructor calls this instead of SetThreadID to avoid the
  /// broadcast.
  ///
  /// \param[in] thread_id
  ///   The new thread ID.
  void SetThreadIDInternal(lldb::tid_t thread_id);

  // Constructors and Destructors
  //
  // Only the Breakpoint can make breakpoint locations, and it owns them.
  /// Constructor.
  ///
  /// \param[in] loc_id
  ///     The location id of the new location.
  ///
  /// \param[in] owner
  ///     A back pointer to the breakpoint that owns this location.
  ///
  /// \param[in] addr
  ///     The Address defining this location.
  ///
  /// \param[in] tid
  ///     The thread for which this breakpoint location is valid, or
  ///     LLDB_INVALID_THREAD_ID if it is valid for all threads.
  ///
  BreakpointLocation(lldb::break_id_t loc_id, Breakpoint &owner,
                     const Address &addr, lldb::tid_t tid,
                     bool check_for_resolver = true);

  /// This is the constructor for locations with no address.  Currently this is
  /// just used for Facade locations.
  ///
  /// \param[in] loc_id
  ///     The location id of the new location.
  ///
  /// \param[in] owner
  ///     A back pointer to the breakpoint that owns this location.
  ///
  ///
public:
  BreakpointLocation(lldb::break_id_t loc_id, Breakpoint &owner);
  bool IsValid() const { return m_is_valid; }
  bool IsFacade() const { return m_is_facade; }

private:
  // Data members:
  bool m_should_resolve_indirect_functions = false;
  bool m_is_reexported = false;
  bool m_is_indirect = false;
  ///< The address defining this location.
  Address m_address;
  ///< The breakpoint that produced this object.
  Breakpoint &m_owner;
  ///< Breakpoint options pointer, nullptr if we're using our breakpoint's
  /// options.
  std::unique_ptr<BreakpointOptions> m_options_up;
  ///< Our breakpoint site (it may be shared by more than one location.)
  lldb::BreakpointSiteSP m_bp_site_sp;
  ///< The compiled expression to use in testing our condition.
  lldb::UserExpressionSP m_user_expression_sp;
  ///< Guards parsing and evaluation of the condition, which could be evaluated
  /// by multiple processes.
  std::mutex m_condition_mutex;
  ///< For testing whether the condition source code changed.
  size_t m_condition_hash = 0;
  ///< Breakpoint location ID.
  lldb::break_id_t m_loc_id;
  ///< Number of times this breakpoint location has been hit.
  StoppointHitCounter m_hit_counter;
  /// If this exists, use it to print the stop description rather than the
  /// LineEntry m_address resolves to directly.  Use this for instance when the
  /// location was given somewhere in the virtual inlined call stack since the
  /// Address always resolves to the lowest entry in the stack.
  std::optional<LineEntry> m_preferred_line_entry;
  /// Because Facade locations don't have sites we can't use the presence of
  /// the site to mean this breakpoint is valid, but must manage the state
  /// directly.
  bool m_is_valid = true;
  /// Facade locations aren't directly triggered and don't have a breakpoint
  /// site.  They are a useful fiction when you want to represent the stop
  /// location as something lldb can't naturally stop at.
  bool m_is_facade = false;

  void SetInvalid() { m_is_valid = false; }

  void SetShouldResolveIndirectFunctions(bool do_resolve) {
    m_should_resolve_indirect_functions = do_resolve;
  }

  void SendBreakpointLocationChangedEvent(lldb::BreakpointEventType eventKind);

  BreakpointLocation(const BreakpointLocation &) = delete;
  const BreakpointLocation &operator=(const BreakpointLocation &) = delete;
};

} // namespace lldb_private

#endif // LLDB_BREAKPOINT_BREAKPOINTLOCATION_H
