On Mon, 29 Nov 2021, Martin Sebor wrote:

> On 11/26/21 5:18 AM, Richard Biener via Gcc-patches wrote:
> > This implements a subset of -Wunreachable-code, unreachable code
> > after a return stmt.  Contrary to the previous attemt at CFG
> > construction time this implements the bits during GIMPLE lowering
> > where there are still all GIMPLE return stmts in the IL.
> > 
> > The lowering phase keeps track of whether stmts can fallthru
> > which is used to determine if the following stmt is reachable.
> > The implementation only considers labels here.
> > 
> > The fallthru flag is transparently extended to allow tracking
> > a reason for non-fallthruness which is used to mark returns.
> > 
> > This patch runs in to the same stray return/gcc_unreachable as the
> > previous one and thus requires cleanup across the GCC code base
> > which seems controversical.  So I'm putting this on hold unless
> > I receive some OK for cleanup in any way, meaning this isn't
> > going to make stage3.
> 
> This isn't meant as an objection to the patch per se, just as
> data points suggesting there's room for improvement.  I do think
> at least some of those should be considered for GCC 12 if the patch
> goes in.  I see just one trivial test which seems a bit light.
> I would recommend beefing it up to exercise some the cases below.

definitely

> I tested the patch with Glibc (no warnings) and Binutils/GDB.
> The latter shows over 900 warnings (600 unique ones) in 46
> files, so it might be a useful test bed.  Lots of those, maybe
> most, are for a break after a return, suggesting that it might
> be worthwhile to treat such inoccuous case specially (e.g., only
> warn for a break after a return at level 2).

I've fixed one bug only after submission which caused quite some
false positives with -g -On, failure to skip debug stmts.

> Some other instances suggest other possible improvements.
> For example:
> 
> /src/binutils-gdb/libiberty/lrealpath.c: In function ‘lrealpath’:
> /src/binutils-gdb/libiberty/lrealpath.c:113:3: warning: statement after return
> is not reachable [-Wunreachable-code-return]
>   113 |   {
>       |   ^
> /src/binutils-gdb/libiberty/lrealpath.c:115:5: warning: statement after return
> is not reachable [-Wunreachable-code-return]
>   115 |     long path_max = pathconf ("/", _PC_PATH_MAX);
>       |     ^~~~
> /src/binutils-gdb/libiberty/lrealpath.c:115:21: warning: statement after
> return is not reachable [-Wunreachable-code-return]
>   115 |     long path_max = pathconf ("/", _PC_PATH_MAX);
>       |                     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
> 
> I think one of them is a true positive but the others all look
> like noise.  None of the locations is very useful.  It might be
> something to look into.  I would suggest to point the warning
> to the first unreachable statement (other instances point to
> it already) and add a note pointing to the statement that makes
> the former unreachable.
> 
> Another example below shows that the warning triggers more than
> once for the same statement, suggestiung it's missing some
> suppression (e.g., a call to suppress_warning()).
> 
> /src/binutils-gdb/bfd/bfdio.c:167:3: warning: statement after return is not
> reachable [-Wunreachable-code-return]
>   167 |   return close_on_exec (fopen (filename, modes));
>       |   ^~~~~~
> /src/binutils-gdb/bfd/bfdio.c:167:10: warning: statement after return is not
> reachable [-Wunreachable-code-return]
>   167 |   return close_on_exec (fopen (filename, modes));
>       |          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> 
> There are some other "unusual" cases worth a look, such as missing
> context of any kind except for like and column:
> 
> elfnn-riscv.c:3346:7: warning: statement after return is not reachable
> [-Wunreachable-code-return]
> elfnn-riscv.c:3349:7: warning: statement after return is not reachable
> [-Wunreachable-code-return]
> elfnn-riscv.c:3352:7: warning: statement after return is not reachable
> [-Wunreachable-code-return]
> elfnn-riscv.c:3355:7: warning: statement after return is not reachable
> [-Wunreachable-code-return]

Yeah, the patch was meant as explorative prototype (and a way to audit
GCC itself), I didn't pay much attention to things like above, I've
also not yet attempted to record the actual stmt causing the warning.

> I also tried a few test cases of my own that might be worth
> handling at some point (not necessarily in the first iteration).
> 
> struct __jmp_buf_tag { };
> typedef struct __jmp_buf_tag jmp_buf[1];
> 
> void f ();
> 
> void test_return ()
> {
>   return;
>   f ();   // warning here (good)
> }
> 
> extern __attribute__ ((noreturn)) void fnoret ();
> 
> void test_noreturn ()
> {
>   fnoret ();
>   f ();   // missing warning
> }
> 
> void test_throw ()
> {
>   throw "";
>   f ();   // missing warning
> }
> 
> jmp_buf jmpbuf;
> 
> void test_longjmp ()
> {
>   __builtin_longjmp (jmpbuf, 1);
>   f ();   // missing warning
> }

All of the last missing cases above would be -Wunreachable-code-ctrl,
they are not after a return ;)

