public inbox for gdb-patches@sourceware.org
 help / color / mirror / Atom feed
* [PATCH 0/7] Handle nested functions in DAP
@ 2023-11-01 17:09 Tom Tromey
  2023-11-01 17:09 ` [PATCH 1/7] Add two convenience methods to block Tom Tromey
                   ` (7 more replies)
  0 siblings, 8 replies; 16+ messages in thread
From: Tom Tromey @ 2023-11-01 17:09 UTC (permalink / raw)
  To: gdb-patches

DAP doesn't really describe what to do for nested functions.  We
discussed it internally a little, and I came up with this series.  The
idea here is that variables from the outer scope ought to be visible,
so this series lets Python code follow a frame's static link, and then
changes the DAP frame decorators to use this ability.

At some point I think we'll need a "frame decorator v2", like we added
for pretty-printers.  But, I've been putting this off a little and
still haven't done it here.

Regression tested on x86-64 Fedora 36.

---
Tom Tromey (7):
      Add two convenience methods to block
      Add block::function_block
      Move follow_static_link to frame.c
      Add gdb.Frame.static_link method
      Fix a bug in DAP scopes code
      Handle the static link in FrameDecorator
      Update gdb.Symbol.is_variable documentation

 gdb/NEWS                                  |  3 +
 gdb/block.c                               | 13 +++++
 gdb/block.h                               | 21 +++++++
 gdb/doc/python.texi                       | 14 ++++-
 gdb/findvar.c                             | 64 +++-------------------
 gdb/frame.c                               | 40 ++++++++++++++
 gdb/frame.h                               |  7 +++
 gdb/python/lib/gdb/FrameDecorator.py      | 64 +++++++++++++++++-----
 gdb/python/lib/gdb/dap/scopes.py          |  8 ++-
 gdb/python/py-frame.c                     | 26 +++++++++
 gdb/testsuite/gdb.dap/ada-nested.exp      | 91 +++++++++++++++++++++++++++++++
 gdb/testsuite/gdb.dap/ada-nested/prog.adb | 32 +++++++++++
 12 files changed, 311 insertions(+), 72 deletions(-)
---
base-commit: 5fbee060e807fbc317aa265eb7e25d1cf94a4dad
change-id: 20231101-dap-nested-function-28a2595863ad

Best regards,
-- 
Tom Tromey <tromey@adacore.com>


^ permalink raw reply	[flat|nested] 16+ messages in thread

* [PATCH 1/7] Add two convenience methods to block
  2023-11-01 17:09 [PATCH 0/7] Handle nested functions in DAP Tom Tromey
@ 2023-11-01 17:09 ` Tom Tromey
  2023-11-01 17:09 ` [PATCH 2/7] Add block::function_block Tom Tromey
                   ` (6 subsequent siblings)
  7 siblings, 0 replies; 16+ messages in thread
From: Tom Tromey @ 2023-11-01 17:09 UTC (permalink / raw)
  To: gdb-patches

This adds a couple of convenience methods, block::is_static_block and
block::is_global_block.
---
 gdb/block.h   | 15 +++++++++++++++
 gdb/findvar.c |  5 ++---
 2 files changed, 17 insertions(+), 3 deletions(-)

diff --git a/gdb/block.h b/gdb/block.h
index 3a197e63754..9fccbe02b99 100644
--- a/gdb/block.h
+++ b/gdb/block.h
@@ -254,10 +254,25 @@ struct block : public allocate_on_obstack
 
   const struct block *static_block () const;
 
+  /* Return true if this block is a static block.  */
+
+  bool is_static_block () const
+  {
+    const block *sup = superblock ();
+    if (sup == nullptr)
+      return false;
+    return sup->is_global_block ();
+  }
+
   /* Return the static block associated with block.  */
 
   const struct block *global_block () const;
 
+  /* Return true if this block is a global block.  */
+
+  bool is_global_block () const
+  { return superblock () == nullptr; }
+
   /* Set the compunit of this block, which must be a global block.  */
 
   void set_compunit_symtab (struct compunit_symtab *);
diff --git a/gdb/findvar.c b/gdb/findvar.c
index 4e992ecdcc7..1079b85df82 100644
--- a/gdb/findvar.c
+++ b/gdb/findvar.c
@@ -460,8 +460,7 @@ get_hosting_frame (struct symbol *var, const struct block *var_block,
      tests that embed global/static symbols with null location lists.
      We want to get <optimized out> instead of <frame required> when evaluating
      them so return a frame instead of raising an error.  */
-  else if (var_block == var_block->global_block ()
-	   || var_block == var_block->static_block ())
+  else if (var_block->is_global_block () || var_block->is_static_block ())
     return frame;
 
   /* We have to handle the "my_func::my_local_var" notation.  This requires us
@@ -486,7 +485,7 @@ get_hosting_frame (struct symbol *var, const struct block *var_block,
 
       /* If we failed to find the proper frame, fallback to the heuristic
 	 method below.  */
-      else if (frame_block == frame_block->global_block ())
+      else if (frame_block->is_global_block ())
 	{
 	  frame = NULL;
 	  break;

-- 
2.40.1


^ permalink raw reply	[flat|nested] 16+ messages in thread

* [PATCH 2/7] Add block::function_block
  2023-11-01 17:09 [PATCH 0/7] Handle nested functions in DAP Tom Tromey
  2023-11-01 17:09 ` [PATCH 1/7] Add two convenience methods to block Tom Tromey
