On Fri, May 16, 2025 at 5:13 PM Matthew Sotoudeh
<sotou...@cs.stanford.edu> wrote:
>
> 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?).

Maybe then the gimplifier is not the right place to do this then.
-Wshadow is handled in warn_if_shadowing inside c-decl.cc which is
called from pushdecl.
Maybe you could do something inside there where you search the current
statement list (cur_stmt_list) for previous usages of the name?

Thanks,
Andrew

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