[binutils-gdb] gdb: add annotation in 'info locals' command for variables shadowing case

Abdul Basit Ijaz via Gdb-cvs <[email protected]> Fri, 17 Jul 2026 17:39:31 +0000 (GMT)
Newsgroups gmane.comp.gdb.cvs
Message-ID <[email protected]>
https://sourceware.org/git/gitweb.cgi?p=3Dbinutils-gdb.git;h=3D2465b9e41315=
14bdb42365076ef65ef6ad0e3f6c

commit 2465b9e4131514bdb42365076ef65ef6ad0e3f6c
Author: Ijaz, Abdul B <[email protected]>
Date:   Fri Sep 15 15:12:10 2023 +0200

    gdb: add annotation in 'info locals' command for variables shadowing ca=
se
   =20
    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 ide=
ntify
    which variable belongs to the current scope.  So for such cases add loc=
ation
    info to the innermost listed variables and for super block variables add
    "shadowed" annotation in the form of "<file.c:line, shadowed>".
   =20
    Suppose we have
   =20
    1:int x =3D 3;
    2:  {
    3:    int x =3D 4;
    4:    int y =3D 52;
    5:    x =3D 99; /* break here */
    6:  }
   =20
    Currently:
   =20
    (gdb) info locals
    x =3D 4
    y =3D 52
    x =3D 3
   =20
    After applying this patch, we obtain:
   =20
    (gdb) info locals
    x =3D 4  <file.c:3>
    y =3D 52
    x =3D 3  <file.c:1, shadowed>
   =20
    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.
   =20
    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 >=3D 1.73 so XFAIL is added according=
ly.
   =20
    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.
   =20
    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.
   =20
    Reviewed-By: Guinevere Larsen <[email protected]>
    Reviewed-By: Eli Zaretskii <[email protected]>
    Co-Authored-By: Andrew Burgess <[email protected]>
    Approved-By: Andrew Burgess <[email protected]>

Diff:
---
 gdb/NEWS                                           |   4 +
 gdb/c-typeprint.c                                  |   3 +-
 gdb/doc/gdb.texinfo                                |  26 +++++
 gdb/language.c                                     |  20 ++++
 gdb/language.h                                     |  18 ++++
 gdb/printcmd.c                                     |  44 +++++++-
 gdb/stack.c                                        | 115 +++++++++++++++++=
++--
 gdb/stack.h                                        |  22 ++++
 gdb/testsuite/gdb.ada/var_shadowing.exp            |  39 +++++++
 .../gdb.ada/var_shadowing/var_shadowing.adb        |  30 ++++++
 gdb/testsuite/gdb.base/var-shadowing.c             |  51 +++++++++
 gdb/testsuite/gdb.base/var-shadowing.exp           |  92 +++++++++++++++++
 gdb/testsuite/gdb.base/var-shadowing2.c            |  16 +++
 gdb/testsuite/gdb.opt/inline-locals.exp            |  21 ++--
 gdb/testsuite/gdb.rust/var_reuse.exp               |  36 +++++++
 gdb/testsuite/gdb.rust/var_reuse.rs                |  20 ++++
 gdb/value.h                                        |  26 ++++-
 17 files changed, 560 insertions(+), 23 deletions(-)

diff --git a/gdb/NEWS b/gdb/NEWS
index ec9b5a33787..5410acc8303 100644
--- 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.
=20
+info locals
+  GDB now shows the "shadowed" annotation and the location information
+  for variables that are shadowed, or which are shadowing.
+
 * Removed commands
=20
 target ctf
diff --git a/gdb/c-typeprint.c b/gdb/c-typeprint.c
index aaace85aa29..1168464a9f9 100644
--- a/gdb/c-typeprint.c
+++ b/gdb/c-typeprint.c
@@ -818,7 +818,8 @@ c_type_print_template_args (const struct type_print_opt=
ions *flags,
       if (sym->loc_class () =3D=3D 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);
     }
=20
   gdb_puts (_("] "), stream);
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index 04d761c904c..d2fbe61d8fc 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -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.
=20
+@smallexample
+@group
+1: int x =3D 3;
+2: @{
+3:       int x =3D 4;
+4:       int y =3D 52;
+5:       x =3D 99; // breakpoint-line
+6: @}
+@end group
+@group
+(gdb) info locals
+x =3D 4	<file.c:3>
+y =3D 52
+x =3D 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).
diff --git a/gdb/language.c b/gdb/language.c
index 3e3be66a676..972018e8f7b 100644
--- a/gdb/language.c
+++ b/gdb/language.c
@@ -1090,6 +1090,26 @@ language_lookup_primitive_type_as_symbol (const stru=
ct language_defn *la,
   return sym;
 }