@ 2023-11-01 17:09 ` Tom Tromey
  2023-11-01 17:09 ` [PATCH 3/7] Move follow_static_link to frame.c Tom Tromey
                   ` (5 subsequent siblings)
  7 siblings, 0 replies; 16+ messages in thread
From: Tom Tromey @ 2023-11-01 17:09 UTC (permalink / raw)
  To: gdb-patches

This adds the method block::function_block, to easily access a block's
enclosing function block.
---
 gdb/block.c | 13 +++++++++++++
 gdb/block.h |  6 ++++++
 2 files changed, 19 insertions(+)

diff --git a/gdb/block.c b/gdb/block.c
index 6ada69c388f..e588a68aa2f 100644
--- a/gdb/block.c
+++ b/gdb/block.c
@@ -378,6 +378,19 @@ block::global_block () const
 
 /* See block.h.  */
 
+const struct block *
+block::function_block () const
+{
+  const block *block = this;
+
+  while (block != nullptr && block->function () == nullptr)
+    block = block->superblock ();
+
+  return block;
+}
+
+/* See block.h.  */
+
 void
 block::set_compunit_symtab (struct compunit_symtab *cu)
 {
diff --git a/gdb/block.h b/gdb/block.h
index 9fccbe02b99..a29298517b0 100644
--- a/gdb/block.h
+++ b/gdb/block.h
@@ -273,6 +273,12 @@ struct block : public allocate_on_obstack
   bool is_global_block () const
   { return superblock () == nullptr; }
 
+  /* Return the function block for this block.  Returns nullptr if
+     there is no enclosing function, i.e., if this block is a static
+     or global block.  */
+
+  const struct block *function_block () const;
+
   /* Set the compunit of this block, which must be a global block.  */
 
   void set_compunit_symtab (struct compunit_symtab *);

-- 
2.40.1


^ permalink raw reply	[flat|nested] 16+ messages in thread

* [PATCH 3/7] Move follow_static_link to frame.c
  2023-11-01 17:09 [PATCH 0/7] Handle nested functions in DAP Tom Tromey
  2023-11-01 17:09 ` [PATCH 1/7] Add two convenience methods to block Tom Tromey
  2023-11-01 17:09 ` [PATCH 2/7] Add block::function_block Tom Tromey
@ 2023-11-01 17:09 ` Tom Tromey
  2023-11-01 17:09 ` [PATCH 4/7] Add gdb.Frame.static_link method Tom Tromey
                   ` (4 subsequent siblings)
  7 siblings, 0 replies; 16+ messages in thread
From: Tom Tromey @ 2023-11-01 17:09 UTC (permalink / raw)
  To: gdb-patches

This moves the follow_static_link function to frame.c and exports it
for use elsewhere.  The API is changed slightly to make it more
generically useful.
---
 gdb/findvar.c | 59 ++++++-----------------------------------------------------
 gdb/frame.c   | 40 ++++++++++++++++++++++++++++++++++++++++
 gdb/frame.h   |  7 +++++++
 3 files changed, 53 insertions(+), 53 deletions(-)

diff --git a/gdb/findvar.c b/gdb/findvar.c
index 1079b85df82..952ec20c0b7 100644
--- a/gdb/findvar.c
+++ b/gdb/findvar.c
@@ -31,7 +31,6 @@
 #include "block.h"
 #include "objfiles.h"
 #include "language.h"
-#include "dwarf2/loc.h"
 #include "gdbsupport/selftest.h"
 
 /* Basic byte-swapping routines.  All 'extract' functions return a
@@ -391,41 +390,6 @@ symbol_read_needs_frame (struct symbol *sym)
   return symbol_read_needs (sym) == SYMBOL_NEEDS_FRAME;
 }
 
-/* Given static link expression and the frame it lives in, look for the frame
-   the static links points to and return it.  Return NULL if we could not find
-   such a frame.   */
-
-static frame_info_ptr
-follow_static_link (frame_info_ptr frame,
-		    const struct dynamic_prop *static_link)
-{
-  CORE_ADDR upper_frame_base;
-
-  if (!dwarf2_evaluate_property (static_link, frame, NULL, &upper_frame_base))
-    return NULL;
-
-  /* Now climb up the stack frame until we reach the frame we are interested
-     in.  */
-  for (; frame != NULL; frame = get_prev_frame (frame))
-    {
-      struct symbol *framefunc = get_frame_function (frame);
-
-      /* Stacks can be quite deep: give the user a chance to stop this.  */
-      QUIT;
-
-      /* If we don't know how to compute FRAME's base address, don't give up:
-	 maybe the frame we are looking for is upper in the stack frame.  */
-      if (framefunc != NULL
-	  && SYMBOL_BLOCK_OPS (framefunc) != NULL
-	  && SYMBOL_BLOCK_OPS (framefunc)->get_frame_base != NULL
-	  && (SYMBOL_BLOCK_OPS (framefunc)->get_frame_base (framefunc, frame)
-	      == upper_frame_base))
-	break;
-    }
-
-  return frame;
-}
-
 /* Assuming VAR is a symbol that can be reached from FRAME thanks to lexical
    rules, look for the frame that is actually hosting VAR and return it.  If,
    for some reason, we found no such frame, return NULL.
@@ -494,25 +458,14 @@ get_hosting_frame (struct symbol *var, const struct block *var_block,
       /* Assuming we have a block for this frame: if we are at the function
 	 level, the immediate upper lexical block is in an outer function:
 	 follow the static link.  */
