[pushed,2/3] pretty-print: support urlification in phase 3

Message ID 20240110134105.749310-2-dmalcolm@redhat.com
State New
Headers
Series [pushed,1/3] pretty-print: add selftest coverage for numbered args |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_gcc_build--master-arm warning Patch is already merged
linaro-tcwg-bot/tcwg_gcc_build--master-aarch64 warning Patch is already merged

Commit Message

David Malcolm Jan. 10, 2024, 1:41 p.m. UTC
  TL;DR: for the case when the user misspells a command-line option
and we suggest one, with this patch we now provide a documentation URL
for the suggestion.

In r14-5118-gc5db4d8ba5f3de I added a mechanism to automatically add
URLs to quoted strings in diagnostics, and in r14-6920-g9e49746da303b8
through r14-6923-g4ded42c2c5a5c9 wired this up so that any time
we mention a command-line option in a diagnostic message in quotes,
the user gets a URL to the HTML documentation for that option.

However this only worked for quoted strings that were fully within
a single "chunk" within the pretty-printer implementation, such as:

* "%<-foption%>" (handled in phase 1)
* "%qs", "-foption" (handled in phase 2)

but not where the the quoted string straddled multiple chunks, in
particular for this important case in the gcc.cc:

	  error ("unrecognized command-line option %<-%s%>;"
		 " did you mean %<-%s%>?",
		 switches[i].part1, hint);

e.g. for:
$ LANG=C ./xgcc -B. -finling-small-functions
xgcc: error: unrecognized command-line option '-finling-small-functions'; did you mean '-finline-small-functions'?

which within pp_format becomes these chunks:

* chunk 0: "unrecognized command-line option `-"
* chunk 1: switches[i].part1  (e.g. "finling-small-functions")
* chunk 2: "'; did you mean `-"
* chunk 3: hint (e.g. "finline-small-functions")
* chunk 4: "'?"

where the first quoted run is in chunks 1-3 and the second in
chunks 2-4.

Hence we were not attempting to provide a URL for the two quoted runs,
and, in particular not for the hint.

This patch refactors the urlification mechanism in pretty-print.cc so
that it checks for quoted runs that appear in phase 3 (as well as in
phases 1 and 2, as before).  With this, the quoted text runs
"-finling-small-functions" and "-finline-small-functions" are passed
to the urlifier, which successfully finds a documentation URL for
the latter.

As before, the urlification code is only run if the URL escapes are
enabled, and only for messages from diagnostic.cc (error, warn, inform,
etc), not for all pretty_printer usage.

Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu.
Pushed to trunk as r14-7105-g5daf9104ed5d4e.

gcc/ChangeLog:
	* diagnostic.cc (diagnostic_context::report_diagnostic): Pass
	m_urlifier to pp_output_formatted_text.
	* pretty-print.cc: Add #define of INCLUDE_VECTOR.
	(obstack_append_string): New overload, taking a length.
	(urlify_quoted_string): Pass in an obstack ptr, rather than using
	that of the pp's buffer.  Generalize to handle trailing text in
	the buffer beyond the run of quoted text.
	(class quoting_info): New.
	(on_begin_quote): New.
	(on_end_quote): New.
	(pp_format): Refactor phase 1 and phase 2 quoting support, moving
	it to calls to on_begin_quote and on_end_quote.
	(struct auto_obstack): New.
	(quoting_info::handle_phase_3): New.
	(pp_output_formatted_text): Add urlifier param.  Use it if there
	is deferred urlification.  Delete m_quotes.
	(selftest::pp_printf_with_urlifier): Pass urlifier to
	pp_output_formatted_text.
	(selftest::test_urlification): Update results for the existing
	case of quoted text stradding chunks; add more such test cases.
	* pretty-print.h (class quoting_info): New forward decl.
	(chunk_info::m_quotes): New field.
	(pp_output_formatted_text): Add optional urlifier param.

Signed-off-by: David Malcolm <dmalcolm@redhat.com>
---
 gcc/diagnostic.cc   |   2 +-
 gcc/pretty-print.cc | 444 ++++++++++++++++++++++++++++++++++++--------
 gcc/pretty-print.h  |   9 +-
 3 files changed, 379 insertions(+), 76 deletions(-)
  

Patch

