diff --git a/gcc/analyzer/analyzer.opt b/gcc/analyzer/analyzer.opt
index 23dfc797cea..4aea52d3a87 100644
--- a/gcc/analyzer/analyzer.opt
+++ b/gcc/analyzer/analyzer.opt
@@ -206,6 +206,10 @@ fanalyzer-call-summaries
 Common Var(flag_analyzer_call_summaries) Init(0)
 Approximate the effect of function calls to simplify analysis.
 
+fanalyzer-undo-inlining
+Common Var(flag_analyzer_undo_inlining) Init(1)
+Try to reconstruct function calls and returns after inlining.
+
 fanalyzer-verbose-edges
 Common Var(flag_analyzer_verbose_edges) Init(0)
 Emit more verbose descriptions of control flow in diagnostics.
diff --git a/gcc/analyzer/checker-path.cc b/gcc/analyzer/checker-path.cc
index 8aa5bf716d9..0133dc94137 100644
--- a/gcc/analyzer/checker-path.cc
+++ b/gcc/analyzer/checker-path.cc
@@ -25,6 +25,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "function.h"
 #include "basic-block.h"
 #include "gimple.h"
+#include "diagnostic-core.h"
 #include "gimple-pretty-print.h"
 #include "fold-const.h"
 #include "function.h"
@@ -54,6 +55,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "analyzer/program-state.h"
 #include "analyzer/checker-path.h"
 #include "gimple-iterator.h"
+#include "inlining-iterator.h"
 #include "analyzer/supergraph.h"
 #include "analyzer/pending-diagnostic.h"
 #include "analyzer/diagnostic-manager.h"
@@ -99,6 +101,8 @@ event_kind_to_string (enum event_kind ek)
       return "EK_START_CONSOLIDATED_CFG_EDGES";
     case EK_END_CONSOLIDATED_CFG_EDGES:
       return "EK_END_CONSOLIDATED_CFG_EDGES";
+    case EK_INLINED_CALL:
+      return "EK_INLINED_CALL";
     case EK_SETJMP:
       return "EK_SETJMP";
     case EK_REWIND_FROM_LONGJMP:
@@ -110,8 +114,71 @@ event_kind_to_string (enum event_kind ek)
     }
 }
 
+/* A class for fixing up fndecls and stack depths in checker_event, based
+   on inlining records.
+
+   The early inliner runs before the analyzer, which can lead to confusing
+   output.
+
+   Tne base fndecl and depth within a checker_event are from call strings
+   in program_points, which reflect the call strings after inlining.
+   This class lets us offset the depth and fix up the reported fndecl and
+   stack depth to better reflect the user's original code.  */
+
+class inlining_info
+{
+public:
+  inlining_info (location_t loc)
+  {
+    inlining_iterator iter (loc);
+    m_inner_fndecl = iter.get_fndecl ();
+    int num_frames = 0;
+    while (!iter.done_p ())
+      {
+	m_outer_fndecl = iter.get_fndecl ();
+	num_frames++;
+	iter.next ();
+      }
+    if (num_frames > 1)
+      m_extra_frames = num_frames - 1;
+    else
+      m_extra_frames = 0;
+  }
+
+  tree get_inner_fndecl () const { return m_inner_fndecl; }
+  int get_extra_frames () const { return m_extra_frames; }
+
+private:
+  tree m_outer_fndecl;
+  tree m_inner_fndecl;
+  int m_extra_frames;
+};
+
 /* class checker_event : public diagnostic_event.  */
 
+/* checker_event's ctor.  */
+
+checker_event::checker_event (enum event_kind kind,
+			      location_t loc, tree fndecl, int depth)
+: m_kind (kind), m_loc (loc),
+  m_original_fndecl (fndecl), m_effective_fndecl (fndecl),
+  m_original_depth (depth), m_effective_depth (depth),
+  m_pending_diagnostic (NULL), m_emission_id (),
+  m_logical_loc (fndecl)
+{
+  /* Update effective fndecl and depth if inlining has been recorded.  */
+  if (flag_analyzer_undo_inlining)
+    {
+      inlining_info info (loc);
+      if (info.get_inner_fndecl ())
+	{
+	  m_effective_fndecl = info.get_inner_fndecl ();
+	  m_effective_depth += info.get_extra_frames ();
+	  m_logical_loc = tree_logical_location (m_effective_fndecl);
+	}
+    }
+}
+
 /* No-op implementation of diagnostic_event::get_meaning vfunc for
    checker_event: checker events have no meaning by default.  */
 