-      else if (frame_block->function ())
+      else if (frame_block->function () != nullptr)
 	{
-	  const struct dynamic_prop *static_link
-	    = frame_block->static_link ();
-	  int could_climb_up = 0;
-
-	  if (static_link != NULL)
-	    {
-	      frame = follow_static_link (frame, static_link);
-	      if (frame != NULL)
-		{
-		  frame_block = get_frame_block (frame, NULL);
-		  could_climb_up = frame_block != NULL;
-		}
-	    }
-	  if (!could_climb_up)
+	  frame = frame_follow_static_link (frame);
+	  if (frame != nullptr)
 	    {
-	      frame = NULL;
-	      break;
+	      frame_block = get_frame_block (frame, nullptr);
+	      if (frame_block == nullptr)
+		frame = nullptr;
 	    }
 	}
 
diff --git a/gdb/frame.c b/gdb/frame.c
index 7077016ccba..4a46ccb9abc 100644
--- a/gdb/frame.c
+++ b/gdb/frame.c
@@ -43,6 +43,7 @@
 #include "hashtab.h"
 #include "valprint.h"
 #include "cli/cli-option.h"
+#include "dwarf2/loc.h"
 
 /* The sentinel frame terminates the innermost end of the frame chain.
    If unwound, it returns the information needed to construct an
@@ -3120,6 +3121,45 @@ get_frame_sp (frame_info_ptr this_frame)
   return gdbarch_unwind_sp (gdbarch, frame_info_ptr (this_frame->next));
 }
 
+/* See frame.h.  */
+
+frame_info_ptr
+frame_follow_static_link (frame_info_ptr frame)
+{
+  const block *frame_block = get_frame_block (frame, nullptr);
+  frame_block = frame_block->function_block ();
+
+  const struct dynamic_prop *static_link = frame_block->static_link ();
+  if (static_link == nullptr)
+    return {};
+
+  CORE_ADDR upper_frame_base;
+
+  if (!dwarf2_evaluate_property (static_link, frame, NULL, &upper_frame_base))
+    return {};
+
+  /* Now climb up the stack frame until we reach the frame we are interested
+     in.  */
+  for (; frame != nullptr; frame = get_prev_frame (frame))
+    {
+      struct symbol *framefunc = get_frame_function (frame);
+
+      /* Stacks can be quite deep: give the user a chance to stop this.  */
+      QUIT;
+
+      /* If we don't know how to compute FRAME's base address, don't give up:
+	 maybe the frame we are looking for is upper in the stack frame.  */
+      if (framefunc != NULL
+	  && SYMBOL_BLOCK_OPS (framefunc) != NULL
+	  && SYMBOL_BLOCK_OPS (framefunc)->get_frame_base != NULL
+	  && (SYMBOL_BLOCK_OPS (framefunc)->get_frame_base (framefunc, frame)
+	      == upper_frame_base))
+	break;
+    }
+
+  return frame;
+}
+
 /* Return the reason why we can't unwind past FRAME.  */
 
 enum unwind_stop_reason
diff --git a/gdb/frame.h b/gdb/frame.h
index 1d7422cac32..19bf8176682 100644
--- a/gdb/frame.h
+++ b/gdb/frame.h
@@ -640,6 +640,13 @@ class address_space;
 /* Return the frame's address space.  */
 extern const address_space *get_frame_address_space (frame_info_ptr);
 
+/* A frame may have a "static link".  That is, in some languages, a
+   nested function may have access to variables from the enclosing
+   block and frame.  This function looks for a frame's static link.
+   If found, returns the corresponding frame; otherwise, returns a
+   null frame_info_ptr.  */
+extern frame_info_ptr frame_follow_static_link (frame_info_ptr frame);
+
 /* For frames where we can not unwind further, describe why.  */
 
 enum unwind_stop_reason

-- 
2.40.1


^ permalink raw reply	[flat|nested] 16+ messages in thread

* [PATCH 4/7] Add gdb.Frame.static_link method
  2023-11-01 17:09 [PATCH 0/7] Handle nested functions in DAP Tom Tromey
                   ` (2 preceding siblings ...)
  2023-11-01 17:09 ` [PATCH 3/7] Move follow_static_link to frame.c Tom Tromey
@ 2023-11-01 17:09 ` Tom Tromey
  2023-11-01 17:17   ` Eli Zaretskii
  2023-11-01 17:09 ` [PATCH 5/7] Fix a bug in DAP scopes code Tom Tromey
                   ` (3 subsequent siblings)
  7 siblings, 1 reply; 16+ messages in thread
From: Tom Tromey @ 2023-11-01 17:09 UTC (permalink / raw)
  To: gdb-patches

This adds a new gdb.Frame.static_link method to the gdb Python layer.
This can be used to find the static link frame for a given frame.
---
 gdb/NEWS              |  3 +++
 gdb/doc/python.texi   | 10 ++++++++++
 gdb/python/py-frame.c | 26 ++++++++++++++++++++++++++
 3 files changed, 39 insertions(+)

diff --git a/gdb/NEWS b/gdb/NEWS
index 95663433f1c..503f53a2ea1 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -17,6 +17,9 @@
   ** New read/write attribute gdb.Value.bytes that contains a bytes
      object holding the contents of this value.
 
