[PATCH] Implement -fdiagnostics-parseable-fixits

David Malcolm dmalcolm@redhat.com
Wed Jun 22 02:59:00 GMT 2016


Both GCC and Clang can emit "fix-it" hints for a diagnostic, giving
a suggestion about how to fix the issue.

Clang has an option -fdiagnostics-parseable-fixits, which emits a
machine-readable version of the fixits, for use by IDEs.  (The only
IDE I know of that supports this format is Xcode [1]; does anyone
know of other IDEs supporting this?  I'd be very happy if someone
implemented Emacs support for it, or could point me at it).

This patch implements the option for gcc.  It seems prudent to be
compatible with Clang here, so I've deliberately used the same option
name and attempted to emulate the output format, based on the
documentation here:
http://llvm.org/releases/3.8.0/tools/clang/docs/UsersManual.html#cmdoption-fdiagnostics-parseable-fixits

Experiments with clang (3.4.2) showed that supplying the option does
*not* suppress the normal printing of fixits, so I emulated that
behavior in my patch.

I implemented tests using both -fself-test and DejaGnu.
For the DejaGnu test coverage, I attempted to implement detection of the
output strings via existing directives, but after several hours of
failing, I instead implemented a new "dg-regexp" directive, which doesn't
expect anything other than the given regexp.  If anyone can see a way
to implement the tests using existing directives, I'll port to that.
(I believe that the injection of leading line numbers was the issue).

I need review of the proposed additions to gcc/testsuite/lib at least
(the rest I believe I can self-approve, but another pair of eyes would
be appreciated).

Successfully bootstrapped&regrtested on x86_64-pc-linux-gnu.
Successful -fself-test of stage1 on powerpc-ibm-aix7.1.3.0.

OK for trunk?

[1] http://clang-developers.42468.n3.nabble.com/Parsing-clang-output-td3275815.html#a3276978

gcc/ChangeLog:
	* common.opt (fdiagnostics-parseable-fixits): New option.
	* diagnostic.c: Include "selftest.h".
	(print_escaped_string): New function.
	(print_parseable_fixits): New function.
	(diagnostic_report_diagnostic): Call print_parseable_fixits.
	(selftest::assert_print_escaped_string): New function.
	(ASSERT_PRINT_ESCAPED_STRING_STREQ): New macro.
	(selftest::test_print_escaped_string): New function.
	(selftest::test_print_parseable_fixits_none): New function.
	(selftest::test_print_parseable_fixits_insert): New function.
	(selftest::test_print_parseable_fixits_remove): New function.
	(selftest::test_print_parseable_fixits_replace): New function.
	(selftest::diagnostic_c_tests): New function.
	* diagnostic.h (struct diagnostic_context): Add field
	"parseable_fixits_p".
	* doc/invoke.texi (Diagnostic Message Formatting Options): Add
	-fdiagnostics-parseable-fixits.
	(-fdiagnostics-parseable-fixits): New option.
	* opts.c (common_handle_option): Handle
	-fdiagnostics-parseable-fixits.
	* selftest-run-tests.c (selftest::run_tests): Call
	selftest::diagnostic_c_tests.
	* selftest.h (selftest::diagnostic_c_tests): New prototype.

gcc/testsuite/ChangeLog:
	* gcc.dg/plugin/diagnostic-test-show-locus-parseable-fixits.c: New
	file.
	* gcc.dg/plugin/plugin.exp (plugin_test_list): Add
	diagnostic-test-show-locus-parseable-fixits.c to sources for
	diagnostic_plugin_test_show_locus.c.
	* lib/gcc-defs.exp (freeform_regexps): New global.
	(dg-regexp): New function.
	(handle-dg-regexps): New function.
	* lib/gcc-dg.exp (cleanup-after-saved-dg-test): Reset
	freeform_regexps to the empty list.
	* lib/prune.exp (prune_gcc_output): Call handle-dg-regexps.

