[v2] implement -Winfinite-recursion [PR88232]

Message ID 426513f2-c6af-9917-3da4-fcc5433d9dd9@gmail.com
State Committed
Headers
Series [v2] implement -Winfinite-recursion [PR88232] |

Commit Message

Martin Sebor Nov. 11, 2021, 9:46 p.m. UTC
  Attached is a v2 of the solution I posted earlier this week
with a few tweaks made after a more careful consideration of
the problem and possible false negatives and positives.

1) It avoids warning for [apparently infinitely recursive] calls
    in noreturn functions where the recursion may be prevented by
    a call to a noreturn function.
2) It avoids warning for calls where the recursion may be prevented
    by a call to a longjmp or siglongjmp.
3) It warns for recursive calls to built-ins in definitions of
    the corresponding library functions (e.g., for a call to
    __builtin_malloc in malloc).
4) It warns for calls to C++ functions even if they call other
    functions that might throw and so break out of the infinite
    recursion.  (E.g., operator new.)  This is the same as Clang.
5) It doesn't warn for calls to C++ functions with the throw
    expression.

Besides these changes to the warning itself, I've also improved
the code a bit by making the workhorse function a member of
the pass so recursive calls don't need to pass as many arguments
to itself.

Retested on x86_64-linux and by building Glibc and Binutils/GDB.

A possible enhancement is to warn for calls to calloc, malloc,
or realloc from within the definition of one of the other two
functions.  That might be a mistake made in code that tries
naively to replace the allocator with its own implementation.

On 11/9/21 9:28 PM, Martin Sebor 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.
> 
> This initial version doesn't attempt to deal with superimposed
> symbols, so those might trigger false positives.  I'm not sure
> that's something to worry about.
> 
> The tests are very light, but those for the exceptional cases
> are exceedingly superficial, so it's possible those might harbor
> some false positives and negatives.
> 
> Tested on x86_64-linux.
> 
> Martin
>
  

Comments

Martin Sebor Nov. 19, 2021, 3:11 p.m. UTC | #1
Ping:
https://gcc.gnu.org/pipermail/gcc-patches/2021-November/584205.html

On 11/11/21 2:46 PM, Martin Sebor wrote:
> Attached is a v2 of the solution I posted earlier this week
> with a few tweaks made after a more careful consideration of
> the problem and possible false negatives and positives.
> 
> 1) It avoids warning for [apparently infinitely recursive] calls
>     in noreturn functions where the recursion may be prevented by
>     a call to a noreturn function.
> 2) It avoids warning for calls where the recursion may be prevented
>     by a call to a longjmp or siglongjmp.
> 3) It warns for recursive calls to built-ins in definitions of
>     the corresponding library functions (e.g., for a call to
>     __builtin_malloc in malloc).
> 4) It warns for calls to C++ functions even if they call other
>     functions that might throw and so break out of the infinite
>     recursion.  (E.g., operator new.)  This is the same as Clang.
> 5) It doesn't warn for calls to C++ functions with the throw
>     expression.
> 
> Besides these changes to the warning itself, I've also improved
> the code a bit by making the workhorse function a member of
> the pass so recursive calls don't need to pass as many arguments
> to itself.
> 
> Retested on x86_64-linux and by building Glibc and Binutils/GDB.
> 
> A possible enhancement is to warn for calls to calloc, malloc,
> or realloc from within the definition of one of the other two
> functions.  That might be a mistake made in code that tries
> naively to replace the allocator with its own implementation.
> 
> On 11/9/21 9:28 PM, Martin Sebor 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.
>>
>> This initial version doesn't attempt to deal with superimposed
>> symbols, so those might trigger false positives.  I'm not sure
>> that's something to worry about.
>>
>> The tests are very light, but those for the exceptional cases
>> are exceedingly superficial, so it's possible those might harbor
>> some false positives and negatives.
>>
>> Tested on x86_64-linux.
>>
>> Martin
>>
>
  
