diff mbox series

[pushed] diagnostics: add automatic URL-ification within messages

Message ID 20231104015955.2389603-1-dmalcolm@redhat.com
State New
Headers show
Series [pushed] diagnostics: add automatic URL-ification within messages | expand

Commit Message

David Malcolm Nov. 4, 2023, 1:59 a.m. UTC
On Thu, 2023-11-02 at 09:19 -0400, David Malcolm wrote:

[...snip...]

I eliminated the dependency on the c-pragma.cc changes from this patch,
updated it for diagnostic_context becoming a class, and pushed it to
trunk as r14-5118-gc5db4d8ba5f3de (after re-testing it).

For reference, here's what I pushed:

In r10-3781-gd26082357676a3 GCC's pretty-print framework gained
the ability to emit embedding URLs via escape sequences
for marking up text output..

In r10-3783-gb4c7ca2ef3915a GCC started using this for the
[-Wname-of-option] emitted at the end of each diagnostic so that it
becomes a hyperlink to the documentation for that option on the GCC
website.

This makes it much more convenient for the user to locate pertinent
documentation when a diagnostic is emitted.

The above involved special-casing in one specific place, but there is
plenty of quoted text throughout GCC's diagnostic messages that could
usefully have a documentation URL: references to options, pragmas, etc

This patch adds a new optional "urlifier" parameter to pp_format.
The idea is that a urlifier object has responsibility for mapping from
quoted strings in diagnostic messages to URLs, and pp_format has the
ability to automatically add URL escapes for strings that the urlifier
gives it URLs for.

For example, given the format string:

  "%<#pragma pack%> has no effect with %<-fpack-struct%>"

with this patch GCC is able to automatically linkify the "#pragma pack"
text to
  https://gcc.gnu.org/onlinedocs/gcc/Structure-Layout-Pragmas.html
and the "-fpack-struct" text to:
  https://gcc.gnu.org/onlinedocs/gcc/Code-Gen-Options.html#index-fpack-struct

and we don't have to modify the format string itself.

This is only done for the pp_format within
diagnostic_context::report_diagnostic i.e. just for the primary message
in each diagnostics, and not for other places within GCC that use pp
format internally.

"urlifier" is an abstract base class, with a GCC-specific subclass
implementing the logic for generating URLs into GCC's HTML
documentation via binary search in a data table.  This patch implements
the gcc_urlifier with a small table generated by hand; the data table in
this patch only covers various pragmas and the option referenced by the
above pragma message.

I have a followup patch that scripts the creation of this data by
directly scraping the output of "make html", thus automating all this,
and (I hope) minimizing the work of ensuring that documentation URLs
emitted by GCC match the generated documentation.

gcc/ChangeLog:
	* Makefile.in (GCC_OBJS): Add gcc-urlifier.o.
	(OBJS): Likewise.

gcc/c-family/ChangeLog:
	* c-pragma.cc:: (handle_pragma_push_options): Fix missing "GCC" in
	name of pragma in "junk" message.
	(handle_pragma_pop_options): Likewise.

gcc/ChangeLog:
	* diagnostic.cc: Include "pretty-print-urlifier.h".
	(diagnostic_context::initialize): Initialize m_urlifier.
	(diagnostic_context::finish): Clean up m_urlifier
	(diagnostic_report::diagnostic): m_urlifier to pp_format.
	* diagnostic.h (diagnostic_context::m_urlifier): New field.
	* gcc-urlifier.cc: New file.
	* gcc-urlifier.def: New file.
	* gcc-urlifier.h: New file.
	* gcc.cc: Include "gcc-urlifier.h".
	(driver::global_initializations): Initialize global_dc->m_urlifier.
	* pretty-print-urlifier.h: New file.
	* pretty-print.cc: Include "pretty-print-urlifier.h".
	(obstack_append_string): New.
	(urlify_quoted_string): New.
	(pp_format): Add "urlifier" param and use it to implement optional
	urlification of quoted text strings.
	(pp_output_formatted_text): Make buffer a const pointer.
	(selftest::pp_printf_with_urlifier): New.
	(selftest::test_urlification): New.
	(selftest::pretty_print_cc_tests): Call it.
	* pretty-print.h (class urlifier): New forward declaration.
	(pp_format): Add optional urlifier param.
	* selftest-run-tests.cc (selftest::run_tests): Call
	selftest::gcc_urlifier_cc_tests .
	* selftest.h (selftest::gcc_urlifier_cc_tests): New decl.
	* toplev.cc: Include "gcc-urlifier.h".
	(general_init): Initialize global_dc->m_urlifier.
