From 95bdc185fa1b997544c2cd5592debc86be149896 Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Thu, 3 Jan 2019 12:53:14 -0500
Subject: [PATCH 010/169] FIXME: diagnostic paths

---
 gcc/diagnostic-core.h                              |   3 +
 gcc/diagnostic-format-json.cc                      |  10 +-
 gcc/diagnostic-path.h                              |  93 +++++++++++
 gcc/diagnostic-show-locus.c                        |  19 ++-
 gcc/diagnostic.c                                   | 130 +++++++++++++++
 gcc/diagnostic.h                                   |   7 +
 gcc/gcc-rich-location.h                            |   4 +-
 .../gcc.dg/plugin/diagnostic-test-paths-1.c        |  38 +++++
 .../gcc.dg/plugin/diagnostic-test-paths-2.c        |  55 +++++++
 .../gcc.dg/plugin/diagnostic-test-paths-3.c        |  38 +++++
 .../gcc.dg/plugin/diagnostic_plugin_test_paths.c   | 176 +++++++++++++++++++++
 gcc/testsuite/gcc.dg/plugin/plugin.exp             |   4 +
 gcc/tree-diagnostic.c                              | 172 +++++++++++++++++++-
 gcc/tree-diagnostic.h                              |  44 +++++-
 libcpp/include/line-map.h                          |   7 +
 libcpp/line-map.c                                  |   3 +-
 16 files changed, 784 insertions(+), 19 deletions(-)
 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-test-paths-2.c
 create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-3.c
 create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_paths.c

diff --git a/gcc/diagnostic-core.h b/gcc/diagnostic-core.h
index efafde4..aeb285f 100644
--- a/gcc/diagnostic-core.h
+++ b/gcc/diagnostic-core.h
@@ -109,6 +109,9 @@ extern bool emit_diagnostic (diagnostic_t, rich_location *, int,
 			     const char *, ...) ATTRIBUTE_GCC_DIAG(4,5);
 extern bool emit_diagnostic_valist (diagnostic_t, location_t, int, const char *,
 				    va_list *) ATTRIBUTE_GCC_DIAG (4,0);
+extern bool emit_diagnostic_valist (diagnostic_t, rich_location *, int,
+				    const char *, va_list *)
+  ATTRIBUTE_GCC_DIAG (4,0);
 extern bool seen_error (void);
 
 #ifdef BUFSIZ
diff --git a/gcc/diagnostic-format-json.cc b/gcc/diagnostic-format-json.cc
index 1fb59ca..a99b431 100644
--- a/gcc/diagnostic-format-json.cc
+++ b/gcc/diagnostic-format-json.cc
@@ -40,7 +40,7 @@ static json::array *cur_children_array;
 
 /* Generate a JSON object for LOC.  */
 
