]> git.ipfire.org Git - thirdparty/binutils-gdb.git/commitdiff
gdb: add annotation in 'info locals' command for variables shadowing case
authorIjaz, Abdul B <abdul.b.ijaz@intel.com>
Fri, 15 Sep 2023 13:12:10 +0000 (15:12 +0200)
committerAbdul Basit Ijaz <abdul.b.ijaz@intel.com>
Fri, 17 Jul 2026 17:36:18 +0000 (19:36 +0200)
For C/C++/Fortran/Ada languages GDB prints same name variable multiple
times in case of variable shadowing and it is confusing for user to identify
which variable belongs to the current scope.  So for such cases add location
info to the innermost listed variables and for super block variables add
"shadowed" annotation in the form of "<file.c:line, shadowed>".

Suppose we have

1:int x = 3;
2:  {
3:    int x = 4;
4:    int y = 52;
5:    x = 99; /* break here */
6:  }

Currently:

(gdb) info locals
x = 4
y = 52
x = 3

After applying this patch, we obtain:

(gdb) info locals
x = 4  <file.c:3>
y = 52
x = 3  <file.c:1, shadowed>

The patch adds the location annotations by keeping track of inner block
and already printed variables to identify shadowing.  So, GDB now prints
"<file.c:line, shadowed>" for shadowed super-block variables and
"<file.c:line>" for innermost declarations of such variables only.

The location annotations are printed for shadowed variables in case of
C/C++/Fortran/Ada languages.  In Rust, it is possible to declare a
variable with the same name many times.  So in this case, just the first
instance of the variable is printed.  RUST language test "var_reuse.exp"
fails with rustc compiler version >= 1.73 so XFAIL is added accordingly.

Fix regex expression in the gdb.opt/inline-locals.exp test according to
this change.  The test update is only required due to the existing gdb
known ticket gdb/25695 where this issue is seen with 7.5.0 version on
sles15sp6 but it is not seen anymore on the newer gcc versions e.g.
gcc-11.4.0.

The symtab()/filename() nullptr check was added specifically to avoid
the crash seen in gdb.dwarf2/missing-type-name-for-templates.exp where
template symbols may have no associated source file.

Reviewed-By: Guinevere Larsen <guinevere@redhat.com>
Reviewed-By: Eli Zaretskii <eliz@gnu.org>
Co-Authored-By: Andrew Burgess <aburgess@redhat.com>
Approved-By: Andrew Burgess <aburgess@redhat.com>
17 files changed:
gdb/NEWS
gdb/c-typeprint.c
gdb/doc/gdb.texinfo
gdb/language.c
gdb/language.h
gdb/printcmd.c
gdb/stack.c
gdb/stack.h
gdb/testsuite/gdb.ada/var_shadowing.exp [new file with mode: 0644]
gdb/testsuite/gdb.ada/var_shadowing/var_shadowing.adb [new file with mode: 0644]
gdb/testsuite/gdb.base/var-shadowing.c [new file with mode: 0755]
gdb/testsuite/gdb.base/var-shadowing.exp [new file with mode: 0755]
gdb/testsuite/gdb.base/var-shadowing2.c [new file with mode: 0644]
gdb/testsuite/gdb.opt/inline-locals.exp
gdb/testsuite/gdb.rust/var_reuse.exp [new file with mode: 0755]
gdb/testsuite/gdb.rust/var_reuse.rs [new file with mode: 0755]
gdb/value.h

index ec9b5a33787c81e207eb18574749e0e345cc16a3..5410acc830325f5a5243d7038640bd94a26e76d1 100644 (file)
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -201,6 +201,10 @@ New command class for help
   commands that we, as developers, believe would be close to a minimal
   set of commands for a new user of GDB.
 
+info locals
+  GDB now shows the "shadowed" annotation and the location information
+  for variables that are shadowed, or which are shadowing.
+
 * Removed commands
 
 target ctf
index aaace85aa29c6da376b4bf88a66c534e04765a08..1168464a9f9f71cc5aa3a9c05509dd253b79fa52 100644 (file)
@@ -818,7 +818,8 @@ c_type_print_template_args (const struct type_print_options *flags,
       if (sym->loc_class () == LOC_TYPEDEF)
        c_print_type (sym->type (), "", stream, -1, 0, language, flags);
       else
-       print_variable_value (sym, {}, stream, 0, language_def (language));
+       print_variable_value (sym, {}, stream, 0, language_def (language),
+                             var_shadowing::NONE);
     }
 
   gdb_puts (_("] "), stream);
