[PATCH] implement -Winfinite-recursion [PR88232]

Marek Polacek polacek@redhat.com
Wed Nov 10 21:27:41 GMT 2021


On Tue, Nov 09, 2021 at 09:28:43PM -0700, Martin Sebor via Gcc-patches wrote:
> The attached patch adds support to the middle end for detecting
> infinitely recursive calls.  The warning is controlled by the new
> -Winfinite-recursion option.  The option name is the same as
> Clang's.
> 
> I scheduled the warning pass to run after early inlining to
> detect mutually recursive calls but before tail recursion which
> turns some recursive calls into infinite loops and so makes
> the two indistinguishable.
> 
> The warning detects a superset of problems detected by Clang
> (based on its tests).  It detects the problem in PR88232
> (the feature request) as well as the one in PR 87742,
> an unrelated problem report that was root-caused to bug due
> to infinite recursion.

Nice, I've long wanted this warning.  I've made this mistake a couple of
times:

struct S {
  operator int() { return S{}; }
};

and the patch warns about it.
 
> --- a/gcc/common.opt
> +++ b/gcc/common.opt
> @@ -629,6 +629,10 @@ Wimplicit-fallthrough=
>  Common Var(warn_implicit_fallthrough) RejectNegative Joined UInteger Warning IntegerRange(0, 5)
>  Warn when a switch case falls through.
>  
> +Winfinite-recursion
> +Var(warn_infinite_recursion) Warning
> +Warn for infinitely recursive calls.

Why do you need this hunk?

> new file mode 100644
> index 00000000000..324e63b2651
> --- /dev/null
> +++ b/gcc/gimple-warn-recursion.c
> @@ -0,0 +1,146 @@
> +/* -Winfinite-recursion support.
> +   Copyright (C) 2021 Free Software Foundation, Inc.
> +   Contributed by Martin Sebor <msebor@redhat.com>
> +
> +   This file is part of GCC.
> +
> +   GCC 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, or (at your option)
> +   any later version.
> +
> +   GCC 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 GCC; see the file COPYING3.  If not see
> +   <http://www.gnu.org/licenses/>.  */
> +
> +#include "config.h"
> +#include "system.h"
> +#include "coretypes.h"
> +#include "backend.h"
> +#include "tree.h"
> +#include "gimple.h"
> +#include "tree-pass.h"
> +#include "ssa.h"
> +#include "diagnostic-core.h"
> +#include "tree-dfa.h"
> +#include "gimple-iterator.h"
> +
> +namespace {
> +
> +const pass_data warn_recursion_data =
> +{
> +  GIMPLE_PASS, /* type */
> +  "*infinite-recursion", /* name */
> +  OPTGROUP_NONE, /* optinfo_flags */
> +  TV_NONE, /* tv_id */
> +  PROP_ssa, /* properties_required */
> +  0, /* properties_provided */
> +  0, /* properties_destroyed */
> +  0, /* todo_flags_start */
> +  0, /* todo_flags_finish */
> +};
> +
> +class pass_warn_recursion : public gimple_opt_pass
> +{
> +public:
> +  pass_warn_recursion (gcc::context *ctxt)
> +    : gimple_opt_pass (warn_recursion_data, ctxt)
> +  {}
> +
> +  virtual bool gate (function *) { return warn_infinite_recursion; }
> +
> +  virtual unsigned int execute (function *);
> +
> +};
> +
> +/* Return true if there is path from BB to EXIT_BB along which there is
> +   no (recursive) call to FNDECL.  Use VISITED to avoid searching already
> +   visited basic blocks.  */
> +
> +static bool
> +find_function_exit (tree fndecl, basic_block bb, int flags,
> +		    basic_block exit_bb,
> +		    auto_vec<gimple *> &calls, auto_bitmap &visited)
> +{
> +  if (!bitmap_set_bit (visited, bb->index))
> +    return false;
> +
> +  if (bb == exit_bb)
> +    return true;
> +
> +  /* Iterate over statements in BB, looking for a call to FNDECL.  */
> +  for (auto si = gsi_start_bb (bb); !gsi_end_p (si); gsi_next_nondebug (&si))
> +    {
> +      gimple *stmt = gsi_stmt (si);
> +      if (!is_gimple_call (stmt))
> +	continue;
> +
> +      tree callee = gimple_call_fndecl (stmt);
> +      if (!fndecl || fndecl != callee)
> +	continue;
> +
> +      /* Add the recursive call to the vector and return false.  */
> +      calls.safe_push (stmt);
> +      return false;
> +    }
> +
> +  /* If no call to FNDECL has been found search all BB's successors.  */
> +  edge e;
> +  edge_iterator ei;
> +  FOR_EACH_EDGE (e, ei, bb->succs)
> +    {
> +      int eflags = e->flags;
> +      if (find_function_exit (fndecl, e->dest, eflags, exit_bb, calls, visited))

Why not use e->flags directly?  find_function_exit can't change it AFAICS.

> +	return true;
> +
> +      flags = 0;
> +    }
> +
> +  return (flags & EDGE_EH) != 0;
> +}
> +
> +
> +/* Search FUNC for unconditionally infinitely recursive calls to self
> +   and issue a warning if it is such a function.  */
> +
> +unsigned int
> +pass_warn_recursion::execute (function *func)
> +{
> +  auto_bitmap visited;
> +  auto_vec<gimple *> calls;
> +  basic_block exit_bb = EXIT_BLOCK_PTR_FOR_FN (func);
> +  basic_block entry_bb = ENTRY_BLOCK_PTR_FOR_FN (func);
> +
> +  if (find_function_exit (func->decl, entry_bb, 0, exit_bb, calls, visited)
> +      || calls.length () == 0)
> +    return 0;
> +
> +  location_t loc = DECL_SOURCE_LOCATION (func->decl);
> +  if (!warning_at (loc, OPT_Winfinite_recursion,
> +		   "infinite recursion detected"))
> +    return 0;
> +
> +  for (auto stmt: calls)
> +    {
> +      location_t loc = gimple_location (stmt);
> +      if (loc == UNKNOWN_LOCATION)
> +	continue;
> +
> +      inform (loc, "recursive call");
> +    }

I find the shadowing of 'loc' unsightly.  While here, I think 

  if (warning_at (DECL_SOURCE_LOCATION (func->decl), OPT_Winfinite_recursion,
		  "infinite recursion detected"))
    for (auto stmt: calls)
      {
	...
      }

would look neater (and avoids the shadowing), but that's just my opinion.

Marek



More information about the Gcc-patches mailing list