-static json::object *
+json::value *
 json_from_expanded_location (location_t loc)
 {
   expanded_location exploc = expand_location (loc);
@@ -207,6 +207,13 @@ json_end_diagnostic (diagnostic_context *context, diagnostic_info *diagnostic,
      TODO: functions
      TODO: inlining information
      TODO: macro expansion information.  */
+
+  const diagnostic_path *path = richloc->get_path ();
+  if (path && context->make_json_for_path)
+    {
+      json::value *path_value = context->make_json_for_path (context, path);
+      diag_obj->set ("path", path_value);
+    }
 }
 
 /* No-op implementation of "begin_group_cb" for JSON output.  */
@@ -263,6 +270,7 @@ diagnostic_output_format_init (diagnostic_context *context,
 	context->begin_group_cb = json_begin_group;
 	context->end_group_cb =  json_end_group;
 	context->final_cb =  json_final_cb;
+	context->print_path = NULL; /* handled in json_end_diagnostic.  */
 
 	/* The option is handled in JSON format, rather than as text.  */
 	context->show_option_requested = false;
diff --git a/gcc/diagnostic-path.h b/gcc/diagnostic-path.h
new file mode 100644
index 0000000..cde0ea5
--- /dev/null
+++ b/gcc/diagnostic-path.h
@@ -0,0 +1,93 @@
+/* Paths through the code associated with a diagnostic.
+   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
+
+/* A diagnostic_path is an optional additional piece of metadata associated
+   with a diagnostic (via its rich_location).
+
+   It describes a sequence of events predicted by the compiler that
+   lead to the problem occurring, with their locations in the user's source,
+   and text descriptions.
+
+   For example, the following error has a 3-event path:
+
+     test.c: In function 'make_a_list_of_random_ints_badly':
+     test.c:29:5: error: passing NULL as argument 1 to 'PyList_Append' which
+       requires a non-NULL parameter
+        29 |     PyList_Append(list, item);
+           |     ^~~~~~~~~~~~~~~~~~~~~~~~~
+     test.c:25:10: note: (1) when 'PyList_New' fails, returning NULL
+        25 |   list = PyList_New(0);
+           |          ^~~~~~~~~~~~~
+        26 |
+        27 |   for (i = 0; i < count; i++) {
+           |               ~~~~~~~~~
+           |                 |
+           |                 (2) when 'i < count'
+        28 |     item = PyLong_FromLong(random());
+        29 |     PyList_Append(list, item);
+           |                   ~~~~
+           |                   |
+           |                   (3) when calling 'PyList_Append', passing
+           |                   NULL as argument 1
+
+    The diagnostic-printing code has consolidated the path into a single
+    note, since all the events are near each other and within the same
+    function; more complicated examples (such as interprocedural paths)
+    might be printed as multiple notes.  */
+
+struct diagnostic_path
+{
+  /* One event within the path.  */
+  struct event
+  {
+    event (location_t loc, tree fndecl, int depth, const char *desc);
+    ~event ();
+
+    location_t m_loc;
+    /* TODO: generalize this to a "backtrace", so that consumers can
+       decipher interprocedural sequences of events:  */
+    tree m_fndecl;
+
+    /* Stack depth, so that consumers can visualizes the interprocedural
+       calls, returns, and frame nesting.  */
+    int m_depth;
+
+    char *m_desc; // has been i18n-ed and formatted
+  };
+
+  diagnostic_path ();
+  diagnostic_path (const diagnostic_path &other);
+  diagnostic_path& operator= (const diagnostic_path &other);
+
+  ~diagnostic_path ();
+
+  void add_event (location_t loc, tree fndecl, int depth, const char *fmt, ...)
+    ATTRIBUTE_GCC_DIAG(5,6);
+
+  auto_vec<event *> m_events;
+};
+
+// FIXME:
+void debug (diagnostic_path *path);
+
+#endif /* ! GCC_DIAGNOSTIC_PATH_H */
diff --git a/gcc/diagnostic-show-locus.c b/gcc/diagnostic-show-locus.c
index 8977e35..9690536 100644
--- a/gcc/diagnostic-show-locus.c
+++ b/gcc/diagnostic-show-locus.c
@@ -1547,7 +1547,7 @@ layout::print_any_labels (linenum_type row)
 
 	    if (label_line == label->m_label_line)
 	      {
-		gcc_assert (column <= label->m_column);
+		//gcc_assert (column <= label->m_column);
 		move_to_column (&column, label->m_column, true);
 		m_colorizer.set_range (label->m_state_idx);
 		pp_string (m_pp, label->m_text.m_buffer);
@@ -1556,7 +1556,7 @@ layout::print_any_labels (linenum_type row)
 	      }
 	    else if (label->m_has_vbar)
 	      {
-		gcc_assert (column <= label->m_column);
+		//gcc_assert (column <= label->m_column);
 		move_to_column (&column, label->m_column, true);
 		m_colorizer.set_range (label->m_state_idx);
 		pp_character (m_pp, '|');
@@ -2238,7 +2238,9 @@ layout::print_line (linenum_type row)
    Otherwise return false.  */
 
 bool
-gcc_rich_location::add_location_if_nearby (location_t loc)
+gcc_rich_location::add_location_if_nearby (location_t loc,
+					   bool restrict_to_current_line_spans,
+					   const range_label *label)
 {
   /* Use the layout location-handling logic to sanitize LOC,
      filtering it to the current line spans within a temporary
@@ -2247,10 +2249,17 @@ gcc_rich_location::add_location_if_nearby (location_t loc)
   location_range loc_range;
   loc_range.m_loc = loc;
   loc_range.m_range_display_kind = SHOW_RANGE_WITHOUT_CARET;
-  if (!layout.maybe_add_location_range (&loc_range, 0, true))
+  if (!layout.maybe_add_location_range (&loc_range, 0,
+					restrict_to_current_line_spans))
     return false;
+  
+#if 0
+  // FIXME: additional constraints (maybe a callback?)
+  if (LOCATION_LINE (loc) < LOCATION_LINE (get_loc ()))
+    return false;
+#endif
 
-  add_range (loc);
+  add_range (loc, SHOW_RANGE_WITHOUT_CARET, label);
   return true;
 }
 
diff --git a/gcc/diagnostic.c b/gcc/diagnostic.c
index 1415075..dd4712d 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"
@@ -644,6 +645,20 @@ diagnostic_report_current_module (diagnostic_context *context, location_t where)
     }
 }
 
+/* FIXME.  */
+
+void
+diagnostic_show_any_path (diagnostic_context *context,
+			  diagnostic_info *diagnostic)
+{
+  const diagnostic_path *path = diagnostic->richloc->get_path ();
+  if (!path)
+    return;
+
+  if (context->print_path)
+    context->print_path (context, path);
+}
+
 void
 default_diagnostic_starter (diagnostic_context *context,
 			    diagnostic_info *diagnostic)
@@ -1056,6 +1071,8 @@ diagnostic_report_diagnostic (diagnostic_context *context,
 
   context->lock--;
 
+  diagnostic_show_any_path (context, diagnostic);
+
   return true;
 }
 
@@ -1248,6 +1265,15 @@ emit_diagnostic_valist (diagnostic_t kind, location_t location, int opt,
   return diagnostic_impl (&richloc, opt, gmsgid, ap, kind);
 }
 
+/* Wrapper around diagnostic_impl taking a va_list parameter.  */
+
+bool
+emit_diagnostic_valist (diagnostic_t kind, rich_location *richloc, int opt,
+			const char *gmsgid, va_list *ap)
+{
+  return diagnostic_impl (richloc, opt, gmsgid, ap, kind);
+}
+
 /* An informative note at LOCATION.  Use this for additional details on an error
    message.  */
 void
@@ -1658,6 +1684,110 @@ auto_diagnostic_group::~auto_diagnostic_group ()
     }
 }
 
+/* struct diagnostic_path */
+
+diagnostic_path::diagnostic_path () : m_events ()
+{
+}
+
+/* FIXME: would it be better to use a ref-counted string to make this copying less expensive??? */
+
+diagnostic_path::diagnostic_path (const diagnostic_path &other)
+: m_events (other.m_events.length ())
+{
+  event *e;
+  int i;
+  FOR_EACH_VEC_ELT (other.m_events, i, e)
+    m_events.quick_push (new event (e->m_loc, e->m_fndecl, e->m_depth,
+				    e->m_desc));
+#if 0
+  fprintf (stderr, "DBG: diagnostic_path copy-ctor (length=%i)\n", m_events.length ());
+#endif
+}
+
+diagnostic_path&
+diagnostic_path::operator= (const diagnostic_path &other)
+{
+  gcc_unreachable (); // FIXME
+}
+
+/* diagnostic_path's dtor.  Delete the events.  */
+
+diagnostic_path::~diagnostic_path ()
+{
+  event *n;
+  int i;
+  FOR_EACH_VEC_ELT (m_events, i, n)
+    delete n;
+}
+
+/* FIXME.  */
+
+void
+diagnostic_path::add_event (location_t loc, tree fndecl, int depth, const char *fmt, ...)
+{
+  /* FIXME: use global_dc's pp.  */
+#if 1
+  pretty_printer *pp = global_dc->printer;
+  pp_clear_output_area (pp);
+#else
+  pretty_printer tmp_pp;
+  pretty_printer *pp = &tmp_pp;
+#endif
+
+  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;
+
+#if 1
+#else
+  pp_show_color (pp) = pp_show_color (global_dc->printer);
+  pp_format_decoder (pp) = pp_format_decoder (global_dc->printer);
+#endif
+  pp_format (pp, &ti);
+  pp_output_formatted_text (pp);
+
+  va_end (ap);
+
+  event *new_event = new event (loc, fndecl, depth, pp_formatted_text (pp));
+  m_events.safe_push (new_event);
+#if 0  
+  fprintf (stderr, "DBG: added to path %p: %s\n", this, new_event->m_desc);
+#endif
+
+  pp_clear_output_area (pp);
+}
+
+/* struct diagnostic_path::event.  */
+
+diagnostic_path::event::event (location_t loc, tree fndecl, int depth,
+			       const char *desc)
+: m_loc (loc), m_fndecl (fndecl), m_depth (depth), m_desc (xstrdup (desc))
+{
+}
+
+diagnostic_path::event::~event ()
+{
+  free (m_desc);
+}
+
+// FIXME:
+void debug (diagnostic_path *path)
+{
+  rich_location richloc (line_table, UNKNOWN_LOCATION);
+  richloc.set_path (path);
+  inform (&richloc, "debug path");
+}
+
 /* 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 da4b06d..cb4a1cf 100644
--- a/gcc/diagnostic.h
+++ b/gcc/diagnostic.h
@@ -76,6 +76,7 @@ typedef void (*diagnostic_finalizer_fn) (diagnostic_context *,
 					 diagnostic_t);
 
 class edit_context;
+namespace json { class value; }
 
 /* This data structure bundles altogether any information relevant to
    the context of a diagnostic message.  */
@@ -200,6 +201,9 @@ struct diagnostic_context
      particular option.  */
   char *(*get_option_url) (diagnostic_context *, int);
 
+  void (*print_path) (diagnostic_context *, const diagnostic_path *);
+  json::value *(*make_json_for_path) (diagnostic_context *, const diagnostic_path *);
+
   /* Auxiliary data for client.  */
   void *x_data;
 
@@ -340,6 +344,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 *,
@@ -431,4 +436,6 @@ extern void diagnostic_output_format_init (diagnostic_context *,
 /* Compute the number of digits in the decimal representation of an integer.  */
 extern int num_digits (int);
 
+extern json::value *json_from_expanded_location (location_t loc);
+
 #endif /* ! GCC_DIAGNOSTIC_H */
diff --git a/gcc/gcc-rich-location.h b/gcc/gcc-rich-location.h
index de92e3b..fee4af7 100644
--- a/gcc/gcc-rich-location.h
+++ b/gcc/gcc-rich-location.h
@@ -62,7 +62,9 @@ class gcc_rich_location : public rich_location
 
      Implemented in diagnostic-show-locus.c.  */
 
-  bool add_location_if_nearby (location_t loc);
+  bool add_location_if_nearby (location_t loc,
+			       bool restrict_to_current_line_spans = true,
+			       const range_label *label = NULL);
 
   /* Add a fix-it hint suggesting the insertion of CONTENT before
      INSERTION_POINT.
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..9b63760
--- /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 "\\(1\\) when 'PyList_New' fails, returning NULL" "" { target *-*-* } PyList_New } */
+  /* { dg-message "\\(2\\) when 'i < count'" "" { target *-*-* } for } */
+  /* { dg-message "\\(3\\) when calling 'PyList_Append', passing NULL as argument 1" "" { target *-*-* } PyList_Append } */
+}
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-2.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-2.c
new file mode 100644
index 0000000..b42f37e
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-2.c
@@ -0,0 +1,55 @@
+/* { dg-do compile } */
+/* { dg-options "-fdiagnostics-show-caret -fdiagnostics-show-line-numbers" } */
+
+#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++) {
+    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-begin-multiline-output "" }
+   29 |     PyList_Append(list, item);
+      |     ^~~~~~~~~~~~~~~~~~~~~~~~~
+     { dg-end-multiline-output "" } */
+
+  /* { dg-message "\\(1\\) when 'PyList_New' fails, returning NULL" "" { target *-*-* } PyList_New } */
+  /* { dg-begin-multiline-output "" }
+   25 |   list = PyList_New(0);
+      |          ^~~~~~~~~~~~~
+   26 | 
+   27 |   for (i = 0; i < count; i++) {
+      |   ~~~     
+      |   |
+      |   (2) when 'i < count'
+   28 |     item = PyLong_FromLong(random());
+   29 |     PyList_Append(list, item);
+      |     ~~~~~~~~~~~~~~~~~~~~~~~~~
+      |     |
+      |     (3) when calling 'PyList_Append', passing NULL as argument 1
+     { dg-end-multiline-output "" } */
+}
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-3.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-3.c
new file mode 100644
index 0000000..6971d7c
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-3.c
@@ -0,0 +1,38 @@
+/* { dg-do compile } */
+/* { dg-options "-fdiagnostics-format=json" } */
+
+#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);
+	
+  for (i = 0; i < count; i++) {
+    item = PyLong_FromLong(random());
+    PyList_Append(list, item);
+  }
+  
+  return list;
+}
+
+/* FIXME: test the events within a path.  */
+/* { dg-regexp "\"kind\": \"error\"" } */
+/* { dg-regexp "\"path\": " } */
+/* { dg-regexp ".*" } */
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..d813348
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_paths.c
@@ -0,0 +1,176 @@
+/* { 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_event (gimple_location (call_to_PyList_New),
+		      fun->decl, 0,
+		      "when %qs fails, returning NULL",
+		      "PyList_New");
+      path.add_event (gimple_location (for_cond),
+		      fun->decl, 0,
+		      "when %qs", "i < count");
+      path.add_event (gimple_location (call_to_PyList_Append),
+		      fun->decl, 0,
+		      "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..6728d72 100644
--- a/gcc/testsuite/gcc.dg/plugin/plugin.exp
+++ b/gcc/testsuite/gcc.dg/plugin/plugin.exp
@@ -94,6 +94,10 @@ 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 \
+	  diagnostic-test-paths-2.c \
+	  diagnostic-test-paths-3.c } \
     { location_overflow_plugin.c \
 	  location-overflow-test-1.c \
 	  location-overflow-test-2.c \
diff --git a/gcc/tree-diagnostic.c b/gcc/tree-diagnostic.c
index 7bd7430..162b31f 100644
--- a/gcc/tree-diagnostic.c
+++ b/gcc/tree-diagnostic.c
@@ -29,6 +29,9 @@ along with GCC; see the file COPYING3.  If not see
 #include "tree-diagnostic.h"
 #include "langhooks.h"
 #include "intl.h"
+#include "diagnostic-path.h"
+#include "json.h"
+#include "gcc-rich-location.h"
 
 /* Prints out, if necessary, the name of the current function
    that caused an error.  Called from all error and warning functions.  */