---
 gcc/Makefile.in             |   3 +-
 gcc/c-family/c-pragma.cc    |   4 +-
 gcc/diagnostic.cc           |   7 +-
 gcc/diagnostic.h            |   4 +
 gcc/gcc-urlifier.cc         | 159 +++++++++++++++++++++++
 gcc/gcc-urlifier.def        |  20 +++
 gcc/gcc-urlifier.h          |  26 ++++
 gcc/gcc.cc                  |   2 +
 gcc/pretty-print-urlifier.h |  33 +++++
 gcc/pretty-print.cc         | 242 +++++++++++++++++++++++++++++++++++-
 gcc/pretty-print.h          |   5 +-
 gcc/selftest-run-tests.cc   |   1 +
 gcc/selftest.h              |   1 +
 gcc/toplev.cc               |   2 +
 14 files changed, 498 insertions(+), 11 deletions(-)
 create mode 100644 gcc/gcc-urlifier.cc
 create mode 100644 gcc/gcc-urlifier.def
 create mode 100644 gcc/gcc-urlifier.h
 create mode 100644 gcc/pretty-print-urlifier.h
diff mbox series

Patch

diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index 41ed8163cd8..ff77d3cdc64 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -1279,7 +1279,7 @@  FORTRAN_TARGET_OBJS=@fortran_target_objs@
 RUST_TARGET_OBJS=@rust_target_objs@
 
 # Object files for gcc many-languages driver.
-GCC_OBJS = gcc.o gcc-main.o ggc-none.o
+GCC_OBJS = gcc.o gcc-main.o ggc-none.o gcc-urlifier.o
 
 c-family-warn = $(STRICT_WARN)
 
@@ -1452,6 +1452,7 @@  OBJS = \
 	function-tests.o \
 	fwprop.o \
 	gcc-rich-location.o \
+	gcc-urlifier.o \
 	gcse.o \
 	gcse-common.o \
 	ggc-common.o \
diff --git a/gcc/c-family/c-pragma.cc b/gcc/c-family/c-pragma.cc
index df3e3e6b3b0..849f8ac8c8b 100644
--- a/gcc/c-family/c-pragma.cc
+++ b/gcc/c-family/c-pragma.cc
@@ -1208,7 +1208,7 @@  handle_pragma_push_options (cpp_reader *)
   token = pragma_lex (&x);
   if (token != CPP_EOF)
     {
-      warning (OPT_Wpragmas, "junk at end of %<#pragma push_options%>");
+      warning (OPT_Wpragmas, "junk at end of %<#pragma GCC push_options%>");
       return;
     }
 
@@ -1245,7 +1245,7 @@  handle_pragma_pop_options (cpp_reader *)
   token = pragma_lex (&x);
   if (token != CPP_EOF)
     {
-      warning (OPT_Wpragmas, "junk at end of %<#pragma pop_options%>");
+      warning (OPT_Wpragmas, "junk at end of %<#pragma GCC pop_options%>");
       return;
     }
 
diff --git a/gcc/diagnostic.cc b/gcc/diagnostic.cc
index 4b92f5461be..e917e6ce4ac 100644
--- a/gcc/diagnostic.cc
+++ b/gcc/diagnostic.cc
@@ -43,6 +43,7 @@  along with GCC; see the file COPYING3.  If not see
 #include "opts.h"
 #include "cpplib.h"
 #include "text-art/theme.h"
+#include "pretty-print-urlifier.h"
 
 #ifdef HAVE_TERMIOS_H
 # include <termios.h>
@@ -193,6 +194,7 @@  diagnostic_context::initialize (int n_opts)
   m_option_state = nullptr;
   m_option_name = nullptr;
   m_get_option_url = nullptr;
+  m_urlifier = nullptr;
   m_last_location = UNKNOWN_LOCATION;
   m_last_module = nullptr;
   m_client_aux_data = nullptr;
@@ -350,6 +352,9 @@  diagnostic_context::finish ()
       delete m_client_data_hooks;
       m_client_data_hooks = nullptr;
     }
+
+  delete m_urlifier;
+  m_urlifier = nullptr;
 }
 
 void
@@ -1567,7 +1572,7 @@  diagnostic_context::report_diagnostic (diagnostic_info *diagnostic)
     m_output_format->on_begin_group ();
   m_diagnostic_groups.m_emission_count++;
 
-  pp_format (this->printer, &diagnostic->message);
+  pp_format (this->printer, &diagnostic->message, m_urlifier);
   m_output_format->on_begin_diagnostic (diagnostic);
   pp_output_formatted_text (this->printer);
   if (m_show_cwe)
diff --git a/gcc/diagnostic.h b/gcc/diagnostic.h
index 2def7bddce9..cf21558c7b2 100644
--- a/gcc/diagnostic.h
+++ b/gcc/diagnostic.h
@@ -518,6 +518,10 @@  public:
      particular option.  */
   char *(*m_get_option_url) (diagnostic_context *, int);
 
+  /* An optional hook for adding URLs to quoted text strings in
+     diagnostics.  Only used for the main diagnostic message.  */
+  urlifier *m_urlifier;
+
   void (*m_print_path) (diagnostic_context *, const diagnostic_path *);
   json::value *(*m_make_json_for_path) (diagnostic_context *,
 					const diagnostic_path *);
diff --git a/gcc/gcc-urlifier.cc b/gcc/gcc-urlifier.cc
new file mode 100644
index 00000000000..269246bc703
--- /dev/null
+++ b/gcc/gcc-urlifier.cc
@@ -0,0 +1,159 @@ 
+/* Automatic generation of links into GCC's documentation.
+   Copyright (C) 2023 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/>.  */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "pretty-print.h"
+#include "pretty-print-urlifier.h"
+#include "gcc-urlifier.h"
+#include "selftest.h"
+
+namespace {
+
+/* Concrete subclass of urlifier for generating links into
+   GCC's HTML documentation.  */
+
+class gcc_urlifier : public urlifier
+{
+public:
+  char *get_url_for_quoted_text (const char *p, size_t sz) const final override;
+
+  const char *get_url_suffix_for_quoted_text (const char *p, size_t sz) const;
+  const char *get_url_suffix_for_quoted_text (const char *p) const;
+
+private:
+  static char *
+  make_doc_url (const char *doc_url_suffix);
+};
+
+/* class gcc_urlifier : public urlifier.  */
+
+#define DOC_URL(QUOTED_TEXT, URL_SUFFIX) \
+  { (QUOTED_TEXT), (URL_SUFFIX) }
+
+const struct
+{
+  const char *quoted_text;
+  const char *url_suffix;
+} doc_urls[] = {
+
+#include "gcc-urlifier.def"
+
+};
+
+char *
+gcc_urlifier::get_url_for_quoted_text (const char *p, size_t sz) const
+{
+  if (const char *url_suffix = get_url_suffix_for_quoted_text (p, sz))
+    return make_doc_url (url_suffix);
+  return nullptr;
+}
+
+const char *
+gcc_urlifier::get_url_suffix_for_quoted_text (const char *p, size_t sz) const
+{
+  /* Binary search.  This assumes that the quoted_text fields of doc_urls
+     are in sorted order.  */
+  int min = 0;
+  int max = ARRAY_SIZE (doc_urls) - 1;
+  while (true)
+    {
+      if (min > max)
+	return nullptr;
+      int midpoint = (min + max) / 2;
+      gcc_assert ((size_t)midpoint < ARRAY_SIZE (doc_urls));
+      int cmp = strncmp (p, doc_urls[midpoint].quoted_text, sz);
+      if (cmp == 0)
+	{
+	  if (doc_urls[midpoint].quoted_text[sz] == '\0')
+	    return doc_urls[midpoint].url_suffix;
+	  else
+	    max = midpoint - 1;
+	}
+      else if (cmp < 0)
+	max = midpoint - 1;
+      else
+	min = midpoint + 1;
+    }
+  return nullptr;
+}
+
+const char *
+gcc_urlifier::get_url_suffix_for_quoted_text (const char *p) const
+{
+  return get_url_suffix_for_quoted_text (p, strlen (p));
+}
+
+char *
+gcc_urlifier::make_doc_url (const char *doc_url_suffix)
+{
+  if (!doc_url_suffix)
+    return nullptr;
+
+  return concat (DOCUMENTATION_ROOT_URL, doc_url_suffix, nullptr);
+}
+
+} // anonymous namespace
+
+urlifier *
+make_gcc_urlifier ()
+{
+  return new gcc_urlifier ();
+}
+
+#if CHECKING_P
+
+namespace selftest {
+
+/* Selftests.  */
+
+/* Run all of the selftests within this file.  */
+
+void
+gcc_urlifier_cc_tests ()
+{
+  /* Check that doc_urls.quoted_text is sorted.  */
+  for (size_t idx = 1; idx < ARRAY_SIZE (doc_urls); idx++)
+    gcc_assert (strcmp (doc_urls[idx - 1].quoted_text,
+			doc_urls[idx].quoted_text)
+		< 0);
+
+  gcc_urlifier u;
+
+  ASSERT_EQ (u.get_url_suffix_for_quoted_text (""), nullptr);
+  ASSERT_EQ (u.get_url_suffix_for_quoted_text (")"), nullptr);
+
+  ASSERT_STREQ (u.get_url_suffix_for_quoted_text ("#pragma message"),
+		"gcc/Diagnostic-Pragmas.html");
+
+  // Incomplete prefix of a quoted_text
+  ASSERT_EQ (u.get_url_suffix_for_quoted_text ("#pragma mess"), nullptr);
+
+  /* Check that every element is findable.  */
+  for (size_t idx = 0; idx < ARRAY_SIZE (doc_urls); idx++)
+    ASSERT_STREQ
+      (u.get_url_suffix_for_quoted_text (doc_urls[idx].quoted_text),
+       doc_urls[idx].url_suffix);
+}
+
+} // namespace selftest
+
+#endif /* #if CHECKING_P */
diff --git a/gcc/gcc-urlifier.def b/gcc/gcc-urlifier.def
new file mode 100644
index 00000000000..360de930e9e
--- /dev/null
+++ b/gcc/gcc-urlifier.def
@@ -0,0 +1,20 @@ 
+/* Keep this file sorted.  */
+DOC_URL ("#pragma GCC diagnostic", "gcc/Diagnostic-Pragmas.html"),
+DOC_URL ("#pragma GCC diagnostic ignored_attributes", "gcc/Diagnostic-Pragmas.html"),
+DOC_URL ("#pragma GCC ivdep", "gcc/Loop-Specific-Pragmas.html#index-pragma-GCC-ivdep"),
+DOC_URL ("#pragma GCC novector", "gcc/Loop-Specific-Pragmas.html#index-pragma-GCC-novector"),
+DOC_URL ("#pragma GCC optimize", "gcc/Function-Specific-Option-Pragmas.html#index-pragma-GCC-optimize"),
+DOC_URL ("#pragma GCC pop_options", "gcc/Push_002fPop-Macro-Pragmas.html"),
+DOC_URL ("#pragma GCC push_options", "gcc/Push_002fPop-Macro-Pragmas.html"),
+DOC_URL ("#pragma GCC reset_options", "gcc/Function-Specific-Option-Pragmas.html#index-pragma-GCC-reset_005foptions"),
+DOC_URL ("#pragma GCC target", "gcc/Function-Specific-Option-Pragmas.html#index-pragma-GCC-target"),
+DOC_URL ("#pragma GCC unroll", "gcc/Loop-Specific-Pragmas.html#index-pragma-GCC-unroll-n"),
+DOC_URL ("#pragma GCC visibility", "gcc/Visibility-Pragmas.html"),
+DOC_URL ("#pragma GCC visibility pop", "gcc/Visibility-Pragmas.html"),
+DOC_URL ("#pragma message", "gcc/Diagnostic-Pragmas.html"),
+DOC_URL ("#pragma pack", "gcc/Structure-Layout-Pragmas.html"),
+DOC_URL ("#pragma redefine_extname", "gcc/Symbol-Renaming-Pragmas.html"),
+DOC_URL ("#pragma scalar_storage_order", "gcc/Structure-Layout-Pragmas.html"),
+DOC_URL ("#pragma weak", "gcc/Weak-Pragmas.html"),
+DOC_URL ("--version", "gcc/Overall-Options.html#index-version"),
+DOC_URL ("-fpack-struct", "gcc/Code-Gen-Options.html#index-fpack-struct"),
diff --git a/gcc/gcc-urlifier.h b/gcc/gcc-urlifier.h
new file mode 100644
index 00000000000..614e1c64b94
--- /dev/null
+++ b/gcc/gcc-urlifier.h
@@ -0,0 +1,26 @@ 
+/* Automatic generation of links into GCC's documentation.
+   Copyright (C) 2023 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_GCC_URLIFIER_H
+#define GCC_GCC_URLIFIER_H
+
+extern urlifier *make_gcc_urlifier ();
+
+#endif /* GCC_GCC_URLIFIER_H */
diff --git a/gcc/gcc.cc b/gcc/gcc.cc
index 0b7e07872cd..02464958f36 100644
--- a/gcc/gcc.cc
+++ b/gcc/gcc.cc
@@ -46,6 +46,7 @@  compilation is specified by a string called a "spec".  */
 #include "spellcheck.h"
 #include "opts-jobserver.h"
 #include "common/common-target.h"