libcpp/ChangeLog:
	* include/line-map.h (fixit_hint::get_start_loc): New pure virtual
	function.
	(fixit_hint::maybe_get_end_loc): Likewise.
	(fixit_insert::get_start_loc): New function, implementing
	fixit_hint::get_start_loc.
	(fixit_insert::maybe_get_end_loc): New function, implementing
	fixit_hint::maybe_get_end_loc.
	(fixit_remove::get_start_loc): New function, implementing
	fixit_hint::get_start_loc.
	(fixit_remove::maybe_get_end_loc): New function, implementing
	fixit_hint::maybe_get_end_loc.
	(fixit_replace::get_start_loc): New function, implementing
	fixit_hint::get_start_loc.
	(fixit_replace::maybe_get_end_loc): New function, implementing
	fixit_hint::maybe_get_end_loc.
---
 gcc/common.opt                                     |   4 +
 gcc/diagnostic.c                                   | 258 +++++++++++++++++++++
 gcc/diagnostic.h                                   |   4 +
 gcc/doc/invoke.texi                                |  34 ++-
 gcc/opts.c                                         |   4 +
 gcc/selftest-run-tests.c                           |   1 +
 gcc/selftest.h                                     |   1 +
 .../diagnostic-test-show-locus-parseable-fixits.c  |  41 ++++
 gcc/testsuite/gcc.dg/plugin/plugin.exp             |   3 +-
 gcc/testsuite/lib/gcc-defs.exp                     |  51 ++++
 gcc/testsuite/lib/gcc-dg.exp                       |   2 +
 gcc/testsuite/lib/prune.exp                        |   3 +
 libcpp/include/line-map.h                          |  16 ++
 13 files changed, 420 insertions(+), 2 deletions(-)
 create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-test-show-locus-parseable-fixits.c

diff --git a/gcc/common.opt b/gcc/common.opt
index f0d7196..5d90385 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -1185,6 +1185,10 @@ Enum(diagnostic_color_rule) String(always) Value(DIAGNOSTICS_COLOR_YES)
 EnumValue
 Enum(diagnostic_color_rule) String(auto) Value(DIAGNOSTICS_COLOR_AUTO)
 
+fdiagnostics-parseable-fixits
+Common Var(flag_diagnostics_parseable_fixits)
+Print fixit hints in machine-readable form.
+
 fdiagnostics-show-option
 Common Var(flag_diagnostics_show_option) Init(1)
 Amend appropriate diagnostic messages with the command line option that controls them.
diff --git a/gcc/diagnostic.c b/gcc/diagnostic.c
index 9f419fb..bb41011 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 "selftest.h"
 
 #ifdef HAVE_TERMIOS_H
 # include <termios.h>
@@ -667,6 +668,112 @@ diagnostic_pop_diagnostics (diagnostic_context *context, location_t where)
   context->n_classification_history ++;
 }
 