@@ -36,9 +39,9 @@ void
 diagnostic_report_current_function (diagnostic_context *context,
 				    diagnostic_info *diagnostic)
 {
-  diagnostic_report_current_module (context, diagnostic_location (diagnostic));
-  lang_hooks.print_error_function (context, LOCATION_FILE (input_location),
-				   diagnostic);
+  location_t loc = diagnostic_location (diagnostic);
+  diagnostic_report_current_module (context, loc);
+  lang_hooks.print_error_function (context, LOCATION_FILE (loc), diagnostic);
 }
 
 static void
@@ -305,6 +308,167 @@ default_tree_printer (pretty_printer *pp, text_info *text, const char *spec,
   return true;
 }
 
+/* Subclass of range_label for showing a consecutive run of events
+   within a diagnostic_path as labelled ranges within one
+   gcc_rich_location.  */
+
+class path_label : public range_label
+{
+ public:
+  path_label (const diagnostic_path *path, unsigned start_idx)
+  : m_path (path), m_start_idx (start_idx)
+  {}
+
+  label_text get_text (unsigned range_idx) const FINAL OVERRIDE
+  {
+    unsigned event_idx = m_start_idx + range_idx;
+    const diagnostic_path::event *event = m_path->m_events[event_idx];
+
+    return label_text (xasprintf ("(%i) %s", event_idx + 1, event->m_desc),
+		       true);
+  }
+
+ private:
+  const diagnostic_path *m_path;
+  unsigned m_start_idx;
+};
+
+static bool
+can_consolidate_events (diagnostic_path::event *e1,
+			diagnostic_path::event *e2)
+{
+  // FIXME: might want to simply group them by function, and also
+  // split whenever there's a backward jump?
+  if (e1->m_fndecl != e2->m_fndecl)
+    return false;
+
+#if 0
+  // FIXME: do we need this?
+  if (e1->m_depth != e2->m_depth)
+    return false;
+#endif
+
+  /* Neither can be macro-based.  */
+  if (linemap_location_from_macro_expansion_p (line_table,
+					       e1->m_loc))
+    return false;
+  if (linemap_location_from_macro_expansion_p (line_table,
+					       e2->m_loc))
+    return false;
+
+  // TODO:
+  /* && LOCATION_LINE (other->m_loc) >= LOCATION_LINE (event->m_loc) */
+
+  /* Passed all the tests.  */
+  return true;
+}
+
+/* FIXME.  */
+// This needs to be in tree-diagnostic.c so we can hack up current_function_decl,
+// which is used by lhd_print_error_function etc.
+
+void
+tree_print_diagnostic_path (diagnostic_context *context,
+			    const diagnostic_path *path)
+{
+
+  /* TODO: options for different printing approaches.  */
+  if (context->show_caret && context->show_line_numbers_p)
+    {
+      /* Try consolidating events into rich locations.  */
+      unsigned start_idx = 0;
+      while (start_idx < path->m_events.length ())
+	{
+	  path_label label (path, start_idx);
+	  diagnostic_path::event *event = path->m_events[start_idx];
+	  gcc_rich_location richloc (event->m_loc);
+	  unsigned j;
+	  for (j = start_idx + 1; j < path->m_events.length (); j++)
+	    {
+	      diagnostic_path::event *other = path->m_events[j];
+	      // TODO: label, and slacken the constraint
+	      if (can_consolidate_events (event, other))
+		if (richloc.add_location_if_nearby (other->m_loc, false,
+						    &label))
+		  continue;
+	      break;
+	    }
+
+#if 1
+	  /* Hack up state so that diagnostic_report_current_function will announce
+	     function name changes.  */
+	  tree saved_current_function_decl = current_function_decl;
+	  location_t saved_input_location = input_location;
+	  current_function_decl = event->m_fndecl;
+	  input_location = event->m_loc;
+#endif
+
+#if 0
+	  /* Alternatively, part of lhd_print_error_function.  */
+	  char *old_prefix = pp_take_prefix (context->printer);
+	  const char *file = LOCATION_FILE (event->m_loc);
+	  char *new_prefix = file ? file_name_as_prefix (context, file) : NULL;
+	  tree fndecl = event->m_fndecl;
+	  if (TREE_CODE (TREE_TYPE (fndecl)) == METHOD_TYPE)
+	    pp_printf
+	      (context->printer, _("In member function %qs"),
+	       identifier_to_locale (lang_hooks.decl_printable_name (fndecl, 2)));
+	  else
+	    pp_printf
+	      (context->printer, _("In function %qs"),
+	       identifier_to_locale (lang_hooks.decl_printable_name (fndecl, 2)));
+	  pp_newline_and_flush (context->printer);
+	  context->printer->prefix = old_prefix;
+	  free ((char*) new_prefix);
+#endif
+
+	  inform (&richloc, "(%i) %s", start_idx + 1, event->m_desc);
+	  start_idx = j;
+
+#if 1
+	  current_function_decl = saved_current_function_decl;
+	  input_location = saved_input_location;
+#endif
+	}
+    }
+  else
+    {
+      /* Simpler implementation: a note per event.  */
+      diagnostic_path::event *event;
+      int i;
+      FOR_EACH_VEC_ELT (path->m_events, i, event)
+	inform (event->m_loc, "(%i) %s", i + 1, event->m_desc);
+    }
+}
+
+/* This has to be here, rather than diagnostic-format-json.cc,
+   since diagnostic-format-json.o is within OBJS-libcommon and thus
+   doesn't have access to trees (for m_fndecl).  */
+
+static json::value *
+tree_make_json_for_path (diagnostic_context *, const diagnostic_path *path)
+{
+  json::array *path_array = new json::array ();
+  int i;
+  diagnostic_path::event *event;
+  FOR_EACH_VEC_ELT (path->m_events, i, event)
+    {
+      json::object *event_obj = new json::object ();
+      if (event->m_loc)
+	event_obj->set ("location", json_from_expanded_location (event->m_loc));
+      event_obj->set ("description", new json::string (event->m_desc));
+      if (event->m_fndecl)
+	{
+	  const char *function
+	    = identifier_to_locale (lang_hooks.decl_printable_name (event->m_fndecl, 2));
+	  event_obj->set ("function", new json::string (function));
+	}
+      event_obj->set ("depth", new json::number (event->m_depth));
+      path_array->append (event_obj);
+    }
+  return path_array;
+}
+
 /* Sets CONTEXT to use language independent diagnostics.  */
 void
 tree_diagnostics_defaults (diagnostic_context *context)