+#include "gcc-urlifier.h"
 
 #ifndef MATH_LIBRARY
 #define MATH_LIBRARY "m"
@@ -8291,6 +8292,7 @@  driver::global_initializations ()
   diagnostic_initialize (global_dc, 0);
   diagnostic_color_init (global_dc);
   diagnostic_urls_init (global_dc);
+  global_dc->m_urlifier = make_gcc_urlifier ();
 
 #ifdef GCC_DRIVER_HOST_INITIALIZATION
   /* Perform host dependent initialization when needed.  */
diff --git a/gcc/pretty-print-urlifier.h b/gcc/pretty-print-urlifier.h
new file mode 100644
index 00000000000..bdb7fca00d4
--- /dev/null
+++ b/gcc/pretty-print-urlifier.h
@@ -0,0 +1,33 @@ 
+/* Copyright (C) 2023 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_PRETTY_PRINT_URLIFIER_H
+#define GCC_PRETTY_PRINT_URLIFIER_H
+
+/* Abstract base class for optional use in pp_format for adding URLs
+   to quoted text strings.  */
+
+class urlifier
+{
+public:
+  virtual ~urlifier () {}
+  virtual char *get_url_for_quoted_text (const char *p, size_t sz) const = 0;
+};
+
+#endif /* GCC_PRETTY_PRINT_URLIFIER_H */
diff --git a/gcc/pretty-print.cc b/gcc/pretty-print.cc
index 80780cfd7b8..9a4827622dc 100644
--- a/gcc/pretty-print.cc
+++ b/gcc/pretty-print.cc
@@ -23,6 +23,7 @@  along with GCC; see the file COPYING3.  If not see
 #include "coretypes.h"
 #include "intl.h"
 #include "pretty-print.h"
+#include "pretty-print-urlifier.h"
 #include "diagnostic-color.h"
 #include "diagnostic-event-id.h"
 #include "selftest.h"
@@ -1022,6 +1023,95 @@  pp_indent (pretty_printer *pp)
 
 static const char *get_end_url_string (pretty_printer *);
 
+/* Append STR to OSTACK, without a null-terminator.  */
+
+static void
+obstack_append_string (obstack *ostack, const char *str)
+{
+  obstack_grow (ostack, str, strlen (str));
+}
+
+/* Given quoted text starting at QUOTED_TEXT_START_IDX within PP's buffer,
+   potentially use URLIFIER (if non-null) to see if there's a URL for the
+   quoted text.
+
+   If so, replace the quoted part of the text in the buffer with a URLified
+   version of the text, using PP's settings.
+
+   For example, given this is the buffer:
+     "this is a test `hello world"
+     .................^~~~~~~~~~~
+   with the quoted text starting at the 'h' of "hello world", the buffer
+   becomes:
+     "this is a test `BEGIN_URL(URL)hello worldEND(URL)"
+     .................^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+     .................-----------replacement-----------
+*/
+
+static void
+urlify_quoted_string (pretty_printer *pp,
+		      const urlifier *urlifier,
+		      size_t quoted_text_start_idx)
+{
+  if (pp->url_format == URL_FORMAT_NONE)
+    return;
+  if (!urlifier)
+    return;
+
+  output_buffer * const buffer = pp_buffer (pp);
+
+  /* Get end of quoted string.  */
+  const size_t close_quote_idx
+    = obstack_object_size (&buffer->chunk_obstack);
+  gcc_assert (close_quote_idx >= quoted_text_start_idx);
+  if (close_quote_idx == quoted_text_start_idx)
+    /* Empty quoted string; do nothing.  */
+    return;
+  const size_t len = close_quote_idx - quoted_text_start_idx;
+  const char *start = (buffer->chunk_obstack.object_base
+		       + quoted_text_start_idx);
+  char *url = urlifier->get_url_for_quoted_text (start, len);
+  if (!url)
+    /* No URL for this quoted text; do nothing.  */
+    return;
+
+  /* Stash a copy of the quoted text.  */
+  char *text = xstrndup (start, len);
+
+  /* Replace quoted text...  */
+  buffer->chunk_obstack.next_free -= len;
+
+  /*  ...with URLified version of the text.  */
+  /* Begin URL.  */
+  switch (pp->url_format)
+    {
+    default:
+    case URL_FORMAT_NONE:
+      gcc_unreachable ();
+    case URL_FORMAT_ST:
+      obstack_append_string (&buffer->chunk_obstack,
+			     "\33]8;;");
+      obstack_append_string (&buffer->chunk_obstack, url);
+      obstack_append_string (&buffer->chunk_obstack,
+			     "\33\\");
+      break;
+    case URL_FORMAT_BEL:
+      obstack_append_string (&buffer->chunk_obstack,
+			     "\33]8;;");
+      obstack_append_string (&buffer->chunk_obstack, url);
+      obstack_append_string (&buffer->chunk_obstack,
+			     "\a");
+      break;
+    }
+  /* Add the text back.  */
+  obstack_append_string (&buffer->chunk_obstack, text);
+  /* End URL.  */
+  obstack_append_string (&buffer->chunk_obstack,
+			 get_end_url_string (pp));
+  free (text);
+  free (url);
+}
+
 /* The following format specifiers are recognized as being client independent:
    %d, %i: (signed) integer in base ten.
    %u: unsigned integer in base ten.
@@ -1064,12 +1154,25 @@  static const char *get_end_url_string (pretty_printer *);
 
 /* Formatting phases 1 and 2: render TEXT->format_spec plus
    text->m_args_ptr into a series of chunks in pp_buffer (PP)->args[].
-   Phase 3 is in pp_output_formatted_text.  */
+   Phase 3 is in pp_output_formatted_text.
+
+   If URLIFIER is non-NULL, then use it to add URLs for quoted
+   strings, so that e.g.
+     "before %<quoted%> after"
+   with a URLIFIER that has a URL for "quoted" might be emitted as:
+     "before `BEGIN_URL(http://example.com)quotedEND_URL' after"
+   This only works for message fragments that are:
+   - quoted entirely in phase 1 (e.g. "%<this is quoted%>"), or
+   - quoted entirely in phase 2 (e.g. "%qs"),
+   but *not* in strings that use a mixture of both phases
+   (e.g. "%<this is a mixture: %s %>").   */
 
 void