Jeff Law Nov. 23, 2021, 7:11 p.m. UTC | #2
On 11/11/2021 2:46 PM, Martin Sebor via Gcc-patches wrote:
> Attached is a v2 of the solution I posted earlier this week
> with a few tweaks made after a more careful consideration of
> the problem and possible false negatives and positives.
>
> 1) It avoids warning for [apparently infinitely recursive] calls
>    in noreturn functions where the recursion may be prevented by
>    a call to a noreturn function.
> 2) It avoids warning for calls where the recursion may be prevented
>    by a call to a longjmp or siglongjmp.
> 3) It warns for recursive calls to built-ins in definitions of
>    the corresponding library functions (e.g., for a call to
>    __builtin_malloc in malloc).
> 4) It warns for calls to C++ functions even if they call other
>    functions that might throw and so break out of the infinite
>    recursion.  (E.g., operator new.)  This is the same as Clang.
> 5) It doesn't warn for calls to C++ functions with the throw
>    expression.
>
> Besides these changes to the warning itself, I've also improved
> the code a bit by making the workhorse function a member of
> the pass so recursive calls don't need to pass as many arguments
> to itself.
>
> Retested on x86_64-linux and by building Glibc and Binutils/GDB.
>
> A possible enhancement is to warn for calls to calloc, malloc,
> or realloc from within the definition of one of the other two
> functions.  That might be a mistake made in code that tries
> naively to replace the allocator with its own implementation.
>
> On 11/9/21 9:28 PM, Martin Sebor 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.
>>
>> This initial version doesn't attempt to deal with superimposed
>> symbols, so those might trigger false positives.  I'm not sure
>> that's something to worry about.
>>
>> The tests are very light, but those for the exceptional cases
>> are exceedingly superficial, so it's possible those might harbor
>> some false positives and negatives.
>>
>> Tested on x86_64-linux.
>>
>> Martin
>>
>
>
> gcc-88232.diff
>
> Implement -Winfinite-recursion [PR88232].
>
> Resolves:
> PR middle-end/88232 - Please implement -Winfinite-recursion
>
> gcc/ChangeLog:
>
> 	PR middle-end/88232
> 	* Makefile.in (OBJS): Add gimple-warn-recursion.o.
> 	* common.opt: Add -Winfinite-recursion.
> 	* doc/invoke.texi (-Winfinite-recursion): Document.
> 	* passes.def (pass_warn_recursion): Schedule a new pass.
> 	* tree-pass.h (make_pass_warn_recursion): Declare.
> 	* gimple-warn-recursion.c: New file.
>
> gcc/c-family/ChangeLog:
>
> 	PR middle-end/88232
> 	* c.opt: Add -Winfinite-recursion.
>
> gcc/testsuite/ChangeLog:
>
> 	PR middle-end/88232
> 	* c-c++-common/attr-used-5.c: Suppress valid warning.
> 	* c-c++-common/attr-used-6.c: Same.
> 	* c-c++-common/attr-used-9.c: Same.
> 	* g++.dg/warn/Winfinite-recursion-2.C: New test.
> 	* g++.dg/warn/Winfinite-recursion-3.C: New test.
> 	* g++.dg/warn/Winfinite-recursion.C: New test.
> 	* gcc.dg/Winfinite-recursion-2.c: New test.
> 	* gcc.dg/Winfinite-recursion.c: New test.
This is OK.  While there may be other improvements that could be made, 
this looks like a reasonable warning as-is and can be extended/refined 
as needed.

jeff
  

Patch

Implement -Winfinite-recursion [PR88232].

Resolves:
PR middle-end/88232 - Please implement -Winfinite-recursion

gcc/ChangeLog:

	PR middle-end/88232
	* Makefile.in (OBJS): Add gimple-warn-recursion.o.
	* common.opt: Add -Winfinite-recursion.
	* doc/invoke.texi (-Winfinite-recursion): Document.
	* passes.def (pass_warn_recursion): Schedule a new pass.
	* tree-pass.h (make_pass_warn_recursion): Declare.
	* gimple-warn-recursion.c: New file.

gcc/c-family/ChangeLog:

	PR middle-end/88232
	* c.opt: Add -Winfinite-recursion.

gcc/testsuite/ChangeLog:

	PR middle-end/88232
	* c-c++-common/attr-used-5.c: Suppress valid warning.
	* c-c++-common/attr-used-6.c: Same.
	* c-c++-common/attr-used-9.c: Same.
	* g++.dg/warn/Winfinite-recursion-2.C: New test.
	* g++.dg/warn/Winfinite-recursion-3.C: New test.
	* g++.dg/warn/Winfinite-recursion.C: New test.
	* gcc.dg/Winfinite-recursion-2.c: New test.
	* gcc.dg/Winfinite-recursion.c: New test.

diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index 571e9c28e29..a4344d67f44 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -1420,6 +1420,7 @@  OBJS = \
 	gimple-streamer-in.o \
 	gimple-streamer-out.o \
 	gimple-walk.o \
+	gimple-warn-recursion.o \
 	gimplify.o \
 	gimplify-me.o \
 	godump.o \
diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
index 06457ac739e..7fb13f278e8 100644
--- a/gcc/c-family/c.opt
+++ b/gcc/c-family/c.opt
@@ -714,6 +714,10 @@  Wincompatible-pointer-types
 C ObjC Var(warn_incompatible_pointer_types) Init(1) Warning
 Warn when there is a conversion between pointers that have incompatible types.
 
+Winfinite-recursion
+C ObjC C++ LTO ObjC++ Var(warn_infinite_recursion) Warning LangEnabledBy(C ObjC C++ LTO ObjC++, Wall)
+Warn for infinitely recursive calls.
+
 Waddress-of-packed-member
 C ObjC C++ ObjC++ Var(warn_address_of_packed_member) Init(1) Warning
 Warn when the address of packed member of struct or union is taken.
diff --git a/gcc/common.opt b/gcc/common.opt
index de9b848eda5..a98545641fa 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -636,6 +636,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.
+
 Winline
 Common Var(warn_inline) Warning Optimization
 Warn when an inlined function cannot be inlined.
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index a7c4d24a762..bd4e2a78695 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -357,6 +357,7 @@  Objective-C and Objective-C++ Dialects}.
 -Wignored-qualifiers  -Wno-incompatible-pointer-types @gol
 -Wimplicit  -Wimplicit-fallthrough  -Wimplicit-fallthrough=@var{n} @gol
 -Wno-implicit-function-declaration  -Wno-implicit-int @gol
