From 5519bab31aa717b798d584ec052a4f90a3222a14 Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Thu, 3 Jan 2019 12:53:14 -0500
Subject: [PATCH 1/4] FIXME: WIP on showing paths

---
 gcc/diagnostic-path.h                              |  46 ++++++
 gcc/diagnostic.c                                   |  72 +++++++++
 gcc/diagnostic.h                                   |   1 +
 .../gcc.dg/plugin/diagnostic-test-paths-1.c        |  38 +++++
 .../gcc.dg/plugin/diagnostic_plugin_test_paths.c   | 173 +++++++++++++++++++++
 gcc/testsuite/gcc.dg/plugin/plugin.exp             |   2 +
 libcpp/include/line-map.h                          |   7 +
 libcpp/line-map.c                                  |   3 +-
 8 files changed, 341 insertions(+), 1 deletion(-)
 create mode 100644 gcc/diagnostic-path.h
 create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-1.c
 create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_paths.c

diff --git a/gcc/diagnostic-path.h b/gcc/diagnostic-path.h
new file mode 100644
index 0000000..9349ccb
--- /dev/null
+++ b/gcc/diagnostic-path.h
@@ -0,0 +1,46 @@
+/* FIXME.
+   Copyright (C) 2019 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_DIAGNOSTIC_PATH_H
+#define GCC_DIAGNOSTIC_PATH_H
+
+/* FIXME.  */
+
+struct diagnostic_path
+{
+  /* FIXME.  */
+  struct node
+  {
+    node (location_t loc, const char *desc);
+    ~node ();
+
+    location_t m_loc;
+    char *m_desc; // has been i18n-ed and formatted
+  };
+
+  ~diagnostic_path ();
+
+  void add_node (location_t loc, const char *fmt, ...)
+    ATTRIBUTE_GCC_DIAG(3,4);
+
+  auto_vec<node *> m_nodes;
+};
+
+#endif /* ! GCC_DIAGNOSTIC_PATH_H */
diff --git a/gcc/diagnostic.c b/gcc/diagnostic.c
index be6b657..423a7e6 100644
--- a/gcc/diagnostic.c
+++ b/gcc/diagnostic.c
@@ -31,6 +31,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "backtrace.h"
 #include "diagnostic.h"
 #include "diagnostic-color.h"
+#include "diagnostic-path.h"
 #include "edit-context.h"
 #include "selftest.h"
 #include "selftest-diagnostic.h"
@@ -631,6 +632,22 @@ diagnostic_report_current_module (diagnostic_context *context, location_t where)
     }
 }
 
+/* FIXME.  */
+
+void
+diagnostic_show_any_path (diagnostic_context *,
+			  diagnostic_info *diagnostic)
+{
+  diagnostic_path *path = diagnostic->richloc->get_path ();
+  if (!path)
+    return;
+
+  diagnostic_path::node *node;
+  int i;
+  FOR_EACH_VEC_ELT (path->m_nodes, i, node)
+    inform (node->m_loc, "%s", node->m_desc);
+}
+
 void
 default_diagnostic_starter (diagnostic_context *context,
 			    diagnostic_info *diagnostic)
@@ -1032,6 +1049,8 @@ diagnostic_report_diagnostic (diagnostic_context *context,
 
   context->lock--;
 
+  diagnostic_show_any_path (context, diagnostic);
+
   return true;
 }
 
@@ -1634,6 +1653,59 @@ auto_diagnostic_group::~auto_diagnostic_group ()
     }
 }
 
