* [PATCH v13 0/2] add annotation in 'info locals' command for variables shadowing case
@ 2026-07-08 18:08 Abdul Basit Ijaz
2026-07-08 18:08 ` [PATCH v13 1/2] gdb: " Abdul Basit Ijaz
2026-07-08 18:08 ` [PATCH v13 2/2] gdb: add shadowed field in '-stack-list-locals/variables' mi commands Abdul Basit Ijaz
0 siblings, 2 replies; 9+ messages in thread
From: Abdul Basit Ijaz @ 2026-07-08 18:08 UTC (permalink / raw)
To: gdb-patches
Cc: pedro, philippe.waroquiers, aburgess, christina.schimpe, lsix,
eliz, abdul.b.ijaz, guinevere
Hi All,
For the variable shadowing case, these patches add the annotation "shadowed"
and location information to the shadowed variables so that it is easier for
the end user to identify which variable was shadowed.
Patch #1 updates "info locals" command
Path #2 is for the mi commands '-stack-list-locals' and '-stack-list-variables'.
Last V12 Series:
https://sourceware.org/pipermail/gdb-patches/2025-December/223219.html
V11 Series:
https://sourceware.org/pipermail/gdb-patches/2025-November/223002.html
V10 Series:
https://sourceware.org/pipermail/gdb-patches/2025-April/217019.html
V9 Series:
https://sourceware.org/pipermail/gdb-patches/2025-March/216590.html
V8 Series:
https://sourceware.org/pipermail/gdb-patches/2024-December/214263.html
Documentation in V7 Series was already approved by Eli:
https://sourceware.org/pipermail/gdb-patches/2024-October/212228.html
https://sourceware.org/pipermail/gdb-patches/2024-October/212229.html
Patch#1 V6 was Reviewed-by Larsen:
https://sourceware.org/pipermail/gdb-patches/2024-January/205923.html
Patch#2 V2 was Tested-by Larsen:
https://sourceware.org/pipermail/gdb-patches/2024-January/205922.html
Changes since V12:
* Rebase the patch and a guard for a file_name nullable passed to styled_string
was required and added now accordingly.
* Update copyright years for new files and some minor fixes from last feedback.
Changes since V11:
* This V11 series fixes the feedback from Eli in documentation and still waiting for the feeback from upstream community regarding use of annotation for the non-shadowed variables also. See:
https://sourceware.org/pipermail/gdb-patches/2025-November/223011.html
https://sourceware.org/pipermail/gdb-patches/2025-November/223012.html
Testing is done for unix/m32/native-gdbserver configurations and no issue is seen.
Thanks & Best Rergards
Abdul Basit
Ijaz, Abdul B (2):
gdb: add annotation in 'info locals' command for variables shadowing
case
gdb: add shadowed field in '-stack-list-locals/variables' mi commands
gdb/NEWS | 8 +
gdb/c-typeprint.c | 3 +-
gdb/doc/gdb.texinfo | 47 ++++++
gdb/language.c | 20 +++
gdb/language.h | 19 +++
gdb/mi/mi-cmd-stack.c | 129 +++++++++++-----
gdb/printcmd.c | 38 ++++-
gdb/stack.c | 115 +++++++++++++-
gdb/stack.h | 14 ++
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.mi/mi-var-shadowing.c | 50 +++++++
gdb/testsuite/gdb.mi/mi-var-shadowing.exp | 141 ++++++++++++++++++
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 +++-
20 files changed, 857 insertions(+), 58 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 100644 gdb/testsuite/gdb.mi/mi-var-shadowing.c
create mode 100644 gdb/testsuite/gdb.mi/mi-var-shadowing.exp
create mode 100755 gdb/testsuite/gdb.rust/var_reuse.exp
create mode 100755 gdb/testsuite/gdb.rust/var_reuse.rs
--
2.34.1
Intel Deutschland GmbH
Registered Address: Dornacher Strasse 1, 85622 Feldkirchen, Germany
Tel: +49 89 991 430, www.intel.de
Managing Directors: Harry Demas, Jeffrey Schneiderman, Yin Chong Sorrell
Chairperson of the Supervisory Board: Nicole Lau
Registered Seat: Munich
Commercial Register: Amtsgericht Muenchen HRB 186928
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v13 1/2] gdb: add annotation in 'info locals' command for variables shadowing case
2026-07-08 18:08 [PATCH v13 0/2] add annotation in 'info locals' command for variables shadowing case Abdul Basit Ijaz
@ 2026-07-08 18:08 ` Abdul Basit Ijaz
2026-07-13 12:49 ` Andrew Burgess
2026-07-13 15:12 ` Andrew Burgess
2026-07-08 18:08 ` [PATCH v13 2/2] gdb: add shadowed field in '-stack-list-locals/variables' mi commands Abdul Basit Ijaz
1 sibling, 2 replies; 9+ messages in thread
From: Abdul Basit Ijaz @ 2026-07-08 18:08 UTC (permalink / raw)
To: gdb-patches
Cc: pedro, philippe.waroquiers, aburgess, christina.schimpe, lsix,
eliz, abdul.b.ijaz, guinevere
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.
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>
---
gdb/NEWS | 4 +
gdb/c-typeprint.c | 3 +-
gdb/doc/gdb.texinfo | 26 ++++
gdb/language.c | 20 +++
gdb/language.h | 19 +++
gdb/printcmd.c | 38 +++++-
gdb/stack.c | 115 ++++++++++++++++--
gdb/stack.h | 14 +++
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, 547 insertions(+), 23 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
diff --git a/gdb/NEWS b/gdb/NEWS
index ec9b5a33787..6807a6c966b 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.
+info locals
+ GDB now shows the "shadowed" annotation and the location information in
+ the output of this command for variables shadowing case.
+
* Removed commands
target ctf
diff --git a/gdb/c-typeprint.c b/gdb/c-typeprint.c
index e418aca56a4..9397160d29b 100644
--- a/gdb/c-typeprint.c
+++ b/gdb/c-typeprint.c
@@ -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);
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index a698b2b8451..ee33bfdba13 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.
+@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). 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 3e3be66a676..972018e8f7b 100644
--- a/gdb/language.c
+++ b/gdb/language.c
@@ -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)
diff --git a/gdb/language.h b/gdb/language.h
index 75154d9c591..d838cc825a1 100644
--- a/gdb/language.h
+++ b/gdb/language.h
@@ -83,6 +83,21 @@ enum macro_expansion
macro_expansion_no, macro_expansion_c
};
+/* In the case of variable shadowing if extra information should be
+ printed for the current language is compared against it. */
+
+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 +816,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
diff --git a/gdb/printcmd.c b/gdb/printcmd.c
index a337a6b7db9..b75a0d06f0f 100644
--- a/gdb/printcmd.c
+++ b/gdb/printcmd.c
@@ -2377,12 +2377,17 @@ 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
{
struct value *val;
struct value_print_options opts;
+ const char *file_name = nullptr;
+
+ if (var->symtab () && var->symtab ()->filename ())
+ file_name = lbasename (var->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
@@ -2392,6 +2397,31 @@ 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)
+ {
+ 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);
+
+ gdb_printf (&out, "%ps",
+ styled_string (metadata_style.style (),
+ printed ? ", shadowed" : ""));
+ gdb_puts (">", &out);
+
+ gdb_puts (out.c_str ());
+ }
}
catch (const gdb_exception_error &except)
{
@@ -2406,7 +2436,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 +2445,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");
}
diff --git a/gdb/stack.c b/gdb/stack.c
index e084976eabf..79aa36a47e4 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"
/* 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,
}
}
+/* 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_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,
}
}
+/* Iterate over all the argument variables in block B. */
+
+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)
{
diff --git a/gdb/stack.h b/gdb/stack.h
index ad2700b59a7..6e2bba15b32 100644
--- a/gdb/stack.h
+++ b/gdb/stack.h
@@ -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,24 @@ gdb::unique_xmalloc_ptr<char> find_frame_funname (const frame_info_ptr &frame,
typedef gdb::function_view<void (const char *print_name, struct symbol *sym)>
iterate_over_block_arg_local_vars_cb;
+typedef gdb::function_view<void (const char *print_name, struct symbol *sym,
+ var_shadowing shadow_status)>
+ iterate_over_block_arg_local_vars_cb_printing;
+
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);
+void iterate_over_block_arg_vars_printing
+ (const struct block *block,
+ iterate_over_block_arg_local_vars_cb_printing cb);
+
+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
index 00000000000..47e2b3831fd
--- /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]] != "" } {
+ 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..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 := 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..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 = 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
index 00000000000..37218eaa94e
--- /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 -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..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 = 999;
diff --git a/gdb/testsuite/gdb.opt/inline-locals.exp b/gdb/testsuite/gdb.opt/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 = \\{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..8bb3fdb1174
--- /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 -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..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 = 5;
+ let _x = _x + 7;
+ let _y = 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);
+/* 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);
--
2.34.1
Intel Deutschland GmbH
Registered Address: Dornacher Strasse 1, 85622 Feldkirchen, Germany
Tel: +49 89 991 430, www.intel.de
Managing Directors: Harry Demas, Jeffrey Schneiderman, Yin Chong Sorrell
Chairperson of the Supervisory Board: Nicole Lau
Registered Seat: Munich
Commercial Register: Amtsgericht Muenchen HRB 186928
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v13 2/2] gdb: add shadowed field in '-stack-list-locals/variables' mi commands
2026-07-08 18:08 [PATCH v13 0/2] add annotation in 'info locals' command for variables shadowing case Abdul Basit Ijaz
2026-07-08 18:08 ` [PATCH v13 1/2] gdb: " Abdul Basit Ijaz
@ 2026-07-08 18:08 ` Abdul Basit Ijaz
2026-07-13 15:11 ` Andrew Burgess
1 sibling, 1 reply; 9+ messages in thread
From: Abdul Basit Ijaz @ 2026-07-08 18:08 UTC (permalink / raw)
To: gdb-patches
Cc: pedro, philippe.waroquiers, aburgess, christina.schimpe, lsix,
eliz, abdul.b.ijaz, guinevere
From: "Ijaz, Abdul B" <abdul.b.ijaz@intel.com>
For C/C++/Fortran 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 GDB now prints location information
for shadowed variables and add 'shadowed' field also in '-stack-list-locals'
and '-stack-list-variables' mi commands for super-block shadowed variable.
Suppose we have test.c file
1:int x = 42;
2: {
3: int x = 99;
4: int y = 52;
5: x = 99; /* break here */
6: }
The "-stack-list-locals" and "-stack-list-variables" mi commands at the
"break here" line gives the following output:
Before the change:
~~~
(gdb)
-stack-list-locals 0
^done,locals=[name="x",name="y",name="x"]
(gdb)
-stack-list-locals 1
^done,locals=[{name="x",value="99"},{name="y",value="52"},{name="x",value="42"}]
(gdb)
-stack-list-locals 2
^done,locals=[{name="x",type="int",value="99"},{name="y",type="int",value="52"},{name="x",type="int",value="42"}]
(gdb)
-stack-list-variables 0
^done,variables=[{name="x"},{name="y"},{name="x"}]
(gdb)
-stack-list-variables 1
^done,variables=[{name="x",value="99"},{name="y",value="52"},{name="x",value="42"}]
(gdb)
-stack-list-variables 2
^done,variables=[{name="x",type="int",value="99"},{name="y",type="int",value="52"},{name="x",type="int",value="42"}]
~~~
With this patch we obtain:
~~~
(gdb)
-stack-list-locals 0
^done,locals=[name="x",name="y",name="x"]
(gdb)
-stack-list-locals 1
^done,locals=[{name="x",value="99",file="/home/src/test.c",line="5"},{name="y",value="52"},{name="x",value="42",file="/home/src/test.c",line="2",shadowed="true"}]
(gdb)
-stack-list-locals 2
^done,locals=[{name="x",type="int",value="99",file="/home/src/test.c",line="5"},{name="y",type="int",value="52"},{name="x",type="int",value="42",file="/home/src/test.c",line="2",shadowed="true"}]
(gdb)
-stack-list-variables 0
^done,variables=[{name="x",file="/home/src/test.c",line="5"},{name="y"},{name="x",file="/home/src/test.c",line="2",shadowed="true"}]
(gdb)
-stack-list-variables 1
^done,variables=[{name="x",value="99",file="/home/src/test.c",line="4"},{name="y",value="52"},{name="x",value="42",file="/home/src/test.c",line="2",shadowed="true"}]
(gdb)
-stack-list-variables 2
^done,variables=[{name="x",type="int",value="99",file="/home/src/test.c",line="4"},{name="y",type="int",value="52"},{name="x",type="int",value="42",file="/home/src/test.c",line="2",shadowed="true"}]
~~~
Reviewed-By: Guinevere Larsen <guinevere@redhat.com>
---
gdb/NEWS | 4 +
gdb/doc/gdb.texinfo | 21 ++++
gdb/mi/mi-cmd-stack.c | 129 ++++++++++++++------
gdb/testsuite/gdb.mi/mi-var-shadowing.c | 50 ++++++++
gdb/testsuite/gdb.mi/mi-var-shadowing.exp | 141 ++++++++++++++++++++++
5 files changed, 310 insertions(+), 35 deletions(-)
create mode 100644 gdb/testsuite/gdb.mi/mi-var-shadowing.c
create mode 100644 gdb/testsuite/gdb.mi/mi-var-shadowing.exp
diff --git a/gdb/NEWS b/gdb/NEWS
index 6807a6c966b..483d2013e41 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -593,6 +593,10 @@ multi-wp-addr in qSupported
multiple times, and the same mapping was being reused. In all
other cases, this field will have the value 'false'.
+** GDB now shows "shadowed", "file" and "line" fields in the output
+ of '-stack-list-locals/variables' mi commands for variables
+ shadowing case.
+
* Support for stabs debugging format and the a.out/dbx object format is
deprecated, and will be removed in GDB 18.
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index ee33bfdba13..64a1345e2f2 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -35585,6 +35585,27 @@ If the @code{--skip-unavailable} option is specified, local variables
and arguments that are not available are not listed. Partially
available arguments and local variables are still displayed, however.
+@smallexample
+@group
+1: int x = 3;
+2: @{
+3: int x = 4;
+4: int y = 5;
+4: x = 99; // breakpoint-line
+5: @}
+@end group
+@group
+(gdb) -stack-list-variables 2
+^done,variables=[@{name="x",type="int",value="4",
+file="/home/src/name.c",line="3"@},@{name="y",type="int",value="5",
+file="/home/src/name.c",line="4"@},@{name="x",type="int",value="3",
+file="/home/src/name.c",line="1",shadowed="true"@}]
+@end group
+@end smallexample
+
+The location information and @samp{shadowed} attribute is only added
+for shadowed variables (@pxref{shadowed variables}).
+
@subsubheading Example
@smallexample
diff --git a/gdb/mi/mi-cmd-stack.c b/gdb/mi/mi-cmd-stack.c
index 280a08d121e..38955cc697b 100644
--- a/gdb/mi/mi-cmd-stack.c
+++ b/gdb/mi/mi-cmd-stack.c
@@ -32,6 +32,7 @@
#include "mi-parse.h"
#include <optional>
#include "inferior.h"
+#include "gdbsupport/unordered_map.h"
enum what_to_list { locals, arguments, all };
@@ -490,7 +491,9 @@ mi_cmd_stack_list_variables (const char *command, const char *const *argv,
static void
list_arg_or_local (const struct frame_arg *arg, enum what_to_list what,
enum print_values values, int skip_unavailable,
- const frame_print_options &fp_opts)
+ const frame_print_options &fp_opts,
+ const gdb::unordered_set<std::string> *shadowed_vars,
+ gdb::unordered_set<std::string> &printed_vars)
{
struct ui_out *uiout = current_uiout;
@@ -519,6 +522,19 @@ list_arg_or_local (const struct frame_arg *arg, enum what_to_list what,
tuple_emitter.emplace (uiout, nullptr);
string_file stb;
+ const char *name = arg->sym->print_name ();
+ /* To distinguish innermost variable from the rest in the shadowed_vars
+ this boolen is needed. */
+ bool already_printed = !printed_vars.insert (name).second;
+ bool shadowed = shadowed_vars->find (name) != shadowed_vars->end ();
+
+ /* In case of Rust language it is possible to declare variable with
+ same name multiple times and only latest declaration of variable
+ is accessible. So print only the first instance and there is no
+ need of printing duplicates. */
+ if (get_lang_vars_shadowing_option (current_language->la_language)
+ == lang_vars_shadowing::HIDE && shadowed && already_printed)
+ return;
stb.puts (arg->sym->print_name ());
if (arg->entry_kind == print_entry_values_only)
@@ -560,6 +576,59 @@ list_arg_or_local (const struct frame_arg *arg, enum what_to_list what,
}
uiout->field_stream ("value", stb);
}
+
+ /* Only for C/C++/Fortran/Ada languages, in case of variables shadowing
+ print shadowed field after the superblock variable and only location
+ of the variables in the innerblock. */
+ if (get_lang_vars_shadowing_option (current_language->la_language)
+ == lang_vars_shadowing::PRINT && shadowed
+ && !(values == PRINT_NO_VALUES && what == locals))
+ {
+ uiout->field_string ("file", arg->sym->symtab ()->filename ());
+ uiout->field_unsigned ("line", arg->sym->m_line);
+ if (already_printed)
+ uiout->field_string ("shadowed", "true");
+ }
+}
+
+/* Returns true if address_class can be printed, otherwise returns false. */
+
+static bool
+can_print_aclass (struct symbol *sym, enum what_to_list what)
+{
+ int print_me = false;
+
+ switch (sym->loc_class ())
+ {
+ default:
+ case LOC_UNDEF: /* catches errors */
+ case LOC_CONST: /* constant */
+ case LOC_TYPEDEF: /* local typedef */
+ case LOC_LABEL: /* local label */
+ case LOC_BLOCK: /* local function */
+ case LOC_CONST_BYTES: /* loc. byte seq. */
+ case LOC_UNRESOLVED: /* unresolved static */
+ case LOC_OPTIMIZED_OUT: /* optimized out */
+ print_me = false;
+ break;
+
+ case LOC_ARG: /* argument */
+ case LOC_REF_ARG: /* reference arg */
+ case LOC_REGPARM_ADDR: /* indirect register arg */
+ case LOC_LOCAL: /* stack local */
+ case LOC_STATIC: /* static */
+ case LOC_REGISTER: /* register */
+ case LOC_COMPUTED: /* computed location */
+ if (what == all)
+ print_me = true;
+ else if (what == locals)
+ print_me = !sym->is_argument ();
+ else
+ print_me = sym->is_argument ();
+ break;
+ }
+
+ return print_me;
}
/* Print a list of the objects for the frame FI in a certain form,
@@ -573,9 +642,10 @@ list_args_or_locals (const frame_print_options &fp_opts,
enum what_to_list what, enum print_values values,
const frame_info_ptr &fi, int skip_unavailable)
{
- const struct block *block;
+ const struct block *block, *orig_block;
const char *name_of_result;
struct ui_out *uiout = current_uiout;
+ gdb::unordered_set<std::string> collected_vars, shadowed_vars, printed_vars;
block = get_frame_block (fi, 0);
@@ -596,42 +666,31 @@ list_args_or_locals (const frame_print_options &fp_opts,
ui_out_emit_list list_emitter (uiout, name_of_result);
- while (block != 0)
+ orig_block = block;
+ /* Stored list of shadowed variables later help in identifying them
+ from the rest. */
+ while (block != nullptr)
{
for (struct symbol *sym : block_iterator_range (block))
{
- int print_me = 0;
-
- switch (sym->loc_class ())
+ if (can_print_aclass (sym, what))
{
- default:
- case LOC_UNDEF: /* catches errors */
- case LOC_CONST: /* constant */
- case LOC_TYPEDEF: /* local typedef */
- case LOC_LABEL: /* local label */
- case LOC_BLOCK: /* local function */
- case LOC_CONST_BYTES: /* loc. byte seq. */
- case LOC_UNRESOLVED: /* unresolved static */
- case LOC_OPTIMIZED_OUT: /* optimized out */
- print_me = 0;
- break;
-
- case LOC_ARG: /* argument */
- case LOC_REF_ARG: /* reference arg */
- case LOC_REGPARM_ADDR: /* indirect register arg */
- case LOC_LOCAL: /* stack local */
- case LOC_STATIC: /* static */
- case LOC_REGISTER: /* register */
- case LOC_COMPUTED: /* computed location */
- if (what == all)
- print_me = 1;
- else if (what == locals)
- print_me = !sym->is_argument ();
- else
- print_me = sym->is_argument ();
- break;
+ const char *name = sym->print_name ();
+ if (!collected_vars.insert (name).second)
+ shadowed_vars.insert (name);
}
- if (print_me)
+ }
+ if (block->function ())
+ break;
+ block = block->superblock ();
+ }
+
+ block = orig_block;
+ while (block != 0)
+ {
+ for (struct symbol *sym : block_iterator_range (block))
+ {
+ if (can_print_aclass (sym, what))
{
struct frame_arg arg, entryarg;
@@ -657,10 +716,10 @@ list_args_or_locals (const frame_print_options &fp_opts,
if (arg.entry_kind != print_entry_values_only)
list_arg_or_local (&arg, what, values, skip_unavailable,
- fp_opts);
+ fp_opts, &shadowed_vars, printed_vars);
if (entryarg.entry_kind != print_entry_values_no)
list_arg_or_local (&entryarg, what, values, skip_unavailable,
- fp_opts);
+ fp_opts, &shadowed_vars, printed_vars);
}
}
diff --git a/gdb/testsuite/gdb.mi/mi-var-shadowing.c b/gdb/testsuite/gdb.mi/mi-var-shadowing.c
new file mode 100644
index 00000000000..875e753b485
--- /dev/null
+++ b/gdb/testsuite/gdb.mi/mi-var-shadowing.c
@@ -0,0 +1,50 @@
+/* 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; /* entry bp */
+ 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 */
+ {
+ 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.mi/mi-var-shadowing.exp b/gdb/testsuite/gdb.mi/mi-var-shadowing.exp
new file mode 100644
index 00000000000..f88c8c81736
--- /dev/null
+++ b/gdb/testsuite/gdb.mi/mi-var-shadowing.exp
@@ -0,0 +1,141 @@
+# 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 mi-support.exp
+set MIFLAGS "-i=mi"
+
+gdb_exit
+if [mi_gdb_start] {
+ continue
+}
+
+standard_testfile
+
+set opts {debug}
+if [build_executable ${testfile}.exp ${testfile} ${srcfile} $opts] {
+ return -1
+}
+
+mi_delete_breakpoints
+mi_gdb_reinitialize_dir $srcdir/$subdir
+mi_gdb_load ${binfile}
+mi_runto main
+
+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 "entry bp"]
+
+set stack_test1_regx "\\^done,(locals|variables)=\\\[\{name=\"a\",type=\"int\",value=\"$decimal\"\},\{name=\"val1\",type=\"unsigned int\",value=\"1\"\},{name=\"val2\",type=\"unsigned int\",value=\"2\"\}\\\]"
+set stack_test2_regx "\\^done,(locals|variables)=\\\[\{name=\"val2\",type=\"unsigned int\",value=\"3\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val2_d2\"\},\{name=\"val3\",type=\"unsigned int\",value=\"4\"\},\{name=\"a\",type=\"int\",value=\"101\"\},\{name=\"val1\",type=\"unsigned int\",value=\"1\"\},\{name=\"val2\",type=\"unsigned int\",value=\"2\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val2_d1\",shadowed=\"true\"\}\\\]"
+set stack_test3_regx "\\^done,(locals|variables)=\\\[\{name=\"val1\",type=\"unsigned int\",value=\"5\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val1_d2\"\},\{name=\"val2\",type=\"unsigned int\",value=\"3\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val2_d2\"\},\{name=\"val3\",type=\"unsigned int\",value=\"4\"\},\{name=\"a\",type=\"int\",value=\"102\"\},\{name=\"val1\",type=\"unsigned int\",value=\"1\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val1_d1\",shadowed=\"true\"\},\{name=\"val2\",type=\"unsigned int\",value=\"2\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val2_d1\",shadowed=\"true\"\}\\\]"
+set stack_test4_regx "\\^done,(locals|variables)=\\\[\{name=\"val1\",type=\"unsigned int\",value=\"6\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val1_d3\"\},\{name=\"val2\",type=\"unsigned int\",value=\"7\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val2_d3\"\},\{name=\"val3\",type=\"unsigned int\",value=\"8\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val3_d2\"\},\{name=\"val1\",type=\"unsigned int\",value=\"5\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val1_d2\",shadowed=\"true\"\},\{name=\"val2\",type=\"unsigned int\",value=\"3\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val2_d2\",shadowed=\"true\"\},\{name=\"val3\",type=\"unsigned int\",value=\"4\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val3_d1\",shadowed=\"true\"\},\{name=\"a\",type=\"int\",value=\"103\"\},\{name=\"val1\",type=\"unsigned int\",value=\"1\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val1_d1\",shadowed=\"true\"\},\{name=\"val2\",type=\"unsigned int\",value=\"2\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val2_d1\",shadowed=\"true\"\}\\\]"
+set stack_test5_regx "\\^done,(locals|variables)=\\\[\{name=\"a\",type=\"int\",value=\"105\"\},\{name=\"val1\",type=\"unsigned int\",value=\"1\"\},\{name=\"val2\",type=\"unsigned int\",value=\"2\"\}\\\]"
+
+mi_gdb_test \
+ "-break-insert --source ${srcfile} --line ${bp_line1}" \
+ "\\^done.*source ${srcfile} \\-line ${bp_line1}.*" \
+ "bp at outermost level"
+mi_execute_to "exec-continue" "breakpoint-hit" ".*" ".*" ".*" "${bp_line1}" \
+ { "" "disp=\"keep\"" } "continue to outermost level"
+mi_gdb_test "-stack-list-locals 0" \
+ "\\^done,locals=\\\[name=\"a\",name=\"val1\",name=\"val2\"\\\]" \
+ "-stack-list-locals 0 at outermost level"
+mi_gdb_test "-stack-list-variables 0" \
+ "\\^done,variables=\\\[{name=\"a\"},{name=\"val1\"},{name=\"val2\"}\\\]" \
+ "-stack-list-variables 0 at outermost level"
+mi_gdb_test "-stack-list-locals 2" "${stack_test1_regx}" \
+ "-stack-list-locals 2 at outermost level"
+mi_gdb_test "-stack-list-variables 2" "${stack_test1_regx}" \
+ "-stack-list-variables 2 at outermost level"
+
+mi_gdb_test \
+ "-break-insert --source ${srcfile} --line ${bp_line2}" \
+ "\\^done.*source ${srcfile} \\-line ${bp_line2}.*" \
+ "bp at first level"
+mi_execute_to "exec-continue" "breakpoint-hit" ".*" ".*" ".*" "${bp_line2}" \
+ { "" "disp=\"keep\"" } "continue to first level"
+mi_gdb_test "-stack-list-locals 0" \
+ "\\^done,locals=\\\[name=\"val2\",name=\"val3\",name=\"a\",name=\"val1\",name=\"val2\"\\\]" \
+ "-stack-list-locals 0 at first level"
+mi_gdb_test "-stack-list-variables 0" \
+ "\\^done,variables=\\\[{name=\"val2\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val2_d2\"},{name=\"val3\"},{name=\"a\"},{name=\"val1\"},{name=\"val2\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val2_d1\",shadowed=\"true\"\}\\\]" \
+ "-stack-list-variables 0 at first level"
+mi_gdb_test "-stack-list-locals 2" "${stack_test2_regx}" \
+ "-stack-list-locals 2 at first level"
+mi_gdb_test "-stack-list-variables 2" "${stack_test2_regx}" \
+ "-stack-list-variables 2 at first level"
+
+mi_gdb_test \
+ "-break-insert --source ${srcfile} --line ${bp_line3}" \
+ "\\^done.*source ${srcfile} \\-line ${bp_line3}.*" \
+ "bp at second level"
+mi_execute_to "exec-continue" "breakpoint-hit" ".*" ".*" ".*" "${bp_line3}" \
+ { "" "disp=\"keep\"" } "continue to second level"
+mi_gdb_test "-stack-list-locals 0" \
+ "\\^done,locals=\\\[name=\"val1\",name=\"val2\",name=\"val3\",name=\"a\",name=\"val1\",name=\"val2\"\\\]" \
+ "-stack-list-locals 0 at second level"
+mi_gdb_test "-stack-list-variables 0" \
+ "\\^done,variables=\\\[{name=\"val1\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val1_d2\"},{name=\"val2\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val2_d2\"},{name=\"val3\"},{name=\"a\"},{name=\"val1\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val1_d1\",shadowed=\"true\"\},{name=\"val2\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val2_d1\",shadowed=\"true\"\}\\\]" \
+ "-stack-list-variables 0 at second level"
+mi_gdb_test "-stack-list-locals 2" "${stack_test3_regx}" \
+ "-stack-list-locals 2 at second level"
+mi_gdb_test "-stack-list-variables 2" "${stack_test3_regx}" \
+ "-stack-list-variables 2 at second level"
+
+mi_gdb_test \
+ "-break-insert --source ${srcfile} --line ${bp_line4}" \
+ "\\^done.*source ${srcfile} \\-line ${bp_line4}.*" \
+ "bp at third level"
+mi_execute_to "exec-continue" "breakpoint-hit" ".*" ".*" ".*" "${bp_line4}" \
+ { "" "disp=\"keep\"" } "continue to third level"
+mi_gdb_test "-stack-list-locals 0" \
+ "\\^done,locals=\\\[name=\"val1\",name=\"val2\",name=\"val3\",name=\"val1\",name=\"val2\",name=\"val3\",name=\"a\",name=\"val1\",name=\"val2\"\\\]" \
+ "-stack-list-locals 0 at third level"
+mi_gdb_test "-stack-list-variables 0" \
+ "\\^done,variables=\\\[{name=\"val1\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val1_d3\"},{name=\"val2\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val2_d3\"},{name=\"val3\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val3_d2\"},{name=\"val1\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val1_d2\",shadowed=\"true\"\},\{name=\"val2\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val2_d2\",shadowed=\"true\"\},\{name=\"val3\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val3_d1\",shadowed=\"true\"\},{name=\"a\"},{name=\"val1\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val1_d1\",shadowed=\"true\"\},{name=\"val2\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val2_d1\",shadowed=\"true\"\}\\\]" \
+ "-stack-list-variables 0 at third level"
+mi_gdb_test "-stack-list-locals 2" "${stack_test4_regx}" \
+ "-stack-list-locals 2 at third level"
+mi_gdb_test "-stack-list-variables 2" "${stack_test4_regx}" \
+ "-stack-list-variables 2 at third level"
+
+mi_gdb_test \
+ "-break-insert --source ${srcfile} --line ${bp_line5}" \
+ "\\^done.*source ${srcfile} \\-line ${bp_line5}.*" \
+ "bp at outermost level last"
+mi_execute_to "exec-continue" "breakpoint-hit" ".*" ".*" ".*" "${bp_line5}" \
+ { "" "disp=\"keep\"" } "continue to outermost level last"
+mi_gdb_test "-stack-list-locals 0" \
+ "\\^done,locals=\\\[name=\"a\",name=\"val1\",name=\"val2\"\\\]" \
+ "-stack-list-locals 0 at outermost level last"
+mi_gdb_test "-stack-list-variables 0" \
+ "\\^done,variables=\\\[{name=\"a\"},{name=\"val1\"},{name=\"val2\"}\\\]" \
+ "-stack-list-variables at outermost level last"
+mi_gdb_test "-stack-list-locals 2" "${stack_test5_regx}" \
+ "-stack-list-locals 2 at outermost level last"
+mi_gdb_test "-stack-list-variables 2" "${stack_test5_regx}" \
+ "-stack-list-variables 2 at outermost level last"
--
2.34.1
Intel Deutschland GmbH
Registered Address: Dornacher Strasse 1, 85622 Feldkirchen, Germany
Tel: +49 89 991 430, www.intel.de
Managing Directors: Harry Demas, Jeffrey Schneiderman, Yin Chong Sorrell
Chairperson of the Supervisory Board: Nicole Lau
Registered Seat: Munich
Commercial Register: Amtsgericht Muenchen HRB 186928
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v13 1/2] gdb: add annotation in 'info locals' command for variables shadowing case
2026-07-08 18:08 ` [PATCH v13 1/2] gdb: " Abdul Basit Ijaz
@ 2026-07-13 12:49 ` Andrew Burgess
2026-07-13 13:45 ` Ijaz, Abdul B
2026-07-13 15:12 ` Andrew Burgess
1 sibling, 1 reply; 9+ messages in thread
From: Andrew Burgess @ 2026-07-13 12:49 UTC (permalink / raw)
To: Abdul Basit Ijaz, gdb-patches
Cc: pedro, philippe.waroquiers, christina.schimpe, lsix, eliz,
abdul.b.ijaz, guinevere
Abdul Basit Ijaz <abdul.b.ijaz@intel.com> writes:
> 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>
Not really a significant issue, but I see you changed the docs, so maybe
this should be updated too. You initialise X to 99, then break on a
line that also sets X to 99. So clearly X will have the value 99, but
in the docs the example is I think clearer, you initialise X to 4, then
break on the line that sets X to 99. As expected, X has the value 4
indicating that the breakpoint line has not yet run. Like I say, it's a
trivial inconsistency, but as I have some real changes below, you might
one to sync the commit message and docs?
>
> 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>
> ---
> gdb/NEWS | 4 +
> gdb/c-typeprint.c | 3 +-
> gdb/doc/gdb.texinfo | 26 ++++
> gdb/language.c | 20 +++
> gdb/language.h | 19 +++
> gdb/printcmd.c | 38 +++++-
> gdb/stack.c | 115 ++++++++++++++++--
> gdb/stack.h | 14 +++
> 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, 547 insertions(+), 23 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
>
> diff --git a/gdb/NEWS b/gdb/NEWS
> index ec9b5a33787..6807a6c966b 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.
>
> +info locals
> + GDB now shows the "shadowed" annotation and the location information in
> + the output of this command for variables shadowing case.
The wording here seems a little off, how about this instead:
GDB now shows the "shadowed" annotation and the location information
for variables that are shadowed, or which are shadowing.
> +
> * Removed commands
>
> target ctf
> diff --git a/gdb/c-typeprint.c b/gdb/c-typeprint.c
> index e418aca56a4..9397160d29b 100644
> --- a/gdb/c-typeprint.c
> +++ b/gdb/c-typeprint.c
> @@ -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);
> diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
> index a698b2b8451..ee33bfdba13 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.
>
> +@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). 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 3e3be66a676..972018e8f7b 100644
> --- a/gdb/language.c
> +++ b/gdb/language.c
> @@ -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)
> diff --git a/gdb/language.h b/gdb/language.h
> index 75154d9c591..d838cc825a1 100644
> --- a/gdb/language.h
> +++ b/gdb/language.h
> @@ -83,6 +83,21 @@ enum macro_expansion
> macro_expansion_no, macro_expansion_c
> };
>
> +/* In the case of variable shadowing if extra information should be
> + printed for the current language is compared against it. */
This comment doesn't read well. How about:
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 +816,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
> diff --git a/gdb/printcmd.c b/gdb/printcmd.c
> index a337a6b7db9..b75a0d06f0f 100644
> --- a/gdb/printcmd.c
> +++ b/gdb/printcmd.c
> @@ -2377,12 +2377,17 @@ 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
> {
> struct value *val;
> struct value_print_options opts;
> + const char *file_name = nullptr;
> +
> + if (var->symtab () && var->symtab ()->filename ())
GDB style is not to use pointers as booleans, so this should be:
if (var->symtab () != nullptr && ....)
Couldn't this whole FILE_NAME calculation logic be moved down inside of
the `if (shadow_status != var_shadowing::NONE)` block? It's only used
in that block, but see the comment below too...
> + file_name = lbasename (var->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
> @@ -2392,6 +2397,31 @@ 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)
> + {
> + 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));
... what if FILE_NAME is NULL at this point? Is there a reason why we
cannot have both a missing symtab AND shadowing going on?
> +
> + 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);
> +
> + gdb_printf (&out, "%ps",
> + styled_string (metadata_style.style (),
> + printed ? ", shadowed" : ""));
This will also style the ', ' text, I think what you should write is:
if (printed)
{
gdb_puts (", ", &out);
fputs_styled ("shadowed", metadata_style.style (), &out);
}
> + gdb_puts (">", &out);
> +
> + gdb_puts (out.c_str ());
Other output from this function is sent to STREAM. I think this line
should be:
gdb_puts (out, stream);
> + }
> }
> catch (const gdb_exception_error &except)
> {
> @@ -2406,7 +2436,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 +2445,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");
> }
> diff --git a/gdb/stack.c b/gdb/stack.c
> index e084976eabf..79aa36a47e4 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"
>
> /* 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,
> }
> }
>
> +/* Iterate over all the local variables in block B, including all its
> + superblocks, stopping when the top-level block is reached. */
This comment should be in the header file, should mention CB. Place the
usual '/* See .... */' comment here instead.
> +
> +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)
Long line, wrap at '&&' please.
> + 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,
> }
> }
>
> +/* Iterate over all the argument variables in block B. */
This comment should be in the header file, should mention CB. Place the
usual '/* See .... */' comment here instead.
> +
> +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)
> {
> diff --git a/gdb/stack.h b/gdb/stack.h
> index ad2700b59a7..6e2bba15b32 100644
> --- a/gdb/stack.h
> +++ b/gdb/stack.h
> @@ -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,24 @@ gdb::unique_xmalloc_ptr<char> find_frame_funname (const frame_info_ptr &frame,
> typedef gdb::function_view<void (const char *print_name, struct symbol *sym)>
> iterate_over_block_arg_local_vars_cb;
>
> +typedef gdb::function_view<void (const char *print_name, struct symbol *sym,
> + var_shadowing shadow_status)>
> + iterate_over_block_arg_local_vars_cb_printing;
> +
> 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);
>
> +void iterate_over_block_arg_vars_printing
> + (const struct block *block,
> + iterate_over_block_arg_local_vars_cb_printing cb);
> +
> +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
> index 00000000000..47e2b3831fd
> --- /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]] != "" } {
> + return -1
This 'return -1' should be just 'return'. There are an additional 4
places below which need this fix, but I'll not point out every one of
them.
> +}
> +
> +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"
Thanks,
Andrew
^ permalink raw reply [flat|nested] 9+ messages in thread
* RE: [PATCH v13 1/2] gdb: add annotation in 'info locals' command for variables shadowing case
2026-07-13 12:49 ` Andrew Burgess
@ 2026-07-13 13:45 ` Ijaz, Abdul B
0 siblings, 0 replies; 9+ messages in thread
From: Ijaz, Abdul B @ 2026-07-13 13:45 UTC (permalink / raw)
To: Andrew Burgess, gdb-patches
Cc: pedro, philippe.waroquiers, Schimpe, Christina, lsix, eliz, guinevere
Hi Andrew,
Thanks for the feedback. Will address these in v14. Replies inline:
>> After applying this patch, we obtain:
>>
>> (gdb) info locals
>> x = 99 <file.c:3>
>> y = 52
>> x = 42 <file.c:1, shadowed>
Andrew>Like I say, it's a trivial inconsistency, but as I have some real changes below, you might one to sync the commit message and docs?
Abdul> Will update the commit message accordingly.
>> + GDB now shows the "shadowed" annotation and the location
>> +information in
>> + the output of this command for variables shadowing case.
Andrew>The wording here seems a little off, how about this instead:
Abdul> Will update the comment with your suggestion.
>> +/* In the case of variable shadowing if extra information should be
>> + printed for the current language is compared against it. */
Andrew>This comment doesn't read well. How about:
Andrew> How should shadowed, or shadowing, variables be printed.
Abdul> Will update the comment with your suggestion.
>> + if (var->symtab () && var->symtab ()->filename ())
Andrew>GDB style is not to use pointers as booleans, so this should be:
Andrew> if (var->symtab () != nullptr && ....)
Andrew> Couldn't this whole FILE_NAME calculation logic be moved down inside of the `if (shadow_status != var_shadowing::NONE)` block? It's only used in that block, but see the comment below too...
Abdul> Will move FILE_NAME calculation down inside of the `if (shadow_status != var_shadowing::NONE)` block
Andrew> what if FILE_NAME is NULL at this point? Is there a reason why we cannot have both a missing symtab AND shadowing going on?
Abdul> Good point — there is no reason why a missing symtab and shadowing cannot coexist, so file_name does need a NULL guard here. For the fallback value, would you prefer "??" or "<unknown file>"?
const char *file_name = (var->symtab () != nullptr && ...)
? lbasename (var->symtab ()->filename ())
: "??";
>> + gdb_printf (&out, "%ps",
>> + styled_string (metadata_style.style (),
>> + printed ? ", shadowed" : ""));
Andrew>This will also style the ', ' text, I think what you should write is:
Abdul> Will update it accordingly.
>> + gdb_puts (out.c_str ());
Andrew>Other output from this function is sent to STREAM. I think this line should be:
Andrew> gdb_puts (out, stream);
Abdul> Will update it accordingly.
>> +/* Iterate over all the local variables in block B, including all its
>> + superblocks, stopping when the top-level block is reached. */
Andrew>This comment should be in the header file, should mention CB. Place the usual '/* See .... */' comment here instead.
Abdul>Will move it to header file.
>> + == lang_vars_shadowing::PRINT && shadowing_status !=
>> +var_shadowing::NONE)
Andrew>Long line, wrap at '&&' please.
Abdul > Will be fixed in v14.
>> +/* Iterate over all the argument variables in block B. */
Andrew> This comment should be in the header file, should mention CB. Place the usual '/* See .... */' comment here instead.
Abdull> Will be fixed in v14.
>> +if {[gdb_compile_ada "${srcfile}" "${binfile}" \
>> + executable [list debug]] != "" } {
>> + return -1
Andrew>This 'return -1' should be just 'return'. There are an additional 4 places below which need this fix, but I'll not point out every one of them.
Abdul>Will fix it at all places in v14.
Thanks & Best Regards
Abdul Basit
-----Original Message-----
From: Andrew Burgess <aburgess@redhat.com>
Sent: Monday, July 13, 2026 2:50 PM
To: Ijaz, Abdul B <abdul.b.ijaz@intel.com>; gdb-patches@sourceware.org
Cc: pedro@palves.net; philippe.waroquiers@skynet.be; Schimpe, Christina <christina.schimpe@intel.com>; lsix@lancelotsix.com; eliz@gnu.org; Ijaz, Abdul B <abdul.b.ijaz@intel.com>; guinevere@redhat.com
Subject: Re: [PATCH v13 1/2] gdb: add annotation in 'info locals' command for variables shadowing case
Abdul Basit Ijaz <abdul.b.ijaz@intel.com> writes:
> 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>
Not really a significant issue, but I see you changed the docs, so maybe this should be updated too. You initialise X to 99, then break on a line that also sets X to 99. So clearly X will have the value 99, but in the docs the example is I think clearer, you initialise X to 4, then break on the line that sets X to 99. As expected, X has the value 4 indicating that the breakpoint line has not yet run. Like I say, it's a trivial inconsistency, but as I have some real changes below, you might one to sync the commit message and docs?
>
> 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>
> ---
> gdb/NEWS | 4 +
> gdb/c-typeprint.c | 3 +-
> gdb/doc/gdb.texinfo | 26 ++++
> gdb/language.c | 20 +++
> gdb/language.h | 19 +++
> gdb/printcmd.c | 38 +++++-
> gdb/stack.c | 115 ++++++++++++++++--
> gdb/stack.h | 14 +++
> 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, 547 insertions(+), 23 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
>
> diff --git a/gdb/NEWS b/gdb/NEWS
> index ec9b5a33787..6807a6c966b 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.
>
> +info locals
> + GDB now shows the "shadowed" annotation and the location
> +information in
> + the output of this command for variables shadowing case.
The wording here seems a little off, how about this instead:
GDB now shows the "shadowed" annotation and the location information
for variables that are shadowed, or which are shadowing.
> +
> * Removed commands
>
> target ctf
> diff --git a/gdb/c-typeprint.c b/gdb/c-typeprint.c index
> e418aca56a4..9397160d29b 100644
> --- a/gdb/c-typeprint.c
> +++ b/gdb/c-typeprint.c
> @@ -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);
> diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo index
> a698b2b8451..ee33bfdba13 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.
>
> +@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). 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
> 3e3be66a676..972018e8f7b 100644
> --- a/gdb/language.c
> +++ b/gdb/language.c
> @@ -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)
> diff --git a/gdb/language.h b/gdb/language.h index
> 75154d9c591..d838cc825a1 100644
> --- a/gdb/language.h
> +++ b/gdb/language.h
> @@ -83,6 +83,21 @@ enum macro_expansion
> macro_expansion_no, macro_expansion_c
> };
>
> +/* In the case of variable shadowing if extra information should be
> + printed for the current language is compared against it. */
This comment doesn't read well. How about:
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,
> + };
> +
>
> /* Per architecture (OS/ABI) language information. */
>
> @@ -801,6 +816,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 diff --git a/gdb/printcmd.c
> b/gdb/printcmd.c index a337a6b7db9..b75a0d06f0f 100644
> --- a/gdb/printcmd.c
> +++ b/gdb/printcmd.c
> @@ -2377,12 +2377,17 @@ 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
> {
> struct value *val;
> struct value_print_options opts;
> + const char *file_name = nullptr;
> +
> + if (var->symtab () && var->symtab ()->filename ())
GDB style is not to use pointers as booleans, so this should be:
if (var->symtab () != nullptr && ....)
Couldn't this whole FILE_NAME calculation logic be moved down inside of the `if (shadow_status != var_shadowing::NONE)` block? It's only used in that block, but see the comment below too...
> + file_name = lbasename (var->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
> @@ -2392,6 +2397,31 @@ 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)
> + {
> + 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));
... what if FILE_NAME is NULL at this point? Is there a reason why we cannot have both a missing symtab AND shadowing going on?
> +
> + 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);
> +
> + gdb_printf (&out, "%ps",
> + styled_string (metadata_style.style (),
> + printed ? ", shadowed" : ""));
This will also style the ', ' text, I think what you should write is:
if (printed)
{
gdb_puts (", ", &out);
fputs_styled ("shadowed", metadata_style.style (), &out);
}
> + gdb_puts (">", &out);
> +
> + gdb_puts (out.c_str ());
Other output from this function is sent to STREAM. I think this line should be:
gdb_puts (out, stream);
> + }
> }
> catch (const gdb_exception_error &except)
> {
> @@ -2406,7 +2436,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 +2445,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");
> }
> diff --git a/gdb/stack.c b/gdb/stack.c index e084976eabf..79aa36a47e4
> 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"
>
> /* 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,
> }
> }
>
> +/* Iterate over all the local variables in block B, including all its
> + superblocks, stopping when the top-level block is reached. */
This comment should be in the header file, should mention CB. Place the usual '/* See .... */' comment here instead.
> +
> +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)
Long line, wrap at '&&' please.
> + 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,
> }
> }
>
> +/* Iterate over all the argument variables in block B. */
This comment should be in the header file, should mention CB. Place the usual '/* See .... */' comment here instead.
> +
> +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)
> {
> diff --git a/gdb/stack.h b/gdb/stack.h index ad2700b59a7..6e2bba15b32
> 100644
> --- a/gdb/stack.h
> +++ b/gdb/stack.h
> @@ -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,24 @@ gdb::unique_xmalloc_ptr<char> find_frame_funname
> (const frame_info_ptr &frame, typedef gdb::function_view<void (const char *print_name, struct symbol *sym)>
> iterate_over_block_arg_local_vars_cb;
>
> +typedef gdb::function_view<void (const char *print_name, struct symbol *sym,
> + var_shadowing shadow_status)>
> + iterate_over_block_arg_local_vars_cb_printing;
> +
> 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);
>
> +void iterate_over_block_arg_vars_printing
> + (const struct block *block,
> + iterate_over_block_arg_local_vars_cb_printing cb);
> +
> +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
> index 00000000000..47e2b3831fd
> --- /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]] != "" } {
> + return -1
This 'return -1' should be just 'return'. There are an additional 4 places below which need this fix, but I'll not point out every one of them.
> +}
> +
> +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"
Thanks,
Andrew
Intel Deutschland GmbH
Registered Address: Dornacher Strasse 1, 85622 Feldkirchen, Germany
Tel: +49 89 991 430, www.intel.de
Managing Directors: Harry Demas, Jeffrey Schneiderman, Yin Chong Sorrell
Chairperson of the Supervisory Board: Nicole Lau
Registered Seat: Munich
Commercial Register: Amtsgericht Muenchen HRB 186928
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v13 2/2] gdb: add shadowed field in '-stack-list-locals/variables' mi commands
2026-07-08 18:08 ` [PATCH v13 2/2] gdb: add shadowed field in '-stack-list-locals/variables' mi commands Abdul Basit Ijaz
@ 2026-07-13 15:11 ` Andrew Burgess
2026-07-13 20:39 ` Ijaz, Abdul B
0 siblings, 1 reply; 9+ messages in thread
From: Andrew Burgess @ 2026-07-13 15:11 UTC (permalink / raw)
To: Abdul Basit Ijaz, gdb-patches
Cc: pedro, philippe.waroquiers, christina.schimpe, lsix, eliz,
abdul.b.ijaz, guinevere
Thank you for continuing to work on this feature. I have some comments
inline below.
Abdul Basit Ijaz <abdul.b.ijaz@intel.com> writes:
> From: "Ijaz, Abdul B" <abdul.b.ijaz@intel.com>
>
> For C/C++/Fortran 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 GDB now prints location information
> for shadowed variables and add 'shadowed' field also in '-stack-list-locals'
> and '-stack-list-variables' mi commands for super-block shadowed variable.
>
> Suppose we have test.c file
>
> 1:int x = 42;
> 2: {
> 3: int x = 99;
> 4: int y = 52;
> 5: x = 99; /* break here */
> 6: }
>
> The "-stack-list-locals" and "-stack-list-variables" mi commands at the
> "break here" line gives the following output:
>
> Before the change:
>
> ~~~
> (gdb)
> -stack-list-locals 0
> ^done,locals=[name="x",name="y",name="x"]
> (gdb)
> -stack-list-locals 1
> ^done,locals=[{name="x",value="99"},{name="y",value="52"},{name="x",value="42"}]
> (gdb)
> -stack-list-locals 2
> ^done,locals=[{name="x",type="int",value="99"},{name="y",type="int",value="52"},{name="x",type="int",value="42"}]
> (gdb)
> -stack-list-variables 0
> ^done,variables=[{name="x"},{name="y"},{name="x"}]
> (gdb)
> -stack-list-variables 1
> ^done,variables=[{name="x",value="99"},{name="y",value="52"},{name="x",value="42"}]
> (gdb)
> -stack-list-variables 2
> ^done,variables=[{name="x",type="int",value="99"},{name="y",type="int",value="52"},{name="x",type="int",value="42"}]
> ~~~
>
> With this patch we obtain:
>
> ~~~
> (gdb)
> -stack-list-locals 0
> ^done,locals=[name="x",name="y",name="x"]
> (gdb)
> -stack-list-locals 1
> ^done,locals=[{name="x",value="99",file="/home/src/test.c",line="5"},{name="y",value="52"},{name="x",value="42",file="/home/src/test.c",line="2",shadowed="true"}]
> (gdb)
> -stack-list-locals 2
> ^done,locals=[{name="x",type="int",value="99",file="/home/src/test.c",line="5"},{name="y",type="int",value="52"},{name="x",type="int",value="42",file="/home/src/test.c",line="2",shadowed="true"}]
> (gdb)
> -stack-list-variables 0
> ^done,variables=[{name="x",file="/home/src/test.c",line="5"},{name="y"},{name="x",file="/home/src/test.c",line="2",shadowed="true"}]
> (gdb)
> -stack-list-variables 1
> ^done,variables=[{name="x",value="99",file="/home/src/test.c",line="4"},{name="y",value="52"},{name="x",value="42",file="/home/src/test.c",line="2",shadowed="true"}]
> (gdb)
> -stack-list-variables 2
> ^done,variables=[{name="x",type="int",value="99",file="/home/src/test.c",line="4"},{name="y",type="int",value="52"},{name="x",type="int",value="42",file="/home/src/test.c",line="2",shadowed="true"}]
> ~~~
> Reviewed-By: Guinevere Larsen <guinevere@redhat.com>
> ---
> gdb/NEWS | 4 +
> gdb/doc/gdb.texinfo | 21 ++++
> gdb/mi/mi-cmd-stack.c | 129 ++++++++++++++------
> gdb/testsuite/gdb.mi/mi-var-shadowing.c | 50 ++++++++
> gdb/testsuite/gdb.mi/mi-var-shadowing.exp | 141 ++++++++++++++++++++++
> 5 files changed, 310 insertions(+), 35 deletions(-)
> create mode 100644 gdb/testsuite/gdb.mi/mi-var-shadowing.c
> create mode 100644 gdb/testsuite/gdb.mi/mi-var-shadowing.exp
>
> diff --git a/gdb/NEWS b/gdb/NEWS
> index 6807a6c966b..483d2013e41 100644
> --- a/gdb/NEWS
> +++ b/gdb/NEWS
> @@ -593,6 +593,10 @@ multi-wp-addr in qSupported
> multiple times, and the same mapping was being reused. In all
> other cases, this field will have the value 'false'.
>
> +** GDB now shows "shadowed", "file" and "line" fields in the output
> + of '-stack-list-locals/variables' mi commands for variables
> + shadowing case.
> +
> * Support for stabs debugging format and the a.out/dbx object format is
> deprecated, and will be removed in GDB 18.
>
> diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
> index ee33bfdba13..64a1345e2f2 100644
> --- a/gdb/doc/gdb.texinfo
> +++ b/gdb/doc/gdb.texinfo
> @@ -35585,6 +35585,27 @@ If the @code{--skip-unavailable} option is specified, local variables
> and arguments that are not available are not listed. Partially
> available arguments and local variables are still displayed, however.
>
> +@smallexample
> +@group
> +1: int x = 3;
> +2: @{
> +3: int x = 4;
> +4: int y = 5;
> +4: x = 99; // breakpoint-line
Duplicate line number in this example.
> +5: @}
> +@end group
> +@group
> +(gdb) -stack-list-variables 2
> +^done,variables=[@{name="x",type="int",value="4",
> +file="/home/src/name.c",line="3"@},@{name="y",type="int",value="5",
> +file="/home/src/name.c",line="4"@},@{name="x",type="int",value="3",
> +file="/home/src/name.c",line="1",shadowed="true"@}]
> +@end group
> +@end smallexample
> +
> +The location information and @samp{shadowed} attribute is only added
> +for shadowed variables (@pxref{shadowed variables}).
> +
> @subsubheading Example
>
> @smallexample
> diff --git a/gdb/mi/mi-cmd-stack.c b/gdb/mi/mi-cmd-stack.c
> index 280a08d121e..38955cc697b 100644
> --- a/gdb/mi/mi-cmd-stack.c
> +++ b/gdb/mi/mi-cmd-stack.c
> @@ -32,6 +32,7 @@
> #include "mi-parse.h"
> #include <optional>
> #include "inferior.h"
> +#include "gdbsupport/unordered_map.h"
>
> enum what_to_list { locals, arguments, all };
>
> @@ -490,7 +491,9 @@ mi_cmd_stack_list_variables (const char *command, const char *const *argv,
> static void
> list_arg_or_local (const struct frame_arg *arg, enum what_to_list what,
> enum print_values values, int skip_unavailable,
> - const frame_print_options &fp_opts)
> + const frame_print_options &fp_opts,
> + const gdb::unordered_set<std::string> *shadowed_vars,
> + gdb::unordered_set<std::string> &printed_vars)
> {
> struct ui_out *uiout = current_uiout;
>
> @@ -519,6 +522,19 @@ list_arg_or_local (const struct frame_arg *arg, enum what_to_list what,
> tuple_emitter.emplace (uiout, nullptr);
>
> string_file stb;
> + const char *name = arg->sym->print_name ();
> + /* To distinguish innermost variable from the rest in the shadowed_vars
> + this boolen is needed. */
Typo: s/boolen/boolean/
> + bool already_printed = !printed_vars.insert (name).second;
> + bool shadowed = shadowed_vars->find (name) != shadowed_vars->end ();
> +
> + /* In case of Rust language it is possible to declare variable with
> + same name multiple times and only latest declaration of variable
> + is accessible. So print only the first instance and there is no
> + need of printing duplicates. */
> + if (get_lang_vars_shadowing_option (current_language->la_language)
> + == lang_vars_shadowing::HIDE && shadowed && already_printed)
> + return;
I haven't created a reproducer, but I think there's an issue here. By
the time we reach this line the `tuple_emitter` has already had a value
emplaced, so if the rust early return triggers the tuple_emitter is
going to emit an empty tuple ... at least I think that's what will
happen.
I think the code that give tuple_emitter a value needs to be after the
rust early return.
>
> stb.puts (arg->sym->print_name ());
> if (arg->entry_kind == print_entry_values_only)
> @@ -560,6 +576,59 @@ list_arg_or_local (const struct frame_arg *arg, enum what_to_list what,
> }
> uiout->field_stream ("value", stb);
> }
> +
> + /* Only for C/C++/Fortran/Ada languages, in case of variables shadowing
> + print shadowed field after the superblock variable and only location
> + of the variables in the innerblock. */
> + if (get_lang_vars_shadowing_option (current_language->la_language)
> + == lang_vars_shadowing::PRINT && shadowed
> + && !(values == PRINT_NO_VALUES && what == locals))
> + {
> + uiout->field_string ("file", arg->sym->symtab ()->filename ());
> + uiout->field_unsigned ("line", arg->sym->m_line);
In the non-MI path you ended up having to guard the equivalent code to
handle the case where symtab was NULL, but here you don't have a similar
guard.
I suspect the issue here is that the test you quote for the non-MI path
gdb.dwarf2/missing-type-name-for-templates.exp, doesn't have shadowing,
but does have the missing symtab case. In the non-MI code you use the
symtab even when shadowing didn't occur (I suggested moving the code),
which is why you hit the issue, but I suspect you might be able to both
shadowing and no symtab ... maybe?
When I tried to reproduce this I was ending up with a symtab with the
name "<unknown>", I'll leave you to investigate this more.
Additionally, here, if you look at the function `mi_symbol_info` and
`output_module_symbols_in_single_module_and_file` you'll see they always
emit two strings, "filename" and "fullname", I think that you should
follow the same pattern here.
Also, the access to m_line should be done via the public 'line ()'
method. One day maybe we'll even make these member variables private.
> + if (already_printed)
> + uiout->field_string ("shadowed", "true");
> + }
> +}
> +
> +/* Returns true if address_class can be printed, otherwise returns false. */
> +
> +static bool
> +can_print_aclass (struct symbol *sym, enum what_to_list what)
> +{
> + int print_me = false;
> +
> + switch (sym->loc_class ())
> + {
> + default:
> + case LOC_UNDEF: /* catches errors */
> + case LOC_CONST: /* constant */
> + case LOC_TYPEDEF: /* local typedef */
> + case LOC_LABEL: /* local label */
> + case LOC_BLOCK: /* local function */
> + case LOC_CONST_BYTES: /* loc. byte seq. */
> + case LOC_UNRESOLVED: /* unresolved static */
> + case LOC_OPTIMIZED_OUT: /* optimized out */
> + print_me = false;
> + break;
Not something I think you need to fix as this is moved code, but I
noticed it while reviewing this patch -- this switch is almost certainly
wrong. There's no reason why we cannot print LOC_CONST variables,
e.g. variables defined with DW_AT_const_value. Also LOC_OPTIMIZED_OUT
should probably be printed, if I do 'info locals' then I will see
optimised out locals, but '-stack-list-locals 1' will skip them, which
doesn't seem right to me.
But this bug exists before this patch, so it's not yours to fix.
You could however convert PRINT_ME from 'int' to 'bool', that would be a
simple fix.
And the indentation of the 'default' and 'case ...' statements seems to
be messed up. Likely everything within the switch needs to be
re-indented I think.
> +
> + case LOC_ARG: /* argument */
> + case LOC_REF_ARG: /* reference arg */
> + case LOC_REGPARM_ADDR: /* indirect register arg */
> + case LOC_LOCAL: /* stack local */
> + case LOC_STATIC: /* static */
> + case LOC_REGISTER: /* register */
> + case LOC_COMPUTED: /* computed location */
> + if (what == all)
> + print_me = true;
> + else if (what == locals)
> + print_me = !sym->is_argument ();
> + else
> + print_me = sym->is_argument ();
> + break;
> + }
> +
> + return print_me;
> }
>
> /* Print a list of the objects for the frame FI in a certain form,
> @@ -573,9 +642,10 @@ list_args_or_locals (const frame_print_options &fp_opts,
> enum what_to_list what, enum print_values values,
> const frame_info_ptr &fi, int skip_unavailable)
> {
> - const struct block *block;
> + const struct block *block, *orig_block;
> const char *name_of_result;
> struct ui_out *uiout = current_uiout;
> + gdb::unordered_set<std::string> collected_vars, shadowed_vars, printed_vars;
>
> block = get_frame_block (fi, 0);
>
> @@ -596,42 +666,31 @@ list_args_or_locals (const frame_print_options &fp_opts,
>
> ui_out_emit_list list_emitter (uiout, name_of_result);
>
> - while (block != 0)
> + orig_block = block;
> + /* Stored list of shadowed variables later help in identifying them
> + from the rest. */
> + while (block != nullptr)
> {
> for (struct symbol *sym : block_iterator_range (block))
> {
> - int print_me = 0;
> -
> - switch (sym->loc_class ())
> + if (can_print_aclass (sym, what))
> {
> - default:
> - case LOC_UNDEF: /* catches errors */
> - case LOC_CONST: /* constant */
> - case LOC_TYPEDEF: /* local typedef */
> - case LOC_LABEL: /* local label */
> - case LOC_BLOCK: /* local function */
> - case LOC_CONST_BYTES: /* loc. byte seq. */
> - case LOC_UNRESOLVED: /* unresolved static */
> - case LOC_OPTIMIZED_OUT: /* optimized out */
> - print_me = 0;
> - break;
> -
> - case LOC_ARG: /* argument */
> - case LOC_REF_ARG: /* reference arg */
> - case LOC_REGPARM_ADDR: /* indirect register arg */
> - case LOC_LOCAL: /* stack local */
> - case LOC_STATIC: /* static */
> - case LOC_REGISTER: /* register */
> - case LOC_COMPUTED: /* computed location */
> - if (what == all)
> - print_me = 1;
> - else if (what == locals)
> - print_me = !sym->is_argument ();
> - else
> - print_me = sym->is_argument ();
> - break;
> + const char *name = sym->print_name ();
> + if (!collected_vars.insert (name).second)
> + shadowed_vars.insert (name);
> }
> - if (print_me)
> + }
> + if (block->function ())
> + break;
> + block = block->superblock ();
> + }
> +
> + block = orig_block;
> + while (block != 0)
> + {
> + for (struct symbol *sym : block_iterator_range (block))
> + {
> + if (can_print_aclass (sym, what))
> {
> struct frame_arg arg, entryarg;
>
> @@ -657,10 +716,10 @@ list_args_or_locals (const frame_print_options &fp_opts,
>
> if (arg.entry_kind != print_entry_values_only)
> list_arg_or_local (&arg, what, values, skip_unavailable,
> - fp_opts);
> + fp_opts, &shadowed_vars, printed_vars);
> if (entryarg.entry_kind != print_entry_values_no)
> list_arg_or_local (&entryarg, what, values, skip_unavailable,
> - fp_opts);
> + fp_opts, &shadowed_vars, printed_vars);
> }
> }
>
> diff --git a/gdb/testsuite/gdb.mi/mi-var-shadowing.c b/gdb/testsuite/gdb.mi/mi-var-shadowing.c
> new file mode 100644
> index 00000000000..875e753b485
> --- /dev/null
> +++ b/gdb/testsuite/gdb.mi/mi-var-shadowing.c
> @@ -0,0 +1,50 @@
> +/* 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; /* entry bp */
> + 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 */
> + {
> + 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.mi/mi-var-shadowing.exp b/gdb/testsuite/gdb.mi/mi-var-shadowing.exp
> new file mode 100644
> index 00000000000..f88c8c81736
> --- /dev/null
> +++ b/gdb/testsuite/gdb.mi/mi-var-shadowing.exp
> @@ -0,0 +1,141 @@
> +# 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 mi-support.exp
> +set MIFLAGS "-i=mi"
> +
> +gdb_exit
> +if [mi_gdb_start] {
> + continue
> +}
> +
> +standard_testfile
> +
> +set opts {debug}
> +if [build_executable ${testfile}.exp ${testfile} ${srcfile} $opts] {
> + return -1
Just 'return' at the top level, no -1 needed.
Thanks,
Andrew
> +}
> +
> +mi_delete_breakpoints
> +mi_gdb_reinitialize_dir $srcdir/$subdir
> +mi_gdb_load ${binfile}
> +mi_runto main
> +
> +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 "entry bp"]
> +
> +set stack_test1_regx "\\^done,(locals|variables)=\\\[\{name=\"a\",type=\"int\",value=\"$decimal\"\},\{name=\"val1\",type=\"unsigned int\",value=\"1\"\},{name=\"val2\",type=\"unsigned int\",value=\"2\"\}\\\]"
> +set stack_test2_regx "\\^done,(locals|variables)=\\\[\{name=\"val2\",type=\"unsigned int\",value=\"3\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val2_d2\"\},\{name=\"val3\",type=\"unsigned int\",value=\"4\"\},\{name=\"a\",type=\"int\",value=\"101\"\},\{name=\"val1\",type=\"unsigned int\",value=\"1\"\},\{name=\"val2\",type=\"unsigned int\",value=\"2\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val2_d1\",shadowed=\"true\"\}\\\]"
> +set stack_test3_regx "\\^done,(locals|variables)=\\\[\{name=\"val1\",type=\"unsigned int\",value=\"5\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val1_d2\"\},\{name=\"val2\",type=\"unsigned int\",value=\"3\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val2_d2\"\},\{name=\"val3\",type=\"unsigned int\",value=\"4\"\},\{name=\"a\",type=\"int\",value=\"102\"\},\{name=\"val1\",type=\"unsigned int\",value=\"1\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val1_d1\",shadowed=\"true\"\},\{name=\"val2\",type=\"unsigned int\",value=\"2\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val2_d1\",shadowed=\"true\"\}\\\]"
> +set stack_test4_regx "\\^done,(locals|variables)=\\\[\{name=\"val1\",type=\"unsigned int\",value=\"6\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val1_d3\"\},\{name=\"val2\",type=\"unsigned int\",value=\"7\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val2_d3\"\},\{name=\"val3\",type=\"unsigned int\",value=\"8\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val3_d2\"\},\{name=\"val1\",type=\"unsigned int\",value=\"5\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val1_d2\",shadowed=\"true\"\},\{name=\"val2\",type=\"unsigned int\",value=\"3\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val2_d2\",shadowed=\"true\"\},\{name=\"val3\",type=\"unsigned int\",value=\"4\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val3_d1\",shadowed=\"true\"\},\{name=\"a\",type=\"int\",value=\"103\"\},\{name=\"val1\",type=\"unsigned int\",value=\"1\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val1_d1\",shadowed=\"true\"\},\{name=\"val2\",type=\"unsigned int\",value=\"2\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val2_d1\",shadowed=\"true\"\}\\\]"
> +set stack_test5_regx "\\^done,(locals|variables)=\\\[\{name=\"a\",type=\"int\",value=\"105\"\},\{name=\"val1\",type=\"unsigned int\",value=\"1\"\},\{name=\"val2\",type=\"unsigned int\",value=\"2\"\}\\\]"
> +
> +mi_gdb_test \
> + "-break-insert --source ${srcfile} --line ${bp_line1}" \
> + "\\^done.*source ${srcfile} \\-line ${bp_line1}.*" \
> + "bp at outermost level"
> +mi_execute_to "exec-continue" "breakpoint-hit" ".*" ".*" ".*" "${bp_line1}" \
> + { "" "disp=\"keep\"" } "continue to outermost level"
> +mi_gdb_test "-stack-list-locals 0" \
> + "\\^done,locals=\\\[name=\"a\",name=\"val1\",name=\"val2\"\\\]" \
> + "-stack-list-locals 0 at outermost level"
> +mi_gdb_test "-stack-list-variables 0" \
> + "\\^done,variables=\\\[{name=\"a\"},{name=\"val1\"},{name=\"val2\"}\\\]" \
> + "-stack-list-variables 0 at outermost level"
> +mi_gdb_test "-stack-list-locals 2" "${stack_test1_regx}" \
> + "-stack-list-locals 2 at outermost level"
> +mi_gdb_test "-stack-list-variables 2" "${stack_test1_regx}" \
> + "-stack-list-variables 2 at outermost level"
> +
> +mi_gdb_test \
> + "-break-insert --source ${srcfile} --line ${bp_line2}" \
> + "\\^done.*source ${srcfile} \\-line ${bp_line2}.*" \
> + "bp at first level"
> +mi_execute_to "exec-continue" "breakpoint-hit" ".*" ".*" ".*" "${bp_line2}" \
> + { "" "disp=\"keep\"" } "continue to first level"
> +mi_gdb_test "-stack-list-locals 0" \
> + "\\^done,locals=\\\[name=\"val2\",name=\"val3\",name=\"a\",name=\"val1\",name=\"val2\"\\\]" \
> + "-stack-list-locals 0 at first level"
> +mi_gdb_test "-stack-list-variables 0" \
> + "\\^done,variables=\\\[{name=\"val2\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val2_d2\"},{name=\"val3\"},{name=\"a\"},{name=\"val1\"},{name=\"val2\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val2_d1\",shadowed=\"true\"\}\\\]" \
> + "-stack-list-variables 0 at first level"
> +mi_gdb_test "-stack-list-locals 2" "${stack_test2_regx}" \
> + "-stack-list-locals 2 at first level"
> +mi_gdb_test "-stack-list-variables 2" "${stack_test2_regx}" \
> + "-stack-list-variables 2 at first level"
> +
> +mi_gdb_test \
> + "-break-insert --source ${srcfile} --line ${bp_line3}" \
> + "\\^done.*source ${srcfile} \\-line ${bp_line3}.*" \
> + "bp at second level"
> +mi_execute_to "exec-continue" "breakpoint-hit" ".*" ".*" ".*" "${bp_line3}" \
> + { "" "disp=\"keep\"" } "continue to second level"
> +mi_gdb_test "-stack-list-locals 0" \
> + "\\^done,locals=\\\[name=\"val1\",name=\"val2\",name=\"val3\",name=\"a\",name=\"val1\",name=\"val2\"\\\]" \
> + "-stack-list-locals 0 at second level"
> +mi_gdb_test "-stack-list-variables 0" \
> + "\\^done,variables=\\\[{name=\"val1\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val1_d2\"},{name=\"val2\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val2_d2\"},{name=\"val3\"},{name=\"a\"},{name=\"val1\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val1_d1\",shadowed=\"true\"\},{name=\"val2\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val2_d1\",shadowed=\"true\"\}\\\]" \
> + "-stack-list-variables 0 at second level"
> +mi_gdb_test "-stack-list-locals 2" "${stack_test3_regx}" \
> + "-stack-list-locals 2 at second level"
> +mi_gdb_test "-stack-list-variables 2" "${stack_test3_regx}" \
> + "-stack-list-variables 2 at second level"
> +
> +mi_gdb_test \
> + "-break-insert --source ${srcfile} --line ${bp_line4}" \
> + "\\^done.*source ${srcfile} \\-line ${bp_line4}.*" \
> + "bp at third level"
> +mi_execute_to "exec-continue" "breakpoint-hit" ".*" ".*" ".*" "${bp_line4}" \
> + { "" "disp=\"keep\"" } "continue to third level"
> +mi_gdb_test "-stack-list-locals 0" \
> + "\\^done,locals=\\\[name=\"val1\",name=\"val2\",name=\"val3\",name=\"val1\",name=\"val2\",name=\"val3\",name=\"a\",name=\"val1\",name=\"val2\"\\\]" \
> + "-stack-list-locals 0 at third level"
> +mi_gdb_test "-stack-list-variables 0" \
> + "\\^done,variables=\\\[{name=\"val1\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val1_d3\"},{name=\"val2\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val2_d3\"},{name=\"val3\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val3_d2\"},{name=\"val1\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val1_d2\",shadowed=\"true\"\},\{name=\"val2\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val2_d2\",shadowed=\"true\"\},\{name=\"val3\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val3_d1\",shadowed=\"true\"\},{name=\"a\"},{name=\"val1\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val1_d1\",shadowed=\"true\"\},{name=\"val2\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val2_d1\",shadowed=\"true\"\}\\\]" \
> + "-stack-list-variables 0 at third level"
> +mi_gdb_test "-stack-list-locals 2" "${stack_test4_regx}" \
> + "-stack-list-locals 2 at third level"
> +mi_gdb_test "-stack-list-variables 2" "${stack_test4_regx}" \
> + "-stack-list-variables 2 at third level"
> +
> +mi_gdb_test \
> + "-break-insert --source ${srcfile} --line ${bp_line5}" \
> + "\\^done.*source ${srcfile} \\-line ${bp_line5}.*" \
> + "bp at outermost level last"
> +mi_execute_to "exec-continue" "breakpoint-hit" ".*" ".*" ".*" "${bp_line5}" \
> + { "" "disp=\"keep\"" } "continue to outermost level last"
> +mi_gdb_test "-stack-list-locals 0" \
> + "\\^done,locals=\\\[name=\"a\",name=\"val1\",name=\"val2\"\\\]" \
> + "-stack-list-locals 0 at outermost level last"
> +mi_gdb_test "-stack-list-variables 0" \
> + "\\^done,variables=\\\[{name=\"a\"},{name=\"val1\"},{name=\"val2\"}\\\]" \
> + "-stack-list-variables at outermost level last"
> +mi_gdb_test "-stack-list-locals 2" "${stack_test5_regx}" \
> + "-stack-list-locals 2 at outermost level last"
> +mi_gdb_test "-stack-list-variables 2" "${stack_test5_regx}" \
> + "-stack-list-variables 2 at outermost level last"
> --
> 2.34.1
>
> Intel Deutschland GmbH
>
> Registered Address: Dornacher Strasse 1, 85622 Feldkirchen, Germany
> Tel: +49 89 991 430, www.intel.de
> Managing Directors: Harry Demas, Jeffrey Schneiderman, Yin Chong Sorrell
> Chairperson of the Supervisory Board: Nicole Lau
> Registered Seat: Munich
> Commercial Register: Amtsgericht Muenchen HRB 186928
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v13 1/2] gdb: add annotation in 'info locals' command for variables shadowing case
2026-07-08 18:08 ` [PATCH v13 1/2] gdb: " Abdul Basit Ijaz
2026-07-13 12:49 ` Andrew Burgess
@ 2026-07-13 15:12 ` Andrew Burgess
2026-07-13 20:06 ` Ijaz, Abdul B
1 sibling, 1 reply; 9+ messages in thread
From: Andrew Burgess @ 2026-07-13 15:12 UTC (permalink / raw)
To: Abdul Basit Ijaz, gdb-patches
Cc: pedro, philippe.waroquiers, christina.schimpe, lsix, eliz,
abdul.b.ijaz, guinevere
Abdul Basit Ijaz <abdul.b.ijaz@intel.com> writes:
> --- a/gdb/printcmd.c
> +++ b/gdb/printcmd.c
> @@ -2377,12 +2377,17 @@ 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
> {
> struct value *val;
> struct value_print_options opts;
> + const char *file_name = nullptr;
> +
> + if (var->symtab () && var->symtab ()->filename ())
> + file_name = lbasename (var->symtab ()->filename ());
I suspect there might be some history here, but did you consider using
`symtab_to_filename_for_display`? If not, then it might be worth a
comment explaining what that isn't being used.
Thanks,
Andrew
^ permalink raw reply [flat|nested] 9+ messages in thread
* RE: [PATCH v13 1/2] gdb: add annotation in 'info locals' command for variables shadowing case
2026-07-13 15:12 ` Andrew Burgess
@ 2026-07-13 20:06 ` Ijaz, Abdul B
0 siblings, 0 replies; 9+ messages in thread
From: Ijaz, Abdul B @ 2026-07-13 20:06 UTC (permalink / raw)
To: Andrew Burgess, gdb-patches
Cc: pedro, philippe.waroquiers, Schimpe, Christina, lsix, eliz, guinevere
Hi Andrew,
Thanks for the feedback.
>> + if (var->symtab () && var->symtab ()->filename ())
>> + file_name = lbasename (var->symtab ()->filename ());
> I suspect there might be some history here, but did you consider using `symtab_to_filename_for_display`? If not, then it might be worth a comment explaining what that isn't being used.
The symtab_to_filename_for_display function has not been tried so far. But a quick test shows that by default GDB is configured to use a relative path for filenames. In the past there was feedback to show only the basename here instead of the full path. I will add a comment explaining why we are using lbasename instead.
Best Regards
Abdul Basit
-----Original Message-----
From: Andrew Burgess <aburgess@redhat.com>
Sent: Monday, July 13, 2026 5:13 PM
To: Ijaz, Abdul B <abdul.b.ijaz@intel.com>; gdb-patches@sourceware.org
Cc: pedro@palves.net; philippe.waroquiers@skynet.be; Schimpe, Christina <christina.schimpe@intel.com>; lsix@lancelotsix.com; eliz@gnu.org; Ijaz, Abdul B <abdul.b.ijaz@intel.com>; guinevere@redhat.com
Subject: Re: [PATCH v13 1/2] gdb: add annotation in 'info locals' command for variables shadowing case
Abdul Basit Ijaz <abdul.b.ijaz@intel.com> writes:
> --- a/gdb/printcmd.c
> +++ b/gdb/printcmd.c
> @@ -2377,12 +2377,17 @@ 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
> {
> struct value *val;
> struct value_print_options opts;
> + const char *file_name = nullptr;
> +
> + if (var->symtab () && var->symtab ()->filename ())
> + file_name = lbasename (var->symtab ()->filename ());
I suspect there might be some history here, but did you consider using `symtab_to_filename_for_display`? If not, then it might be worth a comment explaining what that isn't being used.
Thanks,
Andrew
Intel Deutschland GmbH
Registered Address: Dornacher Strasse 1, 85622 Feldkirchen, Germany
Tel: +49 89 991 430, www.intel.de
Managing Directors: Harry Demas, Jeffrey Schneiderman, Yin Chong Sorrell
Chairperson of the Supervisory Board: Nicole Lau
Registered Seat: Munich
Commercial Register: Amtsgericht Muenchen HRB 186928
^ permalink raw reply [flat|nested] 9+ messages in thread
* RE: [PATCH v13 2/2] gdb: add shadowed field in '-stack-list-locals/variables' mi commands
2026-07-13 15:11 ` Andrew Burgess
@ 2026-07-13 20:39 ` Ijaz, Abdul B
0 siblings, 0 replies; 9+ messages in thread
From: Ijaz, Abdul B @ 2026-07-13 20:39 UTC (permalink / raw)
To: Andrew Burgess, gdb-patches
Cc: pedro, philippe.waroquiers, Schimpe, Christina, lsix, eliz, guinevere
Hi Andrew,
Thanks for the feedback. Will address these in v14. Replies inline:
>> +4: int y = 5;
>> +4: x = 99; // breakpoint-line
Andrew>Duplicate line number in this example.
Abdul>Will fix in v14.
>> + (get_lang_vars_shadowing_option (current_language->la_language)
>> + == lang_vars_shadowing::HIDE && shadowed && already_printed)
>> + return;
Andrew>I haven't created a reproducer, but I think there's an issue here. By the time we reach this line the `tuple_emitter` has already had a value emplaced, Andrew> so if the rust early return triggers the tuple_emitter is going to emit an empty tuple ... at least I think that's what will happen.
Andrew>I think the code that give tuple_emitter a value needs to be after the rust early return.
Abdul>Tested it using var_reuse.rs reproducer already included in patch#1 using the "mi" interpreter. It confirms we got empty {} at the end because of this issue. So, tuple_emitter declaration will be moved after the rust early return to solve this.
>> + uiout->field_unsigned ("line", arg->sym->m_line);
Andrew>In the non-MI path you ended up having to guard the equivalent code to handle the case where symtab was NULL, but here you don't have a similar guard.
Abdul> True, symtab NULL value guarding is better to add it here also. But the same test even if executed in MI mode manually does not trigger the similar issue, Will add the guard for symtab NULL value in V14.
Andrew>I suspect you might be able to both shadowing and no symtab ... maybe?
Andrew>When I tried to reproduce this I was ending up with a symtab with the name "<unknown>", I'll leave you to investigate this more.
Abdul>It’s the same case like the other patch as there is no reason why it cannot. So in any case I will add the guard for NULL here.
Andrew>Additionally, here, if you look at the function `mi_symbol_info` and `output_module_symbols_in_single_module_and_file` you'll see they always emit two strings, "filename" and "fullname", I think that you should follow the same pattern here.
Abdul> Will update in v14 to print both "filename" and "fullname" similar to "mi_symbol_info".
Andrew>Also, the access to m_line should be done via the public 'line ()'
Abdul>Will fix in v14 patch.
Andrew>But this bug exists before this patch, so it's not yours to fix.
Andrew>You could however convert PRINT_ME from 'int' to 'bool', that would be a simple fix.
Abdul> Will convert PRINT_ME from 'int' to 'bool' in v14.
>> + switch (sym->loc_class ())
>> + {
>> + default:
>> + case LOC_UNDEF: /* catches errors */
>> + case LOC_CONST: /* constant */
Andrew>And the indentation of the 'default' and 'case ...' statements seems to be messed up. Likely everything within the switch needs to be re-indented I think.
Abdul>Will fix in v14.
>> +if [build_executable ${testfile}.exp ${testfile} ${srcfile} $opts] {
>> + return -1
Andrew>Just 'return' at the top level, no -1 needed.
Abdul>Will fix in v14.
Thanks & Best Regards
Abdul Basit
-----Original Message-----
From: Andrew Burgess <aburgess@redhat.com>
Sent: Monday, July 13, 2026 5:11 PM
To: Ijaz, Abdul B <abdul.b.ijaz@intel.com>; gdb-patches@sourceware.org
Cc: pedro@palves.net; philippe.waroquiers@skynet.be; Schimpe, Christina <christina.schimpe@intel.com>; lsix@lancelotsix.com; eliz@gnu.org; Ijaz, Abdul B <abdul.b.ijaz@intel.com>; guinevere@redhat.com
Subject: Re: [PATCH v13 2/2] gdb: add shadowed field in '-stack-list-locals/variables' mi commands
Thank you for continuing to work on this feature. I have some comments inline below.
Abdul Basit Ijaz <abdul.b.ijaz@intel.com> writes:
> From: "Ijaz, Abdul B" <abdul.b.ijaz@intel.com>
>
> For C/C++/Fortran 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 GDB now
> prints location information for shadowed variables and add 'shadowed' field also in '-stack-list-locals'
> and '-stack-list-variables' mi commands for super-block shadowed variable.
>
> Suppose we have test.c file
>
> 1:int x = 42;
> 2: {
> 3: int x = 99;
> 4: int y = 52;
> 5: x = 99; /* break here */
> 6: }
>
> The "-stack-list-locals" and "-stack-list-variables" mi commands at
> the "break here" line gives the following output:
>
> Before the change:
>
> ~~~
> (gdb)
> -stack-list-locals 0
> ^done,locals=[name="x",name="y",name="x"]
> (gdb)
> -stack-list-locals 1
> ^done,locals=[{name="x",value="99"},{name="y",value="52"},{name="x",va
> lue="42"}]
> (gdb)
> -stack-list-locals 2
> ^done,locals=[{name="x",type="int",value="99"},{name="y",type="int",va
> lue="52"},{name="x",type="int",value="42"}]
> (gdb)
> -stack-list-variables 0
> ^done,variables=[{name="x"},{name="y"},{name="x"}]
> (gdb)
> -stack-list-variables 1
> ^done,variables=[{name="x",value="99"},{name="y",value="52"},{name="x"
> ,value="42"}]
> (gdb)
> -stack-list-variables 2
> ^done,variables=[{name="x",type="int",value="99"},{name="y",type="int"
> ,value="52"},{name="x",type="int",value="42"}]
> ~~~
>
> With this patch we obtain:
>
> ~~~
> (gdb)
> -stack-list-locals 0
> ^done,locals=[name="x",name="y",name="x"]
> (gdb)
> -stack-list-locals 1
> ^done,locals=[{name="x",value="99",file="/home/src/test.c",line="5"},{
> name="y",value="52"},{name="x",value="42",file="/home/src/test.c",line
> ="2",shadowed="true"}]
> (gdb)
> -stack-list-locals 2
> ^done,locals=[{name="x",type="int",value="99",file="/home/src/test.c",
> line="5"},{name="y",type="int",value="52"},{name="x",type="int",value=
> "42",file="/home/src/test.c",line="2",shadowed="true"}]
> (gdb)
> -stack-list-variables 0
> ^done,variables=[{name="x",file="/home/src/test.c",line="5"},{name="y"
> },{name="x",file="/home/src/test.c",line="2",shadowed="true"}]
> (gdb)
> -stack-list-variables 1
> ^done,variables=[{name="x",value="99",file="/home/src/test.c",line="4"
> },{name="y",value="52"},{name="x",value="42",file="/home/src/test.c",l
> ine="2",shadowed="true"}]
> (gdb)
> -stack-list-variables 2
> ^done,variables=[{name="x",type="int",value="99",file="/home/src/test.
> c",line="4"},{name="y",type="int",value="52"},{name="x",type="int",val
> ue="42",file="/home/src/test.c",line="2",shadowed="true"}]
> ~~~
> Reviewed-By: Guinevere Larsen <guinevere@redhat.com>
> ---
> gdb/NEWS | 4 +
> gdb/doc/gdb.texinfo | 21 ++++
> gdb/mi/mi-cmd-stack.c | 129 ++++++++++++++------
> gdb/testsuite/gdb.mi/mi-var-shadowing.c | 50 ++++++++
> gdb/testsuite/gdb.mi/mi-var-shadowing.exp | 141
> ++++++++++++++++++++++
> 5 files changed, 310 insertions(+), 35 deletions(-) create mode
> 100644 gdb/testsuite/gdb.mi/mi-var-shadowing.c
> create mode 100644 gdb/testsuite/gdb.mi/mi-var-shadowing.exp
>
> diff --git a/gdb/NEWS b/gdb/NEWS
> index 6807a6c966b..483d2013e41 100644
> --- a/gdb/NEWS
> +++ b/gdb/NEWS
> @@ -593,6 +593,10 @@ multi-wp-addr in qSupported
> multiple times, and the same mapping was being reused. In all
> other cases, this field will have the value 'false'.
>
> +** GDB now shows "shadowed", "file" and "line" fields in the output
> + of '-stack-list-locals/variables' mi commands for variables
> + shadowing case.
> +
> * Support for stabs debugging format and the a.out/dbx object format is
> deprecated, and will be removed in GDB 18.
>
> diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo index
> ee33bfdba13..64a1345e2f2 100644
> --- a/gdb/doc/gdb.texinfo
> +++ b/gdb/doc/gdb.texinfo
> @@ -35585,6 +35585,27 @@ If the @code{--skip-unavailable} option is
> specified, local variables and arguments that are not available are
> not listed. Partially available arguments and local variables are still displayed, however.
>
> +@smallexample
> +@group
> +1: int x = 3;
> +2: @{
> +3: int x = 4;
> +4: int y = 5;
> +4: x = 99; // breakpoint-line
Duplicate line number in this example.
> +5: @}
> +@end group
> +@group
> +(gdb) -stack-list-variables 2
> +^done,variables=[@{name="x",type="int",value="4",
> +file="/home/src/name.c",line="3"@},@{name="y",type="int",value="5",
> +file="/home/src/name.c",line="4"@},@{name="x",type="int",value="3",
> +file="/home/src/name.c",line="1",shadowed="true"@}]
> +@end group
> +@end smallexample
> +
> +The location information and @samp{shadowed} attribute is only added
> +for shadowed variables (@pxref{shadowed variables}).
> +
> @subsubheading Example
>
> @smallexample
> diff --git a/gdb/mi/mi-cmd-stack.c b/gdb/mi/mi-cmd-stack.c index
> 280a08d121e..38955cc697b 100644
> --- a/gdb/mi/mi-cmd-stack.c
> +++ b/gdb/mi/mi-cmd-stack.c
> @@ -32,6 +32,7 @@
> #include "mi-parse.h"
> #include <optional>
> #include "inferior.h"
> +#include "gdbsupport/unordered_map.h"
>
> enum what_to_list { locals, arguments, all };
>
> @@ -490,7 +491,9 @@ mi_cmd_stack_list_variables (const char *command,
> const char *const *argv, static void list_arg_or_local (const struct
> frame_arg *arg, enum what_to_list what,
> enum print_values values, int skip_unavailable,
> - const frame_print_options &fp_opts)
> + const frame_print_options &fp_opts,
> + const gdb::unordered_set<std::string> *shadowed_vars,
> + gdb::unordered_set<std::string> &printed_vars)
> {
> struct ui_out *uiout = current_uiout;
>
> @@ -519,6 +522,19 @@ list_arg_or_local (const struct frame_arg *arg, enum what_to_list what,
> tuple_emitter.emplace (uiout, nullptr);
>
> string_file stb;
> + const char *name = arg->sym->print_name ();
> + /* To distinguish innermost variable from the rest in the shadowed_vars
> + this boolen is needed. */
Typo: s/boolen/boolean/
> + bool already_printed = !printed_vars.insert (name).second; bool
> + shadowed = shadowed_vars->find (name) != shadowed_vars->end ();
> +
> + /* In case of Rust language it is possible to declare variable with
> + same name multiple times and only latest declaration of variable
> + is accessible. So print only the first instance and there is no
> + need of printing duplicates. */ if
> + (get_lang_vars_shadowing_option (current_language->la_language)
> + == lang_vars_shadowing::HIDE && shadowed && already_printed)
> + return;
I haven't created a reproducer, but I think there's an issue here. By the time we reach this line the `tuple_emitter` has already had a value emplaced, so if the rust early return triggers the tuple_emitter is going to emit an empty tuple ... at least I think that's what will happen.
I think the code that give tuple_emitter a value needs to be after the rust early return.
>
> stb.puts (arg->sym->print_name ());
> if (arg->entry_kind == print_entry_values_only) @@ -560,6 +576,59
> @@ list_arg_or_local (const struct frame_arg *arg, enum what_to_list what,
> }
> uiout->field_stream ("value", stb);
> }
> +
> + /* Only for C/C++/Fortran/Ada languages, in case of variables shadowing
> + print shadowed field after the superblock variable and only location
> + of the variables in the innerblock. */ if
> + (get_lang_vars_shadowing_option (current_language->la_language)
> + == lang_vars_shadowing::PRINT && shadowed
> + && !(values == PRINT_NO_VALUES && what == locals))
> + {
> + uiout->field_string ("file", arg->sym->symtab ()->filename ());
> + uiout->field_unsigned ("line", arg->sym->m_line);
In the non-MI path you ended up having to guard the equivalent code to handle the case where symtab was NULL, but here you don't have a similar guard.
I suspect the issue here is that the test you quote for the non-MI path gdb.dwarf2/missing-type-name-for-templates.exp, doesn't have shadowing, but does have the missing symtab case. In the non-MI code you use the symtab even when shadowing didn't occur (I suggested moving the code), which is why you hit the issue, but I suspect you might be able to both shadowing and no symtab ... maybe?
When I tried to reproduce this I was ending up with a symtab with the name "<unknown>", I'll leave you to investigate this more.
Additionally, here, if you look at the function `mi_symbol_info` and `output_module_symbols_in_single_module_and_file` you'll see they always emit two strings, "filename" and "fullname", I think that you should follow the same pattern here.
Also, the access to m_line should be done via the public 'line ()'
method. One day maybe we'll even make these member variables private.
> + if (already_printed)
> + uiout->field_string ("shadowed", "true");
> + }
> +}
> +
> +/* Returns true if address_class can be printed, otherwise returns
> +false. */
> +
> +static bool
> +can_print_aclass (struct symbol *sym, enum what_to_list what) {
> + int print_me = false;
> +
> + switch (sym->loc_class ())
> + {
> + default:
> + case LOC_UNDEF: /* catches errors */
> + case LOC_CONST: /* constant */
> + case LOC_TYPEDEF: /* local typedef */
> + case LOC_LABEL: /* local label */
> + case LOC_BLOCK: /* local function */
> + case LOC_CONST_BYTES: /* loc. byte seq. */
> + case LOC_UNRESOLVED: /* unresolved static */
> + case LOC_OPTIMIZED_OUT: /* optimized out */
> + print_me = false;
> + break;
Not something I think you need to fix as this is moved code, but I noticed it while reviewing this patch -- this switch is almost certainly wrong. There's no reason why we cannot print LOC_CONST variables, e.g. variables defined with DW_AT_const_value. Also LOC_OPTIMIZED_OUT should probably be printed, if I do 'info locals' then I will see optimised out locals, but '-stack-list-locals 1' will skip them, which doesn't seem right to me.
But this bug exists before this patch, so it's not yours to fix.
You could however convert PRINT_ME from 'int' to 'bool', that would be a simple fix.
And the indentation of the 'default' and 'case ...' statements seems to be messed up. Likely everything within the switch needs to be re-indented I think.
> +
> + case LOC_ARG: /* argument */
> + case LOC_REF_ARG: /* reference arg */
> + case LOC_REGPARM_ADDR: /* indirect register arg */
> + case LOC_LOCAL: /* stack local */
> + case LOC_STATIC: /* static */
> + case LOC_REGISTER: /* register */
> + case LOC_COMPUTED: /* computed location */
> + if (what == all)
> + print_me = true;
> + else if (what == locals)
> + print_me = !sym->is_argument ();
> + else
> + print_me = sym->is_argument ();
> + break;
> + }
> +
> + return print_me;
> }
>
> /* Print a list of the objects for the frame FI in a certain form, @@
> -573,9 +642,10 @@ list_args_or_locals (const frame_print_options &fp_opts,
> enum what_to_list what, enum print_values values,
> const frame_info_ptr &fi, int skip_unavailable) {
> - const struct block *block;
> + const struct block *block, *orig_block;
> const char *name_of_result;
> struct ui_out *uiout = current_uiout;
> + gdb::unordered_set<std::string> collected_vars, shadowed_vars,
> + printed_vars;
>
> block = get_frame_block (fi, 0);
>
> @@ -596,42 +666,31 @@ list_args_or_locals (const frame_print_options
> &fp_opts,
>
> ui_out_emit_list list_emitter (uiout, name_of_result);
>
> - while (block != 0)
> + orig_block = block;
> + /* Stored list of shadowed variables later help in identifying them
> + from the rest. */
> + while (block != nullptr)
> {
> for (struct symbol *sym : block_iterator_range (block))
> {
> - int print_me = 0;
> -
> - switch (sym->loc_class ())
> + if (can_print_aclass (sym, what))
> {
> - default:
> - case LOC_UNDEF: /* catches errors */
> - case LOC_CONST: /* constant */
> - case LOC_TYPEDEF: /* local typedef */
> - case LOC_LABEL: /* local label */
> - case LOC_BLOCK: /* local function */
> - case LOC_CONST_BYTES: /* loc. byte seq. */
> - case LOC_UNRESOLVED: /* unresolved static */
> - case LOC_OPTIMIZED_OUT: /* optimized out */
> - print_me = 0;
> - break;
> -
> - case LOC_ARG: /* argument */
> - case LOC_REF_ARG: /* reference arg */
> - case LOC_REGPARM_ADDR: /* indirect register arg */
> - case LOC_LOCAL: /* stack local */
> - case LOC_STATIC: /* static */
> - case LOC_REGISTER: /* register */
> - case LOC_COMPUTED: /* computed location */
> - if (what == all)
> - print_me = 1;
> - else if (what == locals)
> - print_me = !sym->is_argument ();
> - else
> - print_me = sym->is_argument ();
> - break;
> + const char *name = sym->print_name ();
> + if (!collected_vars.insert (name).second)
> + shadowed_vars.insert (name);
> }
> - if (print_me)
> + }
> + if (block->function ())
> + break;
> + block = block->superblock ();
> + }
> +
> + block = orig_block;
> + while (block != 0)
> + {
> + for (struct symbol *sym : block_iterator_range (block))
> + {
> + if (can_print_aclass (sym, what))
> {
> struct frame_arg arg, entryarg;
>
> @@ -657,10 +716,10 @@ list_args_or_locals (const frame_print_options
> &fp_opts,
>
> if (arg.entry_kind != print_entry_values_only)
> list_arg_or_local (&arg, what, values, skip_unavailable,
> - fp_opts);
> + fp_opts, &shadowed_vars, printed_vars);
> if (entryarg.entry_kind != print_entry_values_no)
> list_arg_or_local (&entryarg, what, values, skip_unavailable,
> - fp_opts);
> + fp_opts, &shadowed_vars, printed_vars);
> }
> }
>
> diff --git a/gdb/testsuite/gdb.mi/mi-var-shadowing.c
> b/gdb/testsuite/gdb.mi/mi-var-shadowing.c
> new file mode 100644
> index 00000000000..875e753b485
> --- /dev/null
> +++ b/gdb/testsuite/gdb.mi/mi-var-shadowing.c
> @@ -0,0 +1,50 @@
> +/* 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; /* entry bp */
> + 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 */
> + {
> + 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.mi/mi-var-shadowing.exp
> b/gdb/testsuite/gdb.mi/mi-var-shadowing.exp
> new file mode 100644
> index 00000000000..f88c8c81736
> --- /dev/null
> +++ b/gdb/testsuite/gdb.mi/mi-var-shadowing.exp
> @@ -0,0 +1,141 @@
> +# 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 mi-support.exp
> +set MIFLAGS "-i=mi"
> +
> +gdb_exit
> +if [mi_gdb_start] {
> + continue
> +}
> +
> +standard_testfile
> +
> +set opts {debug}
> +if [build_executable ${testfile}.exp ${testfile} ${srcfile} $opts] {
> + return -1
Just 'return' at the top level, no -1 needed.
Thanks,
Andrew
> +}
> +
> +mi_delete_breakpoints
> +mi_gdb_reinitialize_dir $srcdir/$subdir mi_gdb_load ${binfile}
> +mi_runto main
> +
> +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
> +"entry bp"]
> +
> +set stack_test1_regx "\\^done,(locals|variables)=\\\[\{name=\"a\",type=\"int\",value=\"$decimal\"\},\{name=\"val1\",type=\"unsigned int\",value=\"1\"\},{name=\"val2\",type=\"unsigned int\",value=\"2\"\}\\\]"
> +set stack_test2_regx "\\^done,(locals|variables)=\\\[\{name=\"val2\",type=\"unsigned int\",value=\"3\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val2_d2\"\},\{name=\"val3\",type=\"unsigned int\",value=\"4\"\},\{name=\"a\",type=\"int\",value=\"101\"\},\{name=\"val1\",type=\"unsigned int\",value=\"1\"\},\{name=\"val2\",type=\"unsigned int\",value=\"2\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val2_d1\",shadowed=\"true\"\}\\\]"
> +set stack_test3_regx "\\^done,(locals|variables)=\\\[\{name=\"val1\",type=\"unsigned int\",value=\"5\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val1_d2\"\},\{name=\"val2\",type=\"unsigned int\",value=\"3\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val2_d2\"\},\{name=\"val3\",type=\"unsigned int\",value=\"4\"\},\{name=\"a\",type=\"int\",value=\"102\"\},\{name=\"val1\",type=\"unsigned int\",value=\"1\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val1_d1\",shadowed=\"true\"\},\{name=\"val2\",type=\"unsigned int\",value=\"2\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val2_d1\",shadowed=\"true\"\}\\\]"
> +set stack_test4_regx "\\^done,(locals|variables)=\\\[\{name=\"val1\",type=\"unsigned int\",value=\"6\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val1_d3\"\},\{name=\"val2\",type=\"unsigned int\",value=\"7\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val2_d3\"\},\{name=\"val3\",type=\"unsigned int\",value=\"8\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val3_d2\"\},\{name=\"val1\",type=\"unsigned int\",value=\"5\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val1_d2\",shadowed=\"true\"\},\{name=\"val2\",type=\"unsigned int\",value=\"3\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val2_d2\",shadowed=\"true\"\},\{name=\"val3\",type=\"unsigned int\",value=\"4\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val3_d1\",shadowed=\"true\"\},\{name=\"a\",type=\"int\",value=\"103\"\},\{name=\"val1\",type=\"unsigned int\",value=\"1\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val1_d1\",shadowed=\"true\"\},\{name=\"val2\",type=\"unsigned int\",value=\"2\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val2_d1\",shadowed=\"true\"\}\\\]"
> +set stack_test5_regx "\\^done,(locals|variables)=\\\[\{name=\"a\",type=\"int\",value=\"105\"\},\{name=\"val1\",type=\"unsigned int\",value=\"1\"\},\{name=\"val2\",type=\"unsigned int\",value=\"2\"\}\\\]"
> +
> +mi_gdb_test \
> + "-break-insert --source ${srcfile} --line ${bp_line1}" \
> + "\\^done.*source ${srcfile} \\-line ${bp_line1}.*" \
> + "bp at outermost level"
> +mi_execute_to "exec-continue" "breakpoint-hit" ".*" ".*" ".*" "${bp_line1}" \
> + { "" "disp=\"keep\"" } "continue to outermost level"
> +mi_gdb_test "-stack-list-locals 0" \
> + "\\^done,locals=\\\[name=\"a\",name=\"val1\",name=\"val2\"\\\]" \
> + "-stack-list-locals 0 at outermost level"
> +mi_gdb_test "-stack-list-variables 0" \
> + "\\^done,variables=\\\[{name=\"a\"},{name=\"val1\"},{name=\"val2\"}\\\]" \
> + "-stack-list-variables 0 at outermost level"
> +mi_gdb_test "-stack-list-locals 2" "${stack_test1_regx}" \
> + "-stack-list-locals 2 at outermost level"
> +mi_gdb_test "-stack-list-variables 2" "${stack_test1_regx}" \
> + "-stack-list-variables 2 at outermost level"
> +
> +mi_gdb_test \
> + "-break-insert --source ${srcfile} --line ${bp_line2}" \
> + "\\^done.*source ${srcfile} \\-line ${bp_line2}.*" \
> + "bp at first level"
> +mi_execute_to "exec-continue" "breakpoint-hit" ".*" ".*" ".*" "${bp_line2}" \
> + { "" "disp=\"keep\"" } "continue to first level"
> +mi_gdb_test "-stack-list-locals 0" \
> + "\\^done,locals=\\\[name=\"val2\",name=\"val3\",name=\"a\",name=\"val1\",name=\"val2\"\\\]" \
> + "-stack-list-locals 0 at first level"
> +mi_gdb_test "-stack-list-variables 0" \
> + "\\^done,variables=\\\[{name=\"val2\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val2_d2\"},{name=\"val3\"},{name=\"a\"},{name=\"val1\"},{name=\"val2\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val2_d1\",shadowed=\"true\"\}\\\]" \
> + "-stack-list-variables 0 at first level"
> +mi_gdb_test "-stack-list-locals 2" "${stack_test2_regx}" \
> + "-stack-list-locals 2 at first level"
> +mi_gdb_test "-stack-list-variables 2" "${stack_test2_regx}" \
> + "-stack-list-variables 2 at first level"
> +
> +mi_gdb_test \
> + "-break-insert --source ${srcfile} --line ${bp_line3}" \
> + "\\^done.*source ${srcfile} \\-line ${bp_line3}.*" \
> + "bp at second level"
> +mi_execute_to "exec-continue" "breakpoint-hit" ".*" ".*" ".*" "${bp_line3}" \
> + { "" "disp=\"keep\"" } "continue to second level"
> +mi_gdb_test "-stack-list-locals 0" \
> + "\\^done,locals=\\\[name=\"val1\",name=\"val2\",name=\"val3\",name=\"a\",name=\"val1\",name=\"val2\"\\\]" \
> + "-stack-list-locals 0 at second level"
> +mi_gdb_test "-stack-list-variables 0" \
> + "\\^done,variables=\\\[{name=\"val1\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val1_d2\"},{name=\"val2\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val2_d2\"},{name=\"val3\"},{name=\"a\"},{name=\"val1\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val1_d1\",shadowed=\"true\"\},{name=\"val2\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val2_d1\",shadowed=\"true\"\}\\\]" \
> + "-stack-list-variables 0 at second level"
> +mi_gdb_test "-stack-list-locals 2" "${stack_test3_regx}" \
> + "-stack-list-locals 2 at second level"
> +mi_gdb_test "-stack-list-variables 2" "${stack_test3_regx}" \
> + "-stack-list-variables 2 at second level"
> +
> +mi_gdb_test \
> + "-break-insert --source ${srcfile} --line ${bp_line4}" \
> + "\\^done.*source ${srcfile} \\-line ${bp_line4}.*" \
> + "bp at third level"
> +mi_execute_to "exec-continue" "breakpoint-hit" ".*" ".*" ".*" "${bp_line4}" \
> + { "" "disp=\"keep\"" } "continue to third level"
> +mi_gdb_test "-stack-list-locals 0" \
> + "\\^done,locals=\\\[name=\"val1\",name=\"val2\",name=\"val3\",name=\"val1\",name=\"val2\",name=\"val3\",name=\"a\",name=\"val1\",name=\"val2\"\\\]" \
> + "-stack-list-locals 0 at third level"
> +mi_gdb_test "-stack-list-variables 0" \
> + "\\^done,variables=\\\[{name=\"val1\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val1_d3\"},{name=\"val2\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val2_d3\"},{name=\"val3\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val3_d2\"},{name=\"val1\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val1_d2\",shadowed=\"true\"\},\{name=\"val2\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val2_d2\",shadowed=\"true\"\},\{name=\"val3\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val3_d1\",shadowed=\"true\"\},{name=\"a\"},{name=\"val1\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val1_d1\",shadowed=\"true\"\},{name=\"val2\",file=\"$srcdir/$subdir/$srcfile\",line=\"$val2_d1\",shadowed=\"true\"\}\\\]" \
> + "-stack-list-variables 0 at third level"
> +mi_gdb_test "-stack-list-locals 2" "${stack_test4_regx}" \
> + "-stack-list-locals 2 at third level"
> +mi_gdb_test "-stack-list-variables 2" "${stack_test4_regx}" \
> + "-stack-list-variables 2 at third level"
> +
> +mi_gdb_test \
> + "-break-insert --source ${srcfile} --line ${bp_line5}" \
> + "\\^done.*source ${srcfile} \\-line ${bp_line5}.*" \
> + "bp at outermost level last"
> +mi_execute_to "exec-continue" "breakpoint-hit" ".*" ".*" ".*" "${bp_line5}" \
> + { "" "disp=\"keep\"" } "continue to outermost level last"
> +mi_gdb_test "-stack-list-locals 0" \
> + "\\^done,locals=\\\[name=\"a\",name=\"val1\",name=\"val2\"\\\]" \
> + "-stack-list-locals 0 at outermost level last"
> +mi_gdb_test "-stack-list-variables 0" \
> + "\\^done,variables=\\\[{name=\"a\"},{name=\"val1\"},{name=\"val2\"}\\\]" \
> + "-stack-list-variables at outermost level last"
> +mi_gdb_test "-stack-list-locals 2" "${stack_test5_regx}" \
> + "-stack-list-locals 2 at outermost level last"
> +mi_gdb_test "-stack-list-variables 2" "${stack_test5_regx}" \
> + "-stack-list-variables 2 at outermost level last"
> --
> 2.34.1
>
> Intel Deutschland GmbH
>
> Registered Address: Dornacher Strasse 1, 85622 Feldkirchen, Germany
> Tel: +49 89 991 430, www.intel.de
> Managing Directors: Harry Demas, Jeffrey Schneiderman, Yin Chong
> Sorrell Chairperson of the Supervisory Board: Nicole Lau Registered
> Seat: Munich Commercial Register: Amtsgericht Muenchen HRB 186928
Intel Deutschland GmbH
Registered Address: Dornacher Strasse 1, 85622 Feldkirchen, Germany
Tel: +49 89 991 430, www.intel.de
Managing Directors: Harry Demas, Jeffrey Schneiderman, Yin Chong Sorrell
Chairperson of the Supervisory Board: Nicole Lau
Registered Seat: Munich
Commercial Register: Amtsgericht Muenchen HRB 186928
^ permalink raw reply [flat|nested] 9+ messages in thread
end of thread, other threads:[~2026-07-13 20:39 UTC | newest]
Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-07-08 18:08 [PATCH v13 0/2] add annotation in 'info locals' command for variables shadowing case Abdul Basit Ijaz
2026-07-08 18:08 ` [PATCH v13 1/2] gdb: " Abdul Basit Ijaz
2026-07-13 12:49 ` Andrew Burgess
2026-07-13 13:45 ` Ijaz, Abdul B
2026-07-13 15:12 ` Andrew Burgess
2026-07-13 20:06 ` Ijaz, Abdul B
2026-07-08 18:08 ` [PATCH v13 2/2] gdb: add shadowed field in '-stack-list-locals/variables' mi commands Abdul Basit Ijaz
2026-07-13 15:11 ` Andrew Burgess
2026-07-13 20:39 ` Ijaz, Abdul B
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox