This is a small patch to address
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=92386
When a variable is used before being shadowed in the same scope, GCC
outputs incorrect/misleading debug information. The Bugzilla report has
a minimal example and explains why a direct fix was not desirable (debug
info size would blow up).
In the Bugzilla discussion, Richard Biener suggests "this probably
deserves a warning and should be considered bad style?"
This patch implements such a "-Wuse-before-shadow" warning for these
scenarios. When gimplifying a named var, we throw a warning if it isn't
the most-recently bound declaration for that name. I wasn't able to find
a way to get the current stack of bindings for a given name, so I added
a new hash map (binding_stacks) to keep track of this (only when
-Wuse-before-shadow).
I tested on x86-64-linux-gnu (Debian 12) with make -k check and saw no
testsuite regressions using ./contrib/compare_tests. I also built Git
and Linux with the flag: each triggered one 'true positive' warning,
neither of which was an actual bug.
I have not tested on major projects in other languages (C++, etc.) so
there might be more false positives when -Wuse-before-shadow is enabled
for those languages (could restrict the warning to just C if this is a
concern?).
Any comments would be much appreciated; I'm new to the codebase.
(Also, sorry for any duplicate sends --- I had some DMARC issues
originally.)
PR debug/92386 - gdb issue with variable-shadowing
PR debug/92386
gcc/ChangeLog:
* common.opt: Added Wuse-before-shadow option; defaults to off.
* gimplify.cc (gimplify_bind_expr): when gimplifying a bind,
keep track of the stack of variables bound to each identifier.
(gimplify_var_or_parm_decl): when gimplifying a named var,
check if it is the most-recently bound variable for that
identifier.
gcc/testsuite/ChangeLog:
* gcc.dg/warn-use-before-shadow.c: New test.
---
gcc/common.opt | 5 ++
gcc/gimplify.cc | 70 +++++++++++++++++++
gcc/testsuite/gcc.dg/warn-use-before-shadow.c | 28 ++++++++
3 files changed, 103 insertions(+)
create mode 100644 gcc/testsuite/gcc.dg/warn-use-before-shadow.c
diff --git a/gcc/common.opt b/gcc/common.opt
index e3fa0dacec4..cb4a2754912 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -880,6 +880,11 @@ Wunused-variable
Common Var(warn_unused_variable) Warning EnabledBy(Wunused)
Warn when a variable is unused.
+Wuse-before-shadow
+Common Var(warn_use_before_shadow) Warning Init(0)
+Warn if a variable from an outer scope is used before it is shadowed in the
+current scope.
+
Wcoverage-mismatch
Common Var(warn_coverage_mismatch) Init(1) Warning
Warn in case profiles in -fprofile-use do not match.
diff --git a/gcc/gimplify.cc b/gcc/gimplify.cc
index 4f385b1b779..6854ced7b06 100644
--- a/gcc/gimplify.cc
+++ b/gcc/gimplify.cc
@@ -120,6 +120,17 @@ tree_associate_condition_with_expr (tree stmt, unsigned
uid)
/* Hash set of poisoned variables in a bind expr. */
static hash_set<tree> *asan_poisoned_variables = NULL;
+/* Maps a var name to a stack of variables currently bound to that name. This
+ global is only used if -Wuse-before-shadow is passed, in which case every
+ time a variable is gimplified we check this stack and throw a warning if
+ that variable is not the innermost binding to its name. */
+static hash_map <tree, auto_vec <tree>> binding_stacks;
+
+/* A list of all of the currently bound named variables. Basically a flattened
+ version of @binding_stacks. Used to pop bindings off @binding_stacks when
+ gimplify_bind_expr returns. */
+static auto_vec <tree> all_bindings;
+
/* Hash set of already-resolved calls to OpenMP "declare variant"
functions. A call can resolve to the original function and
we don't want to repeat the resolution multiple times. */
@@ -1417,6 +1428,25 @@ gimplify_bind_expr (tree *expr_p, gimple_seq *pre_p)
tree temp = voidify_wrapper_expr (bind_expr, NULL);
+ /* Update the stack of variables bound to a given name. Used for
+ -Wuse-before-shadow to detect when a variable is used in a scope where it is
+ not the innermost bound instance of that name. Note that gimplification will
+ sometimes change the DECL_NAME associated with a tree (e.g., promoting an
+ initialized const array to a static) so we store the names in a separate
+ vector @all_bindings to pop from after exiting the bind expr. */
+ size_t all_bindings_reset_idx = all_bindings.length ();
+ if (warn_use_before_shadow)
+ {
+ for (tree t = BIND_EXPR_VARS (bind_expr); t ; t = DECL_CHAIN (t))
+ {
+ if (VAR_P (t) && DECL_NAME (t) != NULL_TREE)
+ {
+ binding_stacks.get_or_insert (DECL_NAME (t)).safe_push (t);
+ all_bindings.safe_push (DECL_NAME (t));
+ }
+ }
+ }
+
/* Mark variables seen in this bind expr. */
for (t = BIND_EXPR_VARS (bind_expr); t ; t = DECL_CHAIN (t))
{
@@ -1828,6 +1858,17 @@ gimplify_bind_expr (tree *expr_p, gimple_seq *pre_p)
gimplify_seq_add_stmt (pre_p, bind_stmt);
+ /* Pop off bindings used for -Wuse-before-shadow now that we're
+ exiting the bind. */
+ while (all_bindings.length () > all_bindings_reset_idx)
+ {
+ tree name = all_bindings.pop ();
+ binding_stacks.get (name)->pop ();
+ if (!binding_stacks.get (name)->length ())
+ binding_stacks.remove (name);
+ }
+
+
if (temp)
{
*expr_p = temp;
@@ -3330,6 +3371,35 @@ gimplify_var_or_parm_decl (tree *expr_p)
{
tree decl = *expr_p;
+ /* Check that any named variable we use is the one actually bound to that
+ name in this scope. */
+ if (warn_use_before_shadow && VAR_P (decl) && DECL_NAME (decl) != NULL_TREE
+ && !warning_suppressed_p (decl, OPT_Wuse_before_shadow))
+ {
+ vec <tree> *binding_stack = binding_stacks.get (DECL_NAME (decl));
+ if (binding_stack && binding_stack->length ())
+ {
+ tree scope_decl = binding_stack->last ();
+ if ((!DECL_EXTERNAL (scope_decl) && scope_decl != decl)
+ || (DECL_EXTERNAL (scope_decl) && !is_global_var (decl)))
+ {
+ if (warning_at (input_location, OPT_Wuse_before_shadow,
+ "variable %qD is used before being shadowed",
+ decl))
+ {
+ inform (DECL_SOURCE_LOCATION (scope_decl),
+ "the shadowing declaration of %qD in this scope",
+ scope_decl);
+ inform (DECL_SOURCE_LOCATION (decl),
+ "outer scope declaration of %qD being "
+ "used instead",
+ decl);
+ }
+ suppress_warning (decl, OPT_Wuse_before_shadow);
+ }
+ }
+ }
+
/* ??? If this is a local variable, and it has not been seen in any
outer BIND_EXPR, then it's probably the result of a duplicate
declaration, for which we've already issued an error. It would
diff --git a/gcc/testsuite/gcc.dg/warn-use-before-shadow.c
b/gcc/testsuite/gcc.dg/warn-use-before-shadow.c
new file mode 100644
index 00000000000..5d381e4c30f
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/warn-use-before-shadow.c
@@ -0,0 +1,28 @@
+/* { dg-do compile } */
+/* { dg-options "-Wuse-before-shadow" } */
+int A, B, C, D;
+
+int main(void) {
+ A++; /* { dg-warning "used before being shadowed" } */
+ B++;
+ int A = 7, C = 8;
+ {
+ C++; /* { dg-warning "used before being shadowed" } */
+ extern int C, D;
+ C++;
+ D++;
+ }
+
+ D++;
+ extern int D;
+ D++;
+
+ int x = 5, x2 = 6;
+ {
+ x++; /* { dg-warning "used before being shadowed" } */
+ int x = 7, x2 = 7;
+ x--;
+ x2--;
+ }
+ x++;
+}
--
2.39.5