+  ** New method gdb.Frame.static_link that returns the outer frame
+     of a nested function frame.
+
 *** Changes in GDB 14
 
 * GDB now supports the AArch64 Scalable Matrix Extension 2 (SME2), which
diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi
index 8cc3f92cbfe..afe447644c5 100644
--- a/gdb/doc/python.texi
+++ b/gdb/doc/python.texi
@@ -5732,6 +5732,16 @@ Set this frame to be the selected frame.  @xref{Stack, ,Examining the
 Stack}.
 @end defun
 
+@defun Frame.static_link ()
+In some languages (e.g., Ada, but also a GNU C extension), a nested
+function can access the variables in the outer scope.  This is done
+via a ``static link'', which is a reference from the nested frame to
+the appropriate outer frame.
+
+This method returns this frame's static link frame, if one exists.  If
+there is no static link, this method returns @code{None}.
+@end defun
+
 @defun Frame.level ()
 Return an integer, the stack frame level for this frame.  @xref{Frames, ,Stack Frames}.
 @end defun
diff --git a/gdb/python/py-frame.c b/gdb/python/py-frame.c
index 1a55e514e39..0a7e10f09ff 100644
--- a/gdb/python/py-frame.c
+++ b/gdb/python/py-frame.c
@@ -622,6 +622,30 @@ frapy_language (PyObject *self, PyObject *args)
   Py_RETURN_NONE;
 }
 
+/* The static link for this frame.  */
+
+static PyObject *
+frapy_static_link (PyObject *self, PyObject *args)
+{
+  frame_info_ptr link;
+
+  try
+    {
+      FRAPY_REQUIRE_VALID (self, link);
+
+      link = frame_follow_static_link (link);
+    }
+  catch (const gdb_exception &except)
+    {
+      GDB_PY_HANDLE_EXCEPTION (except);
+    }
+
+  if (link == nullptr)
+    Py_RETURN_NONE;
+
+  return frame_info_to_frame_object (link);
+}
+
 /* Implementation of gdb.newest_frame () -> gdb.Frame.
    Returns the newest frame object.  */
 
@@ -800,6 +824,8 @@ Return the value of the variable in this frame." },
     "The stack level of this frame." },
   { "language", frapy_language, METH_NOARGS,
     "The language of this frame." },
+  { "static_link", frapy_static_link, METH_NOARGS,
+    "The static link of this frame, or None." },
   {NULL}  /* Sentinel */
 };
 

-- 
2.40.1


^ permalink raw reply	[flat|nested] 16+ messages in thread

* [PATCH 5/7] Fix a bug in DAP scopes code
  2023-11-01 17:09 [PATCH 0/7] Handle nested functions in DAP Tom Tromey
                   ` (3 preceding siblings ...)
  2023-11-01 17:09 ` [PATCH 4/7] Add gdb.Frame.static_link method Tom Tromey
