[committed] analyzer: fix up paths for inlining (PR analyzer/105962)

David Malcolm dmalcolm@redhat.com
Wed Jun 15 22:00:48 GMT 2022


-fanalyzer runs late compared to other code analysis tools, in that in
runs on the partially-optimized gimple-ssa representation.  I chose this
point to run in the hope of easy integration with LTO.

As PR analyzer/105962 notes, this means that function inlining can occur
before the -fanalyzer "sees" the user's code.  For example given:

void foo (void *p)
{
  __builtin_free (p);
}

void bar (void *q)
{
  foo (q);
  foo (q);
}

Below -O2, -fanalyzer shows the calls and returns:

inline-1.c: In function ‘foo’:
inline-1.c:3:3: warning: double-‘free’ of ‘p’ [CWE-415] [-Wanalyzer-double-free]
    3 |   __builtin_free (p);
      |   ^~~~~~~~~~~~~~~~~~
  ‘bar’: events 1-2
    |
    |    6 | void bar (void *q)
    |      |      ^~~
    |      |      |
    |      |      (1) entry to ‘bar’
    |    7 | {
    |    8 |   foo (q);
    |      |   ~~~~~~~
    |      |   |
    |      |   (2) calling ‘foo’ from ‘bar’
    |
    +--> ‘foo’: events 3-4
           |
           |    1 | void foo (void *p)
           |      |      ^~~
           |      |      |
           |      |      (3) entry to ‘foo’
           |    2 | {
           |    3 |   __builtin_free (p);
           |      |   ~~~~~~~~~~~~~~~~~~
           |      |   |
           |      |   (4) first ‘free’ here
           |
    <------+
    |
  ‘bar’: events 5-6
    |
    |    8 |   foo (q);
    |      |   ^~~~~~~
    |      |   |
    |      |   (5) returning to ‘bar’ from ‘foo’
    |    9 |   foo (q);
    |      |   ~~~~~~~
    |      |   |
    |      |   (6) passing freed pointer ‘q’ in call to ‘foo’ from ‘bar’
    |
    +--> ‘foo’: events 7-8
           |
           |    1 | void foo (void *p)
           |      |      ^~~
           |      |      |
           |      |      (7) entry to ‘foo’
           |    2 | {
           |    3 |   __builtin_free (p);
           |      |   ~~~~~~~~~~~~~~~~~~
           |      |   |
           |      |   (8) second ‘free’ here; first ‘free’ was at (4)
           |

but at -O2, -fanalyzer "sees" this gimple:

void bar (void * q)
{
  <bb 2> [local count: 1073741824]:
  __builtin_free (q_2(D));
  __builtin_free (q_2(D));
  return;
}

where "foo" has been inlined away, leading to this unhelpful output:

In function ‘foo’,
    inlined from ‘bar’ at inline-1.c:9:3:
inline-1.c:3:3: warning: double-‘free’ of ‘q’ [CWE-415] [-Wanalyzer-double-free]
    3 |   __builtin_free (p);
      |   ^~~~~~~~~~~~~~~~~~
  ‘bar’: events 1-2
    |
    |    3 |   __builtin_free (p);
    |      |   ^~~~~~~~~~~~~~~~~~
    |      |   |
    |      |   (1) first ‘free’ here
    |      |   (2) second ‘free’ here; first ‘free’ was at (1)

where the stack frame information in the execution path suggests that these
events are happening in "bar", in the top stack frame.

This is what the analyzer sees, but I find it hard to decipher such
output.  Hence, as a workaround for the fact that -fanalyzer runs so
late, this patch attempts to reconstruct the "true" stack frame
information, and to inject events showing inline calls, based on the
inlining chain information recorded in the location_t values for the events.

Doing so leads to this output at -O2 on the above example (with
-fdiagnostics-show-path-depths):

In function ‘foo’,
    inlined from ‘bar’ at inline-1.c:9:3:
inline-1.c:3:3: warning: double-‘free’ of ‘q’ [CWE-415] [-Wanalyzer-double-free]
    3 |   __builtin_free (p);
      |   ^~~~~~~~~~~~~~~~~~
  ‘bar’: events 1-2 (depth 1)
    |
    |    6 | void bar (void *q)
    |      |      ^~~
    |      |      |
    |      |      (1) entry to ‘bar’
    |    7 | {
    |    8 |   foo (q);
    |      |   ~
    |      |   |
    |      |   (2) inlined call to ‘foo’ from ‘bar’
    |
    +--> ‘foo’: event 3 (depth 2)
           |
           |    3 |   __builtin_free (p);
           |      |   ^~~~~~~~~~~~~~~~~~
           |      |   |
           |      |   (3) first ‘free’ here
           |
    <------+
    |
  ‘bar’: event 4 (depth 1)
    |
    |    9 |   foo (q);
    |      |   ^
    |      |   |
    |      |   (4) inlined call to ‘foo’ from ‘bar’
    |
    +--> ‘foo’: event 5 (depth 2)
           |
           |    3 |   __builtin_free (p);
           |      |   ^~~~~~~~~~~~~~~~~~
           |      |   |
           |      |   (5) second ‘free’ here; first ‘free’ was at (3)
           |

reconstructing the calls and returns.

The patch also adds a new option, -fno-analyzer-undo-inlining, which can
be used to disable this reconstruction, restoring the output listed
above (this time with -fdiagnostics-show-path-depths):

In function ‘foo’,
    inlined from ‘bar’ at inline-1.c:9:3:
inline-1.c:3:3: warning: double-‘free’ of ‘q’ [CWE-415] [-Wanalyzer-double-free]
    3 |   __builtin_free (p);
      |   ^~~~~~~~~~~~~~~~~~
  ‘bar’: events 1-2 (depth 1)
    |
    |    3 |   __builtin_free (p);
    |      |   ^~~~~~~~~~~~~~~~~~
    |      |   |
    |      |   (1) first ‘free’ here
    |      |   (2) second ‘free’ here; first ‘free’ was at (1)
    |

Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu.
Pushed to trunk as r13-1119-g63c073199491b7.

gcc/analyzer/ChangeLog:
	PR analyzer/105962
	* analyzer.opt (fanalyzer-undo-inlining): New option.
	* checker-path.cc: Include "diagnostic-core.h" and
	"inlining-iterator.h".
	(event_kind_to_string): Handle EK_INLINED_CALL.
	(class inlining_info): New class.
	(checker_event::checker_event): Move here from checker-path.h.
	Store original fndecl and depth, and calculate effective fndecl
	and depth based on inlining information.
	(checker_event::dump): Emit original depth as well as effective
	depth when they differ; likewise for fndecl.
	(region_creation_event::get_desc): Use m_effective_fndecl.
	(inlined_call_event::get_desc): New.
	(inlined_call_event::get_meaning): New.
	(checker_path::inject_any_inlined_call_events): New.
	* checker-path.h (enum event_kind): Add EK_INLINED_CALL.
	(checker_event::checker_event): Make protected, and move
	definition to checker-path.cc.
	(checker_event::get_fndecl): Use effective fndecl.
	(checker_event::get_stack_depth): Use effective stack depth.
	(checker_event::get_logical_location): Use effective stack depth.
	(checker_event::get_original_stack_depth): New.
	(checker_event::m_fndecl): Rename to...
	(checker_event::m_original_fndecl): ...this.
	(checker_event::m_depth): Rename to...
	(checker_event::m_original_depth): ...this.
	(checker_event::m_effective_fndecl): New field.
	(checker_event::m_effective_depth): New field.
	(class inlined_call_event): New checker_event subclass.
	(checker_path::inject_any_inlined_call_events): New decl.
	* diagnostic-manager.cc: Include "inlining-iterator.h".
	(diagnostic_manager::emit_saved_diagnostic): Call
	checker_path::inject_any_inlined_call_events.
	(diagnostic_manager::prune_for_sm_diagnostic): Handle
	EK_INLINED_CALL.
	* engine.cc (tainted_args_function_custom_event::get_desc): Use
	effective fndecl.
	* inlining-iterator.h: New file.

gcc/testsuite/ChangeLog:
	PR analyzer/105962
	* gcc.dg/analyzer/inlining-1-multiline.c: New test.
	* gcc.dg/analyzer/inlining-1-no-undo.c: New test.
	* gcc.dg/analyzer/inlining-1.c: New test.
	* gcc.dg/analyzer/inlining-2-multiline.c: New test.
	* gcc.dg/analyzer/inlining-2.c: New test.
	* gcc.dg/analyzer/inlining-3-multiline.c: New test.
	* gcc.dg/analyzer/inlining-3.c: New test.
	* gcc.dg/analyzer/inlining-4-multiline.c: New test.
	* gcc.dg/analyzer/inlining-4.c: New test.
	* gcc.dg/analyzer/inlining-5-multiline.c: New test.
	* gcc.dg/analyzer/inlining-5.c: New test.
	* gcc.dg/analyzer/inlining-6-multiline.c: New test.
	* gcc.dg/analyzer/inlining-6.c: New test.
	* gcc.dg/analyzer/inlining-7-multiline.c: New test.
	* gcc.dg/analyzer/inlining-7.c: New test.

gcc/ChangeLog:
	PR analyzer/105962
	* doc/invoke.texi: Add -fno-analyzer-undo-inlining.
	* tree-diagnostic-path.cc (default_tree_diagnostic_path_printer):
	Extend -fdiagnostics-path-format=separate-events so that with
	-fdiagnostics-show-path-depths it prints fndecls as well as stack
	depths.

Signed-off-by: David Malcolm <dmalcolm@redhat.com>
---
 gcc/analyzer/analyzer.opt                     |   4 +
 gcc/analyzer/checker-path.cc                  | 264 +++++++++++++++++-
 gcc/analyzer/checker-path.h                   |  58 +++-
 gcc/analyzer/diagnostic-manager.cc            |   8 +
 gcc/analyzer/engine.cc                        |   2 +-
 gcc/analyzer/inlining-iterator.h              | 109 ++++++++
 gcc/doc/invoke.texi                           |  25 +-
 .../gcc.dg/analyzer/inlining-1-multiline.c    |  56 ++++
 .../gcc.dg/analyzer/inlining-1-no-undo.c      |  18 ++
 gcc/testsuite/gcc.dg/analyzer/inlining-1.c    |  17 ++
 .../gcc.dg/analyzer/inlining-2-multiline.c    |  46 +++
 gcc/testsuite/gcc.dg/analyzer/inlining-2.c    |  17 ++
 .../gcc.dg/analyzer/inlining-3-multiline.c    |  64 +++++
 gcc/testsuite/gcc.dg/analyzer/inlining-3.c    |  30 ++
 .../gcc.dg/analyzer/inlining-4-multiline.c    |  72 +++++
 gcc/testsuite/gcc.dg/analyzer/inlining-4.c    |  27 ++
 .../gcc.dg/analyzer/inlining-5-multiline.c    |  59 ++++
 gcc/testsuite/gcc.dg/analyzer/inlining-5.c    |  24 ++
 .../gcc.dg/analyzer/inlining-6-multiline.c    |  64 +++++
 gcc/testsuite/gcc.dg/analyzer/inlining-6.c    |  23 ++
 .../gcc.dg/analyzer/inlining-7-multiline.c    | 128 +++++++++
 gcc/testsuite/gcc.dg/analyzer/inlining-7.c    |  49 ++++
 gcc/tree-diagnostic-path.cc                   |  23 +-
 23 files changed, 1165 insertions(+), 22 deletions(-)
 create mode 100644 gcc/analyzer/inlining-iterator.h
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/inlining-1-multiline.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/inlining-1-no-undo.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/inlining-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/inlining-2-multiline.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/inlining-2.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/inlining-3-multiline.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/inlining-3.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/inlining-4-multiline.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/inlining-4.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/inlining-5-multiline.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/inlining-5.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/inlining-6-multiline.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/inlining-6.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/inlining-7-multiline.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/inlining-7.c

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 ();
 	  }
       }
-- 
2.26.3



More information about the Gcc-patches mailing list