+-Winfinite-recursion @gol
 -Winit-self  -Winline  -Wno-int-conversion  -Wint-in-bool-context @gol
 -Wno-int-to-pointer-cast  -Wno-invalid-memory-model @gol
 -Winvalid-pch  -Wjump-misses-init  -Wlarger-than=@var{byte-size} @gol
@@ -6179,6 +6180,14 @@  is only active when @option{-fdelete-null-pointer-checks} is active,
 which is enabled by optimizations in most targets.  The precision of
 the warnings depends on the optimization options used.
 
+@item -Winfinite-recursion
+@opindex Winfinite-recursion
+@opindex Wno-infinite-recursion
+Warn about infinitely recursive calls.  The warning is effective at all
+optimization levels but requires optimization in order to detect infinite
+recursion in calls between two or more functions.
+@option{-Winfinite-recursion} is included in @option{-Wall}.
+
 @item -Winit-self @r{(C, C++, Objective-C and Objective-C++ only)}
 @opindex Winit-self
 @opindex Wno-init-self
diff --git a/gcc/gimple-warn-recursion.c b/gcc/gimple-warn-recursion.c
new file mode 100644
index 00000000000..4dc61b08c50
--- /dev/null
+++ b/gcc/gimple-warn-recursion.c
@@ -0,0 +1,202 @@ 
+/* -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 "attribs.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 *);
+
+private:
+  virtual bool gate (function *) { return warn_infinite_recursion; }
+
+  virtual unsigned int execute (function *);
+
+  bool find_function_exit (basic_block);
+
+  /* Recursive calls found in M_FUNC.  */
+  vec<gimple *> *m_calls;
+  /* Basic blocks already visited in the current function.  */
+  bitmap m_visited;
+  /* The current function.  */
+  function *m_func;
+  /* The current function code if it's (also) a built-in.  */
+  built_in_function m_built_in;
+  /* True if M_FUNC is a noreturn function.  */
+  bool noreturn_p;
+};
+
+/* Initialize the pass and its members.  */
+
+pass_warn_recursion::pass_warn_recursion (gcc::context *ctxt)
+  : gimple_opt_pass (warn_recursion_data, ctxt),
+    m_calls (), m_visited (), m_func (), m_built_in (), noreturn_p ()
+{
+}
+
+/* Return true if there is path from BB to M_FUNC exit point along which
+   there is no (recursive) call to M_FUNC.  */
+
+bool
+pass_warn_recursion::find_function_exit (basic_block bb)
+{
+  if (!bitmap_set_bit (m_visited, bb->index))
+    return false;
+
+  if (bb == EXIT_BLOCK_PTR_FOR_FN (m_func))
+    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;
+
+      if (gimple_call_builtin_p (stmt, BUILT_IN_LONGJMP))
+	/* A longjmp breaks infinite recursion.  */
+	return true;
+
+      if (tree fndecl = gimple_call_fndecl (stmt))
+	{
+	  /* A throw statement breaks infinite recursion.  */
+	  tree id = DECL_NAME (fndecl);
+	  const char *name = IDENTIFIER_POINTER (id);
+	  if (startswith (name, "__cxa_throw"))
+	    return true;
+	  /* As does a call to POSIX siglongjmp.  */
+	  if (!strcmp (name, "siglongjmp"))
+	    return true;
+
+	  if (m_built_in && gimple_call_builtin_p (stmt, BUILT_IN_NORMAL)
+	      && m_built_in == DECL_FUNCTION_CODE (fndecl))
+	    {
+	      /* The call is being made from the definition of a built-in
+		 (e.g., in a replacement of one) to itself.  */
+	      m_calls->safe_push (stmt);
+	      return false;
+	    }
+	}
+
+      if (noreturn_p)
+	{
+	  /* A noreturn call breaks infinite recursion.  */
+	  int flags = gimple_call_flags (stmt);
+	  if (flags & ECF_NORETURN)
+	    return true;
+	}
+
+      tree callee = gimple_call_fndecl (stmt);
+      if (!callee || m_func->decl != callee)
+	continue;
+
+      /* Add the recursive call to the vector and return false.  */
+      m_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)
+    if (find_function_exit (e->dest))
+      return true;
+
+  return false;
+}
+
+
+/* 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;
+
+  m_visited = visited;
+  m_calls = &calls;
+  m_func = func;
+
+  /* Avoid diagnosing an apparently infinitely recursive function that
+     doesn't return where the infinite recursion might be avoided by
+     a call to another noreturn function.  */
+  noreturn_p = lookup_attribute ("noreturn", DECL_ATTRIBUTES (m_func->decl));
+
+  if (fndecl_built_in_p (m_func->decl, BUILT_IN_NORMAL))
+    m_built_in = DECL_FUNCTION_CODE (m_func->decl);
+  else
+    m_built_in = BUILT_IN_NONE;
+
+  basic_block entry_bb = ENTRY_BLOCK_PTR_FOR_FN (func);
+
+  if (find_function_exit (entry_bb) || m_calls->length () == 0)
+    return 0;
+
+  if (warning_at (DECL_SOURCE_LOCATION (func->decl),
+		  OPT_Winfinite_recursion,
+		  "infinite recursion detected"))
+    for (auto stmt: *m_calls)
+      {
+	location_t loc = gimple_location (stmt);
+	if (loc == UNKNOWN_LOCATION)
+	  continue;
+
+	inform (loc, "recursive call");
+      }
+
+  return 0;
+}
+
+} // namespace
+
+gimple_opt_pass *
+make_pass_warn_recursion (gcc::context *ctxt)
+{
+  return new pass_warn_recursion (ctxt);
+}
diff --git a/gcc/passes.def b/gcc/passes.def
index 56dab80a029..918f377cfcf 100644
--- a/gcc/passes.def
+++ b/gcc/passes.def
@@ -71,6 +71,7 @@  along with GCC; see the file COPYING3.  If not see
       NEXT_PASS (pass_rebuild_cgraph_edges);
       NEXT_PASS (pass_local_fn_summary);
       NEXT_PASS (pass_early_inline);