@ 2023-11-01 17:09 ` Tom Tromey
  2023-11-01 17:09 ` [PATCH 6/7] Handle the static link in FrameDecorator Tom Tromey
                   ` (2 subsequent siblings)
  7 siblings, 0 replies; 16+ messages in thread
From: Tom Tromey @ 2023-11-01 17:09 UTC (permalink / raw)
  To: gdb-patches

While working on static links, I noticed that the DAP scopes code does
not handle the scenario where a frame decorator returns None.  This
situation should be handled identically to a frame decorator returning
an empty iterator.
---
 gdb/python/lib/gdb/dap/scopes.py | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/gdb/python/lib/gdb/dap/scopes.py b/gdb/python/lib/gdb/dap/scopes.py
index 87f2ed7547f..13c35817fb6 100644
--- a/gdb/python/lib/gdb/dap/scopes.py
+++ b/gdb/python/lib/gdb/dap/scopes.py
@@ -107,10 +107,14 @@ def _get_scope(id):
     else:
         frame = frame_for_id(id)
         scopes = []
-        args = frame.frame_args()
+        # Make sure to handle the None case as well as the empty
+        # iterator case.
+        args = tuple(frame.frame_args() or ())
         if args:
             scopes.append(_ScopeReference("Arguments", "arguments", frame, args))
-        locs = frame.frame_locals()
+        # Make sure to handle the None case as well as the empty
+        # iterator case.
+        locs = tuple(frame.frame_locals() or ())
         if locs:
             scopes.append(_ScopeReference("Locals", "locals", frame, locs))
         scopes.append(_RegisterReference("Registers", frame))

-- 
2.40.1


^ permalink raw reply	[flat|nested] 16+ messages in thread

* [PATCH 6/7] Handle the static link in FrameDecorator
  2023-11-01 17:09 [PATCH 0/7] Handle nested functions in DAP Tom Tromey
                   ` (4 preceding siblings ...)
  2023-11-01 17:09 ` [PATCH 5/7] Fix a bug in DAP scopes code Tom Tromey
@ 2023-11-01 17:09 ` Tom Tromey
  2023-11-15 12:31   ` Tom de Vries
  2023-11-01 17:09 ` [PATCH 7/7] Update gdb.Symbol.is_variable documentation Tom Tromey
  2023-11-14 15:39 ` [PATCH 0/7] Handle nested functions in DAP Tom Tromey
  7 siblings, 1 reply; 16+ messages in thread
From: Tom Tromey @ 2023-11-01 17:09 UTC (permalink / raw)
  To: gdb-patches

A co-worker requested that the DAP scope for a nested function's frame
also show the variables from outer frames.  DAP doesn't directly
support this notion, so this patch arranges to put these variables
into the inner frames "Locals" scope.

I chose to do this only for DAP.  For CLI and MI, gdb currently does
not do this, so this preserves the behavior.

Note that an earlier patch (see commit 4a1311ba) removed some code
that seemed to do something similar.  However, that code did not
actually work.
---
 gdb/python/lib/gdb/FrameDecorator.py      | 64 +++++++++++++++++-----
 gdb/testsuite/gdb.dap/ada-nested.exp      | 91 +++++++++++++++++++++++++++++++
 gdb/testsuite/gdb.dap/ada-nested/prog.adb | 32 +++++++++++
 3 files changed, 174 insertions(+), 13 deletions(-)

diff --git a/gdb/python/lib/gdb/FrameDecorator.py b/gdb/python/lib/gdb/FrameDecorator.py
index 39ee2e2547c..b6eb1ab1de0 100644
--- a/gdb/python/lib/gdb/FrameDecorator.py
+++ b/gdb/python/lib/gdb/FrameDecorator.py
@@ -213,18 +213,36 @@ class DAPFrameDecorator(_FrameDecoratorBase):
             return sal.symtab.fullname()
         return None
 
+    def frame_locals(self):
+        """Return an iterable of local variables for this frame, if
+        any.  The iterable object contains objects conforming with the
+        Symbol/Value interface.  If there are no frame locals, or if
+        this frame is deemed to be a special case, return None."""
+
+        if hasattr(self._base, "frame_locals"):
+            return self._base.frame_locals()
+
+        frame = self.inferior_frame()
+        args = FrameVars(frame)
+        return args.fetch_frame_locals(True)
+
 
 class SymValueWrapper(object):
     """A container class conforming to the Symbol/Value interface
     which holds frame locals or frame arguments."""
 
-    def __init__(self, symbol, value):
+    # The FRAME argument is needed here because gdb.Symbol doesn't
+    # carry the block with it, and so read_var can't find symbols from
+    # outer (static link) frames.
+    def __init__(self, frame, symbol):
+        self.frame = frame
         self.sym = symbol
-        self.val = value
 
     def value(self):
         """Return the value associated with this symbol, or None"""
-        return self.val
+        if self.frame is None:
+            return None
+        return self.frame.read_var(self.sym)
 
     def symbol(self):
         """Return the symbol, or Python text, associated with this
@@ -240,32 +258,50 @@ class FrameVars(object):
     def __init__(self, frame):
         self.frame = frame
 
-    def fetch_frame_locals(self):
+    def fetch_frame_locals(self, follow_link=False):
         """Public utility method to fetch frame local variables for
         the stored frame.  Frame arguments are not fetched.  If there
         are no frame local variables, return an empty list."""
         lvars = []
 
+        frame = self.frame
         try:
-            block = self.frame.block()
+            block = frame.block()
         except RuntimeError:
             block = None
 
+        traversed_link = False
         while block is not None:
             if block.is_global or block.is_static:
                 break
             for sym in block:
+                # Exclude arguments from the innermost function, but
+                # if we found and traversed a static link, just treat
+                # all such variables as "local".
                 if sym.is_argument:
+                    if not traversed_link:
+                        continue
+                elif not sym.is_variable:
+                    # We use an 'elif' here because is_variable
+                    # returns False for arguments as well.  Anyway,
+                    # don't include non-variables here.
                     continue
-                if sym.is_variable:
-                    lvars.append(SymValueWrapper(sym, None))
+                lvars.append(SymValueWrapper(frame, sym))
 
-            # Stop when the function itself is seen, to avoid showing
-            # variables from outer functions in a nested function.
             if block.function is not None:
-                break
-
-            block = block.superblock
+                if not follow_link:
+                    break
+                # If the frame has a static link, follow it here.
+                traversed_link = True
+                frame = frame.static_link()
+                if frame is None:
+                    break
+                try:
+                    block = frame.block()
+                except RuntimeError:
+                    block = None
+            else:
+                block = block.superblock
 
         return lvars
 
@@ -287,10 +323,12 @@ class FrameVars(object):
             for sym in block:
                 if not sym.is_argument:
                     continue
-                args.append(SymValueWrapper(sym, None))
+                args.append(SymValueWrapper(None, sym))
 
             # Stop when the function itself is seen, to avoid showing
             # variables from outer functions in a nested function.
+            # Note that we don't traverse the static link for
+            # arguments, only for locals.
             if block.function is not None:
                 break
 
diff --git a/gdb/testsuite/gdb.dap/ada-nested.exp b/gdb/testsuite/gdb.dap/ada-nested.exp
new file mode 100644
index 00000000000..1a02f4f352e
--- /dev/null
+++ b/gdb/testsuite/gdb.dap/ada-nested.exp
@@ -0,0 +1,91 @@
+# Copyright 2023 Free Software Foundation, Inc.
+#
+# 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/>.
+
+# Check the scope of a nested function.
+
+load_lib ada.exp
+load_lib dap-support.exp
+
+require allow_ada_tests allow_dap_tests
+
+standard_ada_testfile prog
+
+if {[gdb_compile_ada "${srcfile}" "${binfile}" executable \
+	 {debug additional_flags=-gnata}] != ""} {
+    return -1
+}
+
+if {[dap_launch $binfile] == ""} {
+    return
+}
+
+set line [gdb_get_line_number "STOP"]
+set obj [dap_check_request_and_response "set breakpoint" \
+	     setBreakpoints \
+	     [format {o source [o path [%s]] \
+			  breakpoints [a [o line [i %d]]]} \
+		  [list s $srcfile] $line]]
+set fn_bpno [dap_get_breakpoint_number $obj]
+
+dap_check_request_and_response "start inferior" configurationDone
+
+dap_wait_for_event_and_check "stopped at breakpoint" stopped \
+    "body reason" breakpoint \
+    "body hitBreakpointIds" $fn_bpno
+
+set bt [lindex [dap_check_request_and_response "backtrace" stackTrace \
+		    {o threadId [i 1]}] \
+	    0]
+set frame_id [dict get [lindex [dict get $bt body stackFrames] 0] id]
+
+set scopes [dap_check_request_and_response "get scopes" scopes \
+		[format {o frameId [i %d]} $frame_id]]
+set scopes [dict get [lindex $scopes 0] body scopes]
+
+# This is what the implementation does, so we can assume it, but check
+# just in case something changes.
+lassign $scopes args locals _ignore
+gdb_assert {[dict get $args name] == "Arguments"} "argument scope"
+gdb_assert {[dict get $locals name] == "Locals"} "local scope"
+
+gdb_assert {[dict get $locals namedVariables] == 3} "two locals"
+
+set num [dict get $locals variablesReference]
+set refs [lindex [dap_check_request_and_response "fetch variables" \
+		      "variables" \
+		      [format {o variablesReference [i %d] count [i 3]} \
+			   $num]] \
+	      0]
+
+foreach var [dict get $refs body variables] {
+    set name [dict get $var name]
+
+    switch $name {
+	"i" {
+	    gdb_assert {[dict get $var value] == "1"} "check value of i"
+	}
+	"x" {
+	    gdb_assert {[dict get $var value] == "12"} "check value of x"
+	}
+	"outer_arg" {
+	    gdb_assert {[dict get $var value] == "1"} "check value of outer_arg"
+	}
+	default {
+	    fail "unknown variable $name"
+	}
+    }
+}
+
+dap_shutdown
diff --git a/gdb/testsuite/gdb.dap/ada-nested/prog.adb b/gdb/testsuite/gdb.dap/ada-nested/prog.adb
new file mode 100644
index 00000000000..6b38835a94c
--- /dev/null
+++ b/gdb/testsuite/gdb.dap/ada-nested/prog.adb
@@ -0,0 +1,32 @@
+--  Copyright 2023 Free Software Foundation, Inc.
+--
+--  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/>.
+
+procedure Foo is
+   X  : Integer := 12;
+
+   procedure Outer (Outer_Arg : Integer) is
+      procedure Bump (Stride : Integer) is
+      begin
+         X := X + Stride;          -- STOP
+      end;
+   begin
+      Bump (Outer_Arg);
+   end;
+
+begin
+   for I in 1 .. 20 loop
+      Outer (1);
+   end loop;
+end Foo;

-- 
2.40.1


^ permalink raw reply	[flat|nested] 16+ messages in thread

* [PATCH 7/7] Update gdb.Symbol.is_variable documentation
  2023-11-01 17:09 [PATCH 0/7] Handle nested functions in DAP Tom Tromey
                   ` (5 preceding siblings ...)
  2023-11-01 17:09 ` [PATCH 6/7] Handle the static link in FrameDecorator Tom Tromey
@ 2023-11-01 17:09 ` Tom Tromey
  2023-11-01 17:15   ` Eli Zaretskii
  2023-11-14 15:39 ` [PATCH 0/7] Handle nested functions in DAP Tom Tromey
  7 siblings, 1 reply; 16+ messages in thread