> Please see a few more comments on the code changes inline.
> 
> > 
> > Sorry.
> > 
> > Richard.
> > 
> > 2021-11-26  Richard Biener  <rguent...@suse.de>
> > 
> >     PR c++/46476
> > gcc/cp/
> >  * decl.c (finish_function): Set input_location to
> >  BUILTINS_LOCATION around the code building the return 0
> >  for main().
> >  * cp-gimplify.c (genericize_if_stmt): Avoid optimizing if (true)
> >  and if (false) when -Wunreachable-code-return is in effect.
> > 
> > gcc/
> >  * common.opt (Wunreachable-code): Re-enable.
> >  (Wunreachable-code-return): New diagnostic, enabled by
> >  -Wextra and -Wunreachable-code.
> >  * doc/invoke.texi (Wunreachable-code): Document.
> >  (Wunreachable-code-return): Likewise.
> >  * gimple-low.c: Include diagnostic.h.
> >  (struct cft_reason): New.
> >  (lower_data::cannot_fallthru): Make a cft_reason.
> >  (lower_stmt): Diagnose unreachable stmts after a return.
> >  * Makefile.in (insn-emit.o-warn): Disable
> >  -Wunreachable-code-return.
> > 
> > gcc/testsuite/
> >     * c-c++-common/Wunreachable-code-return-1.c: New testcase.
> > ---
> >   gcc/Makefile.in                               |  1 +
> >   gcc/common.opt                                |  8 +++--
> >   gcc/cp/cp-gimplify.c                          |  6 ++--
> >   gcc/cp/decl.c                                 |  9 +++++-
> >   gcc/doc/invoke.texi                           | 13 ++++++++
> >   gcc/gimple-low.c                              | 30 ++++++++++++++++---
> >   .../c-c++-common/Wunreachable-code-return-1.c |  9 ++++++
> >   7 files changed, 67 insertions(+), 9 deletions(-)
> >   create mode 100644 gcc/testsuite/c-c++-common/Wunreachable-code-return-1.c
> > 
> > diff --git a/gcc/Makefile.in b/gcc/Makefile.in
> > index a4344d67f44..71d326ff98c 100644
> > --- a/gcc/Makefile.in
> > +++ b/gcc/Makefile.in
> > @@ -222,6 +222,7 @@ libgcov-merge-tool.o-warn = -Wno-error
> >   gimple-match.o-warn = -Wno-unused
> >   generic-match.o-warn = -Wno-unused
> >   dfp.o-warn = -Wno-strict-aliasing
> > +insn-emit.o-warn = -Wno-unreachable-code-return
> >   
> >   # All warnings have to be shut off in stage1 if the compiler used then
> >   # isn't gcc; configure determines that.  WARN_CFLAGS will be either
> > diff --git a/gcc/common.opt b/gcc/common.opt
> > index 755e1a233b7..486ea36c8e5 100644
> > --- a/gcc/common.opt
> > +++ b/gcc/common.opt
> > @@ -806,8 +806,12 @@ Common Var(warn_maybe_uninitialized) Warning
> > EnabledBy(Wuninitialized)
> >   Warn about maybe uninitialized automatic variables.
> >   
> >   Wunreachable-code
> > -Common Ignore Warning
> > -Does nothing. Preserved for backward compatibility.
> > +Common Var(warn_unreachable_code) Warning
> > +Catch all for -Wunreachable-code-*.
> > +
> > +Wunreachable-code-return
> 
> -Wunreachable-code-return sounds a bit awkward to me.  I would
> suggest -Wunreachable-return instead (especially if you think
> we might add other similar variations).

I've originally mimiced the clang warning name here but I later
realized clang uses it to diagnose unreachable returns rather than
unrechable code _after_ a return ... still -Wunreachable-return
very much sounds like a return stmt is unreachable.

I think it makes sense to differentiate unreachable code following
a stmt that unconditionally transfers control (return, break, continue,
goto) from unreachable code following a stmt that conditionally
transfers control (if, switch, etc.) [what about if (0)?] and
from unreachable code following a call that does not return
normally (abort (), throw, etc.).

Suggestions for a better option name appreciated.  Maybe we can
simply lump it all under -Wunreachable-code at first and split
it later.

