diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index 4982012f5b2912cf887071ad894b407653e2f936..246a85a167741ce81d1697507aba7df4d6e23d14 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -1250,6 +1250,7 @@ ANALYZER_OBJS = \
 	analyzer/engine.o \
 	analyzer/feasible-graph.o \
 	analyzer/function-set.o \
+	analyzer/infinite-recursion.o \
 	analyzer/known-function-manager.o \
 	analyzer/pending-diagnostic.o \
 	analyzer/program-point.o \
diff --git a/gcc/analyzer/analyzer.opt b/gcc/analyzer/analyzer.opt
index 58ad3262ebfaa663efed34e6187931d20fb2b283..518a5d422ffe24cdd7965005f24e0fd200f2432d 100644
--- a/gcc/analyzer/analyzer.opt
+++ b/gcc/analyzer/analyzer.opt
@@ -110,6 +110,10 @@ Wanalyzer-imprecise-fp-arithmetic
 Common Var(warn_analyzer_imprecise_fp_arithmetic) Init(1) Warning
 Warn about code paths in which floating-point arithmetic is used in locations where precise computation is needed.
 
+Wanalyzer-infinite-recursion
+Common Var(warn_analyzer_infinite_recursion) Init(1) Warning
+Warn about code paths which appear to lead to infinite recursion.
+
 Wanalyzer-jump-through-null
 Common Var(warn_analyzer_jump_through_null) Init(1) Warning
 Warn about code paths in which a NULL function pointer is called.
diff --git a/gcc/analyzer/call-string.cc b/gcc/analyzer/call-string.cc
index 5caf92155b9cd6d8953d78bfed524405238e55e9..d78ae81bd7db091ce94493066e810518e9b9def2 100644
--- a/gcc/analyzer/call-string.cc
+++ b/gcc/analyzer/call-string.cc
@@ -170,6 +170,22 @@ call_string::calc_recursion_depth () const
   return result;
 }
 
+/* Count the number of times FUN appears in the string.  */
+
+int
+call_string::count_occurrences_of_function (function *fun) const
+{
+  int result = 0;
+  for (const call_string::element_t &e : m_elements)
+    {
+      if (e.get_callee_function () == fun)
+	result++;
+      if (e.get_caller_function () == fun)
+	result++;
+    }
+  return result;
+}
+
 /* Comparator for call strings.
    This implements a version of lexicographical order.
    Return negative if A is before B.
diff --git a/gcc/analyzer/call-string.h b/gcc/analyzer/call-string.h
index c3cea9032770c1149f5e0b64bdf5e88c48f8a4c5..d97ff84ce779cb9623856c99a092540f9e232e1f 100644
--- a/gcc/analyzer/call-string.h
+++ b/gcc/analyzer/call-string.h
@@ -105,6 +105,8 @@ public:
     return m_elements[m_elements.length () - 1];
   }
 
+  int count_occurrences_of_function (function *) const;
+
   void validate () const;
 
 private:
diff --git a/gcc/analyzer/checker-path.cc b/gcc/analyzer/checker-path.cc
index ffab91c26ff09499d50da1b2c053376dfe19ded5..39de7453f51a176e70434fcc6f7ee6e58b97d297 100644
--- a/gcc/analyzer/checker-path.cc
+++ b/gcc/analyzer/checker-path.cc
@@ -377,6 +377,14 @@ region_creation_event::get_desc (bool can_colorize) const
 
 /* class function_entry_event : public checker_event.  */
 
+function_entry_event::function_entry_event (const program_point &dst_point)
+: checker_event (EK_FUNCTION_ENTRY,
+		 dst_point.get_supernode ()->get_start_location (),
+		 dst_point.get_fndecl (),
+		 dst_point.get_stack_depth ())
+{
+}
+
 /* Implementation of diagnostic_event::get_desc vfunc for
    function_entry_event.
 
@@ -1304,21 +1312,6 @@ checker_path::add_region_creation_events (const region *reg,
 						   loc, fndecl, depth));
 }
 
-/* Add a warning_event to the end of this path.  */
-
-void
-checker_path::add_final_event (const state_machine *sm,
-			       const exploded_node *enode, const gimple *stmt,
-			       tree var, state_machine::state_t state)
-{
-  add_event
-    (make_unique<warning_event> (get_stmt_location (stmt,
-						    enode->get_function ()),
-				 enode->get_function ()->decl,
-				 enode->get_stack_depth (),
-				 sm, var, state));
-}
-
 void
 checker_path::fixup_locations (pending_diagnostic *pd)
 {
diff --git a/gcc/analyzer/checker-path.h b/gcc/analyzer/checker-path.h
index 46f4875f541497a7ca8e97022bda82426ecf3cfa..53e6bff03ecd583a8ea6e177d5f7c0839dacee1f 100644
--- a/gcc/analyzer/checker-path.h
+++ b/gcc/analyzer/checker-path.h
@@ -260,7 +260,9 @@ public:
   {
   }
 
-  label_text get_desc (bool can_colorize) const final override;
+  function_entry_event (const program_point &dst_point);
+
+  label_text get_desc (bool can_colorize) const override;
   meaning get_meaning () const override;
 
   bool is_function_entry_p () const final override { return true; }
@@ -660,10 +662,6 @@ public:
 				   tree fndecl, int depth,
 				   bool debug);
 
-  void add_final_event (const state_machine *sm,
-			const exploded_node *enode, const gimple *stmt,
-			tree var, state_machine::state_t state);
-
   /* After all event-pruning, a hook for notifying each event what
      its ID will be.  The events are notified in order, allowing
      for later events to refer to the IDs of earlier events in
diff --git a/gcc/analyzer/diagnostic-manager.cc b/gcc/analyzer/diagnostic-manager.cc
index f82dd3a434aee71d0e6532c17597219eba207486..9b319c3a3f58f2cb6685147b905fa5f68e84f4c0 100644
--- a/gcc/analyzer/diagnostic-manager.cc
+++ b/gcc/analyzer/diagnostic-manager.cc
@@ -1368,8 +1368,8 @@ diagnostic_manager::emit_saved_diagnostic (const exploded_graph &eg,
      We use the final enode from the epath, which might be different from
      the sd.m_enode, as the dedupe code doesn't care about enodes, just
      snodes.  */
-  emission_path.add_final_event (sd.m_sm, epath->get_final_enode (), sd.m_stmt,
-				 sd.m_var, sd.m_state);
+  sd.m_d->add_final_event (sd.m_sm, epath->get_final_enode (), sd.m_stmt,
+			   sd.m_var, sd.m_state, &emission_path);
 
   /* The "final" event might not be final; if the saved_diagnostic has a
      trailing eedge stashed, add any events for it.  This is for use
@@ -1922,11 +1922,8 @@ diagnostic_manager::add_events_for_eedge (const path_builder &pb,
       /* Add function entry events.  */
       if (dst_point.get_supernode ()->entry_p ())
 	{
-	  emission_path->add_event
-	    (make_unique<function_entry_event>
-	     (dst_point.get_supernode ()->get_start_location (),
-	      dst_point.get_fndecl (),
-	      dst_stack_depth));
+	  pb.get_pending_diagnostic ()->add_function_entry_event
+	    (eedge, emission_path);
 	  /* Create region_creation_events for on-stack regions within
 	     this frame.  */
 	  if (interest)
diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc
index fc90b49d0413708e8425efd9a96507f825003365..d0595ef0d07768e875248306bacd33935a6cea4c 100644
--- a/gcc/analyzer/engine.cc
+++ b/gcc/analyzer/engine.cc
@@ -4276,7 +4276,12 @@ exploded_graph::process_node (exploded_node *node)
 	    exploded_node *next = get_or_create_node (next_point, next_state,
 						      node);
 	    if (next)
-	      add_edge (node, next, succ);
+	      {
+		add_edge (node, next, succ);
+
+		/* We might have a function entrypoint.  */
+		detect_infinite_recursion (next);
+	      }
 	  }
 
 	/* Return from the calls which doesn't have a return superedge.
diff --git a/gcc/analyzer/exploded-graph.h b/gcc/analyzer/exploded-graph.h
index de927e6c1b7613c0ac910cf3e309485f695a3a45..a4cbc8f688a2bd878110e80213094f8f5db369e7 100644
--- a/gcc/analyzer/exploded-graph.h
+++ b/gcc/analyzer/exploded-graph.h
@@ -851,6 +851,11 @@ public:
 
   void on_escaped_function (tree fndecl);
 
+  /* In infinite-recursion.cc */
+  void detect_infinite_recursion (exploded_node *enode);
+  exploded_node *find_previous_entry_to (function *top_of_stack_fun,
+					 exploded_node *enode) const;
+
 private:
   void print_bar_charts (pretty_printer *pp) const;
 