+      NEXT_PASS (pass_warn_recursion);
       NEXT_PASS (pass_all_early_optimizations);
       PUSH_INSERT_PASSES_WITHIN (pass_all_early_optimizations)
 	  NEXT_PASS (pass_remove_cgraph_callee_edges);
diff --git a/gcc/testsuite/c-c++-common/attr-used-5.c b/gcc/testsuite/c-c++-common/attr-used-5.c
index 448e19f6f0e..7ba5a455706 100644
--- a/gcc/testsuite/c-c++-common/attr-used-5.c
+++ b/gcc/testsuite/c-c++-common/attr-used-5.c
@@ -1,6 +1,6 @@ 
 /* { dg-do compile } */
 /* { dg-skip-if "non-ELF target" { *-*-darwin* } } */
-/* { dg-options "-Wall -O2" } */
+/* { dg-options "-Wall -Wno-infinite-recursion -O2" } */
 
 struct dtv_slotinfo_list
 {
diff --git a/gcc/testsuite/c-c++-common/attr-used-6.c b/gcc/testsuite/c-c++-common/attr-used-6.c
index b9974e2a4f2..00b128205b6 100644
--- a/gcc/testsuite/c-c++-common/attr-used-6.c
+++ b/gcc/testsuite/c-c++-common/attr-used-6.c
@@ -1,6 +1,6 @@ 
 /* { dg-do compile } */
 /* { dg-skip-if "non-ELF target" { *-*-darwin* } } */
-/* { dg-options "-Wall -O2" } */
+/* { dg-options "-Wall  -Wno-infinite-recursion -O2" } */
 
 struct dtv_slotinfo_list
 {
diff --git a/gcc/testsuite/c-c++-common/attr-used-9.c b/gcc/testsuite/c-c++-common/attr-used-9.c
index 049c0beb1ee..c4d86c19bc5 100644
--- a/gcc/testsuite/c-c++-common/attr-used-9.c
+++ b/gcc/testsuite/c-c++-common/attr-used-9.c
@@ -1,6 +1,6 @@ 
 /* { dg-do compile } */
 /* { dg-skip-if "non-ELF target" { *-*-darwin* } } */
-/* { dg-options "-Wall -O2" } */
+/* { dg-options "-Wall -Wno-infinite-recursion -O2" } */
 
 struct dtv_slotinfo_list
 {
diff --git a/gcc/testsuite/g++.dg/warn/Winfinite-recursion-2.C b/gcc/testsuite/g++.dg/warn/Winfinite-recursion-2.C
new file mode 100644
index 00000000000..b3102836664
--- /dev/null
+++ b/gcc/testsuite/g++.dg/warn/Winfinite-recursion-2.C
@@ -0,0 +1,75 @@ 
+/* PR middle-end/88232 - Please implement -Winfinite-recursion
+   Test case from PR 87742 (see PR 88232, comment 2.
+   { dg-do compile { target c++11 } }
+   { dg-options "-Wall -Winfinite-recursion" } */
+
+namespace std
+{
+class type_info {
+public:
+  void k() const;
+};
+
+} // namespace std
+
+using std::type_info;
+
+template <int a> struct f { static constexpr int c = a; };
+struct h {
+  typedef int e;
+};
+
+template <unsigned long, typename...> struct m;
+template <unsigned long ab, typename i, typename j, typename... ac>
+struct m<ab, i, j, ac...> : m<ab + 1, i, ac...> {};
+template <unsigned long ab, typename j, typename... ac>
+struct m<ab, j, j, ac...> : f<ab> {};
+template <unsigned long, typename...> struct n;
+template <unsigned long ab, typename j, typename... ac>
+struct n<ab, j, ac...> : n<ab - 1, ac...> {};
+template <typename j, typename... ac> struct n<0, j, ac...> : h {};
+template <typename... l> class F {
+  template <typename i> struct I : m<0, i, l...> {};
+  template <int ab> struct s : n<ab, l...> {};
+  static const type_info *const b[];
+  struct G {
+    template <typename ag>
+    operator ag() const       // { dg-warning "-Winfinite-recursion" }
+    {
+      return *this;
+    }
+  };
+  unsigned o;
+  G ah;
+
+public:
+  F();
+  long t() const { return o; }
+  const type_info &m_fn3() const { return *b[o]; }
+  template <int ab> typename s<ab>::e *m_fn4() const {
+    if (o != ab)
+      return nullptr;
+    return ah;
+  }
+  template <int ab> void m_fn5() const {
+    m_fn4<ab>();
+    const type_info &r = m_fn3();
+    r.k();
+  }
+  template <typename i> void u() const { m_fn5<I<i>::c>(); }
+};
+template <typename... l> const type_info *const F<l...>::b[] {&typeid(l)...};
+using am = unsigned char;
+class H {
+  enum bd : am { be = 2 };
+  using bf = F<int, int, H>;
+  bf ah;
+  template <typename bg> void v() const { ah.u<bg>(); }
+  void w() const;
+};
+void H::w() const {
+  bd d = bd(ah.t());
+  switch (d)
+  case be:
+    v<H>();
+}
diff --git a/gcc/testsuite/g++.dg/warn/Winfinite-recursion-3.C b/gcc/testsuite/g++.dg/warn/Winfinite-recursion-3.C
new file mode 100644
index 00000000000..f58b44defa0
--- /dev/null
+++ b/gcc/testsuite/g++.dg/warn/Winfinite-recursion-3.C
@@ -0,0 +1,75 @@ 
+/* PR middle-end/88232 - Please implement -Winfinite-recursion
+   { dg-do compile }
+   { dg-options "-Wall -Winfinite-recursion" } */
+
+/* Might throw.  */
+void f ();
+
+/* Verify a warning is issued even though a call to f() might throw,
+   breaking the infinite recursion.  */
+
+void warn_f_call_r (int  n)   // { dg-warning "-Winfinite-recursion" }
+{
+  if (n > 7)
+    f ();
+  warn_f_call_r (n - 1);      // { dg-message "recursive call" }
+}
+
+void warn_f_do_while_call_r (int n)    // { dg-warning "-Winfinite-recursion" }
+{
+  f ();
+  do
+    {
+      f ();
+      warn_f_do_while_call_r (n - 1);  // { dg-message "recursive call" }
+    }
+  while (1);
+}
+
+
+struct X
+{
+  X (int);
+  ~X ();
+};
+
+/* Verify a warning even though the X ctor might throw, breaking
+   the recursion.  Using possible throwing to suppress the warning
+   would make it pretty much useless in C++.  */
+
+int warn_class_with_ctor (int n)    // { dg-warning "-Winfinite-recursion" }
+{
+  X x (n);
+  return n + warn_class_with_ctor (n - 1);
+}
+
+
+int nowarn_throw (int n)
+{
+  if (n > 7)
+    throw "argument too big";
+
+  return n + nowarn_throw (n - 1);
+}
+
+
+/* Verify call operator new doesn't suppress the warning even though
+   it might throw.  */
+
+extern int* eipa[];
+
+void warn_call_new (int i)          // { dg-warning "-Winfinite-recursion" }
+{
+  eipa[i] = new int;
+
+  warn_call_new (i - 1);
+}
+
+/* Verify a recursive call to operator new.  */
+
+void* operator new[] (size_t n)     // { dg-warning "-Winfinite-recursion" }
+{
+  char *p = new char[n + sizeof (n)];   // { dg-message "recursive call" }
+  *(size_t*)p = n;
+  return p + sizeof n;
+}
diff --git a/gcc/testsuite/g++.dg/warn/Winfinite-recursion.C b/gcc/testsuite/g++.dg/warn/Winfinite-recursion.C
new file mode 100644
index 00000000000..faf0984eeb4
--- /dev/null
+++ b/gcc/testsuite/g++.dg/warn/Winfinite-recursion.C
@@ -0,0 +1,34 @@ 
+/* PR middle-end/88232 - Please implement -Winfinite-recursion
+   { dg-do compile }
+   { dg-options "-Wall -Winfinite-recursion" } */
+
+template <typename D>
+struct C
+{
+  void foo ()                       // { dg-warning "-Winfinite-recursion" }
+  {
+    static_cast<D *>(this)->foo ();
+  }
+};
+
+struct D : C<D>
+{
+  // this is missing:
+  // void foo() {}
+};
+
+void f (D *d)
+{
+  d->foo ();
+}
+
+
+struct E : C<D>
+{
+  void foo() {}
+};
+
+void g (E *e)
+{
+  e->foo ();
+}
diff --git a/gcc/testsuite/gcc.dg/Winfinite-recursion-2.c b/gcc/testsuite/gcc.dg/Winfinite-recursion-2.c
new file mode 100644
index 00000000000..23483122a6b
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Winfinite-recursion-2.c
@@ -0,0 +1,252 @@ 
+/* PR middle-end/88232 - Please implement -Winfinite-recursion
+   Exercise warning with optimization.  Same as -Winfinite-recursion.c
+   plus mutually recursive calls that depend on inlining.
+   { dg-do compile }
+   { dg-options "-O2 -Wall -Winfinite-recursion" } */
+
+#define NORETURN __attribute__ ((noreturn))
+
+typedef __SIZE_TYPE__ size_t;
+
+extern void abort (void);
+extern void exit (int);
+
+extern int ei;
+int (*pfi_v)(void);
+
+
+/* Make sure the warning doesn't assume every call has a DECL.  */
+
+int nowarn_pfi_v (void)
+{
+  return pfi_v ();
+}
+
+
+int warn_fi_v (void)                // { dg-warning "-Winfinite-recursion" }
+{
+  return warn_fi_v ();              // { dg-message "recursive call" }
+}
+
+/* Verify #pragma suppression works.  */
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Winfinite-recursion"
+
+int suppress_warn_fi_v (void)
+{
+  return warn_fi_v ();
+}
+
+#pragma GCC diagnostic pop
+
+int nowarn_fi_v (void)
+{
+  if (ei++ == 0)
+    return nowarn_fi_v ();
+  return 0;
+}
+
+
+int warn_if_i (int i)               // { dg-warning "-Winfinite-recursion" }
+{
+  if (i > 0)
+    return warn_if_i (--i);         // { dg-message "recursive call" }
+  else if (i < 0)
+    return warn_if_i (-i);          // { dg-message "recursive call" }
+  else
+    return warn_if_i (7);           // { dg-message "recursive call" }
+}
+
+
+int nowarn_if_i (int i)
+{
+  if (i > 0)
+    return nowarn_if_i (--i);
+  else if (i < 0)
+    return nowarn_if_i (-i);
+  else
+    return -1;
+}
+
+int nowarn_switch (int i, int a[])
+{
+  switch (i)
+    {
+    case 0: return nowarn_switch (a[3], a + 1);
+    case 1: return nowarn_switch (a[5], a + 2);
+    case 2: return nowarn_switch (a[7], a + 3);
+    case 3: return nowarn_switch (a[9], a + 4);
+    }
+  return 77;
+}
+
+int warn_switch (int i, int a[])    // { dg-warning "-Winfinite-recursion" }
+{
+  switch (i)
+    {
+    case 0: return warn_switch (a[3], a + 1);
+    case 1: return warn_switch (a[5], a + 2);
+    case 2: return warn_switch (a[7], a + 3);
+    case 3: return warn_switch (a[9], a + 4);
+    default: return warn_switch (a[1], a + 5);
+    }
+}
+
+NORETURN void fnoreturn (void);
+
+/* Verify there's no warning for a function that doesn't return.  */
+int nowarn_call_noret (void)
+{
+  fnoreturn ();
+}
+
+int warn_call_noret_r (void)        // { dg-warning "-Winfinite-recursion" }
+{
+  warn_call_noret_r ();             // { dg-message "recursive call" }
+  fnoreturn ();
+}
+
+/* Verify a warning even though the abort() call would prevent the infinite
+   recursion.  There's no good way to tell the two cases apart and letting
+   a simple abort prevent the warning would make it ineffective in cases
+   where it's the result of assert() expansion and not meant to actually
+   prevent recursion.  */
+
+int
+warn_noret_call_abort_r (char *s, int n)  // { dg-warning "-Winfinite-recursion" }
+{
+  if (!s)
+    abort ();
+
+  if (n > 7)
+    abort ();
+
+  return n + warn_noret_call_abort_r (s, n - 1);  // { dg-message "recursive call" }
+}
+
+/* Verify that a warning is not issued for an apparently infinitely
+   recursive function like the one above where the recursion would be
+   prevented by a call to a noreturn function if the recursive function
+   is itself declared noreturn.  */
+
+NORETURN void nowarn_noret_call_abort_r (int n)
+{
+  if (n > 7)
+    abort ();
+
+  nowarn_noret_call_abort_r (n - 1);
+}
+
+int warn_call_abort_r (int n)       // { dg-warning "-Winfinite-recursion" }
+{
+  n += warn_call_abort_r (n - 1);   // { dg-message "recursive call" }
+  if (n > 7)   // unreachable
+    abort ();
+  return n;
+}
+
+
+/* And again with exit() for good measure.  */
+
+int warn_call_exit_r (int n)        // { dg-warning "-Winfinite-recursion" }
+{
+  n += warn_call_exit_r (n - 1);    // { dg-message "recursive call" }
+  if (n > 7)
+    exit (0);
+  return n;
+}
+
+struct __jmp_buf_tag { };
+typedef struct __jmp_buf_tag jmp_buf[1];
+
+extern jmp_buf jmpbuf;
+
+/* A call to longjmp() breaks infinite recursion.  Verify it suppresses
+   the warning.  */
+
+int nowarn_call_longjmp_r (int n)
+{
+  if (n > 7)
+    __builtin_longjmp (jmpbuf, 1);
+  return n + nowarn_call_longjmp_r (n - 1);
+}
+
+int warn_call_longjmp_r (int n)     // { dg-warning "-Winfinite-recursion" }
+{
+  n += warn_call_longjmp_r (n - 1); // { dg-message "recursive call" }
+  if (n > 7)
+    __builtin_longjmp (jmpbuf, 1);
+  return n;
+}
+
+
+struct __sigjmp_buf_tag { };
+typedef struct __sigjmp_buf_tag sigjmp_buf[1];
+
+extern sigjmp_buf sigjmpbuf;
+
+/* GCC has no __builtin_siglongjmp().  */
+extern void siglongjmp (sigjmp_buf, int);
+
+/* A call to longjmp() breaks infinite recursion.  Verify it suppresses
+   the warning.  */
+
+int nowarn_call_siglongjmp_r (int n)
+{
+  if (n > 7)
+    siglongjmp (sigjmpbuf, 1);
+  return n + nowarn_call_siglongjmp_r (n - 1);
+}
+
+
+int nowarn_while_do_call_r (int n)
+{
+  int z = 0;
+  while (n)
+    z += nowarn_while_do_call_r (n--);
+  return z;
+}
+
+int warn_do_while_call_r (int n)    // { dg-warning "-Winfinite-recursion" }
+{
+  int z = 0;
+  do
+    z += warn_do_while_call_r (n);  // { dg-message "recursive call" }
+  while (--n);
+  return z;
+}
+
+
+/* Verify warnings for a naive replacement of a built-in fucntion.  */
+
+void* malloc (size_t n)             // { dg-warning "-Winfinite-recursion" }
+{
+  size_t *p =
+    (size_t*)__builtin_malloc (n + sizeof n);   // { dg-message "recursive call" }
+  *p = n;
+  return p + 1;
+}
+
+
+int nowarn_fact (int n)
+{
+  return n ? n * nowarn_fact (n - 1) : 1;
+}
+
+
+static int fi_v (void);
+
+/* It would seem preferable to issue the warning for the extern function
+   but as it happens it's the static function that's inlined into a recursive
+   call to itself and warn_call_fi_v() expands to a call to it.  */
+
+int warn_call_fi_v (void)     // { dg-warning "-Winfinite-recursion" "" { xfail *-*-* } }
+{
+  return fi_v ();             // { dg-message "recursive call" }
+}
+
+static int fi_v (void)        // { dg-warning "-Winfinite-recursion" }
+{
+  return warn_call_fi_v ();
+}
diff --git a/gcc/testsuite/gcc.dg/Winfinite-recursion.c b/gcc/testsuite/gcc.dg/Winfinite-recursion.c
new file mode 100644
index 00000000000..e3253567d34
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Winfinite-recursion.c
@@ -0,0 +1,227 @@ 
+/* PR middle-end/88232 - Please implement -Winfinite-recursion
+   Verify simple cases without optimization.
+   { dg-do compile }
+   { dg-options "-Wall -Winfinite-recursion" } */
+
+#define NORETURN __attribute__ ((noreturn))
+
+typedef __SIZE_TYPE__ size_t;
+
+extern void abort (void);
+extern void exit (int);
+
+extern int ei;
+int (*pfi_v)(void);
+
+
+/* Make sure the warning doesn't assume every call has a DECL.  */
+
+int nowarn_pfi_v (void)
+{
+  return pfi_v ();
+}
+
+
+int warn_fi_v (void)                // { dg-warning "-Winfinite-recursion" }
+{
+  return warn_fi_v ();              // { dg-message "recursive call" }
+}
+
+/* Verify #pragma suppression works.  */
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Winfinite-recursion"
+
+int suppress_warn_fi_v (void)
+{
+  return warn_fi_v ();
+}
+
+#pragma GCC diagnostic pop
+
+
+int nowarn_fi_v (void)
+{
+  if (ei++ == 0)
+    return nowarn_fi_v ();
+  return 0;
+}
+
+int warn_if_i (int i)               // { dg-warning "-Winfinite-recursion" }
+{
+  if (i > 0)
+    return warn_if_i (--i);         // { dg-message "recursive call" }
+  else if (i < 0)
+    return warn_if_i (-i);          // { dg-message "recursive call" }
+  else
+    return warn_if_i (7);           // { dg-message "recursive call" }
+}
+
+
+int nowarn_if_i (int i)
+{
+  if (i > 0)
+    return nowarn_if_i (--i);
+  else if (i < 0)
+    return nowarn_if_i (-i);
+  else
+    return -1;
+}
+
+int nowarn_switch (int i, int a[])
+{
+  switch (i)
+    {
+    case 0: return nowarn_switch (a[3], a + 1);
+    case 1: return nowarn_switch (a[5], a + 2);
+    case 2: return nowarn_switch (a[7], a + 3);
+    case 3: return nowarn_switch (a[9], a + 4);
+    }
+  return 77;
+}
+
+int warn_switch (int i, int a[])    // { dg-warning "-Winfinite-recursion" }
+{
+  switch (i)
+    {
+    case 0: return warn_switch (a[3], a + 1);
+    case 1: return warn_switch (a[5], a + 2);
+    case 2: return warn_switch (a[7], a + 3);
+    case 3: return warn_switch (a[9], a + 4);
+    default: return warn_switch (a[1], a + 5);
+    }
+}
+
+NORETURN void fnoreturn (void);
+
+/* Verify there's no warning for a function that doesn't return.  */
+int nowarn_call_noret (void)
+{
+  fnoreturn ();
+}
+
+int warn_call_noret_r (void)        // { dg-warning "-Winfinite-recursion" }
+{
+  warn_call_noret_r ();             // { dg-message "recursive call" }
+  fnoreturn ();
+}
+
+/* Verify a warning even though the abort() call would prevent the infinite
+   recursion.  There's no good way to tell the two cases apart and letting
+   a simple abort prevent the warning would make it ineffective in cases
+   where it's the result of assert() expansion and not meant to actually
+   prevent recursion.  */
+
+int
+warn_noret_call_abort_r (char *s, int n)  // { dg-warning "-Winfinite-recursion" }
+{
+  if (!s)
+    abort ();
+
+  if (n > 7)
+    abort ();
+
+  return n + warn_noret_call_abort_r (s, n - 1);  // { dg-message "recursive call" }
+}
+
+/* Verify that a warning is not issued for an apparently infinitely
+   recursive function like the one above where the recursion would be
+   prevented by a call to a noreturn function if the recursive function
+   is itself declared noreturn.  */
+
+NORETURN void nowarn_noret_call_abort_r (int n)
+{
+  if (n > 7)
+    abort ();
+
+  nowarn_noret_call_abort_r (n - 1);
+}
+
+int warn_call_abort_r (int n)       // { dg-warning "-Winfinite-recursion" }
+{
+  n += warn_call_abort_r (n - 1);   // { dg-message "recursive call" }
+  if (n > 7)   // unreachable
+    abort ();
+  return n;
+}
+
+
+/* And again with exit() for good measure.  */
+
+int warn_call_exit_r (int n)        // { dg-warning "-Winfinite-recursion" }
+{
+  n += warn_call_exit_r (n - 1);    // { dg-message "recursive call" }
+  if (n > 7)
+    exit (0);
+  return n;
+}
+
+struct __jmp_buf_tag { };
+typedef struct __jmp_buf_tag jmp_buf[1];
+
+extern jmp_buf jmpbuf;
+
+/* A call to longjmp() breaks infinite recursion.  Verify it suppresses
+   the warning.  */
+
+int nowarn_call_longjmp_r (int n)
+{
+  if (n > 7)
+    __builtin_longjmp (jmpbuf, 1);
+  return n + nowarn_call_longjmp_r (n - 1);
+}
+
+int warn_call_longjmp_r (int n)     // { dg-warning "-Winfinite-recursion" }
+{
+  n += warn_call_longjmp_r (n - 1); // { dg-message "recursive call" }
+  if (n > 7)
+    __builtin_longjmp (jmpbuf, 1);
+  return n;
+}
+
+
+struct __sigjmp_buf_tag { };
+typedef struct __sigjmp_buf_tag sigjmp_buf[1];
+
+extern sigjmp_buf sigjmpbuf;
+
+/* GCC has no __builtin_siglongjmp().  */
+extern void siglongjmp (sigjmp_buf, int);
+
+/* A call to longjmp() breaks infinite recursion.  Verify it suppresses
+   the warning.  */
+
+int nowarn_call_siglongjmp_r (int n)
+{
+  if (n > 7)
+    siglongjmp (sigjmpbuf, 1);
+  return n + nowarn_call_siglongjmp_r (n - 1);
+}
+
+
+int nowarn_while_do_call_r (int n)
+{
+  int z = 0;
+  while (n)
+    z += nowarn_while_do_call_r (n--);
+  return z;
+}
+
+int warn_do_while_call_r (int n)    // { dg-warning "-Winfinite-recursion" }
+{
+  int z = 0;
+  do
+    z += warn_do_while_call_r (n);  // { dg-message "recursive call" }
+  while (--n);
+  return z;
+}
+
+/* Verify warnings for a naive replacement of a built-in fucntion.  */
+
+void* malloc (size_t n)             // { dg-warning "-Winfinite-recursion" }
+{
+  size_t *p =
+    (size_t*)__builtin_malloc (n + sizeof n);   // { dg-message "recursive call" }
+  *p = n;
+  return p + 1;
+}
diff --git a/gcc/tree-pass.h b/gcc/tree-pass.h
index d494aff1c4c..3559c3c9f1b 100644
--- a/gcc/tree-pass.h
+++ b/gcc/tree-pass.h
@@ -435,6 +435,7 @@  extern gimple_opt_pass *make_pass_object_sizes (gcc::context *ctxt);
 extern gimple_opt_pass *make_pass_early_object_sizes (gcc::context *ctxt);
 extern gimple_opt_pass *make_pass_warn_access (gcc::context *ctxt);
 extern gimple_opt_pass *make_pass_warn_printf (gcc::context *ctxt);
+extern gimple_opt_pass *make_pass_warn_recursion (gcc::context *ctxt);
 extern gimple_opt_pass *make_pass_strlen (gcc::context *ctxt);
 extern gimple_opt_pass *make_pass_fold_builtins (gcc::context *ctxt);
 extern gimple_opt_pass *make_pass_post_ipa_warn (gcc::context *ctxt);