=20
+/* 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.  */
=20
 INIT_GDB_FILE (language)
diff --git a/gdb/language.h b/gdb/language.h
index 62827ffe02c..fb070edaa54 100644
--- a/gdb/language.h
+++ b/gdb/language.h
@@ -83,6 +83,20 @@ enum macro_expansion
     macro_expansion_no, macro_expansion_c
   };
=20
+/* 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,
+  };
+
 =0C
 /* Per architecture (OS/ABI) language information.  */
=20
@@ -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);
=20
+/* Returns the shadowing option supported for the input language.  */
+
+extern lang_vars_shadowing get_lang_vars_shadowing_option (enum language l=
ang);
+
 /* Save the current language and restore it upon destruction.  */
=20
 class scoped_restore_current_language
diff --git a/gdb/printcmd.c b/gdb/printcmd.c
index 662f43fbef8..15c086a9284 100644
--- a/gdb/printcmd.c
+++ b/gdb/printcmd.c
@@ -2377,7 +2377,8 @@ clear_dangling_display_expressions (struct objfile *o=
bjfile)
 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 =3D 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 !=3D 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 =3D (var->symtab () !=3D nullptr
+				   && var->symtab ()->filename () !=3D nullptr)
+				   ? lbasename (var->symtab ()->filename ())
+				   : "??";
+	  bool printed =3D (shadow_status =3D=3D 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_p=
tr &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 =3D=3D nullptr)
     name =3D var->print_name ();
@@ -2414,7 +2451,8 @@ print_variable_and_value (const char *name, symbol *v=
ar,
   gdb_printf (stream, "%*s%ps =3D ", 2 * indent, "",
 	      styled_string (variable_name_style.style (), name));
=20
-  print_variable_value (var, frame, stream, indent, current_language);
+  print_variable_value (var, frame, stream, indent, current_language,
+			shadow_status);
=20
   gdb_printf (stream, "\n");
 }
diff --git a/gdb/stack.c b/gdb/stack.c
index e084976eabf..f41f818cebb 100644
--- a/gdb/stack.c
+++ b/gdb/stack.c
@@ -59,6 +59,7 @@
 #include "cli/cli-option.h"
 #include "cli/cli-style.h"
 #include "gdbsupport/buildargv.h"
+#include "gdbsupport/unordered_set.h"
=20
 /* 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.  */
=20
 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 *b=
lock,
     }
 }
=20
+/* 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_v=
ars;
+
+  /* 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 =3D !printed_vars.insert (print_name).second;
+      bool shadowed =3D shadowed_vars.find (print_name) !=3D shadowed_vars=
.end ();
+
+      enum var_shadowing shadowing_status;
+      if (already_printed && shadowed)
+	shadowing_status =3D var_shadowing::SHADOWED;
+      else if (!already_printed && shadowed)
+	shadowing_status =3D var_shadowing::SHADOWING;
+      else
+	shadowing_status =3D 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)
+	  =3D=3D lang_vars_shadowing::PRINT
+	  && shadowing_status !=3D 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_langua=
ge)
+	       =3D=3D lang_vars_shadowing::HIDE
+	       && shadowing_status =3D=3D 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.  */
=20
@@ -2192,14 +2259,16 @@ struct print_variable_and_value_data
   struct ui_file *stream;
   int values_printed;
=20
-  void operator() (const char *print_name, struct symbol *sym);
+  void operator() (const char *print_name, struct symbol *sym,
+		   var_shadowing shadow_status);
 };
=20
 /* The callback for the locals and args iterators.  */
=20
 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;
=20
@@ -2219,7 +2288,8 @@ print_variable_and_value_data::operator() (const char=
 *print_name,
       return;
     }
=20
-  print_variable_and_value (print_name, sym, frame, stream, num_tabs);
+  print_variable_and_value (print_name, sym, frame, stream, num_tabs,
+			    shadow_status);
=20
   values_printed =3D 1;
 }
