From b4f278b928f13b1c05f0148fb25bf9606198ae2d Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Thu, 13 Feb 2020 11:55:18 -0500
Subject: [PATCH 12/31] FIXME: WIP on inlining and paths

---
 gcc/analyzer/checker-path.cc            | 168 +++++++++++++++++++++++-
 gcc/analyzer/checker-path.h             |  21 ++-
 gcc/doc/invoke.texi                     |   5 +-
 gcc/testsuite/gcc.dg/analyzer/paths-8.c |  30 +++++
 gcc/testsuite/gcc.dg/analyzer/paths-9.c |  27 ++++
 gcc/tree-diagnostic-path.cc             |  23 +++-
 6 files changed, 255 insertions(+), 19 deletions(-)
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/paths-8.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/paths-9.c

diff --git a/gcc/analyzer/checker-path.cc b/gcc/analyzer/checker-path.cc
index 7f6cdf599cf..f67b5432588 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"
@@ -101,19 +102,176 @@ event_kind_to_string (enum event_kind ek)
     }
 }
 
+/* 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 and final iteration.
+
+   Compare with lhd_print_error_function, cp_print_error_function,
+   and optrecord_json_writer::inlining_chain_to_json.
+
+   TODO(stage 1): move to gcc and rewrite the above to use this.  */
+
+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; }
+
+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;
+};
+
+/* 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.  For example, consider:
+
+   FIXME.
+
+   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 fndecl to better
+   reflect the user's original code.  */
+
+// FIXME: should we just run earlier???
+
+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 ()
+{
+  /* Update effective fndecl and depth if inlining has been recorded.  */
+  inlining_info info (loc);
+  if (info.get_inner_fndecl ())
+    {
+      m_effective_fndecl = info.get_inner_fndecl ();
+      m_effective_depth += info.get_extra_frames ();
+    }
+}
+
 /* Dump this event to PP (for debugging/logging purposes).  */
 
 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
@@ -202,7 +360,7 @@ statement_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);
 }
 
 /* class state_change_event : public checker_event.  */
diff --git a/gcc/analyzer/checker-path.h b/gcc/analyzer/checker-path.h
index 30cb43c13ba..63959333706 100644
--- a/gcc/analyzer/checker-path.h
+++ b/gcc/analyzer/checker-path.h
@@ -75,18 +75,11 @@ 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 ()
-  {
-  }
-
   /* 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; }
 
   /* Additional functionality.  */
 
@@ -99,12 +92,18 @@ public:
 
   void dump (pretty_printer *pp) const;
 
+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
 };
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 3e47d06f0d5..283bae63258 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -4330,7 +4330,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.
diff --git a/gcc/testsuite/gcc.dg/analyzer/paths-8.c b/gcc/testsuite/gcc.dg/analyzer/paths-8.c
new file mode 100644
index 00000000000..7a292ac87fa
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/paths-8.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/paths-9.c b/gcc/testsuite/gcc.dg/analyzer/paths-9.c
new file mode 100644
index 00000000000..7e5872f28f7
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/paths-9.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 2\\)" } */
+    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/tree-diagnostic-path.cc b/gcc/tree-diagnostic-path.cc
index 381a49cb0b4..4c947df2fae 100644
--- a/gcc/tree-diagnostic-path.cc
+++ b/gcc/tree-diagnostic-path.cc
@@ -468,8 +468,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.21.0