From: Tom Tromey @ 2023-11-01 17:09 UTC (permalink / raw)
  To: gdb-patches

Kévin found a bug in an earlier version of this series that was based
on a misconception I had about Symbol.is_variable.  This patch fixes
the documentation to explain the method a bit better.
---
 gdb/doc/python.texi | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi
index afe447644c5..3a7d61af5a5 100644
--- a/gdb/doc/python.texi
+++ b/gdb/doc/python.texi
@@ -6045,7 +6045,9 @@ local variables will require a frame, but other symbols will not.
 @end defvar
 
 @defvar Symbol.is_variable
-@code{True} if the symbol is a variable.
+@code{True} if the symbol is a variable, as opposed to something like
+a function or type.  Note that this also returns @code{False} for
+arguments.
 @end defvar
 
 A @code{gdb.Symbol} object has the following methods:

-- 
2.40.1


^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH 7/7] Update gdb.Symbol.is_variable documentation
  2023-11-01 17:09 ` [PATCH 7/7] Update gdb.Symbol.is_variable documentation Tom Tromey
@ 2023-11-01 17:15   ` Eli Zaretskii
  0 siblings, 0 replies; 16+ messages in thread
From: Eli Zaretskii @ 2023-11-01 17:15 UTC (permalink / raw)
  To: Tom Tromey; +Cc: gdb-patches

> From: Tom Tromey <tromey@adacore.com>
> Date: Wed, 01 Nov 2023 11:09:39 -0600
> 
> Kévin found a bug in an earlier version of this series that was based
> on a misconception I had about Symbol.is_variable.  This patch fixes
> the documentation to explain the method a bit better.
> ---
>  gdb/doc/python.texi | 4 +++-
>  1 file changed, 3 insertions(+), 1 deletion(-)