+/* Helper function for print_parseable_fixits.  Print TEXT to PP, obeying the
+   escaping rules for -fdiagnostics-parseable-fixits.  */
+
+static void
+print_escaped_string (pretty_printer *pp, const char *text)
+{
+  gcc_assert (pp);
+  gcc_assert (text);
+
+  pp_character (pp, '"');
+  for (const char *ch = text; *ch; ch++)
+    {
+      switch (*ch)
+	{
+	case '\\':
+	  /* Escape backslash as two backslashes.  */
+	  pp_string (pp, "\\\\");
+	  break;
+	case '\t':
+	  /* Escape tab as "\t".  */
+	  pp_string (pp, "\\t");
+	  break;
+	case '\n':
+	  /* Escape newline as "\n".  */
+	  pp_string (pp, "\\n");
+	  break;
+	case '"':
+	  /* Escape doublequotes as \".  */
+	  pp_string (pp, "\\\"");
+	  break;
+	default:
+	  if (ISPRINT (*ch))
+	    pp_character (pp, *ch);
+	  else
+	    /* Use octal for non-printable chars.  */
+	    {
+	      unsigned char c = (*ch & 0xff);
+	      pp_printf (pp, "\\%o%o%o", (c / 64), (c / 8) & 007, c & 007);
+	    }
+	  break;
+	}
+    }
+  pp_character (pp, '"');
+}
+
+/* Implementation of -fdiagnostics-parseable-fixits.  Print a
+   machine-parseable version of all fixits in RICHLOC to PP.  */
+
+static void
+print_parseable_fixits (pretty_printer *pp, rich_location *richloc)
+{
+  gcc_assert (pp);
+  gcc_assert (richloc);
+
+  for (unsigned i = 0; i < richloc->get_num_fixit_hints (); i++)
+    {
+      const fixit_hint *hint = richloc->get_fixit_hint (i);
+      source_location start_loc = hint->get_start_loc ();
+      expanded_location start_exploc = expand_location (start_loc);
+      pp_string (pp, "fix-it:");
+      print_escaped_string (pp, start_exploc.file);
+      source_location end_loc;
+
+      /* For compatibility with clang, print as a half-open range.  */
+      if (hint->maybe_get_end_loc (&end_loc))
+	{
+	  expanded_location end_exploc = expand_location (end_loc);
+	  pp_printf (pp, ":{%i:%i-%i:%i}:",
+		     start_exploc.line, start_exploc.column,
+		     end_exploc.line, end_exploc.column + 1);
+	}
+      else
+	{
+	  pp_printf (pp, ":{%i:%i-%i:%i}:",
+		     start_exploc.line, start_exploc.column,
+		     start_exploc.line, start_exploc.column);
+	}
+      switch (hint->get_kind ())
+	{
+	  case fixit_hint::INSERT:
+	    {
+	      const fixit_insert *insert
+		= static_cast <const fixit_insert *> (hint);
+	      print_escaped_string (pp, insert->get_string ());
+	    }
+	    break;
+
+	  case fixit_hint::REMOVE:
+	    print_escaped_string (pp, "");
+	    break;
+
+	  case fixit_hint::REPLACE:
+	    {
+	      const fixit_replace *replace
+		= static_cast <const fixit_replace *> (hint);
+	      print_escaped_string (pp, replace->get_string ());
+	    }
+	    break;
+
+	  default:
+	    gcc_unreachable ();
+	}
+      pp_newline (pp);
+    }
+}
+
 /* Report a diagnostic message (an error or a warning) as specified by
    DC.  This function is *the* subroutine in terms of which front-ends
    should implement their specific diagnostic handling modules.  The
@@ -828,6 +935,11 @@ diagnostic_report_diagnostic (diagnostic_context *context,
   (*diagnostic_starter (context)) (context, diagnostic);
   pp_output_formatted_text (context->printer);
   (*diagnostic_finalizer (context)) (context, diagnostic);
+  if (context->parseable_fixits_p)
+    {
+      print_parseable_fixits (context->printer, diagnostic->richloc);
+      pp_flush (context->printer);
+    }
   diagnostic_action_after_output (context, diagnostic->kind);
   diagnostic->message.format_spec = saved_format_spec;
   diagnostic->x_data = NULL;
@@ -1302,3 +1414,149 @@ real_abort (void)
 {
   abort ();
 }
+
+#if CHECKING_P
+
+namespace selftest {
+
+/* Helper function for test_print_escaped_string.  */
+
+static void
+assert_print_escaped_string (const location &loc, const char *expected_output,
+			     const char *input)
+{
+  pretty_printer pp;
+  print_escaped_string (&pp, input);
+  ASSERT_STREQ_AT (loc, expected_output, pp_formatted_text (&pp));
+}
+
+#define ASSERT_PRINT_ESCAPED_STRING_STREQ(EXPECTED_OUTPUT, INPUT) \
+    assert_print_escaped_string (SELFTEST_LOCATION, EXPECTED_OUTPUT, INPUT)
+
+/* Tests of print_escaped_string.  */
+
+static void
+test_print_escaped_string ()
+{
+  /* Empty string.  */
+  ASSERT_PRINT_ESCAPED_STRING_STREQ ("\"\"", "");
+
+  /* Non-empty string.  */
+  ASSERT_PRINT_ESCAPED_STRING_STREQ ("\"hello world\"", "hello world");
+
+  /* Various things that need to be escaped:  */
+  /* Backslash.  */
+  ASSERT_PRINT_ESCAPED_STRING_STREQ ("\"before\\\\after\"",
+				     "before\\after");
+  /* Tab.  */
+  ASSERT_PRINT_ESCAPED_STRING_STREQ ("\"before\\tafter\"",
+				     "before\tafter");
+  /* Newline.  */
+  ASSERT_PRINT_ESCAPED_STRING_STREQ ("\"before\\nafter\"",
+				     "before\nafter");
+  /* Double quote.  */
+  ASSERT_PRINT_ESCAPED_STRING_STREQ ("\"before\\\"after\"",
+				     "before\"after");
+
+  /* Non-printable characters: BEL: '\a': 0x07 */
+  ASSERT_PRINT_ESCAPED_STRING_STREQ ("\"before\\007after\"",
+				     "before\aafter");
+  /* Non-printable characters: vertical tab: '\v': 0x0b */
+  ASSERT_PRINT_ESCAPED_STRING_STREQ ("\"before\\013after\"",
+				     "before\vafter");
+}
+
+/* Tests of print_parseable_fixits.  */
+
+/* Verify that print_parseable_fixits emits the empty string if there
+   are no fixits.  */
+
+static void
+test_print_parseable_fixits_none ()
+{
+  pretty_printer pp;
+  rich_location richloc (line_table, UNKNOWN_LOCATION);
+
+  print_parseable_fixits (&pp, &richloc);
+  ASSERT_STREQ ("", pp_formatted_text (&pp));
+}
+
+/* Verify that print_parseable_fixits does the right thing if there
+   is an insertion fixit hint.  */
+
+static void
+test_print_parseable_fixits_insert ()
+{
+  pretty_printer pp;
+  rich_location richloc (line_table, UNKNOWN_LOCATION);
+
+  linemap_add (line_table, LC_ENTER, false, "test.c", 0);
+  linemap_line_start (line_table, 5, 100);
+  linemap_add (line_table, LC_LEAVE, false, NULL, 0);
+  location_t where = linemap_position_for_column (line_table, 10);
+  richloc.add_fixit_insert (where, "added content");
+
+  print_parseable_fixits (&pp, &richloc);
+  ASSERT_STREQ ("fix-it:\"test.c\":{5:10-5:10}:\"added content\"\n",
+		pp_formatted_text (&pp));
+}
+
+/* Verify that print_parseable_fixits does the right thing if there
+   is an removal fixit hint.  */
+
+static void
+test_print_parseable_fixits_remove ()
+{
+  pretty_printer pp;
+  rich_location richloc (line_table, UNKNOWN_LOCATION);
+
+  linemap_add (line_table, LC_ENTER, false, "test.c", 0);
+  linemap_line_start (line_table, 5, 100);
+  linemap_add (line_table, LC_LEAVE, false, NULL, 0);
+  source_range where;
+  where.m_start = linemap_position_for_column (line_table, 10);
+  where.m_finish = linemap_position_for_column (line_table, 20);
+  richloc.add_fixit_remove (where);
+
+  print_parseable_fixits (&pp, &richloc);
+  ASSERT_STREQ ("fix-it:\"test.c\":{5:10-5:21}:\"\"\n",
+		pp_formatted_text (&pp));
+}
+
+/* Verify that print_parseable_fixits does the right thing if there
+   is an replacement fixit hint.  */
+
+static void
+test_print_parseable_fixits_replace ()
+{
+  pretty_printer pp;
+  rich_location richloc (line_table, UNKNOWN_LOCATION);
+
+  linemap_add (line_table, LC_ENTER, false, "test.c", 0);
+  linemap_line_start (line_table, 5, 100);
+  linemap_add (line_table, LC_LEAVE, false, NULL, 0);
+  source_range where;
+  where.m_start = linemap_position_for_column (line_table, 10);
+  where.m_finish = linemap_position_for_column (line_table, 20);
+  richloc.add_fixit_replace (where, "replacement");
+
+  print_parseable_fixits (&pp, &richloc);
+  ASSERT_STREQ ("fix-it:\"test.c\":{5:10-5:21}:\"replacement\"\n",
+		pp_formatted_text (&pp));
+}
+
+/* Run all of the selftests within this file.  */
+
+void
+diagnostic_c_tests ()
+{
+  test_print_escaped_string ();
+  test_print_parseable_fixits_none ();
+  test_print_parseable_fixits_insert ();
+  test_print_parseable_fixits_remove ();
+  test_print_parseable_fixits_replace ();
+}
+
+} // namespace selftest
+
+#endif /* #if CHECKING_P */
diff --git a/gcc/diagnostic.h b/gcc/diagnostic.h
index 58d77df..afce285 100644
--- a/gcc/diagnostic.h
+++ b/gcc/diagnostic.h
@@ -205,6 +205,10 @@ struct diagnostic_context
   /* Usable by plugins; if true, print a debugging ruler above the
      source output.  */
   bool show_ruler_p;
