/* Python frame unwinder interface.

   Copyright (C) 2015-2025 Free Software Foundation, Inc.

   This file is part of GDB.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */

#include "arch-utils.h"
#include "frame-unwind.h"
#include "gdbsupport/gdb_obstack.h"
#include "cli/cli-cmds.h"
#include "language.h"
#include "observable.h"
#include "python-internal.h"
#include "regcache.h"
#include "valprint.h"
#include "user-regs.h"
#include "stack.h"
#include "charset.h"
#include "block.h"


/* Debugging of Python unwinders.  */

static bool pyuw_debug;

/* Implementation of "show debug py-unwind".  */

static void
show_pyuw_debug (struct ui_file *file, int from_tty,
		 struct cmd_list_element *c, const char *value)
{
  gdb_printf (file, _("Python unwinder debugging is %s.\n"), value);
}

/* Print a "py-unwind" debug statement.  */

#define pyuw_debug_printf(fmt, ...) \
  debug_prefixed_printf_cond (pyuw_debug, "py-unwind", fmt, ##__VA_ARGS__)

/* Print "py-unwind" enter/exit debug statements.  */

#define PYUW_SCOPED_DEBUG_ENTER_EXIT \
  scoped_debug_enter_exit (pyuw_debug, "py-unwind")

/* Require a valid pending frame.  */
#define PENDING_FRAMEPY_REQUIRE_VALID(pending_frame)	     \
  do {							     \
    if ((pending_frame)->frame_info == nullptr)		     \
      {							     \
	PyErr_SetString (PyExc_ValueError,		     \
			 _("gdb.PendingFrame is invalid.")); \
	return nullptr;					     \
      }							     \
  } while (0)

struct pending_frame_object
{
  PyObject_HEAD

  /* Frame we are unwinding.  */
  frame_info_ptr frame_info;

  /* Its architecture, passed by the sniffer caller.  */
  struct gdbarch *gdbarch;
};

/* Saved registers array item.  */

struct saved_reg
{
  saved_reg (int n, gdbpy_ref<> &&v)
    : number (n),
      value (std::move (v))
  {
  }

  int number;
  gdbpy_ref<> value;
};

/* The data we keep for the PyUnwindInfo: pending_frame, saved registers
   and frame ID.  */

struct unwind_info_object
{
  PyObject_HEAD

  /* gdb.PendingFrame for the frame we are unwinding.  */
  PyObject *pending_frame;

  /* Its ID.  */
  struct frame_id frame_id;

  /* Saved registers array.  */
  std::vector<saved_reg> *saved_regs;
};

/* The data we keep for a frame we can unwind: frame ID and an array of
   (register_number, register_value) pairs.  */

struct cached_frame_info
{
  /* Frame ID.  */
  struct frame_id frame_id;

  /* GDB Architecture.  */
  struct gdbarch *gdbarch;

  /* Length of the `reg' array below.  */
  int reg_count;

  /* Flexible array member.  Note: use a zero-sized array rather than
     an actual C99-style flexible array member (unsized array),
     because the latter would cause an error with Clang:

       error: flexible array member 'reg' of type 'cached_reg_t[]' with non-trivial destruction

     Note we manually call the destructor of each array element in
     pyuw_dealloc_cache.  */
  cached_reg_t reg[0];
};

extern PyTypeObject pending_frame_object_type
    CPYCHECKER_TYPE_OBJECT_FOR_TYPEDEF ("pending_frame_object");

extern PyTypeObject unwind_info_object_type
    CPYCHECKER_TYPE_OBJECT_FOR_TYPEDEF ("unwind_info_object");

/* An enum returned by pyuw_object_attribute_to_pointer, a function which
   is used to extract an attribute from a Python object.  */

enum class pyuw_get_attr_code
{
  /* The attribute was present, and its value was successfully extracted.  */
  ATTR_OK,

  /* The attribute was not present, or was present and its value was None.
     No Python error has been set.  */
  ATTR_MISSING,

  /* The attribute was present, but there was some error while trying to
     get the value from the attribute.  A Python error will be set when
     this is returned.  */
  ATTR_ERROR,
};

/* Get the attribute named ATTR_NAME from the object PYO and convert it to
   an inferior pointer value, placing the pointer in *ADDR.

   Return pyuw_get_attr_code::ATTR_OK if the attribute was present and its
   value was successfully written into *ADDR.  For any other return value
   the contents of *ADDR are undefined.

   Return pyuw_get_attr_code::ATTR_MISSING if the attribute was not
   present, or it was present but its value was None.  The contents of
   *ADDR are undefined in this case.  No Python error will be set in this
   case.

   Return pyuw_get_attr_code::ATTR_ERROR if the attribute was present, but
   there was some error while extracting the attribute's value.  A Python
   error will be set in this case.  The contents of *ADDR are undefined.  */

static pyuw_get_attr_code
pyuw_object_attribute_to_pointer (PyObject *pyo, const char *attr_name,
				  CORE_ADDR *addr)
{
  if (!PyObject_HasAttrString (pyo, attr_name))
    return pyuw_get_attr_code::ATTR_MISSING;

  gdbpy_ref<> pyo_value (PyObject_GetAttrString (pyo, attr_name));
  if (pyo_value == nullptr)
    {
      gdb_assert (PyErr_Occurred ());
      return pyuw_get_attr_code::ATTR_ERROR;
    }
  if (pyo_value == Py_None)
    return pyuw_get_attr_code::ATTR_MISSING;

  if (get_addr_from_python (pyo_value.get (), addr) < 0)
    {
      gdb_assert (PyErr_Occurred ());
      return pyuw_get_attr_code::ATTR_ERROR;
    }

  return pyuw_get_attr_code::ATTR_OK;
}

/* Called by the Python interpreter to obtain string representation
   of the UnwindInfo object.  */

static PyObject *
unwind_infopy_str (PyObject *self)
{
  unwind_info_object *unwind_info = (unwind_info_object *) self;
  string_file stb;

  stb.printf ("Frame ID: %s", unwind_info->frame_id.to_string ().c_str ());
  {
    const char *sep = "";
    struct value_print_options opts;

    get_user_print_options (&opts);
    stb.printf ("\nSaved registers: (");
    for (const saved_reg &reg : *unwind_info->saved_regs)
      {
	struct value *value = value_object_to_value (reg.value.get ());

	stb.printf ("%s(%d, ", sep, reg.number);
	if (value != NULL)
	  {
	    try
	      {
		value_print (value, &stb, &opts);
		stb.puts (")");
	      }
	    catch (const gdb_exception &except)
	      {
		return gdbpy_handle_gdb_exception (nullptr, except);
	      }
	  }
	else
	  stb.puts ("<BAD>)");
	sep = ", ";
      }
    stb.puts (")");
  }

  return PyUnicode_FromString (stb.c_str ());
}

/* Implement UnwindInfo.__repr__().  */

static PyObject *
unwind_infopy_repr (PyObject *self)
{
  unwind_info_object *unwind_info = (unwind_info_object *) self;
  pending_frame_object *pending_frame
    = (pending_frame_object *) (unwind_info->pending_frame);
  frame_info_ptr frame = pending_frame->frame_info;

  if (frame == nullptr)
    return PyUnicode_FromFormat ("<%s for an invalid frame>",
				 Py_TYPE (self)->tp_name);

  std::string saved_reg_names;
  struct gdbarch *gdbarch = pending_frame->gdbarch;

  for (const saved_reg &reg : *unwind_info->saved_regs)
    {
      const char *name = gdbarch_register_name (gdbarch, reg.number);
      if (saved_reg_names.empty ())
	saved_reg_names = name;
      else
	saved_reg_names = (saved_reg_names + ", ") + name;
    }

  return PyUnicode_FromFormat ("<%s frame #%d, saved_regs=(%s)>",
			       Py_TYPE (self)->tp_name,
			       frame_relative_level (frame),
			       saved_reg_names.c_str ());
}

/* Create UnwindInfo instance for given PendingFrame and frame ID.
   Sets Python error and returns NULL on error.

   The PYO_PENDING_FRAME object must be valid.  */

static PyObject *
pyuw_create_unwind_info (PyObject *pyo_pending_frame,
			 struct frame_id frame_id)
{
  gdb_assert (((pending_frame_object *) pyo_pending_frame)->frame_info
	      != nullptr);

  unwind_info_object *unwind_info
    = PyObject_New (unwind_info_object, &unwind_info_object_type);
  if (unwind_info == nullptr)
    return nullptr;

  unwind_info->frame_id = frame_id;
  Py_INCREF (pyo_pending_frame);
  unwind_info->pending_frame = pyo_pending_frame;
  unwind_info->saved_regs = new std::vector<saved_reg>;
  return (PyObject *) unwind_info;
}

/* The implementation of
   gdb.UnwindInfo.add_saved_register (REG, VALUE) -> None.  */

static PyObject *
unwind_infopy_add_saved_register (PyObject *self, PyObject *args, PyObject *kw)
{
  unwind_info_object *unwind_info = (unwind_info_object *) self;
  pending_frame_object *pending_frame
      = (pending_frame_object *) (unwind_info->pending_frame);
  PyObject *pyo_reg_id;
  PyObject *pyo_reg_value;
  int regnum;

  if (pending_frame->frame_info == NULL)
    {
      PyErr_SetString (PyExc_ValueError,
		       "UnwindInfo instance refers to a stale PendingFrame");
      return nullptr;
    }

  static const char *keywords[] = { "register", "value", nullptr };
  if (!gdb_PyArg_ParseTupleAndKeywords (args, kw, "OO!", keywords,
					&pyo_reg_id, &value_object_type,
					&pyo_reg_value))
    return nullptr;

  if (!gdbpy_parse_register_id (pending_frame->gdbarch, pyo_reg_id, &regnum))
    return nullptr;

  /* If REGNUM identifies a user register then *maybe* we can convert this
     to a real (i.e. non-user) register.  The maybe qualifier is because we
     don't know what user registers each target might add, however, the
     following logic should work for the usual style of user registers,
     where the read function just forwards the register read on to some
     other register with no adjusting the value.  */
  if (regnum >= gdbarch_num_cooked_regs (pending_frame->gdbarch))
    {
      struct value *user_reg_value
	= value_of_user_reg (regnum, pending_frame->frame_info);
      if (user_reg_value->lval () == lval_register)
	regnum = user_reg_value->regnum ();
      if (regnum >= gdbarch_num_cooked_regs (pending_frame->gdbarch))
	{
	  PyErr_SetString (PyExc_ValueError, "Bad register");
	  return NULL;
	}
    }

  /* The argument parsing above guarantees that PYO_REG_VALUE will be a
     gdb.Value object, as a result the value_object_to_value call should
     succeed.  */
  gdb_assert (pyo_reg_value != nullptr);
  struct value *value = value_object_to_value (pyo_reg_value);
  gdb_assert (value != nullptr);

  ULONGEST reg_size = register_size (pending_frame->gdbarch, regnum);
  if (reg_size != value->type ()->length ())
    {
      PyErr_Format (PyExc_ValueError,
		    "The value of the register returned by the Python "
		    "sniffer has unexpected size: %s instead of %s.",
		    pulongest (value->type ()->length ()),
		    pulongest (reg_size));
      return nullptr;
    }


  try
    {
      if (value->optimized_out () || !value->entirely_available ())
	{
	  /* If we allow this value to be registered here, pyuw_sniffer is going
	     to run into an exception when trying to access its contents.
	     Throwing an exception here just puts a burden on the user to
	     implement the same checks on the user side.  We could return False
	     here and True otherwise, but again that might require changes in
	     user code.  So, handle this with minimal impact for the user, while
	     improving robustness: silently ignore the register/value pair.  */
	  Py_RETURN_NONE;
	}
    }
  catch (const gdb_exception &except)
    {
      return gdbpy_handle_gdb_exception (nullptr, except);
    }

  gdbpy_ref<> new_value = gdbpy_ref<>::new_reference (pyo_reg_value);
  bool found = false;
  for (saved_reg &reg : *unwind_info->saved_regs)
    {
      if (regnum == reg.number)
	{
	  found = true;
	  reg.value = std::move (new_value);
	  break;
	}
    }
  if (!found)
    unwind_info->saved_regs->emplace_back (regnum, std::move (new_value));

  Py_RETURN_NONE;
}

/* UnwindInfo cleanup.  */

static void
unwind_infopy_dealloc (PyObject *self)
{
  unwind_info_object *unwind_info = (unwind_info_object *) self;

  Py_XDECREF (unwind_info->pending_frame);
  delete unwind_info->saved_regs;
  Py_TYPE (self)->tp_free (self);
}

/* Called by the Python interpreter to obtain string representation
   of the PendingFrame object.  */

static PyObject *
pending_framepy_str (PyObject *self)
{
  frame_info_ptr frame = ((pending_frame_object *) self)->frame_info;
  const char *sp_str = NULL;
  const char *pc_str = NULL;

  if (frame == NULL)
    return PyUnicode_FromString ("Stale PendingFrame instance");
  try
    {
      sp_str = core_addr_to_string_nz (get_frame_sp (frame));
      pc_str = core_addr_to_string_nz (get_frame_pc (frame));
    }
  catch (const gdb_exception &except)
    {
      return gdbpy_handle_gdb_exception (nullptr, except);
    }

  return PyUnicode_FromFormat ("SP=%s,PC=%s", sp_str, pc_str);
}

/* Implement PendingFrame.__repr__().  */

static PyObject *
pending_framepy_repr (PyObject *self)
{
  pending_frame_object *pending_frame = (pending_frame_object *) self;
  frame_info_ptr frame = pending_frame->frame_info;

  if (frame == nullptr)
    return gdb_py_invalid_object_repr (self);

  const char *sp_str = nullptr;
  const char *pc_str = nullptr;

  try
    {
      sp_str = core_addr_to_string_nz (get_frame_sp (frame));
      pc_str = core_addr_to_string_nz (get_frame_pc (frame));
    }
  catch (const gdb_exception &except)
    {
      return gdbpy_handle_gdb_exception (nullptr, except);
    }

  return PyUnicode_FromFormat ("<%s level=%d, sp=%s, pc=%s>",
			       Py_TYPE (self)->tp_name,
			       frame_relative_level (frame),
			       sp_str,
			       pc_str);
}

/* Implementation of gdb.PendingFrame.read_register (self, reg) -> gdb.Value.
   Returns the value of register REG as gdb.Value instance.  */

static PyObject *
pending_framepy_read_register (PyObject *self, PyObject *args, PyObject *kw)
{
  pending_frame_object *pending_frame = (pending_frame_object *) self;
  PENDING_FRAMEPY_REQUIRE_VALID (pending_frame);

  PyObject *pyo_reg_id;
  static const char *keywords[] = { "register", nullptr };
  if (!gdb_PyArg_ParseTupleAndKeywords (args, kw, "O", keywords, &pyo_reg_id))
    return nullptr;

  int regnum;
  if (!gdbpy_parse_register_id (pending_frame->gdbarch, pyo_reg_id, &regnum))
    return nullptr;

  PyObject *result = nullptr;
  try
    {
      scoped_value_mark free_values;

      /* Fetch the value associated with a register, whether it's
	 a real register or a so called "user" register, like "pc",
	 which maps to a real register.  In the past,
	 get_frame_register_value() was used here, which did not
	 handle the user register case.  */
      value *val = value_of_register
        (regnum, get_next_frame_sentinel_okay (pending_frame->frame_info));
      if (val == NULL)
	PyErr_Format (PyExc_ValueError,
		      "Cannot read register %d from frame.",
		      regnum);
      else
	result = value_to_value_object (val);
    }
  catch (const gdb_exception &except)
    {
      return gdbpy_handle_gdb_exception (nullptr, except);
    }

  return result;
}

/* Implement PendingFrame.is_valid().  Return True if this pending frame
   object is still valid.  */

static PyObject *
pending_framepy_is_valid (PyObject *self, PyObject *args)
{
  pending_frame_object *pending_frame = (pending_frame_object *) self;

  if (pending_frame->frame_info == nullptr)
    Py_RETURN_FALSE;

  Py_RETURN_TRUE;
}

/* Implement PendingFrame.name().  Return a string that is the name of the
   function for this frame, or None if the name can't be found.  */

static PyObject *
pending_framepy_name (PyObject *self, PyObject *args)
{
  pending_frame_object *pending_frame = (pending_frame_object *) self;

  PENDING_FRAMEPY_REQUIRE_VALID (pending_frame);

  gdb::unique_xmalloc_ptr<char> name;

  try
    {
      enum language lang;
      frame_info_ptr frame = pending_frame->frame_info;

      name = find_frame_funname (frame, &lang, nullptr);
    }
  catch (const gdb_exception &except)
    {
      return gdbpy_handle_gdb_exception (nullptr, except);
    }

  if (name != nullptr)
    return PyUnicode_Decode (name.get (), strlen (name.get ()),
			     host_charset (), nullptr);

  Py_RETURN_NONE;
}

/* Implement gdb.PendingFrame.pc().  Returns an integer containing the
   frame's current $pc value.  */

static PyObject *
pending_framepy_pc (PyObject *self, PyObject *args)
{
  pending_frame_object *pending_frame = (pending_frame_object *) self;

  PENDING_FRAMEPY_REQUIRE_VALID (pending_frame);

  CORE_ADDR pc = 0;

  try
    {
      pc = get_frame_pc (pending_frame->frame_info);
    }
  catch (const gdb_exception &except)
    {
      return gdbpy_handle_gdb_exception (nullptr, except);
    }

  return gdb_py_object_from_ulongest (pc).release ();
}

/* Implement gdb.PendingFrame.language().  Return the name of the language
   for this frame.  */

static PyObject *
pending_framepy_language (PyObject *self, PyObject *args)
{
  pending_frame_object *pending_frame = (pending_frame_object *) self;

  PENDING_FRAMEPY_REQUIRE_VALID (pending_frame);

  try
    {
      frame_info_ptr fi = pending_frame->frame_info;

      enum language lang = get_frame_language (fi);
      const language_defn *lang_def = language_def (lang);

      return host_string_to_python_string (lang_def->name ()).release ();
    }
  catch (const gdb_exception &except)
    {
      return gdbpy_handle_gdb_exception (nullptr, except);
    }

  Py_RETURN_NONE;
}

/* Implement PendingFrame.find_sal().  Return the PendingFrame's symtab and
   line.  */

static PyObject *
pending_framepy_find_sal (PyObject *self, PyObject *args)
{
  pending_frame_object *pending_frame = (pending_frame_object *) self;

  PENDING_FRAMEPY_REQUIRE_VALID (pending_frame);

  PyObject *sal_obj = nullptr;

  try
    {
      frame_info_ptr frame = pending_frame->frame_info;

      symtab_and_line sal = find_frame_sal (frame);
      sal_obj = symtab_and_line_to_sal_object (sal);
    }
  catch (const gdb_exception &except)
    {
      return gdbpy_handle_gdb_exception (nullptr, except);
    }

  return sal_obj;
}

/* Implement PendingFrame.block().  Return a gdb.Block for the pending
   frame's code, or raise  RuntimeError if the block can't be found.  */

static PyObject *
pending_framepy_block (PyObject *self, PyObject *args)
{
  pending_frame_object *pending_frame = (pending_frame_object *) self;

  PENDING_FRAMEPY_REQUIRE_VALID (pending_frame);

  frame_info_ptr frame = pending_frame->frame_info;
  const struct block *block = nullptr, *fn_block;

  try
    {
      block = get_frame_block (frame, nullptr);
    }
  catch (const gdb_exception &except)
    {
      return gdbpy_handle_gdb_exception (nullptr, except);
    }

  for (fn_block = block;
       fn_block != nullptr && fn_block->function () == nullptr;
       fn_block = fn_block->superblock ())
    ;

  if (block == nullptr
      || fn_block == nullptr
      || fn_block->function () == nullptr)
    {
      PyErr_SetString (PyExc_RuntimeError,
		       _("Cannot locate block for frame."));
      return nullptr;
    }

  return block_to_block_object (block, fn_block->function ()->objfile ());
}

/* Implement gdb.PendingFrame.function().  Return a gdb.Symbol
   representing the function of this frame, or None if no suitable symbol
   can be found.  */

static PyObject *
pending_framepy_function (PyObject *self, PyObject *args)
{
  pending_frame_object *pending_frame = (pending_frame_object *) self;

  PENDING_FRAMEPY_REQUIRE_VALID (pending_frame);

  struct symbol *sym = nullptr;

  try
    {
      enum language funlang;
      frame_info_ptr frame = pending_frame->frame_info;

      gdb::unique_xmalloc_ptr<char> funname
	= find_frame_funname (frame, &funlang, &sym);
    }
  catch (const gdb_exception &except)
    {
      return gdbpy_handle_gdb_exception (nullptr, except);
    }

  if (sym != nullptr)
    return symbol_to_symbol_object (sym);

  Py_RETURN_NONE;
}

/* Implementation of
   PendingFrame.create_unwind_info (self, frameId) -> UnwindInfo.  */

static PyObject *
pending_framepy_create_unwind_info (PyObject *self, PyObject *args,
				    PyObject *kw)
{
  PyObject *pyo_frame_id;
  CORE_ADDR sp;
  CORE_ADDR pc;
  CORE_ADDR special;

  PENDING_FRAMEPY_REQUIRE_VALID ((pending_frame_object *) self);

  static const char *keywords[] = { "frame_id", nullptr };
  if (!gdb_PyArg_ParseTupleAndKeywords (args, kw, "O", keywords,
					&pyo_frame_id))
    return nullptr;

  pyuw_get_attr_code code
    = pyuw_object_attribute_to_pointer (pyo_frame_id, "sp", &sp);
  if (code == pyuw_get_attr_code::ATTR_MISSING)
    {
      PyErr_SetString (PyExc_ValueError,
		       _("frame_id should have 'sp' attribute."));
      return nullptr;
    }
  else if (code == pyuw_get_attr_code::ATTR_ERROR)
    return nullptr;

  /* The logic of building frame_id depending on the attributes of
     the frame_id object:
     Has     Has    Has           Function to call
     'sp'?   'pc'?  'special'?
     ------|------|--------------|-------------------------
     Y       N      *             frame_id_build_wild (sp)
     Y       Y      N             frame_id_build (sp, pc)
     Y       Y      Y             frame_id_build_special (sp, pc, special)
  */
  code = pyuw_object_attribute_to_pointer (pyo_frame_id, "pc", &pc);
  if (code == pyuw_get_attr_code::ATTR_ERROR)
    return nullptr;
  else if (code == pyuw_get_attr_code::ATTR_MISSING)
    return pyuw_create_unwind_info (self, frame_id_build_wild (sp));

  code = pyuw_object_attribute_to_pointer (pyo_frame_id, "special", &special);
  if (code == pyuw_get_attr_code::ATTR_ERROR)
    return nullptr;
  else if (code == pyuw_get_attr_code::ATTR_MISSING)
    return pyuw_create_unwind_info (self, frame_id_build (sp, pc));

  return pyuw_create_unwind_info (self,
				  frame_id_build_special (sp, pc, special));
}

/* Implementation of PendingFrame.architecture (self) -> gdb.Architecture.  */

static PyObject *
pending_framepy_architecture (PyObject *self, PyObject *args)
{
  pending_frame_object *pending_frame = (pending_frame_object *) self;

  PENDING_FRAMEPY_REQUIRE_VALID (pending_frame);

  return gdbarch_to_arch_object (pending_frame->gdbarch);
}

/* Implementation of PendingFrame.level (self) -> Integer.  */

static PyObject *
pending_framepy_level (PyObject *self, PyObject *args)
{
  pending_frame_object *pending_frame = (pending_frame_object *) self;

  PENDING_FRAMEPY_REQUIRE_VALID (pending_frame);

  int level = frame_relative_level (pending_frame->frame_info);
  return gdb_py_object_from_longest (level).release ();
}

/* Class for frame unwinders registered by the Python architecture callback.  */
class frame_unwind_python : public frame_unwind
{
public:
  frame_unwind_python (const struct frame_data *newarch)
    : frame_unwind ("python", NORMAL_FRAME, FRAME_UNWIND_EXTENSION, newarch)
  { }

  /* No need to override stop_reason, we want the default.  */

  int sniff (const frame_info_ptr &this_frame,
	     void **this_prologue_cache) const override;

  void this_id (const frame_info_ptr &this_frame, void **this_prologue_cache,
		struct frame_id *id) const override;

  struct value *prev_register (const frame_info_ptr &this_frame,
			       void **this_prologue_cache,
			       int regnum) const override;

  void dealloc_cache (frame_info *self, void *this_cache) const override;
};

/* frame_unwind.this_id method.  */

void
frame_unwind_python::this_id (const frame_info_ptr &this_frame,
			      void **cache_ptr,
			      struct frame_id *this_id) const
{
  *this_id = ((cached_frame_info *) *cache_ptr)->frame_id;
  pyuw_debug_printf ("frame_id: %s", this_id->to_string ().c_str ());
}

/* frame_unwind.prev_register.  */

struct value *
frame_unwind_python::prev_register (const frame_info_ptr &this_frame,
				    void **cache_ptr, int regnum) const
{
  PYUW_SCOPED_DEBUG_ENTER_EXIT;

  cached_frame_info *cached_frame = (cached_frame_info *) *cache_ptr;
  cached_reg_t *reg_info = cached_frame->reg;
  cached_reg_t *reg_info_end = reg_info + cached_frame->reg_count;

  pyuw_debug_printf ("frame=%d, reg=%d",
		     frame_relative_level (this_frame), regnum);
  for (; reg_info < reg_info_end; ++reg_info)
    {
      if (regnum == reg_info->num)
	return frame_unwind_got_bytes (this_frame, regnum, reg_info->data);
    }

  return frame_unwind_got_optimized (this_frame, regnum);
}

/* Frame sniffer dispatch.  */

int
frame_unwind_python::sniff (const frame_info_ptr &this_frame,
			    void **cache_ptr) const
{
  PYUW_SCOPED_DEBUG_ENTER_EXIT;

  struct gdbarch *gdbarch = (struct gdbarch *) (this->unwind_data ());
  cached_frame_info *cached_frame;

  gdbpy_enter enter_py (gdbarch);

  pyuw_debug_printf ("frame=%d, sp=%s, pc=%s",
		     frame_relative_level (this_frame),
		     paddress (gdbarch, get_frame_sp (this_frame)),
		     paddress (gdbarch, get_frame_pc (this_frame)));

  /* Create PendingFrame instance to pass to sniffers.  */
  pending_frame_object *pfo = PyObject_New (pending_frame_object,
					    &pending_frame_object_type);
  gdbpy_ref<> pyo_pending_frame ((PyObject *) pfo);
  if (pyo_pending_frame == NULL)
    {
      gdbpy_print_stack ();
      return 0;
    }
  pfo->gdbarch = gdbarch;
  pfo->frame_info = nullptr;
  scoped_restore invalidate_frame = make_scoped_restore (&pfo->frame_info,
							 this_frame);

  /* Run unwinders.  */
  if (gdb_python_module == NULL
      || ! PyObject_HasAttrString (gdb_python_module, "_execute_unwinders"))
    {
      PyErr_SetString (PyExc_NameError,
		       "Installation error: gdb._execute_unwinders function "
		       "is missing");
      gdbpy_print_stack ();
      return 0;
    }
  gdbpy_ref<> pyo_execute (PyObject_GetAttrString (gdb_python_module,
						   "_execute_unwinders"));
  if (pyo_execute == nullptr)
    {
      gdbpy_print_stack ();
      return 0;
    }

  /* A (gdb.UnwindInfo, str) tuple, or None.  */
  gdbpy_ref<> pyo_execute_ret
    (PyObject_CallFunctionObjArgs (pyo_execute.get (),
				   pyo_pending_frame.get (), NULL));
  if (pyo_execute_ret == nullptr)
    {
      /* If the unwinder is cancelled due to a Ctrl-C, then propagate
	 the Ctrl-C as a GDB exception instead of swallowing it.  */
      gdbpy_print_stack_or_quit ();
      return 0;
    }
  if (pyo_execute_ret == Py_None)
    return 0;

  /* Verify the return value of _execute_unwinders is a tuple of size 2.  */
  gdb_assert (PyTuple_Check (pyo_execute_ret.get ()));
  gdb_assert (PyTuple_GET_SIZE (pyo_execute_ret.get ()) == 2);

  if (pyuw_debug)
    {
      PyObject *pyo_unwinder_name = PyTuple_GET_ITEM (pyo_execute_ret.get (), 1);
      gdb::unique_xmalloc_ptr<char> name
	= python_string_to_host_string (pyo_unwinder_name);

      /* This could happen if the user passed something else than a string
	 as the unwinder's name.  */
      if (name == nullptr)
	{
	  gdbpy_print_stack ();
	  name = make_unique_xstrdup ("<failed to get unwinder name>");
	}

      pyuw_debug_printf ("frame claimed by unwinder %s", name.get ());
    }

  /* Received UnwindInfo, cache data.  */
  PyObject *pyo_unwind_info = PyTuple_GET_ITEM (pyo_execute_ret.get (), 0);
  if (!PyObject_TypeCheck (pyo_unwind_info, &unwind_info_object_type))
    error (_("an Unwinder should return gdb.UnwindInfo, not %s."),
	   Py_TYPE (pyo_unwind_info)->tp_name);

  {
    unwind_info_object *unwind_info =
      (unwind_info_object *) pyo_unwind_info;
    int reg_count = unwind_info->saved_regs->size ();

    cached_frame
      = ((cached_frame_info *)
	 xmalloc (sizeof (*cached_frame)
		  + reg_count * sizeof (cached_frame->reg[0])));
    cached_frame->gdbarch = gdbarch;
    cached_frame->frame_id = unwind_info->frame_id;
    cached_frame->reg_count = reg_count;

    /* Populate registers array.  */
    for (int i = 0; i < unwind_info->saved_regs->size (); ++i)
      {
	saved_reg *reg = &(*unwind_info->saved_regs)[i];

	struct value *value = value_object_to_value (reg->value.get ());
	size_t data_size = register_size (gdbarch, reg->number);

	/* `value' validation was done before, just assert.  */
	gdb_assert (value != NULL);
	gdb_assert (data_size == value->type ()->length ());

	cached_reg_t *cached = new (&cached_frame->reg[i]) cached_reg_t ();
	cached->num = reg->number;
	cached->data.resize (data_size);
	gdb::array_view<const gdb_byte> contents = value->contents ();
	cached->data.assign (contents.begin (), contents.end ());
      }
  }

  *cache_ptr = cached_frame;
  return 1;
}

/* Frame cache release shim.  */

void
frame_unwind_python::dealloc_cache (frame_info *this_frame, void *cache) const
{
  PYUW_SCOPED_DEBUG_ENTER_EXIT;
  cached_frame_info *cached_frame = (cached_frame_info *) cache;

  for (int i = 0; i < cached_frame->reg_count; i++)
    cached_frame->reg[i].~cached_reg_t ();

  xfree (cache);
}

struct pyuw_gdbarch_data_type
{
  /* Has the unwinder shim been prepended? */
  int unwinder_registered = 0;
};

static const registry<gdbarch>::key<pyuw_gdbarch_data_type> pyuw_gdbarch_data;

/* New inferior architecture callback: register the Python unwinders
   intermediary.  */

static void
pyuw_on_new_gdbarch (gdbarch *newarch)
{
  struct pyuw_gdbarch_data_type *data = pyuw_gdbarch_data.get (newarch);
  if (data == nullptr)
    data= pyuw_gdbarch_data.emplace (newarch);

  if (!data->unwinder_registered)
    {
      struct frame_unwind *unwinder
	  = obstack_new<frame_unwind_python>
	    (gdbarch_obstack (newarch), (const struct frame_data *) newarch);

      frame_unwind_prepend_unwinder (newarch, unwinder);
      data->unwinder_registered = 1;
    }
}

/* Initialize unwind machinery.  */

static int CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION
gdbpy_initialize_unwind (void)
{
  gdb::observers::new_architecture.attach (pyuw_on_new_gdbarch, "py-unwind");

  if (gdbpy_type_ready (&pending_frame_object_type) < 0)
    return -1;

  if (gdbpy_type_ready (&unwind_info_object_type) < 0)
    return -1;

  return 0;
}

INIT_GDB_FILE (py_unwind)
{
  add_setshow_boolean_cmd
      ("py-unwind", class_maintenance, &pyuw_debug,
	_("Set Python unwinder debugging."),
	_("Show Python unwinder debugging."),
	_("When on, Python unwinder debugging is enabled."),
	NULL,
	show_pyuw_debug,
	&setdebuglist, &showdebuglist);
}

GDBPY_INITIALIZE_FILE (gdbpy_initialize_unwind);



static PyMethodDef pending_frame_object_methods[] =
{
  { "read_register", (PyCFunction) pending_framepy_read_register,
    METH_VARARGS | METH_KEYWORDS,
    "read_register (REG) -> gdb.Value\n"
    "Return the value of the REG in the frame." },
  { "create_unwind_info", (PyCFunction) pending_framepy_create_unwind_info,
    METH_VARARGS | METH_KEYWORDS,
    "create_unwind_info (FRAME_ID) -> gdb.UnwindInfo\n"
    "Construct UnwindInfo for this PendingFrame, using FRAME_ID\n"
    "to identify it." },
  { "architecture",
    pending_framepy_architecture, METH_NOARGS,
    "architecture () -> gdb.Architecture\n"
    "The architecture for this PendingFrame." },
  { "name",
    pending_framepy_name, METH_NOARGS,
    "name() -> String.\n\
Return the function name of the frame, or None if it can't be determined." },
  { "is_valid",
    pending_framepy_is_valid, METH_NOARGS,
    "is_valid () -> Boolean.\n\
Return true if this PendingFrame is valid, false if not." },
  { "pc",
    pending_framepy_pc, METH_NOARGS,
    "pc () -> Long.\n\
Return the frame's resume address." },
  { "language", pending_framepy_language, METH_NOARGS,
    "The language of this frame." },
  { "find_sal", pending_framepy_find_sal, METH_NOARGS,
    "find_sal () -> gdb.Symtab_and_line.\n\
Return the frame's symtab and line." },
  { "block", pending_framepy_block, METH_NOARGS,
    "block () -> gdb.Block.\n\
Return the frame's code block." },
  { "function", pending_framepy_function, METH_NOARGS,
    "function () -> gdb.Symbol.\n\
Returns the symbol for the function corresponding to this frame." },
  { "level", pending_framepy_level, METH_NOARGS,
    "The stack level of this frame." },
  {NULL}  /* Sentinel */
};

PyTypeObject pending_frame_object_type =
{
  PyVarObject_HEAD_INIT (NULL, 0)
  "gdb.PendingFrame",             /* tp_name */
  sizeof (pending_frame_object),  /* tp_basicsize */
  0,                              /* tp_itemsize */
  0,                              /* tp_dealloc */
  0,                              /* tp_print */
  0,                              /* tp_getattr */
  0,                              /* tp_setattr */
  0,                              /* tp_compare */
  pending_framepy_repr,           /* tp_repr */
  0,                              /* tp_as_number */
  0,                              /* tp_as_sequence */
  0,                              /* tp_as_mapping */
  0,                              /* tp_hash  */
  0,                              /* tp_call */
  pending_framepy_str,            /* tp_str */
  0,                              /* tp_getattro */
  0,                              /* tp_setattro */
  0,                              /* tp_as_buffer */
  Py_TPFLAGS_DEFAULT,             /* tp_flags */
  "GDB PendingFrame object",      /* tp_doc */
  0,                              /* tp_traverse */
  0,                              /* tp_clear */
  0,                              /* tp_richcompare */
  0,                              /* tp_weaklistoffset */
  0,                              /* tp_iter */
  0,                              /* tp_iternext */
  pending_frame_object_methods,   /* tp_methods */
  0,                              /* tp_members */
  0,                              /* tp_getset */
  0,                              /* tp_base */
  0,                              /* tp_dict */
  0,                              /* tp_descr_get */
  0,                              /* tp_descr_set */
  0,                              /* tp_dictoffset */
  0,                              /* tp_init */
  0,                              /* tp_alloc */
};

static PyMethodDef unwind_info_object_methods[] =
{
  { "add_saved_register",
    (PyCFunction) unwind_infopy_add_saved_register,
    METH_VARARGS | METH_KEYWORDS,
    "add_saved_register (REG, VALUE) -> None\n"
    "Set the value of the REG in the previous frame to VALUE." },
  { NULL }  /* Sentinel */
};

PyTypeObject unwind_info_object_type =
{
  PyVarObject_HEAD_INIT (NULL, 0)
  "gdb.UnwindInfo",               /* tp_name */
  sizeof (unwind_info_object),    /* tp_basicsize */
  0,                              /* tp_itemsize */
  unwind_infopy_dealloc,          /* tp_dealloc */
  0,                              /* tp_print */
  0,                              /* tp_getattr */
  0,                              /* tp_setattr */
  0,                              /* tp_compare */
  unwind_infopy_repr,             /* tp_repr */
  0,                              /* tp_as_number */
  0,                              /* tp_as_sequence */
  0,                              /* tp_as_mapping */
  0,                              /* tp_hash  */
  0,                              /* tp_call */
  unwind_infopy_str,              /* tp_str */
  0,                              /* tp_getattro */
  0,                              /* tp_setattro */
  0,                              /* tp_as_buffer */
  Py_TPFLAGS_DEFAULT,             /* tp_flags */
  "GDB UnwindInfo object",        /* tp_doc */
  0,                              /* tp_traverse */
  0,                              /* tp_clear */
  0,                              /* tp_richcompare */
  0,                              /* tp_weaklistoffset */
  0,                              /* tp_iter */
  0,                              /* tp_iternext */
  unwind_info_object_methods,     /* tp_methods */
  0,                              /* tp_members */
  0,                              /* tp_getset */
  0,                              /* tp_base */
  0,                              /* tp_dict */
  0,                              /* tp_descr_get */
  0,                              /* tp_descr_set */
  0,                              /* tp_dictoffset */
  0,                              /* tp_init */
  0,                              /* tp_alloc */
};
