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

Reply via email to