@@ -127,11 +194,21 @@ void
 checker_event::dump (pretty_printer *pp) const
 {
   label_text event_desc (get_desc (false));
-  pp_printf (pp, "\"%s\" (depth %i, m_loc=%x)",
-	     event_desc.m_buffer,
-	     get_stack_depth (),
-	     get_location ());
+  pp_printf (pp, "\"%s\" (depth %i",
+	     event_desc.m_buffer, m_effective_depth);
   event_desc.maybe_free ();
+
+  if (m_effective_depth != m_original_depth)
+    pp_printf (pp, " corrected from %i",
+	       m_original_depth);
+  if (m_effective_fndecl)
+    {
+      pp_printf (pp, ", fndecl %qE", m_effective_fndecl);
+      if (m_effective_fndecl != m_original_fndecl)
+	pp_printf (pp, " corrected from %qE", m_original_fndecl);
+    }
+  pp_printf (pp, ", m_loc=%x)",
+	     get_location ());
 }
 
 /* Hook for being notified when this event has its final id EMISSION_ID
@@ -248,7 +325,7 @@ region_creation_event::get_desc (bool) const
 label_text
 function_entry_event::get_desc (bool can_colorize) const
 {
-  return make_label_text (can_colorize, "entry to %qE", m_fndecl);
+  return make_label_text (can_colorize, "entry to %qE", m_effective_fndecl);
 }
 
 /* Implementation of diagnostic_event::get_meaning vfunc for
@@ -867,6 +944,26 @@ start_consolidated_cfg_edges_event::get_meaning () const
 		  (m_edge_sense ? PROPERTY_true : PROPERTY_false));
 }
 
+/* class inlined_call_event : public checker_event.  */
+
+label_text
+inlined_call_event::get_desc (bool can_colorize) const
+{
+  return make_label_text (can_colorize,
+			  "inlined call to %qE from %qE",
+			  m_apparent_callee_fndecl,
+			  m_apparent_caller_fndecl);
+}
+
+/* Implementation of diagnostic_event::get_meaning vfunc for
+   reconstructed inlined function calls.  */
+
+diagnostic_event::meaning
+inlined_call_event::get_meaning () const
+{
+  return meaning (VERB_call, NOUN_function);
+}
+
 /* class setjmp_event : public checker_event.  */
 
 /* Implementation of diagnostic_event::get_desc vfunc for
@@ -1180,6 +1277,163 @@ checker_path::cfg_edge_pair_at_p (unsigned idx) const
 	  && m_events[idx + 1]->m_kind == EK_END_CFG_EDGE);
 }
 
+/* Consider a call from "outer" to "middle" which calls "inner",
+   where "inner" and "middle" have been inlined into "outer".
+
+   We expect the stmt locations for the inlined stmts to have a
+   chain like:
+
+     [{fndecl: inner},
+      {fndecl: middle, callsite: within middle to inner},
+      {fndecl: outer, callsite: without outer to middle}]
+
+   The location for the stmt will already be fixed up to reflect
+   the two extra frames, so that we have e.g. this as input
+   (for gcc.dg/analyzer/inlining-4.c):
+
+    before[0]:
+      EK_FUNCTION_ENTRY "entry to ‘outer’"
+      (depth 1, fndecl ‘outer’, m_loc=511c4)
+    before[1]:
+      EK_START_CFG_EDGE "following ‘true’ branch (when ‘flag != 0’)..."
+      (depth 3 corrected from 1,
+       fndecl ‘inner’ corrected from ‘outer’, m_loc=8000000f)
+    before[2]:
+      EK_END_CFG_EDGE "...to here"
+      (depth 1, fndecl ‘outer’, m_loc=0)
+    before[3]:
+      EK_WARNING "here (‘<unknown>’ is in state ‘null’)"
+      (depth 1, fndecl ‘outer’, m_loc=80000004)
+
+   We want to add inlined_call_events showing the calls, so that
+   the above becomes:
+
+    after[0]:
+      EK_FUNCTION_ENTRY "entry to ‘outer’"
+      (depth 1, fndecl ‘outer’, m_loc=511c4)
+    after[1]:
+      EK_INLINED_CALL "inlined call to ‘middle’ from ‘outer’"
+      (depth 1, fndecl ‘outer’, m_loc=53300)
+    after[2]:
+      EK_INLINED_CALL "inlined call to ‘inner’ from ‘middle’"
+      (depth 2, fndecl ‘middle’, m_loc=4d2e0)
+    after[3]:
+      EK_START_CFG_EDGE "following ‘true’ branch (when ‘flag != 0’)..."
+      (depth 3 corrected from 1,
+       fndecl ‘inner’ corrected from ‘outer’, m_loc=8000000f)
+    after[4]: EK_END_CFG_EDGE "...to here"
+      (depth 1, fndecl ‘outer’, m_loc=0)
+    after[5]: EK_WARNING "here (‘<unknown>’ is in state ‘null’)"
+      (depth 1, fndecl ‘outer’, m_loc=80000004)
+
+    where we've added events between before[0] and before[1] to show
+    the inlined calls leading to the effective stack depths, making
+    the generated path much easier for a user to read.
+
+    Note how in the above we have a branch (before[1] to before[2])
+    where the locations were originally in different functions.
+    Hence we have to add these events quite late when generating
+    checker_path.  */
+
+void
+checker_path::inject_any_inlined_call_events (logger *logger)
+{
+  LOG_SCOPE (logger);
+
+  if (!flag_analyzer_undo_inlining)
+    return;
+
+  /* Build a copy of m_events with the new events inserted.  */
+  auto_vec<checker_event *> updated_events;
+
+  maybe_log (logger, "before");
+
+  hash_set<tree> blocks_in_prev_event;
+
+  for (unsigned ev_idx = 0; ev_idx < m_events.length (); ev_idx++)
+    {
+      checker_event *curr_event = m_events[ev_idx];
+      location_t curr_loc = curr_event->get_location ();
+      hash_set<tree> blocks_in_curr_event;
+
+      if (logger)
+	{
+	  logger->start_log_line ();
+	  logger->log_partial ("event[%i]: %s ", ev_idx,
+			       event_kind_to_string (curr_event->m_kind));
+	  curr_event->dump (logger->get_printer ());
+	  logger->end_log_line ();
+	  for (inlining_iterator iter (curr_event->get_location ());
+	       !iter.done_p (); iter.next ())
+	    {
+	      logger->start_log_line ();
+	      logger->log_partial ("  %qE (%p), fndecl: %qE, callsite: 0x%x",
+				   iter.get_block (), iter.get_block (),
+				   iter.get_fndecl (), iter.get_callsite ());
+	      if (iter.get_callsite ())
+		dump_location (logger->get_printer (), iter.get_callsite ());
+	      logger->end_log_line ();
+	    }
+	}
+
+      /* We want to add events to show inlined calls.
+
+	 We want to show changes relative to the previous event, omitting
+	 the commonality between the inlining chain.
+
+	 The chain is ordered from innermost frame to outermost frame;
+	 we want to walk it backwards to show the calls, so capture it
+	 in a vec.  */
+      struct chain_element { tree m_block; tree m_fndecl; };
+      auto_vec<chain_element> elements;
+      for (inlining_iterator iter (curr_loc); !iter.done_p (); iter.next ())
+	{
+	  chain_element ce;
+	  ce.m_block = iter.get_block ();
+	  ce.m_fndecl = iter.get_fndecl ();
+
+	  if (!blocks_in_prev_event.contains (ce.m_block))
+	    elements.safe_push (ce);
+	  blocks_in_curr_event.add (ce.m_block);
+	}
+
+      /* Walk from outermost to innermost.  */
+      if (elements.length () > 0)
+	{
+	  int orig_stack_depth = curr_event->get_original_stack_depth ();
+	  for (unsigned element_idx = elements.length () - 1; element_idx > 0;
+	       element_idx--)
+	    {
+	      const chain_element &ce = elements[element_idx];
+	      int stack_depth_adjustment
+		= (blocks_in_curr_event.elements () - element_idx) - 1;
+	      if (location_t callsite = BLOCK_SOURCE_LOCATION (ce.m_block))
+		updated_events.safe_push
+		  (new inlined_call_event (callsite,
+					   elements[element_idx - 1].m_fndecl,
+					   ce.m_fndecl,
+					   orig_stack_depth,
+					   stack_depth_adjustment));
+	    }
+	}
+
+      /* Ideally we'd use assignment here:
+	   blocks_in_prev_event = blocks_in_curr_event; */
+      blocks_in_prev_event.empty ();
+      for (auto iter : blocks_in_curr_event)
+	blocks_in_prev_event.add (iter);
+
+      /* Add the existing event.  */
+      updated_events.safe_push (curr_event);
+    }
+
+  /* Replace m_events with updated_events.  */
+  m_events.truncate (0);
+  m_events.safe_splice (updated_events);
+
+  maybe_log (logger, " after");
+}
+
 } // namespace ana
 
 #endif /* #if ENABLE_ANALYZER */