Thanks, this is okay.

Approved-By: Eli Zaretskii <eliz@gnu.org>

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH 4/7] Add gdb.Frame.static_link method
  2023-11-01 17:09 ` [PATCH 4/7] Add gdb.Frame.static_link method Tom Tromey
@ 2023-11-01 17:17   ` Eli Zaretskii
  0 siblings, 0 replies; 16+ messages in thread
From: Eli Zaretskii @ 2023-11-01 17:17 UTC (permalink / raw)
  To: Tom Tromey; +Cc: gdb-patches

> From: Tom Tromey <tromey@adacore.com>
> Date: Wed, 01 Nov 2023 11:09:36 -0600
> 
> This adds a new gdb.Frame.static_link method to the gdb Python layer.
> This can be used to find the static link frame for a given frame.
> ---
>  gdb/NEWS              |  3 +++
>  gdb/doc/python.texi   | 10 ++++++++++
>  gdb/python/py-frame.c | 26 ++++++++++++++++++++++++++
>  3 files changed, 39 insertions(+)

The documentation parts of this patch are okay.  Thanks.

Reviewed-By: Eli Zaretskii <eliz@gnu.org>

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH 0/7] Handle nested functions in DAP
  2023-11-01 17:09 [PATCH 0/7] Handle nested functions in DAP Tom Tromey
                   ` (6 preceding siblings ...)
  2023-11-01 17:09 ` [PATCH 7/7] Update gdb.Symbol.is_variable documentation Tom Tromey
@ 2023-11-14 15:39 ` Tom Tromey
  2023-11-14 16:11   ` Tom Tromey
  7 siblings, 1 reply; 16+ messages in thread
From: Tom Tromey @ 2023-11-14 15:39 UTC (permalink / raw)
  To: Tom Tromey; +Cc: gdb-patches

>>>>> "Tom" == Tom Tromey <tromey@adacore.com> writes:

Tom> DAP doesn't really describe what to do for nested functions.  We
Tom> discussed it internally a little, and I came up with this series.  The
Tom> idea here is that variables from the outer scope ought to be visible,
Tom> so this series lets Python code follow a frame's static link, and then
Tom> changes the DAP frame decorators to use this ability.

I'm going to check this in.

I'm also going to backport it to gdb-14, since it is a change for DAP;
and the generic changes should be pretty harmless.

Tom> At some point I think we'll need a "frame decorator v2", like we added
Tom> for pretty-printers.  But, I've been putting this off a little and
Tom> still haven't done it here.

I think this is a bit trickier than it first appears, because frame
filters not only generate objects that conform to the decorator API --
they also use them.

So I'm not sure what to do, if the need really arises.  Maybe we need to
break the API a little.

Tom

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH 0/7] Handle nested functions in DAP
  2023-11-14 15:39 ` [PATCH 0/7] Handle nested functions in DAP Tom Tromey
@ 2023-11-14 16:11   ` Tom Tromey
  0 siblings, 0 replies; 16+ messages in thread
From: Tom Tromey @ 2023-11-14 16:11 UTC (permalink / raw)
  To: Tom Tromey; +Cc: gdb-patches

>>>>> "Tom" == Tom Tromey <tromey@adacore.com> writes:

Tom> I'm also going to backport it to gdb-14, since it is a change for DAP;
Tom> and the generic changes should be pretty harmless.

I regression tested the backport on x86-64 Fedora 38.

Tom

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH 6/7] Handle the static link in FrameDecorator
  2023-11-01 17:09 ` [PATCH 6/7] Handle the static link in FrameDecorator Tom Tromey
@ 2023-11-15 12:31   ` Tom de Vries
  2023-11-15 13:58     ` Tom Tromey
  0 siblings, 1 reply; 16+ messages in thread
From: Tom de Vries @ 2023-11-15 12:31 UTC (permalink / raw)
  To: Tom Tromey, gdb-patches

On 11/1/23 18:09, Tom Tromey wrote:
> +gdb_assert {[dict get $locals namedVariables] == 3} "two locals"

This fails for me with gcc 7.5.0:
...
FAIL: gdb.dap/ada-nested.exp: two locals
...

In more detail:
...
 >>> {"seq": 6, "type": "request", "command": "scopes", "arguments": 
{"frameId": 0}}
Content-Length: 506^M
^M
{"request_seq": 6, "type": "response", "command": "scopes", "body": 
{"scopes": [{"variablesReference": 1, "name": "Arguments", 
"presentationHint": "arguments", "expensive": false, "namedVariables": 
1, "line": 22}, {"variablesReference": 2, "name": "Locals", 
"presentationHint": "locals", "expensive": false, "namedVariables": 4, 
"line": 22}, {"variablesReference": 3, "name": "Registers", 
"presentationHint": "registers", "expensive": false, "namedVariables": 
60, "line": 22}]}, "success": true, "seq": 17}PASS: 
gdb.dap/ada-nested.exp: get scopes success
PASS: gdb.dap/ada-nested.exp: argument scope
PASS: gdb.dap/ada-nested.exp: local scope
FAIL: gdb.dap/ada-nested.exp: two locals
...

AFAICT, the mismatch is because we have named_variables == 4 instead of 3.

Thanks,
- Tom


^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH 6/7] Handle the static link in FrameDecorator
  2023-11-15 12:31   ` Tom de Vries