diff --git a/gcc/analyzer/infinite-recursion.cc b/gcc/analyzer/infinite-recursion.cc
new file mode 100644
index 0000000000000000000000000000000000000000..7055926b156fd76069bf4d30001eeec28ce6c5eb
--- /dev/null
+++ b/gcc/analyzer/infinite-recursion.cc
@@ -0,0 +1,481 @@
+/* Detection of infinite recursion.
+   Copyright (C) 2022 Free Software Foundation, Inc.
+   Contributed by David Malcolm <dmalcolm@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"
+#define INCLUDE_MEMORY
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "fold-const.h"
+#include "gcc-rich-location.h"
+#include "alloc-pool.h"
+#include "fibonacci_heap.h"
+#include "shortest-paths.h"
+#include "diagnostic-core.h"
+#include "diagnostic-event-id.h"
+#include "diagnostic-path.h"
+#include "diagnostic-metadata.h"
+#include "function.h"
+#include "pretty-print.h"
+#include "sbitmap.h"
+#include "bitmap.h"
+#include "tristate.h"
+#include "ordered-hash-map.h"
+#include "selftest.h"
+#include "json.h"
+#include "analyzer/analyzer.h"
+#include "analyzer/analyzer-logging.h"
+#include "analyzer/call-string.h"
+#include "analyzer/program-point.h"
+#include "analyzer/store.h"
+#include "analyzer/region-model.h"
+#include "analyzer/constraint-manager.h"
+#include "analyzer/sm.h"
+#include "analyzer/pending-diagnostic.h"
+#include "analyzer/diagnostic-manager.h"
+#include "cfg.h"
+#include "basic-block.h"
+#include "gimple.h"
+#include "gimple-iterator.h"
+#include "gimple-pretty-print.h"
+#include "cgraph.h"
+#include "digraph.h"
+#include "analyzer/supergraph.h"
+#include "analyzer/program-state.h"
+#include "analyzer/exploded-graph.h"
+#include "make-unique.h"
+#include "analyzer/checker-path.h"
+
+/* A subclass of pending_diagnostic for complaining about suspected
+   infinite recursion.  */
+
+class infinite_recursion_diagnostic
+: public pending_diagnostic_subclass<infinite_recursion_diagnostic>
+{
+public:
+  infinite_recursion_diagnostic (const exploded_node *prev_entry_enode,
+				 const exploded_node *new_entry_enode,
+				 tree callee_fndecl)
+  : m_prev_entry_enode (prev_entry_enode),
+    m_new_entry_enode (new_entry_enode),
+    m_callee_fndecl (callee_fndecl),
+    m_prev_entry_event (NULL)
+  {}
+
+  const char *get_kind () const final override
+  {
+    return "infinite_recursion_diagnostic";
+  }
+
+  bool operator== (const infinite_recursion_diagnostic &other) const
+  {
+    return m_callee_fndecl == other.m_callee_fndecl;
+  }
+
+  int get_controlling_option () const final override
+  {
+    return OPT_Wanalyzer_infinite_recursion;
+  }
+
+  bool emit (rich_location *rich_loc) final override
+  {
+    /* "CWE-674: Uncontrolled Recursion".  */
+    diagnostic_metadata m;
+    m.add_cwe (674);
+    return warning_meta (rich_loc, m, get_controlling_option (),
+			 "infinite recursion");
+  }
+
+  label_text describe_final_event (const evdesc::final_event &ev) final override
+  {
+    const int frames_consumed = (m_new_entry_enode->get_stack_depth ()
+				 - m_prev_entry_enode->get_stack_depth ());
+    if (frames_consumed > 1)
+      return ev.formatted_print
+	("apparently infinite chain of mutually-recursive function calls,"
+	 " consuming %i stack frames per recursion",
+	 frames_consumed);
+    else
+      return ev.formatted_print ("apparently infinite recursion");
+  }
+
+  void
+  add_function_entry_event (const exploded_edge &eedge,
+			    checker_path *emission_path) final override
+  {
+    /* Subclass of function_entry_event for use when reporting both
+       the initial and subsequent entries to the function of interest,
+       allowing for cross-referencing the first event in the description
+       of the second.  */
+    class recursive_function_entry_event : public function_entry_event
+    {
+    public:
+      recursive_function_entry_event (const program_point &dst_point,
+				      const infinite_recursion_diagnostic &pd,
+				      bool topmost)
+      : function_entry_event (dst_point),
+	m_pd (pd),
+	m_topmost (topmost)
+      {
+      }
+
+      label_text
+      get_desc (bool can_colorize) const final override
+      {
+	if (m_topmost)
+	  {
+	    if (m_pd.m_prev_entry_event
+		&& m_pd.m_prev_entry_event->get_id_ptr ()->known_p ())
+	      return make_label_text
+		(can_colorize,
+		 "recursive entry to %qE; previously entered at %@",
+		 m_effective_fndecl,
+		 m_pd.m_prev_entry_event->get_id_ptr ());
+	    else
+	      return make_label_text (can_colorize, "recursive entry to %qE",
+				      m_effective_fndecl);
+	  }
+	else
+	  return make_label_text (can_colorize, "initial entry to %qE",
+				  m_effective_fndecl);
+      }
+
+    private:
+      const infinite_recursion_diagnostic &m_pd;
+      bool m_topmost;
+    };
+    const exploded_node *dst_node = eedge.m_dest;
+    const program_point &dst_point = dst_node->get_point ();
+    if (eedge.m_dest == m_prev_entry_enode)
+      {
+	gcc_assert (m_prev_entry_event == NULL);
+	std::unique_ptr<checker_event> prev_entry_event
+	  = make_unique <recursive_function_entry_event> (dst_point,
+							  *this, false);
+	m_prev_entry_event = prev_entry_event.get ();
+	emission_path->add_event (std::move (prev_entry_event));
+      }
+    else if (eedge.m_dest == m_new_entry_enode)
+      emission_path->add_event
+	(make_unique<recursive_function_entry_event> (dst_point, *this, true));
+    else
+      pending_diagnostic::add_function_entry_event (eedge, emission_path);
+  }
+
+  /* Customize the location where the warning_event appears, putting
+     it at the topmost entrypoint to the function.  */
+  void add_final_event (const state_machine *,
+			const exploded_node *,
+			const gimple *,
+			tree,
+			state_machine::state_t,
+			checker_path *emission_path) final override
+  {
+    gcc_assert (m_new_entry_enode);
+    emission_path->add_event
+      (make_unique<warning_event>
+       (m_new_entry_enode->get_supernode ()->get_start_location (),
+	m_callee_fndecl,
+	m_new_entry_enode->get_stack_depth (),
+	NULL, NULL, NULL));
+  }
+
+private:
+  const exploded_node *m_prev_entry_enode;
+  const exploded_node *m_new_entry_enode;
+  tree m_callee_fndecl;
+  const checker_event *m_prev_entry_event;
+};
+
+/* Return true iff ENODE is the PK_BEFORE_SUPERNODE at a function
+   entrypoint.  */
+
+static bool
+is_entrypoint_p (exploded_node *enode)
+{
+  /* Look for an entrypoint to a function...  */
+  const supernode *snode = enode->get_supernode ();
+  if (!snode)
+    return false;
+  if (!snode->entry_p ())
+    return false;;
+  const program_point &point = enode->get_point ();
+  if (point.get_kind () != PK_BEFORE_SUPERNODE)
+    return false;
+  return true;
+}
+
+/* Walk backwards through the eg, looking for the first
+   enode we find that's also the entrypoint of the same function.  */
+
+exploded_node *
+exploded_graph::find_previous_entry_to (function *top_of_stack_fun,
+					exploded_node *enode) const
+{
+  auto_vec<exploded_node *> worklist;
+  hash_set<exploded_node *> visited;
+
+  visited.add (enode);
+  for (auto in_edge : enode->m_preds)
+    worklist.safe_push (in_edge->m_src);
+
+  while (worklist.length () > 0)
+    {
+      exploded_node *iter = worklist.pop ();
+
+      if (is_entrypoint_p (iter)
+	  && iter->get_function () == top_of_stack_fun)
+	return iter;
+
+      if (visited.contains (iter))
+	continue;
+      visited.add (iter);
+      for (auto in_edge : iter->m_preds)
+	worklist.safe_push (in_edge->m_src);
+    }
+
+  /* Not found.  */
+  return NULL;
+}
+
+/* Given BASE_REG within ENCLOSING_FRAME (such as a function parameter),
+   remap it to the equivalent region within EQUIV_PREV_FRAME.
+
+   For example, given param "n" within frame "foo@3", and equiv prev frame
+   "foo@1", remap it to param "n" within frame "foo@1".  */
+
+static const region *
+remap_enclosing_frame (const region *base_reg,
+		       const frame_region *enclosing_frame,
+		       const frame_region *equiv_prev_frame,
+		       region_model_manager *mgr)
+{
+  gcc_assert (base_reg->get_parent_region () == enclosing_frame);
+  switch (base_reg->get_kind ())
+    {
+    default:
+      /* We should only encounter params and varargs at the topmost
+	 entrypoint.  */
+      gcc_unreachable ();
+
+    case RK_VAR_ARG:
+      {
+	const var_arg_region *var_arg_reg = (const var_arg_region *)base_reg;
+	return mgr->get_var_arg_region (equiv_prev_frame,
+					var_arg_reg->get_index ());
+      }
+    case RK_DECL:
+      {
+	const decl_region *decl_reg = (const decl_region *)base_reg;
+	return equiv_prev_frame->get_region_for_local (mgr,
+						       decl_reg->get_decl (),
+						       NULL);
+      }
+    }
+}
+
+/* Compare the state of memory at NEW_ENTRY_ENODE and PREV_ENTRY_ENODE,
+   both of which are entrypoints to the same function, where recursion has
+   occurred.
+
+   Return true if the state of NEW_ENTRY_ENODE is sufficiently different
+   from PREV_ENTRY_ENODE to suggests that some variant is being modified,
+   and thus the recursion isn't infinite.
+
+   Return false if the states are effectively the same, suggesting that
+   the recursion is infinite.
+
+   For example, consider mutually recursive functions "foo" and "bar".
+   At the entrypoint to a "foo" frame where we've detected recursion,
+   we might have three frames on the stack: the new 'foo'@3, an inner
+   'bar'@2, and the innermost 'foo'@1.
+
+     (gdb) call enode->dump(m_ext_state)
+     EN: 16
+     callstring: [(SN: 9 -> SN: 3 in foo), (SN: 5 -> SN: 8 in bar)]
+     before SN: 0 (NULL from-edge)
+
+     rmodel:
+     stack depth: 3
+       frame (index 2): frame: ‘foo’@3
+       frame (index 1): frame: ‘bar’@2
+       frame (index 0): frame: ‘foo’@1
+     clusters within root region
+       cluster for: (*INIT_VAL(f_4(D)))
+     clusters within frame: ‘bar’@2
+       cluster for: b_2(D): INIT_VAL(f_4(D))
+     clusters within frame: ‘foo’@3
+       cluster for: f_4(D): INIT_VAL(f_4(D))
+     m_called_unknown_fn: FALSE
+
+   whereas for the previous entry node we'd have just the innermost
+   'foo'@1
+
+     (gdb) call prev_entry_enode->dump(m_ext_state)
+     EN: 1
+     callstring: []
+     before SN: 0 (NULL from-edge)
+
+     rmodel:
+     stack depth: 1
+       frame (index 0): frame: ‘foo’@1
+     clusters within root region
+       cluster for: (*INIT_VAL(f_4(D)))
+     m_called_unknown_fn: FALSE
+
+   We want to abstract away frames 1 and 2 in the new entry enode,
+   and compare its frame 3 with the frame 1 in the previous entry
+   enode, and determine if enough state changes between them to
+   rule out infinite recursion.  */
+
+static bool
+sufficiently_different_p (exploded_node *new_entry_enode,
+			  exploded_node *prev_entry_enode,
+			  logger *logger)
+{
+  LOG_SCOPE (logger);
+  gcc_assert (new_entry_enode);
+  gcc_assert (prev_entry_enode);
+  gcc_assert (is_entrypoint_p (new_entry_enode));
+  gcc_assert (is_entrypoint_p (prev_entry_enode));
+
+  const int new_stack_depth = new_entry_enode->get_stack_depth ();
+
+  /* Compare the stores of the two enodes.  */
+  const region_model &new_model
+    = *new_entry_enode->get_state ().m_region_model;
+  const region_model &prev_model
+    = *prev_entry_enode->get_state ().m_region_model;
+  const store &new_store = *new_model.get_store ();
+
+  for (auto kv : new_store)
+    {
+      const region *base_reg = kv.first;
+
+      /* Get the value within the new frame.  */
+      const svalue *new_sval
+	= new_model.get_store_value (base_reg, NULL);
+
+      /* If the value is UNKNOWN (e.g. due to hitting complexity limits)
+	 assume that it differs from the previous value.  */
+      if (new_sval->get_kind () == SK_UNKNOWN)
+	return true;
+
+      /* Get the equivalent value within the old enode.  */
+      const svalue *prev_sval;
+
+      if (const frame_region *enclosing_frame
+	    = base_reg->maybe_get_frame_region ())
+	{
+	  /* We have a binding within a frame in the new entry enode.  */
+
+	  /* Ignore bindings within frames below the new entry node.  */
+	  if (enclosing_frame->get_stack_depth () < new_stack_depth)
+	    continue;
+
+	  /* We have a binding within the frame of the new entry node,
+	     presumably a parameter.  */
+
+	  /* Get the value within the equivalent frame of
+	     the old entrypoint; typically will be the initial_svalue
+	     of the parameter.  */
+	  const frame_region *equiv_prev_frame
+	    = prev_model.get_current_frame ();
+	  const region *equiv_prev_base_reg
+	    = remap_enclosing_frame (base_reg,
+				     enclosing_frame,
+				     equiv_prev_frame,
+				     new_model.get_manager ());
+	  prev_sval = prev_model.get_store_value (equiv_prev_base_reg, NULL);
+	}
+      else
+	prev_sval = prev_model.get_store_value (base_reg, NULL);
+
+      /* If the prev_sval is UNKNOWN (e.g. due to hitting complexity limits)
+	 assume that it will differ from any new value.  */
+      if (prev_sval->get_kind () == SK_UNKNOWN)
+	return true;
+
+      if (new_sval != prev_sval)
+	return true;
+    }
+
+  /* No significant differences found.  */
+  return false;
+}
+
+/* Implementation of -Wanalyzer-infinite-recursion.
+
+   Called when adding ENODE to the graph, after adding its first in-edge.
+
+   For function entrypoints, see if recursion has occurred, and, if so,
+   check if the state of memory changed between the recursion levels,
+   which would suggest some kind of decreasing variant that leads to
+   termination.
+
+   For recursive calls where the state of memory is effectively unchanged
+   between recursion levels, warn with -Wanalyzer-infinite-recursion.  */
+
+void
+exploded_graph::detect_infinite_recursion (exploded_node *enode)
+{
+  if (!is_entrypoint_p (enode))
+    return;
+  function *top_of_stack_fun = enode->get_function ();
+  gcc_assert (top_of_stack_fun);
+
+  /* ....where a call to that function is already in the call string.  */
+  const call_string &call_string = enode->get_point ().get_call_string ();
+
+  if (call_string.count_occurrences_of_function (top_of_stack_fun) < 2)
+    return;
+
+  tree fndecl = top_of_stack_fun->decl;
+
+  log_scope s (get_logger (),
+	       "checking for infinite recursion",
+	       "considering recursion at EN: %i entering %qE",
+	       enode->m_index, fndecl);
+
+  /* Find enode that's the entrypoint for the previous frame for fndecl
+     in the recursion.  */
+  exploded_node *prev_entry_enode
+    = find_previous_entry_to (top_of_stack_fun, enode);
+  gcc_assert (prev_entry_enode);
+  if (get_logger ())
+    get_logger ()->log ("previous entrypoint to %qE is EN: %i",
+			fndecl, prev_entry_enode->m_index);
+
+  /* Look for changes to the state of memory between the recursion levels.  */
+  if (sufficiently_different_p (enode, prev_entry_enode, get_logger ()))
+    return;
+
+  /* Otherwise, the state of memory is effectively the same between the two
+     recursion levels; warn.  */
+
+  const supernode *caller_snode = call_string.get_top_of_stack ().m_caller;
+  const supernode *snode = enode->get_supernode ();
+  gcc_assert (caller_snode->m_returning_call);
+  get_diagnostic_manager ().add_diagnostic
+    (enode, snode, caller_snode->m_returning_call, NULL,
+     make_unique<infinite_recursion_diagnostic> (prev_entry_enode,
+						 enode,
+						 fndecl));
+}
diff --git a/gcc/analyzer/pending-diagnostic.cc b/gcc/analyzer/pending-diagnostic.cc
index fdbe6153f2c73a7564308b34e0ddd63c99635d39..9216a22e64def8547f7fc4c4a421457b7f8d3408 100644
--- a/gcc/analyzer/pending-diagnostic.cc
+++ b/gcc/analyzer/pending-diagnostic.cc
@@ -167,6 +167,18 @@ pending_diagnostic::fixup_location (location_t loc) const
   return loc;
 }
 