> > +Common Var(warn_unreachable_code_return) Warning
> > EnabledBy(Wunreachable-code || Wextra)
> > +Warn about unreachable statements after a return.
> >   
> >   Wunused
> >   Common Var(warn_unused) Init(0) Warning
> > diff --git a/gcc/cp/cp-gimplify.c b/gcc/cp/cp-gimplify.c
> > index c1691c3e073..55e03abba43 100644
> > --- a/gcc/cp/cp-gimplify.c
> > +++ b/gcc/cp/cp-gimplify.c
> > @@ -167,9 +167,11 @@ genericize_if_stmt (tree *stmt_p)
> >        the then_ block regardless of whether else_ has side-effects or not.
> >     */
> >     if (IF_STMT_CONSTEVAL_P (stmt))
> >       stmt = else_;
> > -  else if (integer_nonzerop (cond) && !TREE_SIDE_EFFECTS (else_))
> > +  else if (!warn_unreachable_code_return
> > +      && integer_nonzerop (cond) && !TREE_SIDE_EFFECTS (else_))
> >       stmt = then_;
> > -  else if (integer_zerop (cond) && !TREE_SIDE_EFFECTS (then_))
> > +  else if (!warn_unreachable_code_return
> > +      && integer_zerop (cond) && !TREE_SIDE_EFFECTS (then_))
> >       stmt = else_;
> >     else
> >       stmt = build3 (COND_EXPR, void_type_node, cond, then_, else_);
> > diff --git a/gcc/cp/decl.c b/gcc/cp/decl.c
> > index 56f80775ca0..cb81a794e5b 100644
> > --- a/gcc/cp/decl.c
> > +++ b/gcc/cp/decl.c
> > @@ -17573,7 +17573,14 @@ finish_function (bool inline_p)
> >       {
> >         /* Make it so that `main' always returns 0 by default.  */
> >         if (DECL_MAIN_P (current_function_decl))
> > -   finish_return_stmt (integer_zero_node);
> > +   {
> > +     /* Hack.  We don't want the middle-end to warn that this return
> > +        is unreachable, so we mark its location as special.  */
> > +     auto saved_il = input_location;
> > +     input_location = BUILTINS_LOCATION;
> 
> That does seem like a hack.  Won't it compromise other warnings?
> Would calling suppress_warning (retstmt, OPT_Wunreachable_return)
> on the return statement work?  (Similar to what finish_return_stmt
> already does for OPT_Wreturn_type.)

The C frontend does the very same thing.  No, suppress_warning
doesn't seem to work here because gimplification wrecks it,
at least that's what I remember from the original attemt to diagnose
things at CFG construction time, I didn't re-try with this
approach.

> > +     finish_return_stmt (integer_zero_node);
> > +     input_location = saved_il;
> > +   }
> >   
> >          if (use_eh_spec_block (current_function_decl))
> >     finish_eh_spec_block (TYPE_RAISES_EXCEPTIONS
> > diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
> > index 3bddfbaae6a..7d7f2e7fddc 100644
> > --- a/gcc/doc/invoke.texi
> > +++ b/gcc/doc/invoke.texi
> > @@ -400,6 +400,7 @@ Objective-C and Objective-C++ Dialects}.
> >   -Wsystem-headers  -Wtautological-compare  -Wtrampolines  -Wtrigraphs @gol
> >   -Wtsan -Wtype-limits  -Wundef @gol
> >   -Wuninitialized  -Wunknown-pragmas @gol
> > +-Wunreachable-code -Wunreachable-code-return @gol
> >   -Wunsuffixed-float-constants  -Wunused @gol
> >   -Wunused-but-set-parameter  -Wunused-but-set-variable @gol
> >   -Wunused-const-variable  -Wunused-const-variable=@var{n} @gol
> > @@ -5721,6 +5722,7 @@ name is still supported, but the newer name is more
> > descriptive.)
> >   -Wredundant-move @r{(only for C++)}  @gol
> >   -Wtype-limits  @gol
> >   -Wuninitialized  @gol
> > +-Wunreachable-code-return @gol
> >   -Wshift-negative-value @r{(in C++03 and in C99 and newer)}  @gol
> >   -Wunused-parameter @r{(only with} @option{-Wunused} @r{or}
> >   @option{-Wall}@r{)} @gol
> >   -Wunused-but-set-parameter @r{(only with} @option{-Wunused} @r{or}
> >   @option{-Wall}@r{)}}
> > @@ -6865,6 +6867,17 @@ This warning is enabled by default for C and C++
> > programs.
> >   Warn when @code{__sync_fetch_and_nand} and @code{__sync_nand_and_fetch}
> >   built-in functions are used.  These functions changed semantics in GCC
> >   4.4.
> >   
> > +@item -Wunreachable-code
> > +@opindex Wunreachable-code
> > +@opindex Wno-unreachable-code
> > +Warn about unreachable code.  Enables @option{-Wunreachable-code-return}.
> > +
> > +@item -Wunreachable-code-return
> > +@opindex Wunreachable-code-return
> > +@opindex Wno-unreachable-code-return
> > +Warn about unreachable code after a return stmt.
> 
> The word is "statement" and the @code{return} keyword should be
> quoted.  I would also suggest to consider clarifying the text by
> saying it warns for the first statement that's rendered unreachable
> by a prior return statement (i.e., make clear it doesn't warn for
> all unreachable code, or any arbitrary piece of it).