@@ -2288,7 +2358,7 @@ print_frame_local_vars (const frame_info_ptr &frame,
   scoped_restore_selected_frame restore_selected_frame;
   select_frame (frame);
=20
-  iterate_over_block_local_vars (block, cb_data);
+  iterate_over_block_local_vars_printing (block, cb_data);
=20
   if (!cb_data.values_printed && !quiet)
     {
@@ -2386,6 +2456,37 @@ iterate_over_block_arg_vars (const struct block *b,
     }
 }
=20
+/* 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
+	    =3D 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 =3D stream;
   cb_data.values_printed =3D 0;
=20
-  iterate_over_block_arg_vars (func->value_block (), cb_data);
+  iterate_over_block_arg_vars_printing (func->value_block (), cb_data);
=20
   if (!cb_data.values_printed && !quiet)
     {
diff --git a/gdb/stack.h b/gdb/stack.h
index 48096a29286..4f7c223f47f 100644
--- a/gdb/stack.h
+++ b/gdb/stack.h
@@ -20,6 +20,8 @@
 #ifndef GDB_STACK_H
 #define GDB_STACK_H
=20
+enum class var_shadowing;
+
 gdb::unique_xmalloc_ptr<char> find_frame_funname (const frame_info_ptr &fr=
ame,
 						  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
   =3D gdb::function_view<void (const char *print_name, struct symbol *sym)=
>;
=20
+using iterate_over_block_arg_local_vars_cb_printing
+  =3D 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);
=20
 void iterate_over_block_local_vars (const struct block *block,
 				    iterate_over_block_arg_local_vars_cb cb);
=20
+/* 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.ad=
a/var_shadowing.exp
new file mode 100644
index 00000000000..ffa96b049cf
--- /dev/null
+++ b/gdb/testsuite/gdb.ada/var_shadowing.exp
@@ -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]] !=3D "" } {
+    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 =3D 111\t<$testfile.adb:$i_level3>"  \
+    "i =3D 11\t<$testfile.adb:$i_level2, shadowed>"  \
+    "i =3D 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/te=
stsuite/gdb.ada/var_shadowing/var_shadowing.adb
new file mode 100644
index 00000000000..27983388555
--- /dev/null
+++ b/gdb/testsuite/gdb.ada/var_shadowing/var_shadowing.adb
@@ -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 :=3D 1;  -- I-Level1
+begin
+  declare
+    I : Integer :=3D 11; -- I-Level2
+  begin
+    declare
+      I : Integer :=3D 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.bas=
e/var-shadowing.c
new file mode 100755
index 00000000000..7886ea31aec
--- /dev/null
+++ b/gdb/testsuite/gdb.base/var-shadowing.c
@@ -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 =3D 100;  /* bp for entry */
+  unsigned int val1 =3D 1;		/* val1-d1 */
+  unsigned int val2 =3D 2;		/* val2-d1 */
+  a =3D 101;  /* bp for locals 1 */
+  {
+    unsigned int val2 =3D 3;		/* val2-d2 */
+    unsigned int val3 =3D 4;		/* val3-d1 */
+    a =3D 102;  /* bp for locals 2 */
+    {
+      unsigned int val1 =3D 5;		/* val1-d2 */
+      a =3D 103;  /* bp for locals 3 */
+      {
+	#include "var-shadowing2.c"
+	unsigned int val1 =3D 6;	/* val1-d3 */
+	unsigned int val2 =3D 7;	/* val2-d3 */
+	unsigned int val3 =3D 8;	/* val3-d2 */
+	a =3D 104;  /* bp for locals 4 */
+      }
+    }
+  }
+  a =3D 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.b=
ase/var-shadowing.exp
new file mode 100755
index 00000000000..119b8c39d4a
--- /dev/null
+++ b/gdb/testsuite/gdb.base/var-shadowing.exp
@@ -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 =3D 1"  \
+    "val2 =3D 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 =3D 3\t<$srcfile:$val2_d2>"  \
+    "val3 =3D 4"  \
+    "a =3D 101"   \
+    "val1 =3D 1"  \
+    "val2 =3D 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_line=
3.*"
+gdb_test "info locals" [multi_line \
+    "val1 =3D 5\t<$srcfile:$val1_d2>"  \
+    "val2 =3D 3\t<$srcfile:$val2_d2>"  \
+    "val3 =3D 4"  \
+    "a =3D 102"   \
+    "val1 =3D 1\t<$srcfile:$val1_d1, shadowed>"  \
+    "val2 =3D 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_l=
ine4.*"
+gdb_test "info locals" [multi_line \
+    "a =3D 999\t<${testfile}2.c:16>" \
+    "val1 =3D 6\t<$srcfile:$val1_d3>"  \
+    "val2 =3D 7\t<$srcfile:$val2_d3>"  \
+    "val3 =3D 8\t<$srcfile:$val3_d2>"  \
+    "val1 =3D 5\t<$srcfile:$val1_d2, shadowed>" \
+    "val2 =3D 3\t<$srcfile:$val2_d2, shadowed>" \
+    "val3 =3D 4\t<$srcfile:$val3_d1, shadowed>" \
+    "a =3D 103\t<$srcfile:$a_line, shadowed>"   \
+    "val1 =3D 1\t<$srcfile:$val1_d1, shadowed>" \
+    "val2 =3D 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 =3D 105" \
+    "val1 =3D 1"  \
+    "val2 =3D 2"  \
+    ] "info locals at outermost level last"
diff --git a/gdb/testsuite/gdb.base/var-shadowing2.c b/gdb/testsuite/gdb.ba=
se/var-shadowing2.c
new file mode 100644
index 00000000000..d70fcd3bdd5
--- /dev/null
+++ b/gdb/testsuite/gdb.base/var-shadowing2.c
@@ -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 =3D 999;
diff --git a/gdb/testsuite/gdb.opt/inline-locals.exp b/gdb/testsuite/gdb.op=
t/inline-locals.exp
index 2c3a9f71c2f..6af398e5cf4 100644
--- a/gdb/testsuite/gdb.opt/inline-locals.exp
+++ b/gdb/testsuite/gdb.opt/inline-locals.exp
@@ -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 =3D \\{0 <repeats 64 times>\\}"
+    set shadowed_pass_re "\t<$srcfile:$decimal>"
+    set shadowed_fail_re "\t<$srcfile:$decimal, shadowed>"
+    set pass_re "array =3D \\{0 <repeats 64 times>\\}($shadowed_pass_re)?"
     set kfail_re [multi_line $pass_re \
-		      "array =3D <optimized out>"]
+		      "array =3D <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 =3D {$decimal, \[^\r\n\]*}"
+    set pass_re "array =3D {$decimal, \[^\r\n\]*}($shadowed_pass_re)?"
     set kfail_re [multi_line $pass_re \
-		      "array =3D <optimized out>"]
+		      "array =3D <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
=20
     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} {
 }
=20
 if {! $no_frames } {
-    check_scoped_locals "bp for locals 1" "loc2 =3D 20\r\nloc1 =3D 10"
-    check_scoped_locals "bp for locals 2" "loc3 =3D 30\r\nloc2 =3D 20\r\nl=
oc1 =3D 10"
-    check_scoped_locals "bp for locals 3" "loc1 =3D 10"
+    check_scoped_locals "bp for locals 1" \
+	"loc2 =3D 20(\t<$srcfile:$decimal>)?\r\nloc1 =3D 10(\t<$srcfile:$decimal>=
)?"
+    check_scoped_locals "bp for locals 2" \
+	"loc3 =3D 30(\t<$srcfile:$decimal>)?\r\nloc2 =3D 20(\t<$srcfile:$decimal>=
)?\r\nloc1 =3D 10(\t<$srcfile:$decimal>)?"
+    check_scoped_locals "bp for locals 3" "loc1 =3D 10(\t<$srcfile:$decima=
l>)?"
 }