+/* struct diagnostic_path */
+
+/* diagnostic_path's dtor.  Delete the nodes.  */
+
+diagnostic_path::~diagnostic_path ()
+{
+  node *n;
+  int i;
+  FOR_EACH_VEC_ELT (m_nodes, i, n)
+    delete n;
+}
+
+/* FIXME.  */
+
+void
+diagnostic_path::add_node (location_t loc, const char *fmt, ...)
+{
+  /* FIXME: use global_dc's pp.  */
+  pretty_printer pp;
+  text_info ti;
+  rich_location rich_loc (line_table, UNKNOWN_LOCATION);
+
+  va_list ap;
+
+  va_start (ap, fmt);
+
+  ti.format_spec = _(fmt);
+  ti.args_ptr = &ap;
+  ti.err_no = 0;
+  ti.x_data = NULL;
+  ti.m_richloc = &rich_loc;
+
+  pp_show_color (&pp) = pp_show_color (global_dc->printer);
+  pp_format (&pp, &ti);
+  pp_output_formatted_text (&pp);
+
+  va_end (ap);
+
+  m_nodes.safe_push (new node (loc, pp_formatted_text (&pp)));
+}
+
+/* struct diagnostic_path::node.  */
+
+diagnostic_path::node::node (location_t loc, const char *desc)
+: m_loc (loc), m_desc (xstrdup (desc))
+{
+}
+
+diagnostic_path::node::~node ()
+{
+  free (m_desc);
+}
+
 /* Really call the system 'abort'.  This has to go right at the end of
    this file, so that there are no functions after it that call abort
    and get the system abort instead of our macro.  */
diff --git a/gcc/diagnostic.h b/gcc/diagnostic.h
index 46c3b50..6941f98 100644
--- a/gcc/diagnostic.h
+++ b/gcc/diagnostic.h
@@ -333,6 +333,7 @@ extern void diagnostic_report_current_module (diagnostic_context *, location_t);
 extern void diagnostic_show_locus (diagnostic_context *,
 				   rich_location *richloc,
 				   diagnostic_t diagnostic_kind);