-pp_format (pretty_printer *pp, text_info *text)
+pp_format (pretty_printer *pp,
+	   text_info *text,
+	   const urlifier *urlifier)
 {
-  output_buffer *buffer = pp_buffer (pp);
+  output_buffer * const buffer = pp_buffer (pp);
   const char *p;
   const char **args;
   struct chunk_info *new_chunk_array;
@@ -1079,6 +1182,9 @@  pp_format (pretty_printer *pp, text_info *text)
   bool any_unnumbered = false, any_numbered = false;
   const char **formatters[PP_NL_ARGMAX];
 
+  /* Keep track of location of last "%", if any.  */
+  size_t quoted_text_start_idx = 0;
+
   /* Allocate a new chunk structure.  */
   new_chunk_array = XOBNEW (&buffer->chunk_obstack, struct chunk_info);
   new_chunk_array->prev = buffer->cur_chunk_array;
@@ -1122,11 +1228,21 @@  pp_format (pretty_printer *pp, text_info *text)
 	      = colorize_start (pp_show_color (pp), "quote");
 	    obstack_grow (&buffer->chunk_obstack, colorstr, strlen (colorstr));
 	    p++;
+
+	    /* Stash offset of start of quoted string.  */
+	    quoted_text_start_idx
+	      = obstack_object_size (&buffer->chunk_obstack);
+
 	    continue;
 	  }
 
 	case '>':
 	  {
+	    if (quoted_text_start_idx)
+	      {
+		urlify_quoted_string (pp, urlifier, quoted_text_start_idx);
+		quoted_text_start_idx = 0;
+	      }
 	    const char *colorstr = colorize_stop (pp_show_color (pp));
 	    obstack_grow (&buffer->chunk_obstack, colorstr, strlen (colorstr));
 	  }
@@ -1168,6 +1284,12 @@  pp_format (pretty_printer *pp, text_info *text)
 	  obstack_1grow (&buffer->chunk_obstack, '\0');
 	  gcc_assert (chunk < PP_NL_ARGMAX * 2);
 	  args[chunk++] = XOBFINISH (&buffer->chunk_obstack, const char *);
