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)
     {