+
+  /* If true, print fixits in machine-parseable form after the
+     rest of the diagnostic.  */
+  bool parseable_fixits_p;
 };
 
 static inline void
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index e000218..4b04130 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -246,7 +246,8 @@ Objective-C and Objective-C++ Dialects}.
 @gccoptlist{-fmessage-length=@var{n}  @gol
 -fdiagnostics-show-location=@r{[}once@r{|}every-line@r{]}  @gol
 -fdiagnostics-color=@r{[}auto@r{|}never@r{|}always@r{]}  @gol
--fno-diagnostics-show-option -fno-diagnostics-show-caret}
+-fno-diagnostics-show-option -fno-diagnostics-show-caret @gol
+-fdiagnostics-parseable-fixits}
 
 @item Warning Options
 @xref{Warning Options,,Options to Request or Suppress Warnings}.
@@ -3389,6 +3390,37 @@ the @option{-fmessage-length=n} option is given.  When the output is done
 to the terminal, the width is limited to the width given by the
 @env{COLUMNS} environment variable or, if not set, to the terminal width.
 
+@item -fdiagnostics-parseable-fixits
+@opindex fdiagnostics-parseable-fixits
+Emit fix-it hints in a machine-parseable format, suitable for consumption
+by IDEs.  For each fix-it, a line will be printed after the relevant
+diagnostic, starting with the string ``fix-it:''.  For example:
+
+@smallexample
+fix-it:"test.c":@{45:3-45:21@}:"gtk_widget_show_all"
+@end smallexample
+
+The location is expressed as a half-open range, expressed as a count of
+bytes, starting at byte 1 for the initial column.  In the above example,
+bytes 3 through 20 of line 45 of ``test.c'' are to be replaced with the
+given string:
+
+@smallexample
+00000000011111111112222222222
+12345678901234567890123456789
+  gtk_widget_showall (dlg);
+  ^^^^^^^^^^^^^^^^^^
+  gtk_widget_show_all
+@end smallexample
+
+The filename and replacement string escape backslash as ``\\", tab as ``\t'',
+newline as ``\n'', double quotes as ``\"'', non-printable characters as octal
+(e.g. vertical tab as ``\013'').
+
+An empty replacement string indicates that the given range is to be removed.
+An empty range (e.g. ``45:3-45:3'') indicates that the string is to
+be inserted at the given position.
+
 @end table
 
 @node Warning Options