+/* Base implementation of pending_diagnostic::add_function_entry_event.
+   Add a function_entry_event to EMISSION_PATH.  */
+
+void
+pending_diagnostic::add_function_entry_event (const exploded_edge &eedge,
+					      checker_path *emission_path)
+{
+  const exploded_node *dst_node = eedge.m_dest;
+  const program_point &dst_point = dst_node->get_point ();
+  emission_path->add_event (make_unique<function_entry_event> (dst_point));
+}
+
 /* Base implementation of pending_diagnostic::add_call_event.
    Add a call_event to EMISSION_PATH.  */
 
@@ -187,6 +199,24 @@ pending_diagnostic::add_call_event (const exploded_edge &eedge,
 			      src_stack_depth));
 }
 
+/* Base implementation of pending_diagnostic::add_final_event.
+   Add a warning_event to the end of EMISSION_PATH.  */
+
+void
+pending_diagnostic::add_final_event (const state_machine *sm,
+				     const exploded_node *enode,
+				     const gimple *stmt,
+				     tree var, state_machine::state_t state,
+				     checker_path *emission_path)
+{
+  emission_path->add_event
+    (make_unique<warning_event> (get_stmt_location (stmt,
+						    enode->get_function ()),
+				 enode->get_function ()->decl,
+				 enode->get_stack_depth (),
+				 sm, var, state));
+}
+
 } // namespace ana
 
 #endif /* #if ENABLE_ANALYZER */