diff --git a/gcc/analyzer/checker-path.h b/gcc/analyzer/checker-path.h
index 8960d56c8fa..24decf5ce3d 100644
--- a/gcc/analyzer/checker-path.h
+++ b/gcc/analyzer/checker-path.h
@@ -42,6 +42,7 @@ enum event_kind
   EK_RETURN_EDGE,
   EK_START_CONSOLIDATED_CFG_EDGES,
   EK_END_CONSOLIDATED_CFG_EDGES,
+  EK_INLINED_CALL,
   EK_SETJMP,
   EK_REWIND_FROM_LONGJMP,
   EK_REWIND_TO_SETJMP,
@@ -72,6 +73,7 @@ extern const char *event_kind_to_string (enum event_kind ek);
          return_edge (EK_RETURN_EDGE)
        start_consolidated_cfg_edges_event (EK_START_CONSOLIDATED_CFG_EDGES)
        end_consolidated_cfg_edges_event (EK_END_CONSOLIDATED_CFG_EDGES)
+       inlined_call_event (EK_INLINED_CALL)
        setjmp_event (EK_SETJMP)
        rewind_event
          rewind_from_longjmp_event (EK_REWIND_FROM_LONGJMP)
@@ -84,22 +86,14 @@ extern const char *event_kind_to_string (enum event_kind ek);
 class checker_event : public diagnostic_event
 {
 public:
-  checker_event (enum event_kind kind,
-		 location_t loc, tree fndecl, int depth)
-    : m_kind (kind), m_loc (loc), m_fndecl (fndecl), m_depth (depth),
-      m_pending_diagnostic (NULL), m_emission_id (),
-      m_logical_loc (fndecl)
-  {
-  }
-
   /* Implementation of diagnostic_event.  */
 
   location_t get_location () const final override { return m_loc; }
-  tree get_fndecl () const final override { return m_fndecl; }
-  int get_stack_depth () const final override { return m_depth; }
+  tree get_fndecl () const final override { return m_effective_fndecl; }
+  int get_stack_depth () const final override { return m_effective_depth; }
   const logical_location *get_logical_location () const final override
   {
-    if (m_fndecl)
+    if (m_effective_fndecl)
       return &m_logical_loc;
     else
       return NULL;
@@ -108,6 +102,8 @@ public:
 
   /* Additional functionality.  */
 
+  int get_original_stack_depth () const { return m_original_depth; }
+
   virtual void prepare_for_emission (checker_path *,
 				     pending_diagnostic *pd,
 				     diagnostic_event_id_t emission_id);
@@ -125,12 +121,18 @@ public:
 
   void set_location (location_t loc) { m_loc = loc; }
 
+protected:
+  checker_event (enum event_kind kind,
+		 location_t loc, tree fndecl, int depth);
+
  public:
   const enum event_kind m_kind;
  protected:
   location_t m_loc;
-  tree m_fndecl;
-  int m_depth;
+  tree m_original_fndecl;
+  tree m_effective_fndecl;
+  int m_original_depth;
+  int m_effective_depth;
   pending_diagnostic *m_pending_diagnostic;
   diagnostic_event_id_t m_emission_id; // only set once all pruning has occurred
   tree_logical_location m_logical_loc;
@@ -435,6 +437,34 @@ public:
   }
 };
 
+/* A concrete event subclass for describing an inlined call event
+   e.g. "inlined call to 'callee' from 'caller'".  */
+
+class inlined_call_event : public checker_event
+{
+public:
+  inlined_call_event (location_t loc,
+		      tree apparent_callee_fndecl,
+		      tree apparent_caller_fndecl,
+		      int actual_depth,
+		      int stack_depth_adjustment)
+  : checker_event (EK_INLINED_CALL, loc,
+		   apparent_caller_fndecl,
+		   actual_depth + stack_depth_adjustment),
+    m_apparent_callee_fndecl (apparent_callee_fndecl),
+    m_apparent_caller_fndecl (apparent_caller_fndecl)
+  {
+    gcc_assert (LOCATION_BLOCK (loc) == NULL);
+  }
+
+  label_text get_desc (bool /*can_colorize*/) const final override;
+  meaning get_meaning () const override;
+
+private:
+  tree m_apparent_callee_fndecl;
+  tree m_apparent_caller_fndecl;
+};
+
 /* A concrete event subclass for a setjmp or sigsetjmp call.  */
 
 class setjmp_event : public checker_event
@@ -643,6 +673,8 @@ public:
 
   bool cfg_edge_pair_at_p (unsigned idx) const;
 
+  void inject_any_inlined_call_events (logger *logger);
+
 private:
   DISABLE_COPY_AND_ASSIGN(checker_path);
 
diff --git a/gcc/analyzer/diagnostic-manager.cc b/gcc/analyzer/diagnostic-manager.cc
index 4e96a3667e3..8ea1f61776e 100644
--- a/gcc/analyzer/diagnostic-manager.cc
+++ b/gcc/analyzer/diagnostic-manager.cc
@@ -52,6 +52,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "basic-block.h"
 #include "gimple.h"
 #include "gimple-iterator.h"
+#include "inlining-iterator.h"
 #include "cgraph.h"
 #include "digraph.h"
 #include "analyzer/supergraph.h"
@@ -1390,6 +1391,8 @@ diagnostic_manager::emit_saved_diagnostic (const exploded_graph &eg,
   if (sd.m_trailing_eedge)
     add_events_for_eedge (pb, *sd.m_trailing_eedge, &emission_path, NULL);
 
+  emission_path.inject_any_inlined_call_events (get_logger ());
+
   emission_path.prepare_for_emission (sd.m_d);
 
   location_t loc
@@ -2454,6 +2457,11 @@ diagnostic_manager::prune_for_sm_diagnostic (checker_path *path,
 	  }
 	  break;
 
+	case EK_INLINED_CALL:
+	  /* We don't expect to see these yet, as they're added later.
+	     We'd want to keep them around.  */
+	  break;
+
 	case EK_SETJMP:
 	  /* TODO: only show setjmp_events that matter i.e. those for which
 	     there is a later rewind event using them.  */
diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc
index ca86166173c..7237cc1a1ca 100644
--- a/gcc/analyzer/engine.cc
+++ b/gcc/analyzer/engine.cc
@@ -2904,7 +2904,7 @@ public:
     return make_label_text (can_colorize,
 			    "function %qE used as initializer for field %qE"
 			    " marked with %<__attribute__((tainted_args))%>",
-			    m_fndecl, m_field);
+			    get_fndecl (), m_field);
   }
 
 private:
diff --git a/gcc/analyzer/inlining-iterator.h b/gcc/analyzer/inlining-iterator.h
new file mode 100644
index 00000000000..a7adc7c6bf2
--- /dev/null
+++ b/gcc/analyzer/inlining-iterator.h
@@ -0,0 +1,109 @@
+/* Iterator for walking a chain of inlining locations.
+   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/>.  */
+
+#ifndef GCC_ANALYZER_INLINING_ITERATOR_H
+#define GCC_ANALYZER_INLINING_ITERATOR_H
+
+/* Iterator for walking a chain of inlining locations.
+
+   The fndecls and locations will be traversed from innermost to outermost.
+   For example, given:
+
+    inline void inner (void)
+    {
+       ...LOCATION HERE...
+    }
+    void outer (void)
+    {
+       inner (); <-- CALLSITE
+    }
+
+   then the fndecl will be "inner" on the initial iteration, and "outer" on
+   the second (final) iteration.
+
+   Compare with lhd_print_error_function, cp_print_error_function,
+   and optrecord_json_writer::inlining_chain_to_json.  */
+
+class inlining_iterator
+{
+public:
+  inlining_iterator (location_t loc)
+  : m_abstract_origin (LOCATION_BLOCK (loc)),
+    m_callsite (UNKNOWN_LOCATION), m_fndecl (NULL),
+    m_next_abstract_origin (NULL)
+  {
+    prepare_iteration ();
+  }
+
+  bool done_p () const { return m_abstract_origin == NULL; }
+
+  void next ()
+  {
+    m_abstract_origin = m_next_abstract_origin;
+    prepare_iteration ();
+  }
+
+  tree get_fndecl () const { return m_fndecl; }
+  location_t get_callsite () const { return m_callsite; }
+  tree get_block () const { return m_abstract_origin; }
+
+private:
+  void prepare_iteration ()
+  {
+    if (done_p ())
+      return;
+    tree block = m_abstract_origin;
+    m_callsite = BLOCK_SOURCE_LOCATION (block);
+    m_fndecl = NULL;
+    block = BLOCK_SUPERCONTEXT (block);
+    while (block && TREE_CODE (block) == BLOCK
+	   && BLOCK_ABSTRACT_ORIGIN (block))
+      {
+	tree ao = BLOCK_ABSTRACT_ORIGIN (block);
+	if (TREE_CODE (ao) == FUNCTION_DECL)
+	  {
+	    m_fndecl = ao;
+	    break;
+	  }
+	else if (TREE_CODE (ao) != BLOCK)
+	  break;
+
+	block = BLOCK_SUPERCONTEXT (block);
+      }
+    if (m_fndecl)
+      m_next_abstract_origin = block;
+    else
+      {
+	while (block && TREE_CODE (block) == BLOCK)
+	  block = BLOCK_SUPERCONTEXT (block);
+
+	if (block && TREE_CODE (block) == FUNCTION_DECL)
+	  m_fndecl = block;
+	m_next_abstract_origin = NULL;
+      }
+  }
+
+  tree m_abstract_origin;
+  location_t m_callsite;
+  tree m_fndecl;
+  tree m_next_abstract_origin;
+};
+
+#endif /* GCC_ANALYZER_INLINING_ITERATOR_H */
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index b6c0305f198..0eab7e4d31b 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -425,6 +425,7 @@ Objective-C and Objective-C++ Dialects}.
 -fno-analyzer-state-merge @gol
 -fno-analyzer-state-purge @gol
 -fanalyzer-transitivity @gol
+-fno-analyzer-undo-inlining @gol
 -fanalyzer-verbose-edges @gol
 -fanalyzer-verbose-state-changes @gol
 -fanalyzer-verbosity=@var{level} @gol
@@ -5233,7 +5234,10 @@ This option provides additional information when printing control-flow paths
 associated with a diagnostic.
 
 If this is option is provided then the stack depth will be printed for
-each run of events within @option{-fdiagnostics-path-format=separate-events}.
+each run of events within @option{-fdiagnostics-path-format=inline-events}.
+If provided with @option{-fdiagnostics-path-format=separate-events}, then
+the stack depth and function declaration will be appended when printing
+each event.
 
 This is intended for use by GCC developers and plugin developers when
 debugging diagnostics that report interprocedural control flow.
@@ -10202,6 +10206,25 @@ be suppressed, for debugging state-handling issues.
 @opindex fno-analyzer-transitivity
 This option enables transitivity of constraints within the analyzer.
 
+@item -fno-analyzer-undo-inlining
+@opindex fanalyzer-undo-inlining
+@opindex fno-analyzer-undo-inlining
+This option is intended for analyzer developers.
+
+@option{-fanalyzer} runs relatively late compared to other code analysis
+tools, and some optimizations have already been applied to the code.  In
+particular function inlining may have occurred, leading to the
+interprocedural execution paths emitted by the analyzer containing
+function frames that don't correspond to those in the original source
+code.
+
+By default the analyzer attempts to reconstruct the original function
+frames, and to emit events showing the inlined calls.
+
+With @option{-fno-analyzer-undo-inlining} this attempt to reconstruct
+the original frame information can be be disabled, which may be of help
+when debugging issues in the analyzer.
+
 @item -fanalyzer-verbose-edges
 This option is intended for analyzer developers.  It enables more
 verbose, lower-level detail in the descriptions of control flow