+extern void diagnostic_show_any_path (diagnostic_context *, diagnostic_info *);
 
 /* Force diagnostics controlled by OPTIDX to be kind KIND.  */
 extern diagnostic_t diagnostic_classify_diagnostic (diagnostic_context *,
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-1.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-1.c
new file mode 100644
index 0000000..de0927c
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-1.c
@@ -0,0 +1,38 @@
+/* { dg-do compile } */
+/* { dg-options "" } */
+
+#include <stddef.h>
+#include <stdlib.h>
+
+/* Minimal reimplementation of cpython API.  */
+typedef struct PyObject {} PyObject;
+extern int PyArg_ParseTuple (PyObject *args, const char *fmt, ...);
+extern PyObject *PyList_New (int);
+extern PyObject *PyLong_FromLong(long);
+extern void PyList_Append(PyObject *list, PyObject *item);
+
+PyObject *
+make_a_list_of_random_ints_badly(PyObject *self,
+				 PyObject *args)
+{
+  PyObject *list, *item;
+  long count, i;
+
+  if (!PyArg_ParseTuple(args, "i", &count)) {
+    return NULL;
+  }
+
+  list = PyList_New(0); /* { dg-line PyList_New } */
+	
+  for (i = 0; i < count; i++) { /* { dg-line for } */
+    item = PyLong_FromLong(random());
+    PyList_Append(list, item); /* { dg-line PyList_Append } */
+  }
+  
+  return list;
+
+  /* { dg-error "passing NULL as argument 1 to 'PyList_Append' which requires a non-NULL parameter" "" { target *-*-* } PyList_Append } */
+  /* { dg-message "when 'PyList_New' fails, returning NULL" "" { target *-*-* } PyList_New } */
+  /* { dg-message "when 'i < count'" "" { target *-*-* } for } */
+  /* { dg-message "when calling 'PyList_Append', passing NULL as argument 1" "" { target *-*-* } PyList_Append } */
+}
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_paths.c b/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_paths.c
new file mode 100644
index 0000000..2946df9
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_paths.c
@@ -0,0 +1,173 @@
+/* { dg-options "-O" } */
+
+/* This plugin exercises the path-printing code.
+
+   The goal is to unit-test the path-printing code without needing any
+   specific tests within the compiler's IR.  We can't use any real
+   diagnostics for this, so we have to fake it, hence this plugin.  */
+
+#include "gcc-plugin.h"
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tm.h"
+#include "tree.h"
+#include "stringpool.h"
+#include "toplev.h"
+#include "basic-block.h"
+#include "hash-table.h"
+#include "vec.h"
+#include "ggc.h"
+#include "basic-block.h"
+#include "tree-ssa-alias.h"
+#include "internal-fn.h"
+#include "gimple-fold.h"
+#include "tree-eh.h"
+#include "gimple-expr.h"
+#include "is-a.h"
+#include "gimple.h"
+#include "gimple-iterator.h"
+#include "tree.h"
+#include "tree-pass.h"
+#include "intl.h"
+#include "plugin-version.h"
+#include "diagnostic.h"
+#include "diagnostic-path.h"
+#include "context.h"
+#include "print-tree.h"
+#include "gcc-rich-location.h"
+
+int plugin_is_GPL_compatible;
+
+const pass_data pass_data_test_show_path =
+{
+  GIMPLE_PASS, /* type */
+  "test_show_path", /* name */
+  OPTGROUP_NONE, /* optinfo_flags */
+  TV_NONE, /* tv_id */
+  PROP_ssa, /* properties_required */
+  0, /* properties_provided */
+  0, /* properties_destroyed */
+  0, /* todo_flags_start */
+  0, /* todo_flags_finish */
+};
+
+class pass_test_show_path : public gimple_opt_pass
+{
+public:
+  pass_test_show_path(gcc::context *ctxt)
+    : gimple_opt_pass(pass_data_test_show_path, ctxt)
+  {}
+
+  /* opt_pass methods: */
+  bool gate (function *) { return true; }
+  virtual unsigned int execute (function *);
+
+}; // class pass_test_show_path
+
+/* Determine if STMT is a call with NUM_ARGS arguments to a function
+   named FUNCNAME.
+   If so, return STMT as a gcall *.  Otherwise return NULL.  */
+
+static gcall *
+check_for_named_call (gimple *stmt,
+		      const char *funcname, unsigned int num_args)
+{
+  gcc_assert (funcname);
+
+  gcall *call = dyn_cast <gcall *> (stmt);
+  if (!call)
+    return NULL;
+
+  tree fndecl = gimple_call_fndecl (call);
+  if (!fndecl)
+    return NULL;
+
+  if (strcmp (IDENTIFIER_POINTER (DECL_NAME (fndecl)), funcname))
+    return NULL;
+
+  if (gimple_call_num_args (call) != num_args)
+    {
+      error_at (stmt->location, "expected number of args: %i (got %i)",
+		num_args, gimple_call_num_args (call));
+      return NULL;
+    }
+
+  return call;
+}
+
+/* FIXME.  */
+
+unsigned int
+pass_test_show_path::execute (function *fun)
+{
+  gimple_stmt_iterator gsi;
+  basic_block bb;
+
+  gcall *call_to_PyList_Append = NULL;
+  gcall *call_to_PyList_New = NULL;
+  gcond *for_cond = NULL;
+
+  FOR_EACH_BB_FN (bb, fun)
+    for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi))
+      {
+	gimple *stmt = gsi_stmt (gsi);
+	if (gcall *call = check_for_named_call (stmt, "PyList_New", 1))
+	  call_to_PyList_New = call;
+	if (gcall *call = check_for_named_call (stmt, "PyList_Append", 2))
+	  call_to_PyList_Append = call;
+	if (gcond *cond = dyn_cast <gcond *> (stmt))
+	  for_cond = cond;
+      }
+
+  if (call_to_PyList_New && for_cond && call_to_PyList_Append)
+    {
+      auto_diagnostic_group d;
+      gcc_rich_location richloc (gimple_location (call_to_PyList_Append));
+      diagnostic_path path;
+      path.add_node (gimple_location (call_to_PyList_New),
+		     "when %qs fails, returning NULL",
+		     "PyList_New");
+      path.add_node (gimple_location (for_cond),
+		     "when %qs", "i < count");
+      path.add_node (gimple_location (call_to_PyList_Append),
+		     "when calling %qs, passing NULL as argument %i",
+		     "PyList_Append", 1);
+      richloc.set_path (&path);
+      error_at (&richloc,
+		"passing NULL as argument %i to %qs which requires a non-NULL parameter",
+		1, "PyList_Append");
+      // TODO: show param within decl
+      //inform (
+    }
+
+  return 0;
+}
+
+static gimple_opt_pass *
+make_pass_test_show_path (gcc::context *ctxt)
+{
+  return new pass_test_show_path (ctxt);
+}
+
+int
+plugin_init (struct plugin_name_args *plugin_info,
+	     struct plugin_gcc_version *version)
+{
+  struct register_pass_info pass_info;
+  const char *plugin_name = plugin_info->base_name;
+  int argc = plugin_info->argc;
+  struct plugin_argument *argv = plugin_info->argv;
+
+  if (!plugin_default_version_check (version, &gcc_version))
+    return 1;
+
+  pass_info.pass = make_pass_test_show_path (g);
+  pass_info.reference_pass_name = "ssa";
+  pass_info.ref_pass_instance_number = 1;
+  pass_info.pos_op = PASS_POS_INSERT_AFTER;
+  register_callback (plugin_name, PLUGIN_PASS_MANAGER_SETUP, NULL,
+		     &pass_info);
+
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/plugin/plugin.exp b/gcc/testsuite/gcc.dg/plugin/plugin.exp
index 2f75463..b005544 100644
--- a/gcc/testsuite/gcc.dg/plugin/plugin.exp
+++ b/gcc/testsuite/gcc.dg/plugin/plugin.exp
@@ -94,6 +94,8 @@ set plugin_test_list [list \
 	  diagnostic-test-inlining-2.c \
 	  diagnostic-test-inlining-3.c \
 	  diagnostic-test-inlining-4.c } \
+    { diagnostic_plugin_test_paths.c \
+	  diagnostic-test-paths-1.c } \
     { location_overflow_plugin.c \
 	  location-overflow-test-1.c \
 	  location-overflow-test-2.c \
diff --git a/libcpp/include/line-map.h b/libcpp/include/line-map.h
index a6eb87c..fda250e 100644
--- a/libcpp/include/line-map.h
+++ b/libcpp/include/line-map.h
@@ -1455,6 +1455,7 @@ semi_embedded_vec<T, NUM_EMBEDDED>::truncate (int len)
 }
 
 class fixit_hint;
+class diagnostic_path;
 
 /* A "rich" source code location, for use when printing diagnostics.
    A rich_location has one or more carets&ranges, where the carets
@@ -1750,6 +1751,10 @@ class rich_location
     return !m_fixits_cannot_be_auto_applied;
   }
 
+  /* An optional path through the code.  */
+  diagnostic_path *get_path () const { return m_path; }
+  void set_path (diagnostic_path *path) { m_path = path; }
+
 private:
   bool reject_impossible_fixit (location_t where);
   void stop_supporting_fixits ();
@@ -1774,6 +1779,8 @@ protected:
 
   bool m_seen_impossible_fixit;
   bool m_fixits_cannot_be_auto_applied;
+
+  diagnostic_path *m_path;
 };
 
 /* A struct for the result of range_label::get_text: a NUL-terminated buffer
diff --git a/libcpp/line-map.c b/libcpp/line-map.c
index ff679ed..eb953c3 100644
--- a/libcpp/line-map.c
+++ b/libcpp/line-map.c
@@ -1997,7 +1997,8 @@ rich_location::rich_location (line_maps *set, location_t loc,
   m_have_expanded_location (false),
   m_fixit_hints (),
   m_seen_impossible_fixit (false),
-  m_fixits_cannot_be_auto_applied (false)
+  m_fixits_cannot_be_auto_applied (false),
+  m_path (NULL)
 {
   add_range (loc, SHOW_RANGE_WITH_CARET, label);
 }
-- 
1.8.5.3

