[v9,1/2] gdb: add annotation in 'info locals' command for variables shadowing case

Message ID 20250321223336.13978-2-abdul.b.ijaz@intel.com
State New
Headers
Series add annotation in 'info locals' command for variables shadowing case |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_gdb_build--master-aarch64 success Build passed
linaro-tcwg-bot/tcwg_gdb_build--master-arm success Build passed
linaro-tcwg-bot/tcwg_gdb_check--master-aarch64 success Test passed
linaro-tcwg-bot/tcwg_gdb_check--master-arm success Test passed

Commit Message

Abdul Basit Ijaz March 21, 2025, 10:33 p.m. UTC
  From: "Ijaz, Abdul B" <abdul.b.ijaz@intel.com>

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 = 42;
2:  {
3:    int x = 99;
4:    int y = 52;
5:    x = 99; /* break here */
6:  }

Currently:

(gdb) info locals
x = 99
x = 42
y = 52

After applying this patch, we obtain:

(gdb) info locals
x = 99  <file.c:3>
y = 52
x = 42  <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.

Co-Authored-By: Andrew Burgess <aburgess@redhat.com>
---
 gdb/doc/gdb.texinfo                           | 20 ++++
 gdb/language.c                                | 20 ++++
 gdb/language.h                                | 17 ++++
 gdb/printcmd.c                                | 28 +++++-
 gdb/stack.c                                   | 84 ++++++++++++++---
 gdb/stack.h                                   |  3 +-
 gdb/testsuite/gdb.ada/var_shadowing.exp       | 39 ++++++++
 .../gdb.ada/var_shadowing/var_shadowing.adb   | 30 ++++++
 gdb/testsuite/gdb.base/var-shadowing.c        | 49 ++++++++++
 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/tracepoint.c                              |  3 +-
 gdb/value.h                                   |  4 +-
 16 files changed, 458 insertions(+), 24 deletions(-)
 create mode 100644 gdb/testsuite/gdb.ada/var_shadowing.exp
 create mode 100644 gdb/testsuite/gdb.ada/var_shadowing/var_shadowing.adb
 create mode 100755 gdb/testsuite/gdb.base/var-shadowing.c
 create mode 100755 gdb/testsuite/gdb.base/var-shadowing.exp
 create mode 100644 gdb/testsuite/gdb.base/var-shadowing2.c
 create mode 100755 gdb/testsuite/gdb.rust/var_reuse.exp
 create mode 100755 gdb/testsuite/gdb.rust/var_reuse.rs
  

Comments

Tom Tromey March 25, 2025, 2:12 p.m. UTC | #1
>>>>> Abdul Basit Ijaz <abdul.b.ijaz@intel.com> writes:

> 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>".

Thank you for the patch.

I like the new output.  I think a new feature like this should have an
entry in the NEWS file (and also one for the MI change).

> diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
> index e034ac53295..682792bece6 100644
> --- a/gdb/doc/gdb.texinfo
> +++ b/gdb/doc/gdb.texinfo
> @@ -8942,6 +8942,26 @@ 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
> +1: int x = 3;
> +2: @{
> +3:       int x = 4;
> +4:       int y = 52;
> +5:       x = 99; // breakpt

Would you mind spelling out "breakpoint" here?
I don't think there's a real need to abbreviate in this instance.

> +/* See language.h.  */
> +
> +enum lang_vars_shadowing get_lang_vars_shadowing_option (enum language lang)

Split line after the type.

Normally I think we would make this a method on language_defn rather
than having a switch.

> +{
> +  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_return;
> +
> +    default:
> +      return lang_vars_shadowing_none;

I'm curious why the default is none and not print; and also about other
choices, like why Ada but not Pascal?

> +/* lang_vars_shadowing ==
> +   lang_vars_shadowing_print:  Adds shadowed information for such variables.
> +   lang_vars_shadowing_return: Does not print shadowed variables.
> +   lang_vars_shadowing_none:   Print variables without shadow information.  */
> +
> +extern enum lang_vars_shadowing
> +  {
> +    lang_vars_shadowing_print, lang_vars_shadowing_return,
> +    lang_vars_shadowing_none
> +  }
> +lang_vars_shadowing;

This declares a variable that isn't defined.  I don't think that's
needed.

Cases like this with a prefix on the enumerators can usually be written
as an enum class now.

> +      if (shadowed)
> +	{
> +	  if (var->line () > 0)
> +	    {
> +	      gdb_printf ("\t<%ps:%ps%ps>",
> +			  styled_string (file_name_style.style (), file_name),
> +			  styled_string (line_number_style.style (),
> +					 pulongest (var->line ())),
> +			  styled_string (metadata_style.style (),
> +					 printed ? ", shadowed" : ""));
> +	    }
> +	  else
> +	    {
> +	      gdb_printf ("\t<%ps:No line number information available%ps>",
> +			  styled_string (file_name_style.style (), file_name),
> +			  styled_string (metadata_style.style (),
> +					 printed ? ", shadowed" : ""));
> +	    }
> +
> +	}

I think this is over-braced.  Normally gdb doesn't use braces if there's
just a single statement (with comments counting as a statement for the
purposes of deciding).

>  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)

Seems like just changing the typedef would be fine.
Unless someone requested this change in an earlier review?
(I didn't read the history of the patch.)

> +static void
> +iterate_over_local_vars_to_function_block
> +  (const struct block *block,
> +   gdb::function_view <void (const char *, struct symbol *)> cb) {

Braces go on new lines.

> +void
> +iterate_over_block_local_vars (const struct block *block,
> +			       iterate_over_block_arg_local_vars_cb cb)
> +{

I think the special shadowing logic should not be put in a generic
iteration function, but instead handled by the callers that care about
this detail.  Is there a reason this couldn't be done?

Tom
  
Abdul Basit Ijaz March 28, 2025, 10:13 a.m. UTC | #2
Thanks a lot Tom for the feedback.

Tom> I like the new output.  I think a new feature like this should have an entry in the NEWS file (and also one for the MI change).
Abdul>Sure will add an entry for both patches in the NEWS file.

>> +5:       x = 99; // breakpt
Tom>Would you mind spelling out "breakpoint" here?
Abdul>Will spell out complete "breakpoint" in v10.

>> +enum lang_vars_shadowing get_lang_vars_shadowing_option (enum 
>> +language lang)

Tom>Split line after the type.
Abdul> Will update in V10.

Tom>Normally I think we would make this a method on language_defn rather than having a switch.
Abdul> There were two options discussed in upstream feedback mentioned below for handling it, first one was the way you mentioned or use it in the current enum way.
Abdul> We have three use cases here for shadowing, on/off shadowing information and third case is for rust language.
Abdul> In case of rust language we still handle shadowing variables but just print the inner variable only and skip the printing of the other instances of variable.
Abdul> So accordingly adopt to using switch here. Let me know if you think it will be better to define it in language_dfn instead but in that case also we will still need the enum.
Abdul> https://sourceware.org/pipermail/gdb-patches/2024-December/214189.html

Tom>I'm curious why the default is none and not print; and also about other choices, like why Ada but not Pascal?
Abdul>This patch handles the shadowing behavior for known languages behavior otherwise it keeps the GDB default behavior for other languages so they are not affected with this change.
Abdul>For RUST and ADA I got the upstream feedback on this series so they were handled accordingly. 
Abdul>I am not sure about the Pascal behavior and have not got any special handling feedback for it.  So let me know the expected behavior for it in case it is needed then it can be handled accordingly.

>> +extern enum lang_vars_shadowing
>> +  {
>> +    lang_vars_shadowing_print, lang_vars_shadowing_return,
>> +    lang_vars_shadowing_none
>> +  }
>> +lang_vars_shadowing;

Tom>This declares a variable that isn't defined.  I don't think that's needed.
Tom>Cases like this with a prefix on the enumerators can usually be written as an enum class now.
Abdul>Sure will replace it with "enum class" in v10. 

>> +      if (shadowed)
>> +	{
>> +	  if (var->line () > 0)
>> +	    {
>> +	      gdb_printf ("\t<%ps:%ps%ps>",
>> +			  styled_string (file_name_style.style (), file_name),
>> +			  styled_string (line_number_style.style (),
>> +					 pulongest (var->line ())),
>> +			  styled_string (metadata_style.style (),
>> +					 printed ? ", shadowed" : ""));
>> +	    }
>> +	  else
>> +	    {
>> +	      gdb_printf ("\t<%ps:No line number information available%ps>",
>> +			  styled_string (file_name_style.style (), file_name),
>> +			  styled_string (metadata_style.style (),
>> +					 printed ? ", shadowed" : ""));
>> +	    }
>> +
>> +	}

Tom>I think this is over-braced.  Normally gdb doesn't use braces if there's just a single statement (with comments counting as a statement for the purposes of deciding).
Abdul> I will remove the braces in V10. But as each block is comprised of more than two lines so braces were used according to  " Any two or more lines in code should be wrapped in braces, even if they are comments, as they look like separate statements:" (https://sourceware.org/gdb/wiki/Internals%20GDB-C-Coding-Standards#Indentation_of_lambdas_as_parameters)

>>  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)

Tom>Seems like just changing the typedef would be fine.
Tom>Unless someone requested this change in an earlier review?
Tom>(I didn't read the history of the patch.)
Abdul>This was updated based on this feedback. The type def change was fine but it was updated to handle corner cases for aclass and domain symbols: https://sourceware.org/pipermail/gdb-patches/2024-December/214189.html

>> +   gdb::function_view <void (const char *, struct symbol *)> cb) {

Tom>Braces go on new lines.
Abdul> Will fix in V10.

>> +iterate_over_block_local_vars (const struct block *block,
>> +			       iterate_over_block_arg_local_vars_cb cb) {

Tom>I think the special shadowing logic should not be put in a generic iteration function, but instead handled by the callers that care about this detail.  Is there a reason this couldn't be done?
Abdul> Handling with current approach we have the benefit that we do not need to store any info whether variable is shadowed and we are already iterating over the variables and now just print extra shadowing info only where it is required and easy to detect here. Otherwise probably we need to iterate in caller before jumping to iterator for printing and store whether printing function treat variable as shadowed or not.  Also will move braces to next line here as well.

Thanks & Best Regards,
Abdul Basit

-----Original Message-----
From: Tom Tromey <tom@tromey.com> 
Sent: Tuesday, March 25, 2025 3:13 PM
To: Ijaz, Abdul B <abdul.b.ijaz@intel.com>
Cc: gdb-patches@sourceware.org; pedro@palves.net; philippe.waroquiers@skynet.be; aburgess@redhat.com; Aktemur, Tankut Baris <tankut.baris.aktemur@intel.com>; Schimpe, Christina <christina.schimpe@intel.com>; lsix@lancelotsix.com; eliz@gnu.org
Subject: Re: [PATCH v9 1/2] gdb: add annotation in 'info locals' command for variables shadowing case

>>>>> Abdul Basit Ijaz <abdul.b.ijaz@intel.com> writes:

> 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>".

Thank you for the patch.

I like the new output.  I think a new feature like this should have an entry in the NEWS file (and also one for the MI change).

> diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo index 
> e034ac53295..682792bece6 100644
> --- a/gdb/doc/gdb.texinfo
> +++ b/gdb/doc/gdb.texinfo
> @@ -8942,6 +8942,26 @@ 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
> +1: int x = 3;
> +2: @{
> +3:       int x = 4;
> +4:       int y = 52;
> +5:       x = 99; // breakpt

Would you mind spelling out "breakpoint" here?
I don't think there's a real need to abbreviate in this instance.

> +/* See language.h.  */
> +
> +enum lang_vars_shadowing get_lang_vars_shadowing_option (enum 
> +language lang)

Split line after the type.

Normally I think we would make this a method on language_defn rather than having a switch.

> +{
> +  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_return;
> +
> +    default:
> +      return lang_vars_shadowing_none;

I'm curious why the default is none and not print; and also about other choices, like why Ada but not Pascal?

> +/* lang_vars_shadowing ==
> +   lang_vars_shadowing_print:  Adds shadowed information for such variables.
> +   lang_vars_shadowing_return: Does not print shadowed variables.
> +   lang_vars_shadowing_none:   Print variables without shadow information.  */
> +
> +extern enum lang_vars_shadowing
> +  {
> +    lang_vars_shadowing_print, lang_vars_shadowing_return,
> +    lang_vars_shadowing_none
> +  }
> +lang_vars_shadowing;

This declares a variable that isn't defined.  I don't think that's needed.

Cases like this with a prefix on the enumerators can usually be written as an enum class now.

> +      if (shadowed)
> +	{
> +	  if (var->line () > 0)
> +	    {
> +	      gdb_printf ("\t<%ps:%ps%ps>",
> +			  styled_string (file_name_style.style (), file_name),
> +			  styled_string (line_number_style.style (),
> +					 pulongest (var->line ())),
> +			  styled_string (metadata_style.style (),
> +					 printed ? ", shadowed" : ""));
> +	    }
> +	  else
> +	    {
> +	      gdb_printf ("\t<%ps:No line number information available%ps>",
> +			  styled_string (file_name_style.style (), file_name),
> +			  styled_string (metadata_style.style (),
> +					 printed ? ", shadowed" : ""));
> +	    }
> +
> +	}

I think this is over-braced.  Normally gdb doesn't use braces if there's just a single statement (with comments counting as a statement for the purposes of deciding).

>  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)

Seems like just changing the typedef would be fine.
Unless someone requested this change in an earlier review?
(I didn't read the history of the patch.)

> +static void
> +iterate_over_local_vars_to_function_block
> +  (const struct block *block,
> +   gdb::function_view <void (const char *, struct symbol *)> cb) {

Braces go on new lines.

> +void
> +iterate_over_block_local_vars (const struct block *block,
> +			       iterate_over_block_arg_local_vars_cb cb) {

I think the special shadowing logic should not be put in a generic iteration function, but instead handled by the callers that care about this detail.  Is there a reason this couldn't be done?

Tom
Intel Deutschland GmbH
Registered Address: Am Campeon 10, 85579 Neubiberg, Germany
Tel: +49 89 99 8853-0, www.intel.de
Managing Directors: Sean Fennelly, Jeffrey Schneiderman, Tiffany Doon Silva
Chairperson of the Supervisory Board: Nicole Lau
Registered Office: Munich
Commercial Register: Amtsgericht Muenchen HRB 186928
  

Patch

diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index e034ac53295..682792bece6 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -8942,6 +8942,26 @@  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
+1: int x = 3;
+2: @{
+3:       int x = 4;
+4:       int y = 52;
+5:       x = 99; // breakpt
+6: @}
+(gdb) info locals
+x = 4	<file.c:3>
+y = 52
+x = 3	<file.c:1, shadowed>
+@end smallexample
+
+A variable is shadowed when there's another variable with the same
+name which is declared within an inner scope (decision block,
+method, or inner class).  For such cases, its location for the
+outermost scope is followed by @samp{shadowed}.  The location can
+help to locate the instances of shadowed variables.  So,
+location information is only added for shadowed variables.
+
 @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 a8548a2e82f..c8222c6680c 100644
--- a/gdb/language.c
+++ b/gdb/language.c
@@ -1101,6 +1101,26 @@  language_lookup_primitive_type_as_symbol (const struct language_defn *la,
   return sym;
 }
 
+/* See language.h.  */
+
+enum 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_return;
+
+    default:
+      return lang_vars_shadowing_none;
+    }
+}
+
 /* Initialize the language routines.  */
 
 void _initialize_language ();
diff --git a/gdb/language.h b/gdb/language.h
index e6bfa3c68f1..b13592b7d18 100644
--- a/gdb/language.h
+++ b/gdb/language.h
@@ -84,6 +84,18 @@  enum macro_expansion
     macro_expansion_no, macro_expansion_c
   };
 
+/* lang_vars_shadowing ==
+   lang_vars_shadowing_print:  Adds shadowed information for such variables.
+   lang_vars_shadowing_return: Does not print shadowed variables.
+   lang_vars_shadowing_none:   Print variables without shadow information.  */
+
+extern enum lang_vars_shadowing
+  {
+    lang_vars_shadowing_print, lang_vars_shadowing_return,
+    lang_vars_shadowing_none
+  }
+lang_vars_shadowing;
+
 
 /* Per architecture (OS/ABI) language information.  */
 
@@ -857,6 +869,11 @@  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 enum 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
diff --git a/gdb/printcmd.c b/gdb/printcmd.c
index 3cd84204574..e0d20793ecb 100644
--- a/gdb/printcmd.c
+++ b/gdb/printcmd.c
@@ -56,6 +56,7 @@ 
 #include <optional>
 #include "gdbsupport/gdb-safe-ctype.h"
 #include "inferior.h"
+#include "include/libiberty.h"
 
 /* Chain containing all defined memory-tag subcommands.  */
 
@@ -2331,7 +2332,8 @@  clear_dangling_display_expressions (struct objfile *objfile)
 void
 print_variable_and_value (const char *name, struct symbol *var,
 			  const frame_info_ptr &frame,
-			  struct ui_file *stream, int indent)
+			  struct ui_file *stream, int indent,
+			  bool shadowed, bool printed)
 {
 
   if (!name)
@@ -2344,6 +2346,7 @@  print_variable_and_value (const char *name, struct symbol *var,
     {
       struct value *val;
       struct value_print_options opts;
+      const char *file_name = lbasename (var->owner.symtab->filename);
 
       /* READ_VAR_VALUE needs a block in order to deal with non-local
 	 references (i.e. to handle nested functions).  In this context, we
@@ -2353,6 +2356,29 @@  print_variable_and_value (const char *name, struct symbol *var,
       get_user_print_options (&opts);
       opts.deref_ref = true;
       common_val_print_checked (val, stream, indent, &opts, current_language);
+
+      /* Print <%line, shadowed> after the variable value only when it is variable
+	 shadowing case.  */
+      if (shadowed)
+	{
+	  if (var->line () > 0)
+	    {
+	      gdb_printf ("\t<%ps:%ps%ps>",
+			  styled_string (file_name_style.style (), file_name),
+			  styled_string (line_number_style.style (),
+					 pulongest (var->line ())),
+			  styled_string (metadata_style.style (),
+					 printed ? ", shadowed" : ""));
+	    }
+	  else
+	    {
+	      gdb_printf ("\t<%ps:No line number information available%ps>",
+			  styled_string (file_name_style.style (), file_name),
+			  styled_string (metadata_style.style (),
+					 printed ? ", shadowed" : ""));
+	    }
+
+	}
     }
   catch (const gdb_exception_error &except)
     {
diff --git a/gdb/stack.c b/gdb/stack.c
index 6f986aa8297..cfc23cde856 100644
--- a/gdb/stack.c
+++ b/gdb/stack.c
@@ -58,6 +58,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.  */
@@ -2213,8 +2214,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))
     {
@@ -2241,13 +2243,13 @@  iterate_over_block_locals (const struct block *b,
     }
 }
 
-/* Iterate over all the local variables in block B, including all its
-   superblocks, stopping when the top-level block is reached.  */
+/* Call iterate_over_block_locals, passing through CB for BLOCK, and every
+   superblock up to, and including, the enclosing function block.  */
 
-void
-iterate_over_block_local_vars (const struct block *block,
-			       iterate_over_block_arg_local_vars_cb cb)
-{
+static void
+iterate_over_local_vars_to_function_block
+  (const struct block *block,
+   gdb::function_view <void (const char *, struct symbol *)> cb) {
   while (block)
     {
       iterate_over_block_locals (block, cb);
@@ -2260,6 +2262,61 @@  iterate_over_block_local_vars (const struct block *block,
     }
 }
 
+/* Iterate over all the local variables in block B, including all its
+   superblocks, stopping when the top-level block is reached.  */
+
+void
+iterate_over_block_local_vars (const struct block *block,
+			       iterate_over_block_arg_local_vars_cb 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_local_vars_to_function_block
+    (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_local_vars_to_function_block
+    (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 ();
+
+      /* 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 && shadowed)
+	cb (print_name, sym, true, already_printed);
+      /* 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_return && shadowed && already_printed)
+	{
+	  /* Nothing.  */
+	}
+      else
+	cb (print_name, sym, false, false);
+    });
+}
+
 /* Data to be passed around in the calls to the locals and args
    iterators.  */
 
@@ -2272,14 +2329,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, bool shadowed,
+		   bool printed);
 };
 
 /* 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,
+					   bool shadowed, bool printed)
 {
   frame_info_ptr frame;
 
@@ -2299,7 +2358,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, shadowed,
+			    printed);
 
   values_printed = 1;
 }
@@ -2477,7 +2537,7 @@  iterate_over_block_arg_vars (const struct block *b,
 	  struct symbol *sym2
 	    = lookup_symbol_search_name (sym->search_name (),
 					 b, SEARCH_VAR_DOMAIN).symbol;
-	  cb (sym->print_name (), sym2);
+	  cb (sym->print_name (), sym2, false, false);
 	}
     }
 }
diff --git a/gdb/stack.h b/gdb/stack.h
index e87ba322dee..8bedc613cec 100644
--- a/gdb/stack.h
+++ b/gdb/stack.h
@@ -24,7 +24,8 @@  gdb::unique_xmalloc_ptr<char> find_frame_funname (const frame_info_ptr &frame,
 						  enum language *funlang,
 						  struct symbol **funcp);
 
-typedef gdb::function_view<void (const char *print_name, struct symbol *sym)>
+typedef gdb::function_view<void (const char *print_name, struct symbol *sym,
+				 bool shadowed, bool printed)>
      iterate_over_block_arg_local_vars_cb;
 
 void iterate_over_block_arg_vars (const struct block *block,
diff --git a/gdb/testsuite/gdb.ada/var_shadowing.exp b/gdb/testsuite/gdb.ada/var_shadowing.exp
new file mode 100644
index 00000000000..8f0619d3e7e
--- /dev/null
+++ b/gdb/testsuite/gdb.ada/var_shadowing.exp
@@ -0,0 +1,39 @@ 
+# Copyright 2023-2025 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 -1
+}
+
+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
index 00000000000..1c8f4e395a2
--- /dev/null
+++ b/gdb/testsuite/gdb.ada/var_shadowing/var_shadowing.adb
@@ -0,0 +1,30 @@ 
+--  Copyright 2023-2025 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
index 00000000000..454bbcb5235
--- /dev/null
+++ b/gdb/testsuite/gdb.base/var-shadowing.c
@@ -0,0 +1,49 @@ 
+/* Copyright (C) 2023-2025 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>
+
+void
+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;
+} /* 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
index 00000000000..53bf9474fbd
--- /dev/null
+++ b/gdb/testsuite/gdb.base/var-shadowing.exp
@@ -0,0 +1,92 @@ 
+# Copyright 2023-2025 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 -1
+}
+
+if ![runto_main] {
+    return -1
+}
+
+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
index 00000000000..b678f4d3916
--- /dev/null
+++ b/gdb/testsuite/gdb.base/var-shadowing2.c
@@ -0,0 +1,16 @@ 
+/* Copyright (C) 2023-2025 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;
diff --git a/gdb/testsuite/gdb.opt/inline-locals.exp b/gdb/testsuite/gdb.opt/inline-locals.exp
index 358a8d29d89..e12f79fa94e 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 = \\{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
index 00000000000..2a5fb43bbf7
--- /dev/null
+++ b/gdb/testsuite/gdb.rust/var_reuse.exp
@@ -0,0 +1,36 @@ 
+# Copyright 2023-2025 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 -1
+}
+
+set line [gdb_get_line_number "set breakpoint here"]
+if {![runto ${srcfile}:$line]} {
+    untested "could not run to breakpoint"
+    return -1
+}
+
+# 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
index 00000000000..361744b9503
--- /dev/null
+++ b/gdb/testsuite/gdb.rust/var_reuse.rs
@@ -0,0 +1,20 @@ 
+// Copyright (C) 2023-2025 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
+}
diff --git a/gdb/tracepoint.c b/gdb/tracepoint.c
index ca6f616e3b9..9cbace7daec 100644
--- a/gdb/tracepoint.c
+++ b/gdb/tracepoint.c
@@ -1046,7 +1046,8 @@  collection_list::add_local_symbols (struct gdbarch *gdbarch, CORE_ADDR pc,
   int count = 0;
 
   auto do_collect_symbol = [&] (const char *print_name,
-				struct symbol *sym)
+				struct symbol *sym,
+				bool shadowed, bool printed)
     {
       collect_symbol (sym, gdbarch, frame_regno,
 		      frame_offset, pc, trace_string);
diff --git a/gdb/value.h b/gdb/value.h
index 8e5fd5403cc..90cbf7ec9e8 100644
--- a/gdb/value.h
+++ b/gdb/value.h
@@ -1561,7 +1561,9 @@  extern void print_variable_and_value (const char *name,
 				      struct symbol *var,
 				      const frame_info_ptr &frame,
 				      struct ui_file *stream,
-				      int indent);
+				      int indent,
+				      bool shadowed,
+				      bool printed);
 
 extern void typedef_print (struct type *type, struct symbol *news,
 			   struct ui_file *stream);