diff --git a/gcc/analyzer/pending-diagnostic.h b/gcc/analyzer/pending-diagnostic.h
index 6ca8ab9f4aad73b2759bfc5b654a113bfd716fe5..0e91e71a2089e4ba06f98cb0c78f76e525bf410f 100644
--- a/gcc/analyzer/pending-diagnostic.h
+++ b/gcc/analyzer/pending-diagnostic.h
@@ -309,6 +309,14 @@ class pending_diagnostic
 
   /* End of precision-of-wording vfuncs.  */
 
+  /* Vfunc for adding a function_entry_event to a checker_path, so that e.g.
+     the infinite recursion diagnostic can add a custom event subclass
+     that annotates recursively entering a function.  */
+
+  virtual void
+  add_function_entry_event (const exploded_edge &eedge,
+			    checker_path *emission_path);
+
   /* Vfunc for extending/overriding creation of the events for an
      exploded_edge that corresponds to a superedge, allowing for custom
      events to be created that are pertinent to a particular
@@ -330,6 +338,16 @@ class pending_diagnostic
   virtual void add_call_event (const exploded_edge &,
 			       checker_path *);
 
+  /* Vfunc for adding the final warning_event to a checker_path, so that e.g.
+     the infinite recursion diagnostic can have its diagnostic appear at
+     the callsite, but the final event in the path be at the entrypoint
+     of the called function.  */
+  virtual void add_final_event (const state_machine *sm,
+				const exploded_node *enode,
+				const gimple *stmt,
+				tree var, state_machine::state_t state,
+				checker_path *emission_path);
+
   /* Vfunc for determining that this pending_diagnostic supercedes OTHER,
      and that OTHER should therefore not be emitted.
      They have already been tested for being at the same stmt.  */
diff --git a/gcc/doc/gcc/gcc-command-options/options-that-control-static-analysis.rst b/gcc/doc/gcc/gcc-command-options/options-that-control-static-analysis.rst
index 09bf049036f1bad130dcd3932a6711737457caaf..32a626c16a9aaf410628f4889bcb382ddb84ea4e 100644
--- a/gcc/doc/gcc/gcc-command-options/options-that-control-static-analysis.rst
+++ b/gcc/doc/gcc/gcc-command-options/options-that-control-static-analysis.rst
@@ -32,6 +32,7 @@ Options That Control Static Analysis
   :option:`-Wanalyzer-file-leak` |gol|
   :option:`-Wanalyzer-free-of-non-heap` |gol|
   :option:`-Wanalyzer-imprecise-fp-arithmetic` |gol|
+  :option:`-Wanalyzer-infinite-recursion` |gol|
   :option:`-Wanalyzer-jump-through-null` |gol|
   :option:`-Wanalyzer-malloc-leak` |gol|
   :option:`-Wanalyzer-mismatching-deallocation` |gol|
@@ -308,6 +309,33 @@ Options That Control Static Analysis
 
   Default setting; overrides :option:`-Wno-analyzer-imprecise-fp-arithmetic`.
 
+.. option:: -Wno-analyzer-infinite-recursion
+
+  This warning requires :option:`-fanalyzer`, which enables it; use
+  :option:`-Wno-analyzer-infinite-recursion` to disable it.
+
+  This diagnostics warns for paths through the code which appear to
+  lead to infinite recursion.
+
+  Specifically, when the analyzer "sees" a recursive call, it will compare
+  the state of memory at the entry to the new frame with that at the entry
+  to the previous frame of that function on the stack.  The warning is
+  issued if nothing in memory appears to be changing; any changes observed
+  to parameters or globals are assumed to lead to termination of the
+  recursion and thus suppress the warning.
+
+  This diagnostic is likely to miss cases of infinite recursion that
+  are convered to iteration by the optimizer before the analyzer "sees"
+  them.  Hence optimization should be disabled when attempting to trigger
+  this diagnostic.
+
+  Compare with :option:`-Winfinite-recursion`, which provides a similar
+  diagnostic, but is implemented in a different way.
+
+.. option:: -Wanalyzer-infinite-recursion
+
+  Default setting; overrides :option:`-Wno-analyzer-infinite-recursion`.
+
 .. option:: -Wno-analyzer-jump-through-null
 
   This warning requires :option:`-fanalyzer`, which enables it; use
diff --git a/gcc/doc/gcc/gcc-command-options/options-to-request-or-suppress-warnings.rst b/gcc/doc/gcc/gcc-command-options/options-to-request-or-suppress-warnings.rst
index a4a288859c6992a5a99fe4b4b09bae211ffd6795..b57f4784780530157ca874fc438060a91303d704 100644
--- a/gcc/doc/gcc/gcc-command-options/options-to-request-or-suppress-warnings.rst
+++ b/gcc/doc/gcc/gcc-command-options/options-to-request-or-suppress-warnings.rst
@@ -804,6 +804,10 @@ warnings, in some cases it may also cause false positives.
   recursion in calls between two or more functions.
   :option:`-Winfinite-recursion` is included in :option:`-Wall`.
 