index 04d761c904ca250462084f7bdf6bff60a9613774..d2fbe61d8fca8a13451afc2a189992c3a9f280d4 100644 (file)
@@ -9188,6 +9188,32 @@ The optional flag @samp{-q}, which stands for @samp{quiet}, disables
 printing header information and messages explaining why no local variables
 have been printed.
 
+@smallexample
+@group
+1: int x = 3;
+2: @{
+3:       int x = 4;
+4:       int y = 52;
+5:       x = 99; // breakpoint-line
+6: @}
+@end group
+@group
+(gdb) info locals
+x = 4  <file.c:3>
+y = 52
+x = 3  <file.c:1, shadowed>
+@end group
+@end smallexample
+
+@anchor{shadowed variables}
+@cindex shadowed variables
+A variable is @dfn{shadowed} when there's another variable with the
+same name which is declared within an inner scope (decision block,
+method, or inner class).  When shadowing is detected, location
+information is added to all instances of the shadowed variable name.
+The outermost instances are additionally followed by @samp{shadowed}
+to indicate that they are not the active variable.
+
 @item info locals [-q] [-t @var{type_regexp}] [@var{regexp}]
 Like @kbd{info locals}, but only print the local variables selected
 with the provided regexp(s).
index 3e3be66a6768a3973a89e606837e58881b77029d..972018e8f7b710f3e943aabf41f1aede058e5867 100644 (file)
@@ -1090,6 +1090,26 @@ language_lookup_primitive_type_as_symbol (const struct language_defn *la,
   return sym;
 }
 
+/* See language.h.  */
+
+lang_vars_shadowing get_lang_vars_shadowing_option (enum language lang)
+{
+  switch (lang)
+    {
+    case language_c:
+    case language_cplus:
+    case language_fortran:
+    case language_ada:
+      return lang_vars_shadowing::PRINT;
+
+    case language_rust:
+      return lang_vars_shadowing::HIDE;
+
+    default:
+      return lang_vars_shadowing::NONE;
+    }
+}
+
 /* Initialize the language routines.  */
 
 INIT_GDB_FILE (language)
index 62827ffe02cc99d1295c6ad8b611de90105d0828..fb070edaa54d94473fff607302ee79989ca033c0 100644 (file)
@@ -83,6 +83,20 @@ enum macro_expansion
     macro_expansion_no, macro_expansion_c
   };
 
+/* How should shadowed, or shadowing, variables be printed.  */
+
+enum class lang_vars_shadowing
+  {
+    /* Adds shadowed information for such variables.  */
+    PRINT,
+
+    /* Does not print shadowed variables.  */
+    HIDE,
+
+    /* Print variables without shadow information.  */
+    NONE,
+  };
+
 \f
 /* Per architecture (OS/ABI) language information.  */
 