diff --git a/gcc/testsuite/gcc.dg/analyzer/inlining-1-multiline.c b/gcc/testsuite/gcc.dg/analyzer/inlining-1-multiline.c
new file mode 100644
index 00000000000..79621f10e9b
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/inlining-1-multiline.c
@@ -0,0 +1,56 @@
+/* As per inlining-1.c, but testing how the ASCII art version of
+   the path looks.  */
+
+/* { dg-additional-options "-O2 -fdiagnostics-show-path-depths" } */
+/* { dg-additional-options "-fdiagnostics-path-format=inline-events -fdiagnostics-show-caret" } */
+
+void foo (void *p)
+{
+  __builtin_free (p); /* { dg-warning "double-'free' of 'q'" "warning" } */
+}
+
+void bar (void *q)
+{
+  foo (q);
+  foo (q);
+}
+
+/* { dg-begin-multiline-output "" }
+   __builtin_free (p);
+   ^~~~~~~~~~~~~~~~~~
+  'bar': events 1-2 (depth 1)
+    |
+    | void bar (void *q)
+    |      ^~~
+    |      |
+    |      (1) entry to 'bar'
+    |
+    |   foo (q);
+    |   ~   
+    |   |
+    |   (2) inlined call to 'foo' from 'bar'
+    |
+    +--> 'foo': event 3 (depth 2)
+           |
+           |   __builtin_free (p);
+           |   ^~~~~~~~~~~~~~~~~~
+           |   |
+           |   (3) first 'free' here
+           |
+    <------+
+    |
+  'bar': event 4 (depth 1)
+    |
+    |   foo (q);
+    |   ^
+    |   |
+    |   (4) inlined call to 'foo' from 'bar'
+    |
+    +--> 'foo': event 5 (depth 2)
+           |
+           |   __builtin_free (p);
+           |   ^~~~~~~~~~~~~~~~~~
+           |   |
+           |   (5) second 'free' here; first 'free' was at (3)
+           |
+   { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/inlining-1-no-undo.c b/gcc/testsuite/gcc.dg/analyzer/inlining-1-no-undo.c
new file mode 100644
index 00000000000..bad0f68fec0
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/inlining-1-no-undo.c
@@ -0,0 +1,18 @@
+/* Test for -fno-analyzer-undo-inlining.
+   Verify that we can disable reconstruction of fndecl and stack depth
+   information.  */
+
+/* { dg-additional-options "-O2 -fdiagnostics-show-path-depths -fno-analyzer-undo-inlining" } */
+
+void foo (void *p)
+{
+  __builtin_free (p); /* { dg-warning "double-'free' of 'q'" "warning" } */
+  /* { dg-message "\\(1\\) first 'free' here \\(fndecl 'bar', depth 1\\)" "1st free message" { target *-*-* } .-1 } */
+  /* { dg-message "\\(2\\) second 'free' here; first 'free' was at \\(1\\) \\(fndecl 'bar', depth 1\\)" "2nd free message" { target *-*-* } .-2 } */
+}
+
+void bar (void *q)
+{
+  foo (q);
+  foo (q);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/inlining-1.c b/gcc/testsuite/gcc.dg/analyzer/inlining-1.c
new file mode 100644
index 00000000000..a9797eace19
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/inlining-1.c
@@ -0,0 +1,17 @@
+/* Verify that we can reconstruct fndecl and stack depth information
+   after early inlining.  */
+
+/* { dg-additional-options "-O2 -fdiagnostics-show-path-depths" } */
+
+void foo (void *p)
+{
+  __builtin_free (p); /* { dg-warning "double-'free' of 'q'" "warning" } */
+  /* { dg-message "\\(3\\) first 'free' here \\(fndecl 'foo', depth 2\\)" "1st free message" { target *-*-* } .-1 } */
+  /* { dg-message "\\(5\\) second 'free' here; first 'free' was at \\(3\\) \\(fndecl 'foo', depth 2\\)" "2nd free message" { target *-*-* } .-2 } */
+}
+
+void bar (void *q) /* { dg-message "\\(1\\) entry to 'bar' \\(fndecl 'bar', depth 1\\)" } */
+{
+  foo (q); /* { dg-message "\\(2\\) inlined call to 'foo' from 'bar' \\(fndecl 'bar', depth 1\\)" } */
+  foo (q); /* { dg-message "\\(4\\) inlined call to 'foo' from 'bar' \\(fndecl 'bar', depth 1\\)" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/inlining-2-multiline.c b/gcc/testsuite/gcc.dg/analyzer/inlining-2-multiline.c
new file mode 100644
index 00000000000..0a006b3a58f
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/inlining-2-multiline.c
@@ -0,0 +1,46 @@
+/* As per inlining-2.c, but testing how the ASCII art version of
+   the path looks.  */
+
+/* { dg-additional-options "-O2 -fdiagnostics-show-path-depths" } */
+/* { dg-additional-options "-fdiagnostics-path-format=inline-events -fdiagnostics-show-caret" } */
+
+static void __analyzer_foo (void *p)
+{
+  __builtin_free (p);
+
+  __builtin_free (p); /* { dg-warning "double-'free' of 'q'" "warning" } */
+}
+
+void bar (void *q)
+{
+  __analyzer_foo (q);
+}
+
+/* { dg-begin-multiline-output "" }
+   __builtin_free (p);
+   ^~~~~~~~~~~~~~~~~~
+  'bar': events 1-2 (depth 1)
+    |
+    | void bar (void *q)
+    |      ^~~
+    |      |
+    |      (1) entry to 'bar'
+    |
+    |   __analyzer_foo (q);
+    |   ~   
+    |   |
+    |   (2) inlined call to '__analyzer_foo' from 'bar'
+    |
+    +--> '__analyzer_foo': events 3-4 (depth 2)
+           |
+           |   __builtin_free (p);
+           |   ^~~~~~~~~~~~~~~~~~
+           |   |
+           |   (3) first 'free' here
+           |
+           |   __builtin_free (p);
+           |   ~~~~~~~~~~~~~~~~~~
+           |   |
+           |   (4) second 'free' here; first 'free' was at (3)
+           |
+   { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/inlining-2.c b/gcc/testsuite/gcc.dg/analyzer/inlining-2.c
new file mode 100644
index 00000000000..da06fa29d87
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/inlining-2.c
@@ -0,0 +1,17 @@
+/* Verify that we can reconstruct fndecl and stack depth information
+   after early inlining.  */
+
+/* { dg-additional-options "-O2 -fdiagnostics-show-path-depths" } */
+
+static void __analyzer_foo (void *p)
+{
+  __builtin_free (p); /* { dg-message "\\(3\\) first 'free' here \\(fndecl '__analyzer_foo', depth 2\\)" "1st free message" } */
+
+  __builtin_free (p); /* { dg-warning "double-'free' of 'q'" "warning" } */
+  /* { dg-message "\\(4\\) second 'free' here; first 'free' was at \\(3\\) \\(fndecl '__analyzer_foo', depth 2\\)" "2nd free message" { target *-*-* } .-1 } */
+}
+
+void bar (void *q) /* { dg-message "\\(1\\) entry to 'bar' \\(fndecl 'bar', depth 1\\)" } */
+{
+  __analyzer_foo (q); /* { dg-message "\\(2\\) inlined call to '__analyzer_foo' from 'bar' \\(fndecl 'bar', depth 1\\)" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/inlining-3-multiline.c b/gcc/testsuite/gcc.dg/analyzer/inlining-3-multiline.c
new file mode 100644
index 00000000000..15a2dd8f0e1
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/inlining-3-multiline.c
@@ -0,0 +1,64 @@
+/* As per inlining-3.c, but testing how the ASCII art version of
+   the path looks.  */
+
+/* { dg-additional-options "-O2 -fdiagnostics-show-path-depths" } */
+/* { dg-additional-options "-fdiagnostics-path-format=inline-events -fdiagnostics-show-caret" } */
+
+typedef __SIZE_TYPE__ size_t;
+#define NULL ((void *)0)
+
+struct input_file_st 
+{
+  char inpname[1];
+};
+
+typedef struct input_file_st input_file;
+
+static inline const char*
+get_input_file_name (const input_file *inpf)
+{
+  if (inpf)
+    return inpf->inpname;
+  return NULL;
+}
+
+size_t
+test (const input_file *inpf)
+{
+  const char *f = get_input_file_name (inpf);
+  return __builtin_strlen (f); /* { dg-warning "use of NULL" "warning" } */
+}
+
+/* { dg-begin-multiline-output "" }
+   return __builtin_strlen (f);
+          ^~~~~~~~~~~~~~~~~~~~
+  'test': events 1-2 (depth 1)
+    |
+    | test (const input_file *inpf)
+    | ^~~~
+    | |
+    | (1) entry to 'test'
+    |
+    |   const char *f = get_input_file_name (inpf);
+    |                   ~
+    |                   |
+    |                   (2) inlined call to 'get_input_file_name' from 'test'
+    |
+    +--> 'get_input_file_name': event 3 (depth 2)
+           |
+           |   if (inpf)
+           |      ^
+           |      |
+           |      (3) following 'false' branch (when 'inpf' is NULL)...
+           |
+    <------+
+    |
+  'test': events 4-5 (depth 1)
+    |
+    |   return __builtin_strlen (f);
+    |          ^~~~~~~~~~~~~~~~~~~~
+    |          |
+    |          (4) ...to here
+    |          (5) argument 1 ('<unknown>') NULL where non-null expected
+    |
+   { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/inlining-3.c b/gcc/testsuite/gcc.dg/analyzer/inlining-3.c
new file mode 100644
index 00000000000..7a292ac87fa
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/inlining-3.c
@@ -0,0 +1,30 @@
+/* Verify that we can reconstruct fndecl and stack depth information
+   after early inlining.  */
+
+/* { dg-additional-options "-O2 -fdiagnostics-show-path-depths" } */
+
+typedef __SIZE_TYPE__ size_t;
+#define NULL ((void *)0)
+
+struct input_file_st 
+{
+  char inpname[1];
+};
+
+typedef struct input_file_st input_file;
+
+static inline const char*
+get_input_file_name (const input_file *inpf)
+{
+  if (inpf) /* { dg-message "following 'false' branch \\(when 'inpf' is NULL\\)\\.\\.\\. \\(fndecl 'get_input_file_name', depth 2\\)" } */
+    return inpf->inpname;
+  return NULL;
+}
+
+size_t
+test (const input_file *inpf)
+{
+  const char *f = get_input_file_name (inpf);
+  return __builtin_strlen (f); /* { dg-warning "use of NULL" "warning" } */
+  /* { dg-message "NULL where non-null expected \\(fndecl 'test', depth 1\\)" "message" { target *-*-* } .-1 } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/inlining-4-multiline.c b/gcc/testsuite/gcc.dg/analyzer/inlining-4-multiline.c
new file mode 100644
index 00000000000..0413c39af03
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/inlining-4-multiline.c
@@ -0,0 +1,72 @@
+/* As per inlining-4.c, but testing how the ASCII art version of
+   the path looks.  */
+
+/* { dg-additional-options "-O2 -fdiagnostics-show-path-depths" } */
+/* { dg-additional-options "-fdiagnostics-path-format=inline-events -fdiagnostics-show-caret" } */
+
+#define NULL ((void *)0)
+
+static inline const char*
+inner (int flag)
+{
+  if (flag)
+    return NULL;
+  return "foo";
+}
+
+static inline const char*
+middle (int flag)
+{
+  return inner (flag);
+}
+
+char
+outer (int flag)
+{
+  return *middle (flag); /* { dg-warning "dereference of NULL" "warning" } */
+}
+
+/* { dg-begin-multiline-output "" }
+   return *middle (flag);
+          ^~~~~~~~~~~~~~
+  'outer': events 1-2 (depth 1)
+    |
+    | outer (int flag)
+    | ^~~~~
+    | |
+    | (1) entry to 'outer'
+    |
+    |   return *middle (flag);
+    |           ~
+    |           |
+    |           (2) inlined call to 'middle' from 'outer'
+    |
+    +--> 'middle': event 3 (depth 2)
+           |
+           |   return inner (flag);
+           |          ^
+           |          |
+           |          (3) inlined call to 'inner' from 'middle'
+           |
+           +--> 'inner': event 4 (depth 3)
+                  |
+                  |   if (flag)
+                  |      ^
+                  |      |
+                  |      (4) following 'true' branch (when 'flag != 0')...
+                  |
+    <-------------+
+    |
+  'outer': event 5 (depth 1)
+    |
+    |cc1:
+    | (5): ...to here
+    |
+  'outer': event 6 (depth 1)
+    |
+    |   return *middle (flag);
+    |          ^~~~~~~~~~~~~~
+    |          |
+    |          (6) dereference of NULL '<unknown>'
+    |
+   { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/inlining-4.c b/gcc/testsuite/gcc.dg/analyzer/inlining-4.c
new file mode 100644
index 00000000000..f4e42084c91
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/inlining-4.c
@@ -0,0 +1,27 @@
+/* Verify that we can reconstruct fndecl and stack depth information
+   after early inlining.  */
+
+/* { dg-additional-options "-O2 -fdiagnostics-show-path-depths" } */
+
+#define NULL ((void *)0)
+
+static inline const char*
+inner (int flag)
+{
+  if (flag) /* { dg-message "following 'true' branch \\(when 'flag != 0'\\)\\.\\.\\. \\(fndecl 'inner', depth 3\\)" } */
+    return NULL;
+  return "foo";
+}
+
+static inline const char*
+middle (int flag)
+{
+  return inner (flag);
+}
+
+char
+outer (int flag)
+{
+  return *middle (flag); /* { dg-warning "dereference of NULL" "warning" } */
+  /* { dg-message "\\(fndecl 'outer', depth 1\\)" "message" { target *-*-* } .-1 } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/inlining-5-multiline.c b/gcc/testsuite/gcc.dg/analyzer/inlining-5-multiline.c
new file mode 100644
index 00000000000..21b8fb978c8
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/inlining-5-multiline.c
@@ -0,0 +1,59 @@
+/* As per inlining-5.c, but testing how the ASCII art version of
+   the path looks.  */
+
+/* { dg-additional-options "-O2 -fdiagnostics-show-path-depths" } */
+/* { dg-additional-options "-fdiagnostics-path-format=inline-events -fdiagnostics-show-caret" } */
+
+static inline void
+inner (void *p)
+{
+  __builtin_free (p); /* { dg-warning "double-'free' of 'r'" } */
+}
+
+static inline void
+middle (void *q)
+{
+  __builtin_free (q);
+  inner (q);
+}
+
+void
+outer (void *r)
+{
+  middle (r);
+}
+
+/* { dg-begin-multiline-output "" }
+   __builtin_free (p);
+   ^~~~~~~~~~~~~~~~~~
+  'outer': events 1-2 (depth 1)
+    |
+    | outer (void *r)
+    | ^~~~~
+    | |
+    | (1) entry to 'outer'
+    |
+    |   middle (r);
+    |   ~
+    |   |
+    |   (2) inlined call to 'middle' from 'outer'
+    |
+    +--> 'middle': events 3-4 (depth 2)
+           |
+           |   __builtin_free (q);
+           |   ^~~~~~~~~~~~~~~~~~
+           |   |
+           |   (3) first 'free' here
+           |   inner (q);
+           |   ~
+           |   |
+           |   (4) inlined call to 'inner' from 'middle'
+           |
+           +--> 'inner': event 5 (depth 3)
+                  |
+                  |   __builtin_free (p);
+                  |   ^~~~~~~~~~~~~~~~~~
+                  |   |
+                  |   (5) second 'free' here; first 'free' was at (3)
+                  |
+   { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/inlining-5.c b/gcc/testsuite/gcc.dg/analyzer/inlining-5.c
new file mode 100644
index 00000000000..5104be0c615
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/inlining-5.c
@@ -0,0 +1,24 @@
+/* Verify that we can reconstruct fndecl and stack depth information
+   after early inlining.  */
+
+/* { dg-additional-options "-O2 -fdiagnostics-show-path-depths" } */
+
+static inline void
+inner (void *p)
+{
+  __builtin_free (p); /* { dg-warning "double-'free' of 'r'" } */
+  /* { dg-message "\\(5\\) second 'free' here; first 'free' was at \\(3\\) \\(fndecl 'inner', depth 3\\)" "2nd free message" { target *-*-* } .-1 } */
+}
+
+static inline void
+middle (void *q)
+{
+  __builtin_free (q); /* { dg-message "\\(3\\) first 'free' here \\(fndecl 'middle', depth 2\\)" "1st free message" } */
+  inner (q); /* { dg-message "\\(4\\) inlined call to 'inner' from 'middle' \\(fndecl 'middle', depth 2\\)" } */
+}
+
+void
+outer (void *r) /* { dg-message "\\(1\\) entry to 'outer' \\(fndecl 'outer', depth 1\\)" } */
+{
+  middle (r); /* { dg-message "\\(2\\) inlined call to 'middle' from 'outer' \\(fndecl 'outer', depth 1\\)" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/inlining-6-multiline.c b/gcc/testsuite/gcc.dg/analyzer/inlining-6-multiline.c
new file mode 100644
index 00000000000..9cec6146f37
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/inlining-6-multiline.c
@@ -0,0 +1,64 @@
+/* As per inlining-6.c, but testing how the ASCII art version of
+   the path looks.  */
+
+/* { dg-additional-options "-O2 -fdiagnostics-show-path-depths" } */
+/* { dg-additional-options "-fdiagnostics-path-format=inline-events -fdiagnostics-show-caret" } */
+
+static inline void
+inner (void *p)
+{
+  __builtin_free (p);
+}
+
+static inline void
+middle (void *q)
+{
+  inner (q);
+  __builtin_free (q); /* { dg-warning "double-'free' of 'r'" } */
+}
+
+void
+outer (void *r)
+{
+  middle (r);
+}
+
+/* { dg-begin-multiline-output "" }
+   __builtin_free (q);
+   ^~~~~~~~~~~~~~~~~~
+  'outer': events 1-2 (depth 1)
+    |
+    | outer (void *r)
+    | ^~~~~
+    | |
+    | (1) entry to 'outer'
+    |
+    |   middle (r);
+    |   ~
+    |   |
+    |   (2) inlined call to 'middle' from 'outer'
+    |
+    +--> 'middle': event 3 (depth 2)
+           |
+           |   inner (q);
+           |   ^
+           |   |
+           |   (3) inlined call to 'inner' from 'middle'
+           |
+           +--> 'inner': event 4 (depth 3)
+                  |
+                  |   __builtin_free (p);
+                  |   ^~~~~~~~~~~~~~~~~~
+                  |   |
+                  |   (4) first 'free' here
+                  |
+           <------+
+           |
+         'middle': event 5 (depth 2)
+           |
+           |   __builtin_free (q);
+           |   ^~~~~~~~~~~~~~~~~~
+           |   |
+           |   (5) second 'free' here; first 'free' was at (4)
+           |
+   { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/inlining-6.c b/gcc/testsuite/gcc.dg/analyzer/inlining-6.c
new file mode 100644
index 00000000000..e19e85a78e9
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/inlining-6.c
@@ -0,0 +1,23 @@
+/* Verify that we can reconstruct fndecl and stack depth information
+   after early inlining.  */
+
+/* { dg-additional-options "-O2 -fdiagnostics-show-path-depths" } */
+
+static inline void
+inner (void *p)
+{
+  __builtin_free (p);
+}
+
+static inline void
+middle (void *q)
+{
+  inner (q);
+  __builtin_free (q);  /* { dg-warning "double-'free' of 'r'" "warning" } */
+}
+
+void
+outer (void *r)
+{
+  middle (r);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/inlining-7-multiline.c b/gcc/testsuite/gcc.dg/analyzer/inlining-7-multiline.c
new file mode 100644
index 00000000000..956c6b90bb0
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/inlining-7-multiline.c
@@ -0,0 +1,128 @@
+/* As per inlining-7.c, but testing how the ASCII art version of
+   the path looks.  */
+
+/* { dg-additional-options "-O2 -fdiagnostics-show-path-depths" } */
+/* { dg-additional-options "-fdiagnostics-path-format=inline-events -fdiagnostics-show-caret" } */
+
+static inline void
+depth_6 (void *p)
+{
+  __builtin_free (p); /* { dg-warning "double-'free' of 'p1'" "warning" } */
+}
+
+static inline void
+depth_5 (void *p5)
+{
+  depth_6 (p5);
+}
+
+static inline void
+depth_4 (void *p4)
+{
+  depth_5 (p4);
+}
+
+static inline void
+depth_3 (void *p3)
+{
+  depth_4 (p3);
+  depth_4 (p3);
+}
+
+static inline void
+depth_2 (void *p2)
+{
+  depth_3 (p2);
+}
+
+void
+depth_1 (void *p1)
+{
+  depth_2 (p1);
+}
+
+/* We want the reconstructed call/return hierarchy to show
+   that two calls happen at depth_3, without popping the stack
+   back any further.  */
+
+/* { dg-begin-multiline-output "" }
+   __builtin_free (p);
+   ^~~~~~~~~~~~~~~~~~
+  'depth_1': events 1-2 (depth 1)
+    |
+    | depth_1 (void *p1)
+    | ^~~~~~~
+    | |
+    | (1) entry to 'depth_1'
+    |
+    |   depth_2 (p1);
+    |   ~
+    |   |
+    |   (2) inlined call to 'depth_2' from 'depth_1'
+    |
+    +--> 'depth_2': event 3 (depth 2)
+           |
+           |   depth_3 (p2);
+           |   ^
+           |   |
+           |   (3) inlined call to 'depth_3' from 'depth_2'
+           |
+           +--> 'depth_3': event 4 (depth 3)
+                  |
+                  |   depth_4 (p3);
+                  |   ^
+                  |   |
+                  |   (4) inlined call to 'depth_4' from 'depth_3'
+                  |
+                  +--> 'depth_4': event 5 (depth 4)
+                         |
+                         |   depth_5 (p4);
+                         |   ^
+                         |   |
+                         |   (5) inlined call to 'depth_5' from 'depth_4'
+                         |
+                         +--> 'depth_5': event 6 (depth 5)
+                                |
+                                |   depth_6 (p5);
+                                |   ^
+                                |   |
+                                |   (6) inlined call to 'depth_6' from 'depth_5'
+                                |
+                                +--> 'depth_6': event 7 (depth 6)
+                                       |
+                                       |   __builtin_free (p);
+                                       |   ^~~~~~~~~~~~~~~~~~
+                                       |   |
+                                       |   (7) first 'free' here
+                                       |
+                  <--------------------+
+                  |
+                'depth_3': event 8 (depth 3)
+                  |
+                  |   depth_4 (p3);
+                  |   ^
+                  |   |
+                  |   (8) inlined call to 'depth_4' from 'depth_3'
+                  |
+                  +--> 'depth_4': event 9 (depth 4)
+                         |
+                         |   depth_5 (p4);
+                         |   ^
+                         |   |
+                         |   (9) inlined call to 'depth_5' from 'depth_4'
+                         |
+                         +--> 'depth_5': event 10 (depth 5)
+                                |
+                                |   depth_6 (p5);
+                                |   ^
+                                |   |
+                                |   (10) inlined call to 'depth_6' from 'depth_5'
+                                |
+                                +--> 'depth_6': event 11 (depth 6)
+                                       |
+                                       |   __builtin_free (p);
+                                       |   ^~~~~~~~~~~~~~~~~~
+                                       |   |
+                                       |   (11) second 'free' here; first 'free' was at (7)
+                                       |
+   { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/inlining-7.c b/gcc/testsuite/gcc.dg/analyzer/inlining-7.c
new file mode 100644
index 00000000000..fe0404253b6
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/inlining-7.c
@@ -0,0 +1,49 @@
+/* Verify that we can reconstruct fndecl and stack depth information
+   after early inlining.  */
+
+/* { dg-additional-options "-O2 -fdiagnostics-show-path-depths" } */
+
+/* We want the reconstructed call/return hierarchy to show
+   that two calls happen at depth_3, without any spurious events
+   popping the stack back any further.  */
+
+static inline void
+depth_6 (void *p)
+{
+  __builtin_free (p); /* { dg-warning "double-'free' of 'p1'" "warning" } */
+ /* { dg-message "\\(7\\) first 'free' here \\(fndecl 'depth_6', depth 6\\)" "1st free message" { target *-*-* } .-1 } */
+ /* { dg-message "\\(11\\) second 'free' here; first 'free' was at \\(7\\) \\(fndecl 'depth_6', depth 6\\)" "2nd free message" { target *-*-* } .-2 } */
+}
+
+static inline void
+depth_5 (void *p5)
+{
+  depth_6 (p5); /* { dg-message "\\(6\\) inlined call to 'depth_6' from 'depth_5' \\(fndecl 'depth_5', depth 5\\)" "event 6" } */
+  /* { dg-message "\\(10\\) inlined call to 'depth_6' from 'depth_5' \\(fndecl 'depth_5', depth 5\\)" "event 10" { target *-*-* } .-1 } */
+}
+
+static inline void
+depth_4 (void *p4)
+{
+  depth_5 (p4); /* { dg-message "\\(5\\) inlined call to 'depth_5' from 'depth_4' \\(fndecl 'depth_4', depth 4\\)" "event 5" } */
+  /* { dg-message "\\(9\\) inlined call to 'depth_5' from 'depth_4' \\(fndecl 'depth_4', depth 4\\)" "event 9" { target *-*-* } .-1 } */
+}
+
+static inline void
+depth_3 (void *p3)
+{
+  depth_4 (p3); /* { dg-message "\\(4\\) inlined call to 'depth_4' from 'depth_3' \\(fndecl 'depth_3', depth 3\\)" } */
+  depth_4 (p3); /* { dg-message "\\(8\\) inlined call to 'depth_4' from 'depth_3' \\(fndecl 'depth_3', depth 3\\)" } */
+}
+
+static inline void
+depth_2 (void *p2)
+{
+  depth_3 (p2); /* { dg-message "\\(3\\) inlined call to 'depth_3' from 'depth_2' \\(fndecl 'depth_2', depth 2\\)" } */
+}
+
+void
+depth_1 (void *p1) /* { dg-message "\\(1\\) entry to 'depth_1' \\(fndecl 'depth_1', depth 1\\)" } */
+{
+  depth_2 (p1); /* { dg-message "\\(2\\) inlined call to 'depth_2' from 'depth_1' \\(fndecl 'depth_1', depth 1\\)" } */
+}
diff --git a/gcc/tree-diagnostic-path.cc b/gcc/tree-diagnostic-path.cc
index 4aa694448c5..ae2f8a2d262 100644
--- a/gcc/tree-diagnostic-path.cc
+++ b/gcc/tree-diagnostic-path.cc
@@ -463,8 +463,27 @@ default_tree_diagnostic_path_printer (diagnostic_context *context,
 	    label_text event_text (event.get_desc (false));
 	    gcc_assert (event_text.m_buffer);
 	    diagnostic_event_id_t event_id (i);
-	    inform (event.get_location (),
-		    "%@ %s", &event_id, event_text.m_buffer);
+	    if (context->show_path_depths)
+	      {
+		int stack_depth = event.get_stack_depth ();
+		tree fndecl = event.get_fndecl ();
+		/* -fdiagnostics-path-format=separate-events doesn't print
+		   fndecl information, so with -fdiagnostics-show-path-depths
+		   print the fndecls too, if any.  */
+		if (fndecl)
+		  inform (event.get_location (),
+			  "%@ %s (fndecl %qD, depth %i)",
+			  &event_id, event_text.m_buffer,
+			  fndecl, stack_depth);
+		else
+		  inform (event.get_location (),
+			  "%@ %s (depth %i)",
+			  &event_id, event_text.m_buffer,
+			  stack_depth);
+	      }
+	    else
+	      inform (event.get_location (),
+		      "%@ %s", &event_id, event_text.m_buffer);
 	    event_text.maybe_free ();
 	  }
       }