@@ -312,4 +476,6 @@ tree_diagnostics_defaults (diagnostic_context *context)
   diagnostic_starter (context) = default_tree_diagnostic_starter;
   diagnostic_finalizer (context) = default_diagnostic_finalizer;
   diagnostic_format_decoder (context) = default_tree_printer;
+  context->print_path = tree_print_diagnostic_path;
+  context->make_json_for_path = tree_make_json_for_path;
 }
diff --git a/gcc/tree-diagnostic.h b/gcc/tree-diagnostic.h
index 3ce61c0..8e70acc 100644
--- a/gcc/tree-diagnostic.h
+++ b/gcc/tree-diagnostic.h
@@ -33,20 +33,48 @@ along with GCC; see the file COPYING3.  If not see
 #define diagnostic_last_function(DC)			\
   ((tree) diagnostic_context_auxiliary_data (DC))
 
+#define diagnostic_get_function(DI)		\
+  ( diagnostic_abstract_origin (DI)		\
+    ? diagnostic_abstract_origin (DI)		\
+    : current_function_decl)
+
 /* True if the last function in which a diagnostic was reported is
    different from the current one.  */
-#define diagnostic_last_function_changed(DC, DI)			\
-  (diagnostic_last_function (DC) != (diagnostic_abstract_origin (DI)	\
-				     ? diagnostic_abstract_origin (DI)	\
-				     : current_function_decl))
+#if 1
+#define diagnostic_last_function_changed(DC, DI)                       \
+  (diagnostic_last_function (DC) != (diagnostic_abstract_origin (DI)   \
+                                    ? diagnostic_abstract_origin (DI)  \
+                                    : current_function_decl))
+#else
+static inline bool
+diagnostic_last_function_changed (diagnostic_context *context,
+				  diagnostic_info *diagnostic)
+{
+  tree last_fn = diagnostic_last_function (context);
+  tree diag_fn = diagnostic_get_function (diagnostic);
+  return last_fn != diag_fn;
+}
+#endif
 
 /* Remember the current function as being the last one in which we report
    a diagnostic.  */
-#define diagnostic_set_last_function(DC, DI)		\
-  diagnostic_context_auxiliary_data (DC)		\
-    = (((DI) && diagnostic_abstract_origin (DI))	\
-       ? diagnostic_abstract_origin (DI)		\
+#if 1
+#define diagnostic_set_last_function(DC, DI)           \
+  diagnostic_context_auxiliary_data (DC)               \
+    = (((DI) && diagnostic_abstract_origin (DI))       \
+       ? diagnostic_abstract_origin (DI)               \
        : current_function_decl)
+#else
+static inline void
+diagnostic_set_last_function (diagnostic_context *context,
+			      diagnostic_info *diagnostic)
+{
+  diagnostic_context_auxiliary_data (context)
+    = (diagnostic
+       ? diagnostic_get_function (diagnostic)
+       : current_function_decl);
+}
+#endif
 
 void diagnostic_report_current_function (diagnostic_context *,
 					 diagnostic_info *);
diff --git a/libcpp/include/line-map.h b/libcpp/include/line-map.h
index a6eb87c..8ca4cf3 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.  */
+  const diagnostic_path *get_path () const { return m_path; }
+  void set_path (const 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;
+
+  const 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