@@ -801,6 +815,10 @@ void c_get_string (struct value *value,
 symbol_name_matcher_ftype *get_symbol_name_matcher
   (const language_defn *lang, const lookup_name_info &lookup_name);
 
+/* Returns the shadowing option supported for the input language.  */
+
+extern lang_vars_shadowing get_lang_vars_shadowing_option (enum language lang);
+
 /* Save the current language and restore it upon destruction.  */
 
 class scoped_restore_current_language
index 662f43fbef895d20308f04bf415fce1719592306..15c086a92845ca7ecc13b67695f4d4f85992faed 100644 (file)
@@ -2377,7 +2377,8 @@ clear_dangling_display_expressions (struct objfile *objfile)
 void
 print_variable_value (symbol *var, const frame_info_ptr &frame,
                      ui_file *stream, int indent,
-                     const language_defn *language)
+                     const language_defn *language,
+                     var_shadowing shadow_status)
 {
   try
     {
@@ -2392,6 +2393,41 @@ print_variable_value (symbol *var, const frame_info_ptr &frame,
       get_user_print_options (&opts);
       opts.deref_ref = true;
       common_val_print_checked (val, stream, indent, &opts, language);
+
+      /* Print <%line, shadowed> after the variable value only when it is
+        variable shadowing case.  */
+      if (shadow_status != var_shadowing::NONE)
+       {
+         /* Use lbasename instead of symtab_to_filename_for_display as the
+            latter defaults to relative path while basename is preferred
+            here.  */
+         const char *file_name = (var->symtab () != nullptr
+                                  && var->symtab ()->filename () != nullptr)
+                                  ? lbasename (var->symtab ()->filename ())
+                                  : "??";
+         bool printed = (shadow_status == var_shadowing::SHADOWED);
+         string_file out (current_uiout->can_emit_style_escape ());
+
+         gdb_printf (&out, "\t<%ps:",
+                     styled_string (file_name_style.style (), file_name));
+
+         if (var->line () > 0)
+           gdb_printf (&out, "%ps",
+                       styled_string (line_number_style.style (),
+                                      pulongest (var->line ())));
+         else
+           gdb_puts ("No line number information available", &out);
+
+         if (printed)
+           {
+             gdb_puts (", ", &out);
+             fputs_styled ("shadowed", metadata_style.style (), &out);
+           }
+
+         gdb_puts (">", &out);
+
+         gdb_puts (out.c_str (), stream);
+       }
     }
   catch (const gdb_exception_error &except)
     {
@@ -2406,7 +2442,8 @@ print_variable_value (symbol *var, const frame_info_ptr &frame,
 void
 print_variable_and_value (const char *name, symbol *var,
                          const frame_info_ptr &frame,
-                         ui_file *stream, int indent)
+                         ui_file *stream, int indent,
+                         var_shadowing shadow_status)
 {
   if (name == nullptr)
     name = var->print_name ();
@@ -2414,7 +2451,8 @@ print_variable_and_value (const char *name, symbol *var,
   gdb_printf (stream, "%*s%ps = ", 2 * indent, "",
              styled_string (variable_name_style.style (), name));
 
-  print_variable_value (var, frame, stream, indent, current_language);
+  print_variable_value (var, frame, stream, indent, current_language,
+                       shadow_status);
 
   gdb_printf (stream, "\n");
 }
index e084976eabfaf20344d6594ee755a31b168a705e..f41f818cebb0b71e00db3e6061bfc4bb3d0603b9 100644 (file)
@@ -59,6 +59,7 @@
 #include "cli/cli-option.h"
 #include "cli/cli-style.h"
 #include "gdbsupport/buildargv.h"
+#include "gdbsupport/unordered_set.h"
 
 /* The possible choices of "set print frame-arguments", and the value
    of this setting.  */
@@ -2133,8 +2134,9 @@ backtrace_command_completer (struct cmd_list_element *ignore,
 /* Iterate over the local variables of a block B, calling CB.  */
 
 static void
-iterate_over_block_locals (const struct block *b,
-                          iterate_over_block_arg_local_vars_cb cb)
+iterate_over_block_locals
+  (const struct block *b,
+   gdb::function_view <void (const char *, struct symbol *)> cb)
 {
   for (struct symbol *sym : block_iterator_range (b))
     {
@@ -2180,6 +2182,71 @@ iterate_over_block_local_vars (const struct block *block,
     }
 }
 
+/* See stack.h.  */
+
+void
+iterate_over_block_local_vars_printing
+  (const struct block *block,
+   iterate_over_block_arg_local_vars_cb_printing cb)
+{
+  gdb::unordered_set<std::string> collected_vars, shadowed_vars, printed_vars;
+
+  /* Phase one: iterate over all locals within the block, and every parent
+     block up to the enclosing function block.  Record all of the locals
+     seen, this allows us to know which locals are shadowing locals from a
+     more outer scope.  */
+  iterate_over_block_local_vars
+    (block, [&] (const char *print_name, struct symbol *sym)
+    {
+      if (!sym->is_argument ())
+       {
+         if (!collected_vars.insert (print_name).second)
+           shadowed_vars.insert (print_name);
+       }
+    });
+
+  /* Phase two: iterate over all locals within the block, and every parent
+     block up to the enclosing function block.  Print all the locals seen
+     by calling CB.  Depending on the current language we vary the
+     arguments to CB to indicate shadowing.  Or in some cases, we don't
+     print the local at all.  */
+  iterate_over_block_local_vars
+    (block, [&] (const char *print_name, struct symbol *sym)
+    {
+      bool already_printed = !printed_vars.insert (print_name).second;
+      bool shadowed = shadowed_vars.find (print_name) != shadowed_vars.end ();
+
+      enum var_shadowing shadowing_status;
+      if (already_printed && shadowed)
+       shadowing_status = var_shadowing::SHADOWED;
+      else if (!already_printed && shadowed)
+       shadowing_status = var_shadowing::SHADOWING;
+      else
+       shadowing_status = var_shadowing::NONE;
+
+      /* Only for C/C++/Fortran/Ada languages, in case of variables
+        shadowing print <file:line, shadowed> annotation after
+        the superblock variable.  Iteration of block starts from inner
+        block which is printed only with location information.  */
+      if (get_lang_vars_shadowing_option (current_language->la_language)
+         == lang_vars_shadowing::PRINT
+         && shadowing_status != var_shadowing::NONE)
+       cb (print_name, sym, shadowing_status);
+      /* In case of Rust language it is possible to declare variable with
+        same name multiple times and only innermost instance of variable
+        is accessible.  So print only the innermost instance and there is
+        no need of printing duplicates.  */
+      else if (get_lang_vars_shadowing_option (current_language->la_language)
+              == lang_vars_shadowing::HIDE
+              && shadowing_status == var_shadowing::SHADOWED)
+       {
+         /* Nothing.  */
+       }
+      else
+       cb (print_name, sym, var_shadowing::NONE);
+    });
+}
+
 /* Data to be passed around in the calls to the locals and args
    iterators.  */
 
@@ -2192,14 +2259,16 @@ struct print_variable_and_value_data
   struct ui_file *stream;
   int values_printed;
 
-  void operator() (const char *print_name, struct symbol *sym);
+  void operator() (const char *print_name, struct symbol *sym,
+                  var_shadowing shadow_status);
 };
 
 /* The callback for the locals and args iterators.  */
 
 void
 print_variable_and_value_data::operator() (const char *print_name,
-                                          struct symbol *sym)
+                                          struct symbol *sym,
+                                          var_shadowing shadow_status)
 {
   frame_info_ptr frame;
 
@@ -2219,7 +2288,8 @@ print_variable_and_value_data::operator() (const char *print_name,
       return;
     }
 
-  print_variable_and_value (print_name, sym, frame, stream, num_tabs);
+  print_variable_and_value (print_name, sym, frame, stream, num_tabs,
+                           shadow_status);
 
   values_printed = 1;
 }
@@ -2288,7 +2358,7 @@ print_frame_local_vars (const frame_info_ptr &frame,
   scoped_restore_selected_frame restore_selected_frame;
   select_frame (frame);
 
-  iterate_over_block_local_vars (block, cb_data);
+  iterate_over_block_local_vars_printing (block, cb_data);
 
   if (!cb_data.values_printed && !quiet)
     {
@@ -2386,6 +2456,37 @@ iterate_over_block_arg_vars (const struct block *b,
     }
 }
 
+/* See stack.h.  */
+
+void
+iterate_over_block_arg_vars_printing
+  (const struct block *b,
+   iterate_over_block_arg_local_vars_cb_printing cb)
+{
+  for (struct symbol *sym : block_iterator_range (b))
+    {
+      /* Don't worry about things which aren't arguments.  */
+      if (sym->is_argument ())
+       {
+         /* We have to look up the symbol because arguments can have
+            two entries (one a parameter, one a local) and the one we
+            want is the local, which lookup_symbol will find for us.
+            This includes gcc1 (not gcc2) on the sparc when passing a
+            small structure and gcc2 when the argument type is float
+            and it is passed as a double and converted to float by
+            the prologue (in the latter case the type of the LOC_ARG
+            symbol is double and the type of the LOC_LOCAL symbol is
+            float).  There are also LOC_ARG/LOC_REGISTER pairs which
+            are not combined in symbol-reading.  */
+
+         struct symbol *sym2
+           = lookup_symbol_search_name (sym->search_name (),
+                                        b, SEARCH_VAR_DOMAIN).symbol;
+         cb (sym->print_name (), sym2, var_shadowing::NONE);
+       }
+    }
+}
+
 /* Print all argument variables of the function of FRAME.
    Print them with values to STREAM.
    If REGEXP is not NULL, only print argument variables whose name
@@ -2428,7 +2529,7 @@ print_frame_arg_vars (const frame_info_ptr &frame,
   cb_data.stream = stream;
   cb_data.values_printed = 0;
 
-  iterate_over_block_arg_vars (func->value_block (), cb_data);
+  iterate_over_block_arg_vars_printing (func->value_block (), cb_data);
 
   if (!cb_data.values_printed && !quiet)
     {
index 48096a29286c50f4e9f9d4f0d8c362e33355b51a..4f7c223f47fd573119e60167c6b2ecf5823d47a5 100644 (file)
@@ -20,6 +20,8 @@
 #ifndef GDB_STACK_H
 #define GDB_STACK_H
 
+enum class var_shadowing;
+
 gdb::unique_xmalloc_ptr<char> find_frame_funname (const frame_info_ptr &frame,
                                                  enum language *funlang,
                                                  struct symbol **funcp);
@@ -27,12 +29,32 @@ gdb::unique_xmalloc_ptr<char> find_frame_funname (const frame_info_ptr &frame,
 using iterate_over_block_arg_local_vars_cb
   = gdb::function_view<void (const char *print_name, struct symbol *sym)>;
 
+using iterate_over_block_arg_local_vars_cb_printing
+  = gdb::function_view<void (const char *print_name, struct symbol *sym,
+                            var_shadowing shadow_status)>;
+
 void iterate_over_block_arg_vars (const struct block *block,
                                  iterate_over_block_arg_local_vars_cb cb);
 
 void iterate_over_block_local_vars (const struct block *block,
                                    iterate_over_block_arg_local_vars_cb cb);
 
+/* Iterate over all the argument variables in block B, call CB for
+   each variable with its print name, symbol, and shadowing status.  */
+
+void iterate_over_block_arg_vars_printing
+  (const struct block *block,
+   iterate_over_block_arg_local_vars_cb_printing cb);
+
+/* Iterate over all the local variables in block B, including all its
+   superblocks, stopping when the top-level block is reached.  Call CB
+   for each variable with its print name, symbol, and shadowing status
+   relative to inner blocks already visited.  */
+
+void iterate_over_block_local_vars_printing
+  (const struct block *block,
+   iterate_over_block_arg_local_vars_cb_printing cb);
+
 /* Initialize *WHAT to be a copy of the user desired print what frame info.
    If !WHAT.has_value (), the printing function chooses a default set of
    information to print, otherwise the printing function should print
diff --git a/gdb/testsuite/gdb.ada/var_shadowing.exp b/gdb/testsuite/gdb.ada/var_shadowing.exp
new file mode 100644 (file)
index 0000000..ffa96b0
--- /dev/null
@@ -0,0 +1,39 @@
+# Copyright 2023-2026 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/>.
+
+load_lib "ada.exp"
+
+require allow_ada_tests
+
+standard_ada_testfile var_shadowing
+
+if {[gdb_compile_ada "${srcfile}" "${binfile}" \
+    executable [list debug]] != "" } {
+    return
+}
+
+clean_restart ${testfile}
+
+set i_level1 [gdb_get_line_number "I-Level1"]
+set i_level2 [gdb_get_line_number "I-Level2"]
+set i_level3 [gdb_get_line_number "I-Level3"]
+set bp_location [gdb_get_line_number "BREAK"]
+runto "var_shadowing.adb:$bp_location"
+
+gdb_test "info locals" [multi_line \
+    "i = 111\t<$testfile.adb:$i_level3>"  \
+    "i = 11\t<$testfile.adb:$i_level2, shadowed>"  \
+    "i = 1\t<$testfile.adb:$i_level1, shadowed>"  \
+] "info locals at innermost level"
diff --git a/gdb/testsuite/gdb.ada/var_shadowing/var_shadowing.adb b/gdb/testsuite/gdb.ada/var_shadowing/var_shadowing.adb
new file mode 100644 (file)
index 0000000..2798338
--- /dev/null
@@ -0,0 +1,30 @@
+--  Copyright 2023-2026 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/>.
+
+with Ada.Text_IO; use Ada.Text_IO;
+
+procedure Varshadow is
+  I : Integer := 1;  -- I-Level1
+begin
+  declare
+    I : Integer := 11; -- I-Level2
+  begin
+    declare
+      I : Integer := 111; -- I-Level3
+    begin
+      Put_Line ("hello");  --  BREAK
+    end;
+  end;
+end;
diff --git a/gdb/testsuite/gdb.base/var-shadowing.c b/gdb/testsuite/gdb.base/var-shadowing.c
new file mode 100755 (executable)
index 0000000..7886ea3
--- /dev/null
@@ -0,0 +1,51 @@
+/* Copyright (C) 2023-2026 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/>.  */
+
+#include <stdlib.h>
+
+int
+shadowing (void)
+{
+  int a = 100;  /* bp for entry */
+  unsigned int val1 = 1;               /* val1-d1 */
+  unsigned int val2 = 2;               /* val2-d1 */
+  a = 101;  /* bp for locals 1 */
+  {
+    unsigned int val2 = 3;             /* val2-d2 */
+    unsigned int val3 = 4;             /* val3-d1 */
+    a = 102;  /* bp for locals 2 */
+    {
+      unsigned int val1 = 5;           /* val1-d2 */
+      a = 103;  /* bp for locals 3 */
+      {
+       #include "var-shadowing2.c"
+       unsigned int val1 = 6;  /* val1-d3 */
+       unsigned int val2 = 7;  /* val2-d3 */
+       unsigned int val3 = 8;  /* val3-d2 */
+       a = 104;  /* bp for locals 4 */
+      }
+    }
+  }
+  a = 105;
+
+  return 0; /* bp for locals 5 */
+}
+
+int
+main (void)
+{
+  shadowing ();
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.base/var-shadowing.exp b/gdb/testsuite/gdb.base/var-shadowing.exp
new file mode 100755 (executable)
index 0000000..119b8c3
--- /dev/null
@@ -0,0 +1,92 @@
+# Copyright 2023-2026 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/>.
+
+standard_testfile
+if [prepare_for_testing "failed to prepare" $testfile $srcfile] {
+    return
+}
+
+if ![runto_main] {
+    return
+}
+
+set bp_line1 [gdb_get_line_number "bp for locals 1"]
+set bp_line2 [gdb_get_line_number "bp for locals 2"]
+set bp_line3 [gdb_get_line_number "bp for locals 3"]
+set bp_line4 [gdb_get_line_number "bp for locals 4"]
+set bp_line5 [gdb_get_line_number "bp for locals 5"]
+
+set val1_d1 [gdb_get_line_number "val1-d1"]
+set val1_d2 [gdb_get_line_number "val1-d2"]
+set val1_d3 [gdb_get_line_number "val1-d3"]
+set val2_d1 [gdb_get_line_number "val2-d1"]
+set val2_d2 [gdb_get_line_number "val2-d2"]
+set val2_d3 [gdb_get_line_number "val2-d3"]
+set val3_d1 [gdb_get_line_number "val3-d1"]
+set val3_d2 [gdb_get_line_number "val3-d2"]
+set a_line [gdb_get_line_number "bp for entry"]
+
+gdb_breakpoint $srcfile:$bp_line1
+gdb_continue_to_breakpoint "continue to outermost level" \
+    ".*$srcfile:$bp_line1.*"
+gdb_test "info locals"  [multi_line \
+    "val1 = 1"  \
+    "val2 = 2"  \
+    ] "info locals at outermost level"
+
+gdb_breakpoint $srcfile:$bp_line2
+gdb_continue_to_breakpoint "continue to first level" ".*$srcfile:$bp_line2.*"
+gdb_test "info locals"  [multi_line \
+    "val2 = 3\t<$srcfile:$val2_d2>"  \
+    "val3 = 4"  \
+    "a = 101"   \
+    "val1 = 1"  \
+    "val2 = 2\t<$srcfile:$val2_d1, shadowed>"  \
+    ] "info locals first level"
+
+gdb_breakpoint $srcfile:$bp_line3
+gdb_continue_to_breakpoint "continue to second level" ".*$srcfile:$bp_line3.*"
+gdb_test "info locals" [multi_line \
+    "val1 = 5\t<$srcfile:$val1_d2>"  \
+    "val2 = 3\t<$srcfile:$val2_d2>"  \
+    "val3 = 4"  \
+    "a = 102"   \
+    "val1 = 1\t<$srcfile:$val1_d1, shadowed>"  \
+    "val2 = 2\t<$srcfile:$val2_d1, shadowed>"  \
+    ] "info locals second level"
+
+gdb_breakpoint $srcfile:$bp_line4
+gdb_continue_to_breakpoint "continue to innermost level" ".*$srcfile:$bp_line4.*"
+gdb_test "info locals" [multi_line \
+    "a = 999\t<${testfile}2.c:16>" \
+    "val1 = 6\t<$srcfile:$val1_d3>"  \
+    "val2 = 7\t<$srcfile:$val2_d3>"  \
+    "val3 = 8\t<$srcfile:$val3_d2>"  \
+    "val1 = 5\t<$srcfile:$val1_d2, shadowed>" \
+    "val2 = 3\t<$srcfile:$val2_d2, shadowed>" \
+    "val3 = 4\t<$srcfile:$val3_d1, shadowed>" \
+    "a = 103\t<$srcfile:$a_line, shadowed>"   \
+    "val1 = 1\t<$srcfile:$val1_d1, shadowed>" \
+    "val2 = 2\t<$srcfile:$val2_d1, shadowed>" \
+    ] "info locals at innermost level"
+
+gdb_breakpoint $srcfile:$bp_line5
+gdb_continue_to_breakpoint "continue to outermost level last" \
+    ".*$srcfile:$bp_line5.*"
+gdb_test "info locals" [multi_line \
+    "a = 105" \
+    "val1 = 1"  \
+    "val2 = 2"  \
+    ] "info locals at outermost level last"
diff --git a/gdb/testsuite/gdb.base/var-shadowing2.c b/gdb/testsuite/gdb.base/var-shadowing2.c
new file mode 100644 (file)
index 0000000..d70fcd3
--- /dev/null
@@ -0,0 +1,16 @@
+/* Copyright (C) 2023-2026 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/>.  */
+
+int a = 999;
index 2c3a9f71c2fb1472b6670f4c8577c198c9f0a776..6af398e5cf4f242cd9a9ce371cd60e1547a84b2b 100644 (file)
@@ -42,9 +42,11 @@ if { ! $no_frames } {
        "backtrace from bar 2"
     gdb_test "up" "#1  .*func1 .* at .*" "up from bar 2"
     gdb_test "info frame" ".*inlined into frame.*" "func1 inlined 2"
-    set pass_re "array = \\{0 <repeats 64 times>\\}"
+    set shadowed_pass_re "\t<$srcfile:$decimal>"
+    set shadowed_fail_re "\t<$srcfile:$decimal, shadowed>"
+    set pass_re "array = \\{0 <repeats 64 times>\\}($shadowed_pass_re)?"
     set kfail_re [multi_line $pass_re \
-                     "array = <optimized out>"]
+                     "array = <optimized out>$shadowed_fail_re"]
     gdb_test_multiple "info locals" "info locals above bar 2" {
        -re -wrap $pass_re {
            pass $gdb_test_name
@@ -91,9 +93,9 @@ if { ! $no_frames } {
        "backtrace from bar 3"
     gdb_test "up" "#1  .*func1 .* at .*" "up from bar 3"
     gdb_test "info frame" ".*inlined into frame.*" "func1 inlined 3"
-    set pass_re "array = {$decimal, \[^\r\n\]*}"
+    set pass_re "array = {$decimal, \[^\r\n\]*}($shadowed_pass_re)?"
     set kfail_re [multi_line $pass_re \
-                     "array = <optimized out>"]
+                     "array = <optimized out>$shadowed_fail_re"]
     gdb_test_multiple "info locals" "info locals above bar 3" {
        -re -wrap $pass_re {
            pass $gdb_test_name
@@ -133,7 +135,8 @@ proc check_scoped_locals {bp_label pass_re} {
     gdb_breakpoint $srcfile:$locals_bp
 
     gdb_continue_to_breakpoint "$bp_label" ".*$srcfile:$locals_bp.*"
-    set kfail_re [multi_line $pass_re ".*<optimized out>"]
+    set kfail_re [multi_line $pass_re \
+                     ".*<optimized out>(.*<$srcfile:$::decimal, shadowed>)?"]
     gdb_test_multiple "info locals" "scoped info locals at $bp_label" {
        -re -wrap $pass_re {
            pass $gdb_test_name
@@ -149,7 +152,9 @@ proc check_scoped_locals {bp_label pass_re} {
 }
 
 if {! $no_frames } {
-    check_scoped_locals "bp for locals 1" "loc2 = 20\r\nloc1 = 10"
-    check_scoped_locals "bp for locals 2" "loc3 = 30\r\nloc2 = 20\r\nloc1 = 10"
-    check_scoped_locals "bp for locals 3" "loc1 = 10"
+    check_scoped_locals "bp for locals 1" \
+       "loc2 = 20(\t<$srcfile:$decimal>)?\r\nloc1 = 10(\t<$srcfile:$decimal>)?"
+    check_scoped_locals "bp for locals 2" \
+       "loc3 = 30(\t<$srcfile:$decimal>)?\r\nloc2 = 20(\t<$srcfile:$decimal>)?\r\nloc1 = 10(\t<$srcfile:$decimal>)?"
+    check_scoped_locals "bp for locals 3" "loc1 = 10(\t<$srcfile:$decimal>)?"
 }
diff --git a/gdb/testsuite/gdb.rust/var_reuse.exp b/gdb/testsuite/gdb.rust/var_reuse.exp
new file mode 100755 (executable)
index 0000000..dcba42b
--- /dev/null
@@ -0,0 +1,36 @@
+# Copyright 2023-2026 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/>.
+
+load_lib rust-support.exp
+require allow_rust_tests
+require {can_compile rust}
+
+standard_testfile .rs
+if {[prepare_for_testing "failed to prepare" \
+       $testfile $srcfile {debug rust}]} {
+    return
+}
+
+set line [gdb_get_line_number "set breakpoint here"]
+if {![runto ${srcfile}:$line]} {
+    untested "could not run to breakpoint"
+    return
+}
+
+# Wrong local values are shown for rustc version >= 1.73.
+if {[rust_at_least 1.73]} {
+    setup_xfail "*-*-*" "gdb/31079"
+}
+gdb_test "info local _x" "_x = 12" "print local _x variable"
diff --git a/gdb/testsuite/gdb.rust/var_reuse.rs b/gdb/testsuite/gdb.rust/var_reuse.rs
new file mode 100755 (executable)
index 0000000..70101a0
--- /dev/null
@@ -0,0 +1,20 @@
+// Copyright (C) 2023-2026 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/>.
+
+fn main() {
+    let _x = 5;
+    let _x = _x + 7;
+    let _y = 8;       // set breakpoint here
+}
index e94c69e49d355867a8eb270a14ce372cbff7425f..4cc0d7d0902baef52bfa2b12fb0f5012d00a1e28 100644 (file)
@@ -1578,28 +1578,46 @@ extern int val_print_string (struct type *elttype, const char *encoding,
                             struct ui_file *stream,
                             const struct value_print_options *options);
 
+/* Track the shadowing status of a variable.  */
+enum class var_shadowing
+{
+  /* This variable is not shadowed, and not shadowing.  */
+  NONE,
+
+  /* This variable is shadowed by a later one.  */
+  SHADOWED,
+
+  /* This variable is shadowing an earlier one, and is not itself
+     shadowed.  */
+  SHADOWING
+};
+
 /* Print the value in stack frame FRAME of a variable specified by a
    struct symbol.  STREAM is the ui_file on which to print the value.
    INDENT specifies the number of indent levels to print before
    printing the variable name.  LANGUAGE is the language to use for
-   printing.  */
+   printing.  SHADOW_STATUS specifies the variable shadowing
+   information.  */
 
 extern void print_variable_value (symbol *var,
                                  const frame_info_ptr &frame,
                                  ui_file *stream, int indent,
-                                 const language_defn *language);
+                                 const language_defn *language,
+                                 var_shadowing shadow_status);
 
 /* Print the value in stack frame FRAME of a variable specified by a
    struct symbol.  NAME is the name to print; if NULL then VAR's print
    name will be used.  STREAM is the ui_file on which to print the
    value.  INDENT specifies the number of indent levels to print
-   before printing the variable name.  */
+   before printing the variable name.  SHADOW_STATUS specifies the
+   variable shadowing information.  */
 
 extern void print_variable_and_value (const char *name,
                                      symbol *var,
                                      const frame_info_ptr &frame,
                                      ui_file *stream,
-                                     int indent);
+                                     int indent,
+                                     var_shadowing shadow_status);
 
 extern void typedef_print (struct type *type, struct symbol *news,
                           struct ui_file *stream);