OK, will do.

> > Enabled by
> > +@option{-Wunreachable-code} and @option{-Wextra}.
> > +
> >   @item -Wunused-but-set-parameter
> >   @opindex Wunused-but-set-parameter
> >   @opindex Wno-unused-but-set-parameter
> > diff --git a/gcc/gimple-low.c b/gcc/gimple-low.c
> > index 7e39c22df44..f0eb82f72f6 100644
> > --- a/gcc/gimple-low.c
> > +++ b/gcc/gimple-low.c
> > @@ -33,6 +33,7 @@ along with GCC; see the file COPYING3.  If not see
> >   #include "predict.h"
> >   #include "gimple-predict.h"
> >   #include "gimple-fold.h"
> > +#include "diagnostic.h"
> >   
> >   /* The differences between High GIMPLE and Low GIMPLE are the
> >      following:
> > @@ -56,6 +57,22 @@ struct return_statements_t
> >   };
> >   typedef struct return_statements_t return_statements_t;
> >   
> > +/* Helper tracking the reason a previous stmt cannot fallthru.  */
> > +struct cft_reason
> > +{
> > +  enum reason { CAN_FALLTHRU = false, UNKNOWN = true, RETURN };
> > +  cft_reason () : m_reason (CAN_FALLTHRU) {}
> > +  cft_reason (bool b) : m_reason (b ? UNKNOWN : CAN_FALLTHRU) {}
> > +  cft_reason (reason r) : m_reason (r) {}
> > +  operator bool () const { return m_reason != CAN_FALLTHRU; }
> > +  cft_reason& operator|= (const cft_reason& other)
> > +    {
> > +      if (other.m_reason != m_reason && (bool)other)
> > +   m_reason = UNKNOWN;
> > +      return *this;
> > +    }
> > +  reason m_reason;
> > +};
> >   
> >   struct lower_data
> >   {
> > @@ -67,7 +84,7 @@ struct lower_data
> >     vec<return_statements_t> return_statements;
> >   
> >     /* True if the current statement cannot fall through.  */
> > -  bool cannot_fallthru;
> > +  cft_reason cannot_fallthru;
> >   };
> >   
> >   static void lower_stmt (gimple_stmt_iterator *, struct lower_data *);
> > @@ -84,7 +101,7 @@ static void lower_builtin_posix_memalign
> > (gimple_stmt_iterator *);
> >   static unsigned int
> >   lower_function_body (void)
> >   {
> > -  struct lower_data data;
> > +  struct lower_data data = {};
> >     gimple_seq body = gimple_body (current_function_decl);
> >     gimple_seq lowered_body;
> >     gimple_stmt_iterator i;
> > @@ -96,7 +113,6 @@ lower_function_body (void)
> >     gcc_assert (gimple_seq_first (body) == gimple_seq_last (body)
> >          && gimple_code (gimple_seq_first_stmt (body)) == GIMPLE_BIND);
> >   -  memset (&data, 0, sizeof (data));
> >     data.block = DECL_INITIAL (current_function_decl);
> >     BLOCK_SUBBLOCKS (data.block) = NULL_TREE;
> >     BLOCK_CHAIN (data.block) = NULL_TREE;
> > @@ -249,6 +265,12 @@ lower_stmt (gimple_stmt_iterator *gsi, struct
> > lower_data *data)
> >   
> >     gimple_set_block (stmt, data->block);
> >   +  if (data->cannot_fallthru.m_reason == cft_reason::RETURN
> > +      && gimple_code (stmt) != GIMPLE_LABEL
> > +      && LOCATION_LOCUS (gimple_location (stmt)) > BUILTINS_LOCATION)
> > +    warning_at (gimple_location (stmt), OPT_Wunreachable_code_return,
> > +           "statement after return is not reachable");
> 
> As a keyword the word return should be quoted.

Fixed.

Thanks,
Richard.

Reply via email to