diff --git a/gcc/diagnostic.cc b/gcc/diagnostic.cc
index cc2b1840c661..f5411b1ede0d 100644
--- a/gcc/diagnostic.cc
+++ b/gcc/diagnostic.cc
@@ -1603,7 +1603,7 @@  diagnostic_context::report_diagnostic (diagnostic_info *diagnostic)
 
   pp_format (this->printer, &diagnostic->message, m_urlifier);
   m_output_format->on_begin_diagnostic (*diagnostic);
-  pp_output_formatted_text (this->printer);
+  pp_output_formatted_text (this->printer, m_urlifier);
   if (m_show_cwe)
     print_any_cwe (*diagnostic);
   if (m_show_rules)
diff --git a/gcc/pretty-print.cc b/gcc/pretty-print.cc
index 859ae2a273db..de454ab7a401 100644
--- a/gcc/pretty-print.cc
+++ b/gcc/pretty-print.cc
@@ -19,6 +19,7 @@  along with GCC; see the file COPYING3.  If not see
 <http://www.gnu.org/licenses/>.  */
 
 #include "config.h"
+#define INCLUDE_VECTOR
 #include "system.h"
 #include "coretypes.h"
 #include "intl.h"
@@ -1031,7 +1032,16 @@  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,
+/* Append STR to OSTACK, without a null-terminator.  */
+
+static void
+obstack_append_string (obstack *ostack, const char *str, size_t len)
+{
+  obstack_grow (ostack, str, len);
+}
+
+/* Given quoted text within the buffer OBSTACK
+   at the half-open interval [QUOTED_TEXT_START_IDX, QUOTED_TEXT_END_IDX),
    potentially use URLIFIER (if non-null) to see if there's a URL for the
    quoted text.
 
@@ -1039,47 +1049,45 @@  obstack_append_string (obstack *ostack, const char *str)
    version of the text, using PP's settings.
 
    For example, given this is the buffer:
-     "this is a test `hello world"
+     "this is a test `hello worldTRAILING-CONTENT"
      .................^~~~~~~~~~~
    with the quoted text starting at the 'h' of "hello world", the buffer
    becomes:
-     "this is a test `BEGIN_URL(URL)hello worldEND(URL)"
+     "this is a test `BEGIN_URL(URL)hello worldEND(URL)TRAILING-CONTENT"
      .................^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      .................-----------replacement-----------
-*/
 
-static void
+   Return the new offset into the buffer of the quoted text endpoint i.e.
+   the offset of "TRAILING-CONTENT" in the above.  */
+
+static size_t
 urlify_quoted_string (pretty_printer *pp,
+		      obstack *obstack,
 		      const urlifier *urlifier,
-		      size_t quoted_text_start_idx)
+		      size_t quoted_text_start_idx,
+		      size_t quoted_text_end_idx)
 {
   if (pp->url_format == URL_FORMAT_NONE)
-    return;
+    return quoted_text_end_idx;
   if (!urlifier)
-    return;
+    return quoted_text_end_idx;
 
-  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)
+  const size_t quoted_len = quoted_text_end_idx - quoted_text_start_idx;
+  if (quoted_len == 0)
     /* 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);
+    return quoted_text_end_idx;
+  const char *start = (obstack->object_base + quoted_text_start_idx);
+  char *url = urlifier->get_url_for_quoted_text (start, quoted_len);
   if (!url)
     /* No URL for this quoted text; do nothing.  */
-    return;
+    return quoted_text_end_idx;
 
-  /* Stash a copy of the quoted text.  */
-  char *text = xstrndup (start, len);
+  /* Stash a copy of the remainder of the chunk.  */
+  char *text = xstrndup (start,
+			 obstack_object_size (obstack) - quoted_text_start_idx);
 
   /* Replace quoted text...  */
-  buffer->chunk_obstack.next_free -= len;
+  obstack->next_free = obstack->object_base + quoted_text_start_idx;
 
   /*  ...with URLified version of the text.  */
   /* Begin URL.  */
