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