diff --git a/gdb/NEWS b/gdb/NEWS index 5410acc8303..8f40ca5cb11 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 the "shadowed", "filename", "fullname" and "line" + fields in the output of '-stack-list-locals/variables' mi + commands for variables that are shadowed, or which are shadowing. + * 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 d2fbe61d8fc..0030698dcee 100644 --- a/gdb/doc/gdb.texinfo +++ b/gdb/doc/gdb.texinfo @@ -35585,6 +35585,28 @@ 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; +5: x = 99; // breakpoint-line +6: @} +@end group +@group +(gdb) -stack-list-variables 2 +^done,variables=[@{name="x",type="int",value="4", +filename="name.c",fullname="/home/src/name.c",line="3"@}, +@{name="y",type="int",value="5"@},@{name="x",type="int", +value="3",filename="name.c",fullname="/home/src/name.c", +line="1",shadowed="true"@}] +@end group +@end smallexample + +The location information and @samp{shadowed} attributes are only added +when variable shadowing is detected (@pxref{shadowed variables}). + @subsubheading Example @smallexample diff --git a/gdb/mi/mi-cmd-stack.c b/gdb/mi/mi-cmd-stack.c index 280a08d121e..37a470701af 100644 --- a/gdb/mi/mi-cmd-stack.c +++ b/gdb/mi/mi-cmd-stack.c @@ -32,6 +32,8 @@ #include "mi-parse.h" #include #include "inferior.h" +#include "source.h" +#include "gdbsupport/unordered_map.h" enum what_to_list { locals, arguments, all }; @@ -490,7 +492,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 *shadowed_vars, + gdb::unordered_set &printed_vars) { struct ui_out *uiout = current_uiout; @@ -514,12 +518,25 @@ list_arg_or_local (const struct frame_arg *arg, enum what_to_list what, arg->val->type ()->length ())))) return; + string_file stb; + const char *name = arg->sym->print_name (); + /* To distinguish innermost variable from the rest in the shadowed_vars + this boolean 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; + std::optional tuple_emitter; if (values != PRINT_NO_VALUES || what == all) tuple_emitter.emplace (uiout, nullptr); - string_file stb; - stb.puts (arg->sym->print_name ()); if (arg->entry_kind == print_entry_values_only) stb.puts ("@entry"); @@ -560,6 +577,67 @@ 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)) + { + if (arg->sym->symtab () != nullptr) + { + symtab *symtab = arg->sym->symtab (); + + uiout->field_string ("filename", + symtab_to_filename_for_display (symtab)); + uiout->field_string ("fullname", symtab_to_fullname (symtab)); + uiout->field_unsigned ("line", arg->sym->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) +{ + bool 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 +651,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 collected_vars, shadowed_vars, printed_vars; block = get_frame_block (fi, 0); @@ -596,42 +675,31 @@ list_args_or_locals (const frame_print_options &fp_opts, ui_out_emit_list list_emitter (uiout, name_of_result); + 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)) + { + if (can_print_aclass (sym, what)) + { + const char *name = sym->print_name (); + if (!collected_vars.insert (name).second) + shadowed_vars.insert (name); + } + } + if (block->function ()) + break; + block = block->superblock (); + } + + block = orig_block; while (block != 0) { for (struct symbol *sym : block_iterator_range (block)) { - int print_me = 0; - - 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 = 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; - } - if (print_me) + if (can_print_aclass (sym, what)) { struct frame_arg arg, entryarg; @@ -657,10 +725,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 . */ + +#include + +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..3c0dbc2884c --- /dev/null +++ b/gdb/testsuite/gdb.mi/mi-var-shadowing.exp @@ -0,0 +1,144 @@ +# 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 . + +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 +} + +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 fullname "${fullname_syntax}$srcfile" +set filename "\[^\r\n\]+$srcfile" + +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\",filename=\"$filename\",fullname=\"$fullname\",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\",filename=\"$filename\",fullname=\"$fullname\",line=\"$val2_d1\",shadowed=\"true\"\}\\\]" +set stack_test3_regx "\\^done,(locals|variables)=\\\[\{name=\"val1\",type=\"unsigned int\",value=\"5\",filename=\"$filename\",fullname=\"$fullname\",line=\"$val1_d2\"\},\{name=\"val2\",type=\"unsigned int\",value=\"3\",filename=\"$filename\",fullname=\"$fullname\",line=\"$val2_d2\"\},\{name=\"val3\",type=\"unsigned int\",value=\"4\"\},\{name=\"a\",type=\"int\",value=\"102\"\},\{name=\"val1\",type=\"unsigned int\",value=\"1\",filename=\"$filename\",fullname=\"$fullname\",line=\"$val1_d1\",shadowed=\"true\"\},\{name=\"val2\",type=\"unsigned int\",value=\"2\",filename=\"$filename\",fullname=\"$fullname\",line=\"$val2_d1\",shadowed=\"true\"\}\\\]" +set stack_test4_regx "\\^done,(locals|variables)=\\\[\{name=\"val1\",type=\"unsigned int\",value=\"6\",filename=\"$filename\",fullname=\"$fullname\",line=\"$val1_d3\"\},\{name=\"val2\",type=\"unsigned int\",value=\"7\",filename=\"$filename\",fullname=\"$fullname\",line=\"$val2_d3\"\},\{name=\"val3\",type=\"unsigned int\",value=\"8\",filename=\"$filename\",fullname=\"$fullname\",line=\"$val3_d2\"\},\{name=\"val1\",type=\"unsigned int\",value=\"5\",filename=\"$filename\",fullname=\"$fullname\",line=\"$val1_d2\",shadowed=\"true\"\},\{name=\"val2\",type=\"unsigned int\",value=\"3\",filename=\"$filename\",fullname=\"$fullname\",line=\"$val2_d2\",shadowed=\"true\"\},\{name=\"val3\",type=\"unsigned int\",value=\"4\",filename=\"$filename\",fullname=\"$fullname\",line=\"$val3_d1\",shadowed=\"true\"\},\{name=\"a\",type=\"int\",value=\"103\"\},\{name=\"val1\",type=\"unsigned int\",value=\"1\",filename=\"$filename\",fullname=\"$fullname\",line=\"$val1_d1\",shadowed=\"true\"\},\{name=\"val2\",type=\"unsigned int\",value=\"2\",filename=\"$filename\",fullname=\"$fullname\",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\",filename=\"$filename\",fullname=\"$fullname\",line=\"$val2_d2\"},{name=\"val3\"},{name=\"a\"},{name=\"val1\"},{name=\"val2\",filename=\"$filename\",fullname=\"$fullname\",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\",filename=\"$filename\",fullname=\"$fullname\",line=\"$val1_d2\"},{name=\"val2\",filename=\"$filename\",fullname=\"$fullname\",line=\"$val2_d2\"},{name=\"val3\"},{name=\"a\"},{name=\"val1\",filename=\"$filename\",fullname=\"$fullname\",line=\"$val1_d1\",shadowed=\"true\"\},{name=\"val2\",filename=\"$filename\",fullname=\"$fullname\",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\",filename=\"$filename\",fullname=\"$fullname\",line=\"$val1_d3\"},{name=\"val2\",filename=\"$filename\",fullname=\"$fullname\",line=\"$val2_d3\"},{name=\"val3\",filename=\"$filename\",fullname=\"$fullname\",line=\"$val3_d2\"},{name=\"val1\",filename=\"$filename\",fullname=\"$fullname\",line=\"$val1_d2\",shadowed=\"true\"\},\{name=\"val2\",filename=\"$filename\",fullname=\"$fullname\",line=\"$val2_d2\",shadowed=\"true\"\},\{name=\"val3\",filename=\"$filename\",fullname=\"$fullname\",line=\"$val3_d1\",shadowed=\"true\"\},{name=\"a\"},{name=\"val1\",filename=\"$filename\",fullname=\"$fullname\",line=\"$val1_d1\",shadowed=\"true\"\},{name=\"val2\",filename=\"$filename\",fullname=\"$fullname\",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"