diff --git a/gcc/opts.c b/gcc/opts.c
index e80331f..7406210 100644
--- a/gcc/opts.c
+++ b/gcc/opts.c
@@ -1871,6 +1871,10 @@ common_handle_option (struct gcc_options *opts,
       diagnostic_color_init (dc, value);
       break;
 
+    case OPT_fdiagnostics_parseable_fixits:
+      dc->parseable_fixits_p = value;
+      break;
+
     case OPT_fdiagnostics_show_option:
       dc->show_option_requested = value;
       break;
diff --git a/gcc/selftest-run-tests.c b/gcc/selftest-run-tests.c
index d4a9c0b..bddf0b2 100644
--- a/gcc/selftest-run-tests.c
+++ b/gcc/selftest-run-tests.c
@@ -59,6 +59,7 @@ selftest::run_tests ()
   /* Higher-level tests, or for components that other selftests don't
      rely on.  */
   diagnostic_show_locus_c_tests ();
+  diagnostic_c_tests ();
   fold_const_c_tests ();
   spellcheck_c_tests ();
   spellcheck_tree_c_tests ();
diff --git a/gcc/selftest.h b/gcc/selftest.h
index 2bc7316..c6becdd 100644
--- a/gcc/selftest.h
+++ b/gcc/selftest.h
@@ -72,6 +72,7 @@ extern void assert_streq (const location &loc,
 /* Declarations for specific families of tests (by source file), in
    alphabetical order.  */
 extern void bitmap_c_tests ();
+extern void diagnostic_c_tests ();
 extern void diagnostic_show_locus_c_tests ();
 extern void et_forest_c_tests ();
 extern void fold_const_c_tests ();
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-show-locus-parseable-fixits.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-show-locus-parseable-fixits.c
new file mode 100644
index 0000000..1490c98
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-show-locus-parseable-fixits.c
@@ -0,0 +1,41 @@
+/* { dg-do compile } */
+/* { dg-options "-O -fdiagnostics-parseable-fixits" } */
+
+/* This is a collection of unittests for diagnostic_show_locus;
+   see the overview in diagnostic_plugin_test_show_locus.c.
+
+   In particular, note the discussion of why we need a very long line here:
+01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
+   and that we can't use macros in this file.  */
+
+/* Unit test for rendering of insertion fixit hints
+   (example taken from PR 62316).  */
+
+void test_fixit_insert (void)
+{
+#if 0
+   int a[2][2] = { 0, 1 , 2, 3 }; /* { dg-warning "insertion hints" } */
+/* { dg-regexp "fix-it:.*\\{17:20-17:20\\}:.*" } */
+/* { dg-regexp "fix-it:.*\\{17:24-17:24\\}:.*" } */
+#endif
+}
+
+/* Unit test for rendering of "remove" fixit hints.  */
+
+void test_fixit_remove (void)
+{
+#if 0
+  int a;; /* { dg-warning "example of a removal hint" } */
+/* { dg-regexp "fix-it:.*\\{28:9-28:10\\}:.*" } */
+#endif
+}
+
+/* Unit test for rendering of "replace" fixit hints.  */
+
+void test_fixit_replace (void)
+{
+#if 0
+  gtk_widget_showall (dlg); /* { dg-warning "example of a replacement hint" } */
+/* { dg-regexp "fix-it:.*\\{38:3-38:21\\}:.*" } */
+#endif
+}
diff --git a/gcc/testsuite/gcc.dg/plugin/plugin.exp b/gcc/testsuite/gcc.dg/plugin/plugin.exp
index be2ac8d..f039c8d 100644
--- a/gcc/testsuite/gcc.dg/plugin/plugin.exp
+++ b/gcc/testsuite/gcc.dg/plugin/plugin.exp
@@ -65,7 +65,8 @@ set plugin_test_list [list \
     { wide-int_plugin.c wide-int-test-1.c } \
     { diagnostic_plugin_test_show_locus.c \
 	  diagnostic-test-show-locus-bw.c \
-	  diagnostic-test-show-locus-color.c } \
+	  diagnostic-test-show-locus-color.c \
+	  diagnostic-test-show-locus-parseable-fixits.c } \
     { diagnostic_plugin_test_tree_expression_range.c \
 	  diagnostic-test-expressions-1.c } \
     { diagnostic_plugin_show_trees.c \
diff --git a/gcc/testsuite/lib/gcc-defs.exp b/gcc/testsuite/lib/gcc-defs.exp
index 0210af8..1186e5b 100644
--- a/gcc/testsuite/lib/gcc-defs.exp
+++ b/gcc/testsuite/lib/gcc-defs.exp
@@ -352,3 +352,54 @@ proc gcc-set-multilib-library-path { compiler } {
 
     return $libpath
 }
+
+# A list of all uses of dg-regexp, each entry of the form:
+#   line-number regexp
+# This is cleared at the end of each test by gcc-dg.exp's wrapper for dg-test.
+set freeform_regexps []
+
+# Directive for looking for a regexp, without any line numbers or other
+# prefixes.
+
+proc dg-regexp { args } {
+    verbose "dg-regexp: args: $args" 2
+
+    global freeform_regexps
+    lappend freeform_regexps $args
+}
+
+# Hook to be called by prune.exp's prune_gcc_output to
+# look for the expected dg-regexp expressions, pruning them,
+# reporting PASS for those that are found, and FAIL for
+# those that weren't found.
+#
+# It returns a pruned version of its output.
+
+proc handle-dg-regexps { text } {
+    global freeform_regexps
+    global testname_with_flags
+
+    foreach entry $freeform_regexps {
+	verbose "  entry: $entry" 3
+
+	set linenum [lindex $entry 0]
+	set rexp [lindex $entry 1]
+
+	# Escape newlines in $rexp so that we can print them in
+	# pass/fail results.
+	set escaped_regex [string map {"\n" "\\n"} $rexp]
+	verbose "escaped_regex: ${escaped_regex}" 4
+
+	set title "$testname_with_flags dg-regexp $linenum"
+
+	# Use "regsub" to attempt to prune the pattern from $text
+	if {[regsub -line $rexp $text "" text]} {
+	    # Success; the multiline pattern was pruned.
+	    pass "$title was found: \"$escaped_regex\""
+	} else {
+	    fail "$title not found: \"$escaped_regex\""
+	}
+    }
+
+    return $text
+}
diff --git a/gcc/testsuite/lib/gcc-dg.exp b/gcc/testsuite/lib/gcc-dg.exp
index 7039140..b7f0ff7 100644
--- a/gcc/testsuite/lib/gcc-dg.exp
+++ b/gcc/testsuite/lib/gcc-dg.exp
@@ -899,6 +899,7 @@ if { [info procs saved-dg-test] == [list] } {
 	global saved_compiler_env_var
 	global keep_saved_temps_suffixes
 	global multiline_expected_outputs
+	global freeform_regexps
 
 	set additional_files ""
 	set additional_sources ""
@@ -924,6 +925,7 @@ if { [info procs saved-dg-test] == [list] } {
 	    unset testname_with_flags
 	}
 	set multiline_expected_outputs []
+	set freeform_regexps []
     }
 
     proc dg-test { args } {
diff --git a/gcc/testsuite/lib/prune.exp b/gcc/testsuite/lib/prune.exp
index ab6f369..d3ff493 100644
--- a/gcc/testsuite/lib/prune.exp
+++ b/gcc/testsuite/lib/prune.exp
@@ -73,6 +73,9 @@ proc prune_gcc_output { text } {
     # Call into multiline.exp to handle any multiline output directives.
     set text [handle-multiline-outputs $text]
 
+    # Handle any freeform regexps.
+    set text [handle-dg-regexps $text]
+
     #send_user "After:$text\n"
 
     return $text
diff --git a/libcpp/include/line-map.h b/libcpp/include/line-map.h
index 292abce..416419c 100644
--- a/libcpp/include/line-map.h
+++ b/libcpp/include/line-map.h
@@ -1418,6 +1418,8 @@ public:
 
   virtual enum kind get_kind () const = 0;
   virtual bool affects_line_p (const char *file, int line) = 0;
+  virtual source_location get_start_loc () const = 0;
+  virtual bool maybe_get_end_loc (source_location *out) const = 0;
 };
 
 class fixit_insert : public fixit_hint
@@ -1428,6 +1430,8 @@ class fixit_insert : public fixit_hint
   ~fixit_insert ();
   enum kind get_kind () const { return INSERT; }
   bool affects_line_p (const char *file, int line);
+  source_location get_start_loc () const { return m_where; }
+  bool maybe_get_end_loc (source_location *) const { return false; }
 
   source_location get_location () const { return m_where; }
   const char *get_string () const { return m_bytes; }
@@ -1447,6 +1451,12 @@ class fixit_remove : public fixit_hint
 
   enum kind get_kind () const { return REMOVE; }
   bool affects_line_p (const char *file, int line);
+  source_location get_start_loc () const { return m_src_range.m_start; }
+  bool maybe_get_end_loc (source_location *out) const
+  {
+    *out = m_src_range.m_finish;
+    return true;
+  }
 
   source_range get_range () const { return m_src_range; }
 
@@ -1463,6 +1473,12 @@ class fixit_replace : public fixit_hint
 
   enum kind get_kind () const { return REPLACE; }
   bool affects_line_p (const char *file, int line);
+  source_location get_start_loc () const { return m_src_range.m_start; }
+  bool maybe_get_end_loc (source_location *out) const
+  {
+    *out = m_src_range.m_finish;
+    return true;
+  }
 
   source_range get_range () const { return m_src_range; }
   const char *get_string () const { return m_bytes; }
-- 
1.8.5.3



More information about the Gcc-patches mailing list