@ 2023-11-15 13:58     ` Tom Tromey
  2023-11-15 14:07       ` Tom de Vries
  0 siblings, 1 reply; 16+ messages in thread
From: Tom Tromey @ 2023-11-15 13:58 UTC (permalink / raw)
  To: Tom de Vries; +Cc: Tom Tromey, gdb-patches

>>>>> "Tom" == Tom de Vries <tdevries@suse.de> writes:

Tom> On 11/1/23 18:09, Tom Tromey wrote:
>> +gdb_assert {[dict get $locals namedVariables] == 3} "two locals"

Tom> This fails for me with gcc 7.5.0:
Tom> ...
Tom> FAIL: gdb.dap/ada-nested.exp: two locals
Tom> ...

I see the test is misnamed :(

Tom> AFAICT, the mismatch is because we have named_variables == 4 instead of 3.

Can you modify this line:

set refs [lindex [dap_check_request_and_response "fetch variables" \
		      "variables" \
		      [format {o variablesReference [i %d] count [i 3]} \
			   $num]] \
	      0]

to use 'i 4' after 'count', then send the gdb.log?
That will show what the extra variables are.

Tom

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH 6/7] Handle the static link in FrameDecorator
  2023-11-15 13:58     ` Tom Tromey
@ 2023-11-15 14:07       ` Tom de Vries
  2023-11-15 14:58         ` Tom Tromey
  0 siblings, 1 reply; 16+ messages in thread
From: Tom de Vries @ 2023-11-15 14:07 UTC (permalink / raw)
  To: Tom Tromey; +Cc: gdb-patches

[-- Attachment #1: Type: text/plain, Size: 809 bytes --]

On 11/15/23 14:58, Tom Tromey wrote:
>>>>>> "Tom" == Tom de Vries <tdevries@suse.de> writes:
> 
> Tom> On 11/1/23 18:09, Tom Tromey wrote:
>>> +gdb_assert {[dict get $locals namedVariables] == 3} "two locals"
> 
> Tom> This fails for me with gcc 7.5.0:
> Tom> ...
> Tom> FAIL: gdb.dap/ada-nested.exp: two locals
> Tom> ...
> 
> I see the test is misnamed :(
> 
> Tom> AFAICT, the mismatch is because we have named_variables == 4 instead of 3.
> 
> Can you modify this line:
> 
> set refs [lindex [dap_check_request_and_response "fetch variables" \
> 		      "variables" \
> 		      [format {o variablesReference [i %d] count [i 3]} \
> 			   $num]] \
> 	      0]
> 
> to use 'i 4' after 'count', then send the gdb.log?
> That will show what the extra variables are.

Done, gdb.log.gz attached.

Thanks,
- Tom

[-- Attachment #2: gdb.log.gz --]
[-- Type: application/gzip, Size: 2956 bytes --]

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH 6/7] Handle the static link in FrameDecorator
  2023-11-15 14:07       ` Tom de Vries
@ 2023-11-15 14:58         ` Tom Tromey
  0 siblings, 0 replies; 16+ messages in thread
From: Tom Tromey @ 2023-11-15 14:58 UTC (permalink / raw)
  To: Tom de Vries; +Cc: Tom Tromey, gdb-patches

>>>>> "Tom" == Tom de Vries <tdevries@suse.de> writes:

>> That will show what the extra variables are.

Tom> Done, gdb.log.gz attached.

It looks like a compiler bug to me.

The second variable is also 'x':

    FAIL: gdb.dap/ada-nested.exp: unknown variable x #2

... with the same value as the first 'x'.
So it seems to be some kind of duplicate.

We could either work around this (allow it in the test) or just require
some newer version of GNAT.

I think I'll update my recent patch to this test to allow this
weirdness.

Tom

^ permalink raw reply	[flat|nested] 16+ messages in thread

end of thread, other threads:[~2023-11-15 14:58 UTC | newest]

Thread overview: 16+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-11-01 17:09 [PATCH 0/7] Handle nested functions in DAP Tom Tromey
2023-11-01 17:09 ` [PATCH 1/7] Add two convenience methods to block Tom Tromey
2023-11-01 17:09 ` [PATCH 2/7] Add block::function_block Tom Tromey
2023-11-01 17:09 ` [PATCH 3/7] Move follow_static_link to frame.c Tom Tromey
2023-11-01 17:09 ` [PATCH 4/7] Add gdb.Frame.static_link method Tom Tromey
2023-11-01 17:17   ` Eli Zaretskii
2023-11-01 17:09 ` [PATCH 5/7] Fix a bug in DAP scopes code Tom Tromey
2023-11-01 17:09 ` [PATCH 6/7] Handle the static link in FrameDecorator Tom Tromey
2023-11-15 12:31   ` Tom de Vries
2023-11-15 13:58     ` Tom Tromey
2023-11-15 14:07       ` Tom de Vries
2023-11-15 14:58         ` Tom Tromey
2023-11-01 17:09 ` [PATCH 7/7] Update gdb.Symbol.is_variable documentation Tom Tromey
2023-11-01 17:15   ` Eli Zaretskii
2023-11-14 15:39 ` [PATCH 0/7] Handle nested functions in DAP Tom Tromey
2023-11-14 16:11   ` Tom Tromey

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).