+	  /* We can't yet handle urlifying quoted strings that use
+	     a combination of phase 1 and phase 2 e.g.
+	     "did you mean %<-%s%>".
+	     Stop any phase 1 quoted text if there are going to be any
+	     phase 2 quoted chunks.  */
+	  quoted_text_start_idx = 0;
 	  break;
 	}
 
@@ -1270,6 +1392,7 @@  pp_format (pretty_printer *pp, text_info *text)
       bool plus = false;
       bool hash = false;
       bool quote = false;
+      quoted_text_start_idx = 0;
 
       /* We do not attempt to enforce any ordering on the modifier
 	 characters.  */
@@ -1310,7 +1433,11 @@  pp_format (pretty_printer *pp, text_info *text)
       gcc_assert (!wide || precision == 0);
 
       if (quote)
-	pp_begin_quote (pp, pp_show_color (pp));
+	{
+	  pp_begin_quote (pp, pp_show_color (pp));
+	  quoted_text_start_idx
+	    = obstack_object_size (&buffer->chunk_obstack);
+	}
 
       switch (*p)
 	{
@@ -1480,7 +1607,14 @@  pp_format (pretty_printer *pp, text_info *text)
 	}
 
       if (quote)
-	pp_end_quote (pp, pp_show_color (pp));
+	{
+	  if (quoted_text_start_idx)
+	    {
+	      urlify_quoted_string (pp, urlifier, quoted_text_start_idx);
+	      quoted_text_start_idx = 0;
+	    }
+	  pp_end_quote (pp, pp_show_color (pp));
+	}
 
       obstack_1grow (&buffer->chunk_obstack, '\0');
       *formatters[argno] = XOBFINISH (&buffer->chunk_obstack, const char *);
@@ -1507,7 +1641,7 @@  void
 pp_output_formatted_text (pretty_printer *pp)
 {
   unsigned int chunk;
-  output_buffer *buffer = pp_buffer (pp);
+  output_buffer * const buffer = pp_buffer (pp);
   struct chunk_info *chunk_array = buffer->cur_chunk_array;
   const char **args = chunk_array->args;
 
@@ -2640,6 +2774,101 @@  test_null_urls ()
   }
 }
 
+/* Verify that URLification works as expected.  */
+
+static void
+pp_printf_with_urlifier (pretty_printer *pp,
+			 const urlifier *urlifier,
+			 const char *msg, ...)
+{
+  va_list ap;
+
+  va_start (ap, msg);
+  text_info text (msg, &ap, errno);
+  pp_format (pp, &text, urlifier);
+  pp_output_formatted_text (pp);
+  va_end (ap);
+}
+
+
+void
+test_urlification ()
+{
+  class test_urlifier : public urlifier
+  {
+  public:
+    char *
+    get_url_for_quoted_text (const char *p, size_t sz) const final override
+    {
+      if (!strncmp (p, "-foption", sz))
+	return xstrdup ("http://example.com");
+      return nullptr;
+    }
+  };
+
+  auto_fix_quotes fix_quotes;
+  const test_urlifier urlifier;
+
+  /* Uses of "%<" and "%>".  */
+  {
+    {
+      pretty_printer pp;
+      pp.url_format = URL_FORMAT_NONE;
+      pp_printf_with_urlifier (&pp, &urlifier,
+			       "foo %<-foption%> %<unrecognized%> bar");
+      ASSERT_STREQ ("foo `-foption' `unrecognized' bar",
+		    pp_formatted_text (&pp));
+    }
+    {
+      pretty_printer pp;
+      pp.url_format = URL_FORMAT_ST;
+      pp_printf_with_urlifier (&pp, &urlifier,
+			       "foo %<-foption%> %<unrecognized%> bar");
+      ASSERT_STREQ
+	("foo `\33]8;;http://example.com\33\\-foption\33]8;;\33\\'"
+	 " `unrecognized' bar",
+	 pp_formatted_text (&pp));
+    }
+    {
+      pretty_printer pp;
+      pp.url_format = URL_FORMAT_BEL;
+      pp_printf_with_urlifier (&pp, &urlifier,
+			       "foo %<-foption%> %<unrecognized%> bar");
+      ASSERT_STREQ
+	("foo `\33]8;;http://example.com\a-foption\33]8;;\a'"
+	 " `unrecognized' bar",
+	 pp_formatted_text (&pp));
+    }
+  }
+
+  /* Use of "%qs".  */
+  {
+    pretty_printer pp;
+    pp.url_format = URL_FORMAT_ST;
+    pp_printf_with_urlifier (&pp, &urlifier,
+			     "foo %qs %qs bar",
+			     "-foption", "unrecognized");
+    ASSERT_STREQ
+      ("foo `\33]8;;http://example.com\33\\-foption\33]8;;\33\\'"
+       " `unrecognized' bar",
+       pp_formatted_text (&pp));
+  }
+
+  /* Mixed usage of %< and %s, where the quoted string is built between
+     a mixture of phase 1 and phase 2.  */
+  {
+    pretty_printer pp;
+    pp.url_format = URL_FORMAT_ST;
+    pp_printf_with_urlifier (&pp, &urlifier,
+			     "foo %<-f%s%> bar",
+			     "option");
+    /* We don't support this, but make sure we don't crash.  */
+    ASSERT_STREQ
+      ("foo `-foption' bar",
+       pp_formatted_text (&pp));
+  }
+}
+
 /* Test multibyte awareness.  */
 static void test_utf8 ()
 {
@@ -2690,6 +2919,7 @@  pretty_print_cc_tests ()
   test_prefixes_and_wrapping ();
   test_urls ();
   test_null_urls ();
+  test_urlification ();
   test_utf8 ();
 }
 