@@ -1089,27 +1097,136 @@  urlify_quoted_string (pretty_printer *pp,
     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\\");
+      obstack_append_string (obstack, "\33]8;;");
+      obstack_append_string (obstack, url);
+      obstack_append_string (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");
+      obstack_append_string (obstack, "\33]8;;");
+      obstack_append_string (obstack, url);
+      obstack_append_string (obstack, "\a");
       break;
     }
-  /* Add the text back.  */
-  obstack_append_string (&buffer->chunk_obstack, text);
+  /* Add back the quoted part of the text.  */
+  obstack_append_string (obstack, text, quoted_len);
   /* End URL.  */
-  obstack_append_string (&buffer->chunk_obstack,
+  obstack_append_string (obstack,
 			 get_end_url_string (pp));
+
+  size_t new_end_idx = obstack_object_size (obstack);
+
+  /* Add back the remainder of the text after the quoted part.  */
+  obstack_append_string (obstack, text + quoted_len);
   free (text);
   free (url);
+  return new_end_idx;
+}
+
+/* A class for tracking quoted text within a buffer for
+   use by a urlifier.  */
+
+class quoting_info
+{
+public:
+  /* Called when quoted text is begun in phase 1 or 2.  */
+  void on_begin_quote (const output_buffer &buf,
+		       unsigned chunk_idx)
+  {
+    /* Stash location of start of quoted string.  */
+    size_t byte_offset = obstack_object_size (&buf.chunk_obstack);
+    m_loc_last_open_quote = location (chunk_idx, byte_offset);
+  }
+
+  /* Called when quoted text is ended in phase 1 or 2.  */
+  void on_end_quote (pretty_printer *pp,
+		     output_buffer &buf,
+		     unsigned chunk_idx,
+		     const urlifier &urlifier)
+  {
+    /* If possible, do urlification now.  */
+    if (chunk_idx == m_loc_last_open_quote.m_chunk_idx)
+      {
+	urlify_quoted_string (pp,
+			      &buf.chunk_obstack,
+			      &urlifier,
+			      m_loc_last_open_quote.m_byte_offset,
+			      obstack_object_size (&buf.chunk_obstack));
+	m_loc_last_open_quote = location ();
+	return;
+      }
+    /* Otherwise the quoted text straddles multiple chunks.
+       Stash the location of end of quoted string for use in phase 3.  */
+    size_t byte_offset = obstack_object_size (&buf.chunk_obstack);
+    m_phase_3_quotes.push_back (run (m_loc_last_open_quote,
+				     location (chunk_idx, byte_offset)));
+    m_loc_last_open_quote = location ();
+  }
+
+  bool has_phase_3_quotes_p () const
+  {
+    return m_phase_3_quotes.size () > 0;
+  }
+  void handle_phase_3 (pretty_printer *pp,
+		       const urlifier &urlifier);
+
+private:
+  struct location
+  {
+    location ()
+    : m_chunk_idx (UINT_MAX),
+      m_byte_offset (SIZE_MAX)
+    {
+    }
+
+    location (unsigned chunk_idx,
+	      size_t byte_offset)
+    : m_chunk_idx (chunk_idx),
+      m_byte_offset (byte_offset)
+    {
+    }
+
+    unsigned m_chunk_idx;
+    size_t m_byte_offset;
+  };
+
+  struct run
+  {
+    run (location start, location end)
+    : m_start (start), m_end (end)
+    {
+    }
+
+    location m_start;
+    location m_end;
+  };
+
+  location m_loc_last_open_quote;
+  std::vector<run> m_phase_3_quotes;
+};
+
+static void
+on_begin_quote (const output_buffer &buf,
+		unsigned chunk_idx,
+		const urlifier *urlifier)
+{
+  if (!urlifier)
+    return;
+  if (!buf.cur_chunk_array->m_quotes)
+    buf.cur_chunk_array->m_quotes = new quoting_info ();
+  buf.cur_chunk_array->m_quotes->on_begin_quote (buf, chunk_idx);
+}
+
+static void
+on_end_quote (pretty_printer *pp,
+	      output_buffer &buf,
+	      unsigned chunk_idx,
+	      const urlifier *urlifier)
+{
+  if (!urlifier)
+    return;
+  if (!buf.cur_chunk_array->m_quotes)
+    buf.cur_chunk_array->m_quotes = new quoting_info ();
+  buf.cur_chunk_array->m_quotes->on_end_quote (pp, buf, chunk_idx, *urlifier);
 }
 
 /* The following format specifiers are recognized as being client independent:
@@ -1161,11 +1278,12 @@  urlify_quoted_string (pretty_printer *pp,
      "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:
+   This is handled here 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 %>").   */
+   Quoted fragments that use a mixture of both phases
+   (e.g. "%<this is a mixture: %s %>")
+   are stashed into the output_buffer's m_quotes for use in phase 3.  */
 
 void
 pp_format (pretty_printer *pp,
@@ -1182,12 +1300,11 @@  pp_format (pretty_printer *pp,
   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;
+  new_chunk_array->m_quotes = nullptr;
   buffer->cur_chunk_array = new_chunk_array;
   args = new_chunk_array->args;
 
@@ -1229,20 +1346,14 @@  pp_format (pretty_printer *pp,
 	    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);
-
+	    on_begin_quote (*buffer, chunk, urlifier);
 	    continue;
 	  }
 
 	case '>':
 	  {
-	    if (quoted_text_start_idx)
-	      {
-		urlify_quoted_string (pp, urlifier, quoted_text_start_idx);
-		quoted_text_start_idx = 0;
-	      }
+	    on_end_quote (pp, *buffer, chunk, urlifier);
+
 	    const char *colorstr = colorize_stop (pp_show_color (pp));
 	    obstack_grow (&buffer->chunk_obstack, colorstr, strlen (colorstr));
 	  }
@@ -1282,14 +1393,7 @@  pp_format (pretty_printer *pp,
 	default:
 	  /* Handled in phase 2.  Terminate the plain chunk here.  */
 	  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;
 	}
 
@@ -1392,7 +1496,6 @@  pp_format (pretty_printer *pp,
       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.  */
@@ -1435,8 +1538,7 @@  pp_format (pretty_printer *pp,
       if (quote)
 	{
 	  pp_begin_quote (pp, pp_show_color (pp));
-	  quoted_text_start_idx
-	    = obstack_object_size (&buffer->chunk_obstack);
+	  on_begin_quote (*buffer, chunk, urlifier);
 	}
 
       switch (*p)
@@ -1608,11 +1710,7 @@  pp_format (pretty_printer *pp,
 
       if (quote)
 	{
-	  if (quoted_text_start_idx)
-	    {
-	      urlify_quoted_string (pp, urlifier, quoted_text_start_idx);
-	      quoted_text_start_idx = 0;
-	    }
+	  on_end_quote (pp, *buffer, chunk, urlifier);
 	  pp_end_quote (pp, pp_show_color (pp));
 	}
 
@@ -1636,9 +1734,136 @@  pp_format (pretty_printer *pp,
   pp_clear_state (pp);
 }
 
-/* Format of a message pointed to by TEXT.  */
+struct auto_obstack
+{
+  auto_obstack ()
+  {
+    obstack_init (&m_obstack);
+  }
+
+  ~auto_obstack ()
+  {
+    obstack_free (&m_obstack, NULL);
+  }
+
+  void grow (const void *src, size_t length)
+  {
+    obstack_grow (&m_obstack, src, length);
+  }
+
+  void *object_base () const
+  {
+    return m_obstack.object_base;
+  }
+
+  size_t object_size () const
+  {
+    return obstack_object_size (&m_obstack);
+  }
+
+  obstack m_obstack;
+};
+
+/* Subroutine of pp_output_formatted_text for the awkward case where
+   quoted text straddles multiple chunks.
+
+   Flush PP's buffer's chunks to PP's output buffer, whilst inserting
+   URLs for any quoted text that should be URLified.
+
+   For example, given:
+   |  pp_format (pp,
+   |            "unrecognized option %qs; did you mean %<-%s%>",
+   |            "foo", "foption");
+   we would have these chunks:
+   |  chunk 0: "unrecognized option "
+   |  chunk 1: "`foo'" (already checked for urlification)
+   |  chunk 2: "; did you mean `-"
+   |                           ^*
+   |  chunk 3: "foption"
+   |            *******
+   |  chunk 4: "'"
+   |            ^
+   and this quoting_info would have recorded the open quote near the end
+   of chunk 2 and close quote at the start of chunk 4; this function would
+   check the combination of the end of chunk 2 and all of chunk 3 ("-foption")
+   for urlification.  */
+
+void
+quoting_info::handle_phase_3 (pretty_printer *pp,
+			      const urlifier &urlifier)
+{
+  unsigned int chunk;
+  output_buffer * const buffer = pp_buffer (pp);
+  struct chunk_info *chunk_array = buffer->cur_chunk_array;
+  const char **args = chunk_array->args;
+
+  /* We need to construct the string into an intermediate buffer
+     for this case, since using pp_string can introduce prefixes
+     and line-wrapping, and omit whitespace at the start of lines.  */
+  auto_obstack combined_buf;
+
+  /* Iterate simultaneously through both
+     - the chunks and
+     - the runs of quoted characters
+     Accumulate text from the chunks into combined_buf, and handle
+     runs of quoted characters when handling the chunks they
+     correspond to.  */
+  size_t start_of_run_byte_offset = 0;
+  std::vector<quoting_info::run>::const_iterator iter_run
+    = buffer->cur_chunk_array->m_quotes->m_phase_3_quotes.begin ();
+  std::vector<quoting_info::run>::const_iterator end_runs
+    = buffer->cur_chunk_array->m_quotes->m_phase_3_quotes.end ();
+  for (chunk = 0; args[chunk]; chunk++)
+    {
+      size_t start_of_chunk_idx = combined_buf.object_size ();
+
+      combined_buf.grow (args[chunk], strlen (args[chunk]));
+
+      if (iter_run != end_runs
+	  && chunk == iter_run->m_end.m_chunk_idx)
+	{
+	  /* A run is ending; consider for it urlification.  */
+	  const size_t end_of_run_byte_offset
+	    = start_of_chunk_idx + iter_run->m_end.m_byte_offset;
+	  const size_t end_offset
+	    = urlify_quoted_string (pp,
+				    &combined_buf.m_obstack,
+				    &urlifier,
+				    start_of_run_byte_offset,
+				    end_of_run_byte_offset);
+
+	  /* If URLification occurred it will have grown the buffer.
+	     We need to update start_of_chunk_idx so that offsets
+	     relative to it are still correct, for the case where
+	     we have a chunk that both ends a quoted run and starts
+	     another quoted run.  */
+	  gcc_assert (end_offset >= end_of_run_byte_offset);
+	  start_of_chunk_idx += end_offset - end_of_run_byte_offset;
+
+	  iter_run++;
+	}
+      if (iter_run != end_runs
+	  && chunk == iter_run->m_start.m_chunk_idx)
+	{
+	  /* Note where the run starts w.r.t. the composed buffer.  */
+	  start_of_run_byte_offset
+	    = start_of_chunk_idx + iter_run->m_start.m_byte_offset;
+	}
+    }
+
+  /* Now print to PP.  */
+  const char *start
+    = static_cast <const char *> (combined_buf.object_base ());
+  pp_maybe_wrap_text (pp, start, start + combined_buf.object_size ());
+}
+
+/* Format of a message pointed to by TEXT.
+   If URLIFIER is non-null then use it on any quoted text that was not
+   handled in phases 1 or 2 to potentially add URLs.  */
+
 void
-pp_output_formatted_text (pretty_printer *pp)
+pp_output_formatted_text (pretty_printer *pp,
+			  const urlifier *urlifier)
 {
   unsigned int chunk;
   output_buffer * const buffer = pp_buffer (pp);
@@ -1649,11 +1874,20 @@  pp_output_formatted_text (pretty_printer *pp)
 
   /* This is a third phase, first 2 phases done in pp_format_args.
      Now we actually print it.  */
-  for (chunk = 0; args[chunk]; chunk++)
-    pp_string (pp, args[chunk]);
+
+  /* If we have any deferred urlification, handle it now.  */
+  if (urlifier
+      && pp->url_format != URL_FORMAT_NONE
+      && buffer->cur_chunk_array->m_quotes
+      && buffer->cur_chunk_array->m_quotes->has_phase_3_quotes_p ())
+    buffer->cur_chunk_array->m_quotes->handle_phase_3 (pp, *urlifier);
+  else
+    for (chunk = 0; args[chunk]; chunk++)
+      pp_string (pp, args[chunk]);
 
   /* Deallocate the chunk structure and everything after it (i.e. the
      associated series of formatted strings).  */
+  delete buffer->cur_chunk_array->m_quotes;
   buffer->cur_chunk_array = chunk_array->prev;
   obstack_free (&buffer->chunk_obstack, chunk_array);
 }
@@ -2800,7 +3034,7 @@  pp_printf_with_urlifier (pretty_printer *pp,
   va_start (ap, msg);
   text_info text (msg, &ap, errno);
   pp_format (pp, &text, urlifier);
-  pp_output_formatted_text (pp);
+  pp_output_formatted_text (pp, urlifier);
   va_end (ap);
 }
 
@@ -2876,9 +3110,71 @@  test_urlification ()
     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",
+      ("foo `\33]8;;http://example.com\33\\-foption\33]8;;\33\\' bar",
+       pp_formatted_text (&pp));
+  }
+
+  /* Likewise, where there is trailing phase 1 content within the
+     quoted region.  */
+  {
+    pretty_printer pp;
+    pp.url_format = URL_FORMAT_ST;
+    pp_printf_with_urlifier (&pp, &urlifier,
+			     "foo %<-f%sion%> bar %<-f%sion%> baz",
+			     "opt", "opt");
+    ASSERT_STREQ
+      ("foo `\33]8;;http://example.com\33\\-foption\33]8;;\33\\' bar `\33]8;;http://example.com\33\\-foption\33]8;;\33\\' baz",
+       pp_formatted_text (&pp));
+  }
+
+  /* Likewise.  */
+  {
+    pretty_printer pp;
+    pp.url_format = URL_FORMAT_ST;
+    pp_printf_with_urlifier (&pp, &urlifier,
+			     "foo %<%sption%> bar %<-f%sion%> baz",
+			     "-fo", "opt");
+    ASSERT_STREQ
+      ("foo `\33]8;;http://example.com\33\\-foption\33]8;;\33\\' bar `\33]8;;http://example.com\33\\-foption\33]8;;\33\\' baz",
+       pp_formatted_text (&pp));
+  }
+
+  /* Another mixed usage of %< and %s, where the quoted string is built
+     between a mixture of phase 1 and multiple phase 2.  */
+  {
+    pretty_printer pp;
+    pp.url_format = URL_FORMAT_ST;
+    pp_printf_with_urlifier (&pp, &urlifier,
+			     "foo %<-f%s%s%> bar",
+			     "opt", "ion");
+    ASSERT_STREQ
+      ("foo `\33]8;;http://example.com\33\\-foption\33]8;;\33\\' bar",
+       pp_formatted_text (&pp));
+  }
+
+  /* Mixed usage of %< and %s with a prefix.  */
+  {
+    pretty_printer pp;
+    pp.url_format = URL_FORMAT_ST;
+    pp_set_prefix (&pp, xstrdup ("PREFIX"));
+    pp_printf_with_urlifier (&pp, &urlifier,
+			     "foo %<-f%s%> bar",
+			     "option");
+    ASSERT_STREQ
+      ("PREFIXfoo `\33]8;;http://example.com\33\\-foption\33]8;;\33\\' bar",
+       pp_formatted_text (&pp));
+  }
+
+  /* Example of mixed %< and %s with numbered args.  */
+  {
+    pretty_printer pp;
+    pp.url_format = URL_FORMAT_ST;
+    pp_printf_with_urlifier (&pp, &urlifier,
+			     "foo %<-f%2$st%1$sn%> bar",
+			     "io", "op");
+    ASSERT_STREQ
+      ("foo `\33]8;;http://example.com\33\\-foption\33]8;;\33\\' bar",
        pp_formatted_text (&pp));
   }
 }
diff --git a/gcc/pretty-print.h b/gcc/pretty-print.h
index 4548c25a42de..14a225eefe06 100644
--- a/gcc/pretty-print.h
+++ b/gcc/pretty-print.h
@@ -69,6 +69,8 @@  enum diagnostic_prefixing_rule_t
   DIAGNOSTICS_SHOW_PREFIX_EVERY_LINE = 0x2
 };
 
+class quoting_info;
+
 /* The chunk_info data structure forms a stack of the results from the
    first phase of formatting (pp_format) which have not yet been
    output (pp_output_formatted_text).  A stack is necessary because
@@ -86,6 +88,10 @@  struct chunk_info
      text, and the third phase simply emits all the chunks in sequence
      with appropriate line-wrapping.  */
   const char *args[PP_NL_ARGMAX * 2];
+
+  /* If non-null, information on quoted text runs within the chunks
+     for use by a urlifier.  */
+  quoting_info *m_quotes;
 };
 
 /* The output buffer datatype.  This is best seen as an abstract datatype
@@ -409,7 +415,8 @@  extern void pp_flush (pretty_printer *);
 extern void pp_really_flush (pretty_printer *);
 extern void pp_format (pretty_printer *, text_info *,
 		       const urlifier * = nullptr);
-extern void pp_output_formatted_text (pretty_printer *);
+extern void pp_output_formatted_text (pretty_printer *,
+				      const urlifier * = nullptr);
 extern void pp_format_verbatim (pretty_printer *, text_info *);
 
 extern void pp_indent (pretty_printer *);