diff --git a/gdb/testsuite/gdb.rust/var_reuse.exp b/gdb/testsuite/gdb.rust/=
var_reuse.exp
new file mode 100755
index 00000000000..dcba42b554d
--- /dev/null
+++ b/gdb/testsuite/gdb.rust/var_reuse.exp
@@ -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 >=3D 1.73.
+if {[rust_at_least 1.73]} {
+    setup_xfail "*-*-*" "gdb/31079"
+}
+gdb_test "info local _x" "_x =3D 12" "print local _x variable"
diff --git a/gdb/testsuite/gdb.rust/var_reuse.rs b/gdb/testsuite/gdb.rust/v=
ar_reuse.rs
new file mode 100755
index 00000000000..70101a043c5
--- /dev/null
+++ b/gdb/testsuite/gdb.rust/var_reuse.rs
@@ -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 =3D 5;
+    let _x =3D _x + 7;
+    let _y =3D 8;       // set breakpoint here
+}
diff --git a/gdb/value.h b/gdb/value.h
index e94c69e49d3..4cc0d7d0902 100644
--- a/gdb/value.h
+++ b/gdb/value.h
@@ -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);
=20
+/* 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.  */
=20
 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);
=20
 /* 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.  */
=20
 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);
=20
 extern void typedef_print (struct type *type, struct symbol *news,
 			   struct ui_file *stream);