diff --git a/gcc/pretty-print.h b/gcc/pretty-print.h
index 8759f0def38..9ba2c0a406e 100644
--- a/gcc/pretty-print.h
+++ b/gcc/pretty-print.h
@@ -228,6 +228,8 @@  class format_postprocessor
 /* True if colors should be shown.  */
 #define pp_show_color(PP) (PP)->show_color
 
+class urlifier;
+
 /* The data structure that contains the bare minimum required to do
    proper pretty-printing.  Clients may derived from this structure
    and add additional fields they need.  */
@@ -404,7 +406,8 @@  extern void pp_verbatim (pretty_printer *, const char *, ...)
      ATTRIBUTE_GCC_PPDIAG(2,3);
 extern void pp_flush (pretty_printer *);
 extern void pp_really_flush (pretty_printer *);
-extern void pp_format (pretty_printer *, text_info *);
+extern void pp_format (pretty_printer *, text_info *,
+		       const urlifier * = nullptr);
 extern void pp_output_formatted_text (pretty_printer *);
 extern void pp_format_verbatim (pretty_printer *, text_info *);
 
diff --git a/gcc/selftest-run-tests.cc b/gcc/selftest-run-tests.cc
index e2fc8f84b1b..cb53e90ebaf 100644
--- a/gcc/selftest-run-tests.cc
+++ b/gcc/selftest-run-tests.cc
@@ -120,6 +120,7 @@  selftest::run_tests ()
   lang_hooks.run_lang_selftests ();
 
   text_art_tests ();
+  gcc_urlifier_cc_tests ();
 
   /* Run the analyzer selftests (if enabled).  */
   ana::selftest::run_analyzer_selftests ();
diff --git a/gcc/selftest.h b/gcc/selftest.h
index 20d522afda4..b0a4142fe34 100644
--- a/gcc/selftest.h
+++ b/gcc/selftest.h
@@ -231,6 +231,7 @@  extern void et_forest_cc_tests ();
 extern void fibonacci_heap_cc_tests ();
 extern void fold_const_cc_tests ();
 extern void function_tests_cc_tests ();
+extern void gcc_urlifier_cc_tests ();
 extern void ggc_tests_cc_tests ();
 extern void gimple_cc_tests ();
 extern void hash_map_tests_cc_tests ();
diff --git a/gcc/toplev.cc b/gcc/toplev.cc
index d098a7c72a0..e39162a3e49 100644
--- a/gcc/toplev.cc
+++ b/gcc/toplev.cc
@@ -88,6 +88,7 @@  along with GCC; see the file COPYING3.  If not see
 #include "ipa-modref.h"
 #include "ipa-param-manipulation.h"
 #include "dbgcnt.h"
+#include "gcc-urlifier.h"
 
 #include "selftest.h"
 
@@ -1048,6 +1049,7 @@  general_init (const char *argv0, bool init_signals)
   global_dc->m_option_state = &global_options;
   global_dc->m_option_name = option_name;
   global_dc->m_get_option_url = get_option_url;
+  global_dc->m_urlifier = make_gcc_urlifier ();
 
   if (init_signals)
     {