+  Compare with :option:`-Wanalyzer-infinite-recursion` which provides a
+  similar diagnostic, but is implemented in a different way (as part of
+  :option:`-fanalyzer`).
+
 .. option:: -Wno-infinite-recursion
 
   Default setting; overrides :option:`-Winfinite-recursion`.
diff --git a/gcc/testsuite/g++.dg/analyzer/infinite-recursion-1.C b/gcc/testsuite/g++.dg/analyzer/infinite-recursion-1.C
new file mode 100644
index 0000000000000000000000000000000000000000..33f3b33f33edcf8e30cc49dca5f411db579a29e2
--- /dev/null
+++ b/gcc/testsuite/g++.dg/analyzer/infinite-recursion-1.C
@@ -0,0 +1,84 @@
+class widget
+{
+public:
+  virtual void draw ()
+  {
+    /* no-op */
+  }
+};
+
+class foo_widget : public widget
+{
+public:
+  void draw ();
+};
+
+void foo_widget::draw ()
+{
+  // Bogus attempt to chain up to base class leading to infinite recursion:
+  foo_widget::draw (); /* { dg-warning "infinite recursion" } */
+
+  // [...snip...]
+}
+
+/* Infinite recursion due to a buggy "operator int".  */
+
+class boxed_int
+{
+  int m_val;
+public:
+  operator int ();
+};
+
+boxed_int::operator int ()
+{
+  return *this; /* { dg-warning "infinite recursion" } */
+}
+
+template <typename T>
+class buggy_getter
+{
+public:
+  T get_value () const
+  {
+    return get_value (); /* { dg-warning "infinite recursion" } */
+  }
+};
+
+int test_buggy_getter (buggy_getter<int> g)
+{
+  return g.get_value ();
+}
+
+/* Copy of g++.dg/warn/Winfinite-recursion.C  */
+
+template <typename D>
+struct C
+{
+  void foo ()
+  {
+    static_cast<D *>(this)->foo (); /* { dg-warning "-Wanalyzer-infinite-recursion" } */
+  }
+};
+
+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/g++.dg/analyzer/infinite-recursion-2.C b/gcc/testsuite/g++.dg/analyzer/infinite-recursion-2.C
new file mode 100644
index 0000000000000000000000000000000000000000..c582fbf4f073584687c670178609b9c49f889263
--- /dev/null
+++ b/gcc/testsuite/g++.dg/analyzer/infinite-recursion-2.C
@@ -0,0 +1,74 @@
+/* Copy of g++.dg/warn/Winfinite-recursion-2.C  */
+
+/* { dg-do compile { target c++11 } }  */
+
+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
+    {
+      return *this; /* { dg-warning "-Wanalyzer-infinite-recursion" } */
+    }
+  };
+  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/analyzer/infinite-recursion-3.C b/gcc/testsuite/g++.dg/analyzer/infinite-recursion-3.C
new file mode 100644
index 0000000000000000000000000000000000000000..a749f6266667b3ef939de089f598c470fd331f5c
--- /dev/null
+++ b/gcc/testsuite/g++.dg/analyzer/infinite-recursion-3.C
@@ -0,0 +1,62 @@
+/* Adapted from g++.dg/warn/Winfinite-recursion-3.C  */
+
+typedef __SIZE_TYPE__ size_t;
+
+/* Might throw.  */
+void f ();
+
+void warn_f_call_r (int  n)
+{
+  if (n > 7)
+    f ();
+  warn_f_call_r (n - 1);
+}
+
+void warn_f_do_while_call_r (int n)
+{
+  f ();
+  do
+    {
+      f ();
+      warn_f_do_while_call_r (n - 1);
+    }
+  while (1);
+}
+
+
+struct X
+{
+  X (int);
+  ~X ();
+};
+
+int warn_class_with_ctor (int n)
+{
+  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);
+}
+
+extern int* eipa[];
+
+void warn_call_new (int i)
+{
+  eipa[i] = new int;
+
+  warn_call_new (i - 1);
+}
+
+void* operator new[] (size_t n)
+{
+  char *p = new char[n + sizeof (n)];
+  *(size_t*)p = n;
+  return p + sizeof n;
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/infinite-recursion-2.c b/gcc/testsuite/gcc.dg/analyzer/infinite-recursion-2.c
new file mode 100644
index 0000000000000000000000000000000000000000..f0ab1300b8450ba8bac9ad4fdc0cc1d5eacc70eb
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/infinite-recursion-2.c
@@ -0,0 +1,109 @@
+void test_direct (void)
+{
+  test_direct (); /* { dg-warning "infinite recursion" } */
+}
+
+void test_guarded (int flag)
+{
+  if (flag)
+    test_guarded (flag); /* { dg-warning "infinite recursion" } */
+}
+
+void test_flipped_guard (int flag)
+{
+  if (flag)
+    test_guarded (!flag); /* { dg-bogus "infinite recursion" } */
+}
+
+void test_param_variant (int depth)
+{
+  if (depth > 0)
+    test_param_variant (depth - 1); /* { dg-bogus "infinite recursion" } */
+}
+
+void test_unguarded_param_variant (int depth)
+{
+  /* We fail to report this: we see that depth is being decremented,
+     but don't notice that every path through the function is
+     recursing.  */
+  test_unguarded_param_variant (depth - 1); /* { dg-warning "infinite recursion" "TODO" { xfail *-*-* } } */
+}
+
+int g;
+
+void test_global_variant ()
+{
+  if (g-- > 0)
+    test_global_variant (); /* { dg-bogus "infinite recursion" } */
+}
+
+/* This is a bounded recursion, as "n" is decremented before recursing... */
+
+int test_while_do_predecrement_param (int n)
+{
+  int x = 0;
+  while (n)
+    x += test_while_do_predecrement_param (--n); /* { dg-bogus "infinite recursion" } */
+  return x;
+}
+
+/* ...whereas this one is unbounded, as "n" is decremented *after* the
+   recursive call, and so is repeatedly called with the same value.  */
+
+int test_while_do_postdecrement_param (int n)
+{
+  int x = 0;
+  while (n)
+    x += test_while_do_postdecrement_param (n--); /* { dg-warning "infinite recursion" } */
+  return x;
+}
+/* This is a bounded recursion, as "n" is decremented before recursing... */
+
+int test_do_while_predecrement_param (int n)
+{
+  int x = 0;
+  do
+    x += test_do_while_predecrement_param (--n); /* { dg-bogus "infinite recursion" } */
+  while (--n);
+  return x;
+}
+
+/* ...whereas this one is unbounded, as "n" is decremented *after* the
+   recursive call, and so is repeatedly called with the same value.  */
+
+int test_do_while_postdecrement_param (int n)
+{
+  int x = 0;
+  do
+    x += test_do_while_postdecrement_param (n--); /* { dg-warning "infinite recursion" } */
+  while (--n);
+  return x;
+}
+
+/* Various cases of decrementing "n" as the recursion proceeds where
+   not every path recurses, but we're not actually checking "n", so
+   if "flag" is true it's an infinite recursion.  */
+
+void test_partially_guarded_postdecrement (int flag, int n)
+{
+  /* We catch this; the "n--" means we recurse with the
+     same value for the 2nd param.  */
+  if (flag) /* { dg-message "when 'flag != 0'" } */
+    test_partially_guarded_postdecrement (flag, n--); /* { dg-warning "infinite recursion" } */
+}
+
+void test_partially_guarded_predecrement (int flag, int n)
+{
+  /* We fail to report this; we see that "n" is changing,
+     though it isn't relevant to whether we recurse.  */
+  if (flag)
+    test_partially_guarded_predecrement (flag, --n); /* { dg-warning "infinite recursion" "TODO" { xfail *-*-* } } */
+}
+
+void test_partially_guarded_subtract (int flag, int n)
+{
+  /* We fail to report this; we see that "n" is changing,
+     though it isn't relevant to whether we recurse.  */
+  if (flag)
+    test_partially_guarded_subtract (flag, n - 1); /* { dg-warning "infinite recursion" "TODO" { xfail *-*-* } } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/infinite-recursion-3.c b/gcc/testsuite/gcc.dg/analyzer/infinite-recursion-3.c
new file mode 100644
index 0000000000000000000000000000000000000000..68c4fa396caa11a0ee40b923b4cf7d9fc9a5d118
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/infinite-recursion-3.c
@@ -0,0 +1,18 @@
+/* { dg-additional-options "-fno-analyzer-call-summaries -Wno-analyzer-too-complex" } */
+
+struct node
+{
+  struct node *left;
+  struct node *right;
+  int val;
+};
+
+int sum (struct node *n)
+{
+  int result = 0;
+  if (n->left)
+    result += sum (n->left); /* { dg-bogus "infinite recursion" } */
+  if (n->right)
+    result += sum (n->right); /* { dg-bogus "infinite recursion" } */
+  return result;
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/infinite-recursion-4-limited-buggy.c b/gcc/testsuite/gcc.dg/analyzer/infinite-recursion-4-limited-buggy.c
new file mode 100644
index 0000000000000000000000000000000000000000..a71b9029af97b77fd70864d094f0af2a91da2a59
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/infinite-recursion-4-limited-buggy.c
@@ -0,0 +1,25 @@
+/* { dg-additional-options "-fno-analyzer-call-summaries -Wno-analyzer-too-complex" } */
+
+/* A two-deep mutual recursion, and failing to walk a list,
+   but with a depth limit, thus not an infinite recursion (assuming a
+   suitable depth limit).  */
+
+struct node
+{
+  struct node *child;
+};
+
+void foo (struct node *f, int depth);
+
+void bar (struct node *b, int depth)
+{
+  foo (b, depth); /* { dg-bogus "infinite recursion" } */
+}
+
+void foo (struct node *f, int depth)
+{
+  if (f->child && depth > 0)
+    /* Bug: should have recursed to f->child, not to f,
+       but we assume that the depth limit should save us.  */
+    bar (f, depth - 1); /* { dg-bogus "infinite recursion" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/infinite-recursion-4-limited.c b/gcc/testsuite/gcc.dg/analyzer/infinite-recursion-4-limited.c
new file mode 100644
index 0000000000000000000000000000000000000000..55326d2b473464f92cec98df643feb472e2955bc
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/infinite-recursion-4-limited.c
@@ -0,0 +1,22 @@
+/* { dg-additional-options "-fno-analyzer-call-summaries -Wno-analyzer-too-complex" } */
+
+/* A two-deep mutual recursion, walking a singly-linked list,
+   with a depth limit.  */
+
+struct node
+{
+  struct node *child;
+};
+
+void foo (struct node *f, int depth);
+
+void bar (struct node *b, int depth)
+{
+  foo (b, depth); /* { dg-bogus "infinite recursion" } */
+}
+
+void foo (struct node *f, int depth)
+{
+  if (f->child && depth > 0)
+    bar (f->child, depth - 1); /* { dg-bogus "infinite recursion" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/infinite-recursion-4-unlimited-buggy.c b/gcc/testsuite/gcc.dg/analyzer/infinite-recursion-4-unlimited-buggy.c
new file mode 100644
index 0000000000000000000000000000000000000000..7ed1a2bb38c732614ec577b639969a995f0abb23
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/infinite-recursion-4-unlimited-buggy.c
@@ -0,0 +1,23 @@
+/* { dg-additional-options "-fno-analyzer-call-summaries -Wno-analyzer-too-complex" } */
+
+/* A two-deep mutual recursion, with no limit, and
+   failing to walk the list, thus leading to an infinite recursion.  */
+
+struct node
+{
+  struct node *child;
+};
+
+void foo (struct node *f);
+
+void bar (struct node *b)
+{
+  foo (b); /* { dg-warning "infinite recursion" } */
+}
+
+void foo (struct node *f)
+{
+  if (f->child)
+    /* Bug: should have recursed to f->child, not to f.  */
+    bar (f); /* { dg-warning "infinite recursion" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/infinite-recursion-4-unlimited.c b/gcc/testsuite/gcc.dg/analyzer/infinite-recursion-4-unlimited.c
new file mode 100644
index 0000000000000000000000000000000000000000..bdb54a3ee78705b5a53d3a361209def02aa6333d
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/infinite-recursion-4-unlimited.c
@@ -0,0 +1,22 @@
+/* { dg-additional-options "-fno-analyzer-call-summaries -Wno-analyzer-too-complex" } */
+
+/* A two-deep mutual recursion, walking a linked list (and thus presumably
+   terminating), with no explicit depth limit.  */
+
+struct node
+{
+  struct node *child;
+};
+
+void foo (struct node *f);
+
+void bar (struct node *b)
+{
+  foo (b); /* { dg-bogus "infinite recursion" } */
+}
+
+void foo (struct node *f)
+{
+  if (f->child)
+    bar (f->child); /* { dg-bogus "infinite recursion" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/infinite-recursion-5.c b/gcc/testsuite/gcc.dg/analyzer/infinite-recursion-5.c
new file mode 100644
index 0000000000000000000000000000000000000000..bf206394186a829b2463d83185505ae6d92f816b
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/infinite-recursion-5.c
@@ -0,0 +1,221 @@
+/* Adapted from gcc.dg/Winfinite-recursion.c.  */
+
+#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)
+{
+  return warn_fi_v ();               // { dg-warning "-Wanalyzer-infinite-recursion" }
+}
+
+/* Verify #pragma suppression works.  */
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wanalyzer-infinite-recursion"
+
+int suppress_warn_fi_v (void)
+{
+  return suppress_warn_fi_v ();
+}
+
+#pragma GCC diagnostic pop
+
+
+int nowarn_fi_v (void)
+{
+  if (ei++ == 0)
+    return nowarn_fi_v ();
+  return 0;
+}
+
+/* -Winfinite-recursion warns for this, but
+   -Wanalyzer-infinite-recursion doesn't.  */
+
+int warn_if_i (int i)
+{
+  if (i > 0)
+    return warn_if_i (--i);
+  else if (i < 0)
+    return warn_if_i (-i);
+  else
+    return warn_if_i (7);
+}
+
+
+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;
+}
+
+/* -Winfinite-recursion warns for this, but
+   -Wanalyzer-infinite-recursion doesn't.  */
+
+int warn_switch (int i, int a[])
+{
+  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)
+{
+  warn_call_noret_r ();             // { dg-warning "-Wanalyzer-infinite-recursion" }
+  fnoreturn ();
+}
+
+/* -Winfinite-recursion warns for this, but
+   -Wanalyzer-infinite-recursion doesn't.  */
+
+int
+warn_noret_call_abort_r (char *s, int n)
+{
+  if (!s)
+    abort ();
+
+  if (n > 7)
+    abort ();
+
+  return n + warn_noret_call_abort_r (s, n - 1);
+}
+
+NORETURN void nowarn_noret_call_abort_r (int n)
+{
+  if (n > 7)
+    abort ();
+
+  nowarn_noret_call_abort_r (n - 1);
+}
+
+/* -Winfinite-recursion warns for this, but
+   -Wanalyzer-infinite-recursion doesn't.  */
+
+int warn_call_abort_r (int n)
+{
+  n += warn_call_abort_r (n - 1);
+  if (n > 7)   // unreachable
+    abort ();
+  return n;
+}
+
+
+/* -Winfinite-recursion warns for this, but
+   -Wanalyzer-infinite-recursion doesn't.  */
+
+int warn_call_exit_r (int n)
+{
+  n += warn_call_exit_r (n - 1);
+  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);
+}
+
+/* -Winfinite-recursion warns for this, but
+   -Wanalyzer-infinite-recursion doesn't.  */
+
+int warn_call_longjmp_r (int n)
+{
+  n += warn_call_longjmp_r (n - 1);
+  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);
+}
+
+/* -Winfinite-recursion doesn't warn for this unbounded recursion, but
+   -Wanalyzer-infinite-recursion does.  */
+
+int nowarn_while_do_call_r (int n)
+{
+  int z = 0;
+  while (n)
+    z += nowarn_while_do_call_r (n--); // { dg-warning "-Wanalyzer-infinite-recursion" }
+  return z;
+}
+
+int warn_do_while_call_r (int n)
+{
+  int z = 0;
+  do
+    z += warn_do_while_call_r (n); // { dg-warning "-Wanalyzer-infinite-recursion" }
+  while (--n);
+  return z;
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/infinite-recursion-alloca.c b/gcc/testsuite/gcc.dg/analyzer/infinite-recursion-alloca.c
new file mode 100644
index 0000000000000000000000000000000000000000..8c50631d8ceb820c4e134ad9930831e7fa1f89eb
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/infinite-recursion-alloca.c
@@ -0,0 +1,27 @@
+typedef __SIZE_TYPE__ size_t;
+
+int test_alloca_1 (void)
+{
+  void *buf = __builtin_alloca (1024);
+  return test_alloca_1 (); /* { dg-warning "-Wanalyzer-infinite-recursion" } */
+}
+
+int test_alloca_2 (size_t n)
+{
+  void *buf = __builtin_alloca (n);
+  return test_alloca_2 (n); /* { dg-warning "-Wanalyzer-infinite-recursion" } */
+}
+
+int test_alloca_3 (size_t n)
+{
+  void *buf = __builtin_alloca (n);
+  return test_alloca_2 (n - 1);
+}
+
+int test_alloca_4 (size_t n)
+{
+  void *buf = __builtin_alloca (n);
+  if (n > 0)
+    return test_alloca_2 (n - 1);
+  return 42;
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/infinite-recursion-inlining.c b/gcc/testsuite/gcc.dg/analyzer/infinite-recursion-inlining.c
new file mode 100644
index 0000000000000000000000000000000000000000..c885c922fe187490a3230459b6e228a67ff1f441
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/infinite-recursion-inlining.c
@@ -0,0 +1,116 @@
+/* A copy of infinite-recursion-2.c, to see what inlining does to the IR
+   when we see it.
+
+   Many cases get converted by the optimizer into iteration, and
+   into infinite loops, sometimes trivial ones.
+
+   Right now this is a documented limitation of the warning, but perhaps
+   could be readdressed by moving the analyzer earlier.   */
+
+/* { dg-additional-options "-O3" } */
+
+void test_direct (void)
+{
+  test_direct (); /* Ideally would warn here, but it becomes an infinite loop.  */
+}
+
+void test_guarded (int flag)
+{
+  if (flag)
+    test_guarded (flag);  /* Ideally would warn here, but it becomes an infinite loop.  */
+}
+
+void test_flipped_guard (int flag)
+{
+  if (flag)
+    test_guarded (!flag);
+}
+
+void test_param_variant (int depth)
+{
+  if (depth > 0)
+    test_param_variant (depth - 1);
+}
+
+void test_unguarded_param_variant (int depth)
+{
+  test_unguarded_param_variant (depth - 1); /* Ideally would warn here, but it becomes an infinite loop.  */
+}
+
+int g;
+
+void test_global_variant ()
+{
+  if (g-- > 0)
+    test_global_variant ();
+}
+
+/* This is a bounded recursion, as "n" is decremented before recursing... */
+
+int test_while_do_predecrement_param (int n)
+{
+  int x = 0;
+  while (n)
+    x += test_while_do_predecrement_param (--n);
+  return x;
+}
+
+/* ...whereas this one is unbounded, as "n" is decremented *after* the
+   recursive call, and so is repeatedly called with the same value.  */
+
+int test_while_do_postdecrement_param (int n)
+{
+  int x = 0;
+  while (n)
+    x += test_while_do_postdecrement_param (n--); /* { dg-warning "infinite recursion" } */
+  return x;
+}
+/* This is a bounded recursion, as "n" is decremented before recursing... */
+
+int test_do_while_predecrement_param (int n)
+{
+  int x = 0;
+  do
+    x += test_do_while_predecrement_param (--n);
+  while (--n);
+  return x;
+}
+
+/* ...whereas this one is unbounded, as "n" is decremented *after* the
+   recursive call, and so is repeatedly called with the same value.  */
+
+int test_do_while_postdecrement_param (int n)
+{
+  int x = 0;
+  do
+    x += test_do_while_postdecrement_param (n--); /* { dg-warning "infinite recursion" } */
+  while (--n);
+  return x;
+}
+
+/* Various cases of decrementing "n" as the recursion proceeds where
+   not every path recurses, but we're not actually checking "n", so
+   if "flag" is true it's an infinite recursion.  */
+
+void test_partially_guarded_postdecrement (int flag, int n)
+{
+  /* Ideally we'd catch this, but it becomes an infinite loop.  */
+  if (flag)
+    test_partially_guarded_postdecrement (flag, n--);
+}
+
+void test_partially_guarded_predecrement (int flag, int n)
+{
+  /* We fail to report this; we see that "n" is changing,
+     though it isn't relevant to whether we recurse.  */
+  if (flag)
+    test_partially_guarded_predecrement (flag, --n); /* { dg-warning "infinite recursion" "TODO" { xfail *-*-* } } */
+}
+
+void test_partially_guarded_subtract (int flag, int n)
+{
+  /* We fail to report this; we see that "n" is changing,
+     though it isn't relevant to whether we recurse.  */
+  if (flag)
+    test_partially_guarded_subtract (flag, n - 1); /* { dg-warning "infinite recursion" "TODO" { xfail *-*-* } } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/infinite-recursion-multiline-1.c b/gcc/testsuite/gcc.dg/analyzer/infinite-recursion-multiline-1.c
new file mode 100644
index 0000000000000000000000000000000000000000..e236dd48712dbbf61949cccaa4c82c32fb5ed8d8
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/infinite-recursion-multiline-1.c
@@ -0,0 +1,41 @@
+/* Integration test of how the execution path looks for
+   -Wanalyzer-infinite-recursion.  */
+
+/* { dg-additional-options "-fdiagnostics-show-path-depths" } */
+/* { dg-additional-options "-fdiagnostics-path-format=inline-events -fdiagnostics-show-caret" } */
+
+void foo (int flag)
+{
+  if (flag)
+    foo (flag); /* { dg-warning "infinite recursion" } */
+}
+
+/* { dg-begin-multiline-output "" }
+     foo (flag);
+     ^~~~~~~~~~
+  'foo': events 1-4 (depth 1)
+    |
+    | void foo (int flag)
+    |      ^~~
+    |      |
+    |      (1) initial entry to 'foo'
+    |
+    |   if (flag)
+    |      ~
+    |      |
+    |      (2) following 'true' branch (when 'flag != 0')...
+    |     foo (flag);
+    |     ~~~~~~~~~~
+    |     |
+    |     (3) ...to here
+    |     (4) calling 'foo' from 'foo'
+    |
+    +--> 'foo': events 5-6 (depth 2)
+           |
+           | void foo (int flag)
+           |      ^~~
+           |      |
+           |      (5) recursive entry to 'foo'; previously entered at (1)
+           |      (6) apparently infinite recursion
+           |
+   { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/infinite-recursion-multiline-2.c b/gcc/testsuite/gcc.dg/analyzer/infinite-recursion-multiline-2.c
new file mode 100644
index 0000000000000000000000000000000000000000..2c69dd508f0df59a23f4441b995105b78e122672
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/infinite-recursion-multiline-2.c
@@ -0,0 +1,93 @@
+/* Integration test of how the execution path looks for
+   -Wanalyzer-infinite-recursion.  */
+
+/* { dg-additional-options "-fdiagnostics-show-path-depths" } */
+/* { dg-additional-options "-fdiagnostics-path-format=inline-events -fdiagnostics-show-caret" } */
+
+void mutual_2 (void);
+
+void mutual_1 (void)
+{
+  mutual_2 (); /* { dg-warning "infinite recursion" } */
+}
+
+void mutual_2 (void)
+{
+  mutual_1 (); /* { dg-warning "infinite recursion" } */
+}
+
+
+/* { dg-begin-multiline-output "" }
+   mutual_2 ();
+   ^~~~~~~~~~~
+  'mutual_2': events 1-2 (depth 1)
+    |
+    | void mutual_2 (void)
+    |      ^~~~~~~~
+    |      |
+    |      (1) initial entry to 'mutual_2'
+    |
+    |   mutual_1 ();
+    |   ~~~~~~~~~~~
+    |   |
+    |   (2) calling 'mutual_1' from 'mutual_2'
+    |
+    +--> 'mutual_1': events 3-4 (depth 2)
+           |
+           | void mutual_1 (void)
+           |      ^~~~~~~~
+           |      |
+           |      (3) entry to 'mutual_1'
+           |
+           |   mutual_2 ();
+           |   ~~~~~~~~~~~
+           |   |
+           |   (4) calling 'mutual_2' from 'mutual_1'
+           |
+           +--> 'mutual_2': events 5-6 (depth 3)
+                  |
+                  | void mutual_2 (void)
+                  |      ^~~~~~~~
+                  |      |
+                  |      (5) recursive entry to 'mutual_2'; previously entered at (1)
+                  |      (6) apparently infinite chain of mutually-recursive function calls, consuming 2 stack frames per recursion
+                  |
+   { dg-end-multiline-output "" } */
+
+
+/* { dg-begin-multiline-output "" }
+   mutual_1 ();
+   ^~~~~~~~~~~
+  'mutual_1': events 1-2 (depth 1)
+    |
+    | void mutual_1 (void)
+    |      ^~~~~~~~
+    |      |
+    |      (1) initial entry to 'mutual_1'
+    |
+    |   mutual_2 ();
+    |   ~~~~~~~~~~~
+    |   |
+    |   (2) calling 'mutual_2' from 'mutual_1'
+    |
+    +--> 'mutual_2': events 3-4 (depth 2)
+           |
+           | void mutual_2 (void)
+           |      ^~~~~~~~
+           |      |
+           |      (3) entry to 'mutual_2'
+           |
+           |   mutual_1 ();
+           |   ~~~~~~~~~~~
+           |   |
+           |   (4) calling 'mutual_1' from 'mutual_2'
+           |
+           +--> 'mutual_1': events 5-6 (depth 3)
+                  |
+                  | void mutual_1 (void)
+                  |      ^~~~~~~~
+                  |      |
+                  |      (5) recursive entry to 'mutual_1'; previously entered at (1)
+                  |      (6) apparently infinite chain of mutually-recursive function calls, consuming 2 stack frames per recursion
+                  |
+   { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/infinite-recursion-variadic.c b/gcc/testsuite/gcc.dg/analyzer/infinite-recursion-variadic.c
new file mode 100644
index 0000000000000000000000000000000000000000..eafbeba08cdb6cb70299a7809aebc5fe1c4686e8
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/infinite-recursion-variadic.c
@@ -0,0 +1,34 @@
+int test_variadic_1 (int n, ...)
+{
+  __builtin_va_list args;
+  int total =0;
+  int i;
+
+  __builtin_va_start(args, n);
+
+  for (i = 0; i < n; i++)
+    total += __builtin_va_arg(args, int);
+
+  __builtin_va_end(args);
+
+  return total;  
+}
+
+int test_variadic_2 (int n, ...)
+{
+  return test_variadic_2 (n, 42); /* { dg-warning "-Wanalyzer-infinite-recursion" } */
+}
+
+int test_variadic_3 (int n, ...)
+{
+  if (n > 0) /* { dg-message "when 'n > 0'" } */
+    return test_variadic_3 (n, 42); /* { dg-warning "-Wanalyzer-infinite-recursion" } */
+  return 0;
+}
+
+int test_variadic_4 (int n, ...)
+{
+  if (n > 0)
+    return test_variadic_4 (n - 1, 42);
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/infinite-recursion.c b/gcc/testsuite/gcc.dg/analyzer/infinite-recursion.c
index b770e128bc689c39d568e612332f6608220c56ef..6b7d25cfabe6c576b0e13964813020fe7d6b7779 100644
--- a/gcc/testsuite/gcc.dg/analyzer/infinite-recursion.c
+++ b/gcc/testsuite/gcc.dg/analyzer/infinite-recursion.c
@@ -11,7 +11,7 @@ void test(int flag)
     marker_B();
 
     /* Recurse, infinitely, as it happens: */
-    test(flag);
+    test(flag); /* { dg-warning "infinite recursion" } */
 
     marker_C();
   }
@@ -30,26 +30,26 @@ void mutual_test_1 (int flag)
 {
   marker_A ();
   if (flag)
-    mutual_test_2 (flag);
+    mutual_test_2 (flag); /* { dg-warning "infinite recursion" } */
 }
 
 void mutual_test_2 (int flag)
 {
   marker_B ();
   if (flag)
-    mutual_test_3 (flag);
+    mutual_test_3 (flag); /* { dg-warning "infinite recursion" } */
 }
 
 void mutual_test_3 (int flag)
 {
   marker_C ();
   if (flag)
-    mutual_test_4 (flag);
+    mutual_test_4 (flag); /* { dg-warning "infinite recursion" } */
 }
 
 void mutual_test_4 (int flag)
 {
   marker_D ();
   if (flag)
-    mutual_test_1 (flag);
+    mutual_test_1 (flag); /* { dg-warning "infinite recursion" } */
 }
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-12.c b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-12.c
index fce5437edd16e6ab2824d97086ca3747d85bf0f7..3813c9ae45ea8f44dbbe79b9fdebd46dbf470768 100644
--- a/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-12.c
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-12.c
@@ -3,5 +3,5 @@
 void recursive_free (void *ptr)
 {
   free (ptr); /* { dg-warning "double-'free' of 'ptr'" } */
-  recursive_free (ptr);
+  recursive_free (ptr); /* { dg-warning "infinite recursion" } */
 }
diff --git a/gcc/testsuite/gcc.dg/analyzer/pr105365.c b/gcc/testsuite/gcc.dg/analyzer/pr105365.c
index aa576d086328950e4c8a15b2314f826a33b10a75..c2e6b2e16fc24287c55c17a5ad194b3d5dbbd419 100644
--- a/gcc/testsuite/gcc.dg/analyzer/pr105365.c
+++ b/gcc/testsuite/gcc.dg/analyzer/pr105365.c
@@ -13,5 +13,5 @@ foo(_Float32 k) {
   f /= (_Complex char)__builtin_llround(g);
   k /= (cf32)__builtin_copysignf(0, i);
   bar(f + k);
-  foo(0);
+  foo(0); /* { dg-warning "infinite recursion" } */
 }
diff --git a/gcc/testsuite/gcc.dg/analyzer/pr105366.c b/gcc/testsuite/gcc.dg/analyzer/pr105366.c
index 3dba870e4e96031ae0cfe2ee68c6b4d445e014e2..af8a2c4f9cc9208b9dda6b1bd049ef04fb61806e 100644
--- a/gcc/testsuite/gcc.dg/analyzer/pr105366.c
+++ b/gcc/testsuite/gcc.dg/analyzer/pr105366.c
@@ -15,5 +15,5 @@ foo(u32 u, __int128 i) {
   u /= (_Complex short)s;
   u32 r = u + c;
   bar(r);
-  foo(0, 0);
+  foo(0, 0); /* { dg-warning "infinite recursion" } */
 }
diff --git a/gcc/testsuite/gcc.dg/analyzer/pr97029.c b/gcc/testsuite/gcc.dg/analyzer/pr97029.c
index e3b453117791173a7bff8ec23ce1219b172da4b5..2ab2d4155ccff8f379b1ac60e92057ca2dae2d07 100644
--- a/gcc/testsuite/gcc.dg/analyzer/pr97029.c
+++ b/gcc/testsuite/gcc.dg/analyzer/pr97029.c
@@ -5,5 +5,5 @@ struct vj {
 void
 setjmp (struct vj pl)
 {
-  setjmp (pl);
+  setjmp (pl); /* { dg-warning "infinite recursion" } */
 }