From a8a5deb0b4db83866b00d2ffe557681a485f7b07 Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Fri, 13 Dec 2019 11:28:58 -0500
Subject: [PATCH 064/113] FIXME: WIP on print_desc rather than get_desc

---
 gcc/analyzer/analyzer.cc           |  39 ---
 gcc/analyzer/analyzer.h            |   2 -
 gcc/analyzer/checker-path.cc       | 428 ++++++++++++-----------------
 gcc/analyzer/checker-path.h        |  38 +--
 gcc/analyzer/diagnostic-manager.cc |  15 +-
 gcc/analyzer/engine.cc             |   5 +-
 gcc/analyzer/pending-diagnostic.cc |  17 +-
 gcc/analyzer/pending-diagnostic.h  |  48 ++--
 gcc/analyzer/region-model.cc       |  17 +-
 gcc/analyzer/sm-file.cc            |  72 +++--
 gcc/analyzer/sm-malloc.cc          | 162 ++++++-----
 gcc/analyzer/sm-sensitive.cc       |  41 +--
 gcc/analyzer/sm-signal.cc          |  15 +-
 gcc/analyzer/sm-taint.cc           |  53 ++--
 gcc/diagnostic-path.h              |  12 +-
 gcc/tree-diagnostic-path.cc        |  64 +++--
 16 files changed, 504 insertions(+), 524 deletions(-)

diff --git a/gcc/analyzer/analyzer.cc b/gcc/analyzer/analyzer.cc
index 2a3ffaee852..934fc205243 100644
--- a/gcc/analyzer/analyzer.cc
+++ b/gcc/analyzer/analyzer.cc
@@ -109,43 +109,4 @@ is_longjmp_call_p (const gcall *call)
   return false;
 }
 
-/* Generate a label_text instance by formatting FMT, using a
-   temporary clone of the global_dc's printer (thus using its
-   formatting callbacks).
-
-   Colorize if the global_dc supports colorization and CAN_COLORIZE is
-   true.  */
-
-label_text
-make_label_text (bool can_colorize, const char *fmt, ...)
-{
-  pretty_printer *pp = global_dc->printer->clone ();
-  pp_clear_output_area (pp);
-
-  if (!can_colorize)
-    pp_show_color (pp) = false;
-
-  text_info ti;
-  rich_location rich_loc (line_table, UNKNOWN_LOCATION);
-
-  va_list ap;
-
-  va_start (ap, fmt);
-
-  ti.format_spec = _(fmt);
-  ti.args_ptr = &ap;
-  ti.err_no = 0;
-  ti.x_data = NULL;
-  ti.m_richloc = &rich_loc;
-
-  pp_format (pp, &ti);
-  pp_output_formatted_text (pp);
-
-  va_end (ap);
-
-  label_text result = label_text::take (xstrdup (pp_formatted_text (pp)));
-  delete pp;
-  return result;
-}
-
 #endif /* #if ENABLE_ANALYZER */
diff --git a/gcc/analyzer/analyzer.h b/gcc/analyzer/analyzer.h
index 7fc6959c460..199084c4c79 100644
--- a/gcc/analyzer/analyzer.h
+++ b/gcc/analyzer/analyzer.h
@@ -78,8 +78,6 @@ extern bool is_longjmp_call_p (const gcall *call);
 
 extern void register_analyzer_pass ();
 
-extern label_text make_label_text (bool can_colorize, const char *fmt, ...);
-
 extern bool fndecl_has_gimple_body_p (tree fndecl);
 
 /* An RAII-style class for pushing/popping cfun within a scope.
diff --git a/gcc/analyzer/checker-path.cc b/gcc/analyzer/checker-path.cc
index 6c0f66cc275..23c8c880b78 100644
--- a/gcc/analyzer/checker-path.cc
+++ b/gcc/analyzer/checker-path.cc
@@ -47,6 +47,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "selftest.h"
 #include "analyzer/region-model.h"
 #include "analyzer/program-state.h"
+#include "intl.h"
 #include "analyzer/checker-path.h"
 #include "gimple-iterator.h"
 #include "analyzer/supergraph.h"
@@ -106,12 +107,11 @@ event_kind_to_string (enum event_kind ek)
 void
 checker_event::dump (pretty_printer *pp) const
 {
-  label_text event_desc (get_desc (false));
-  pp_printf (pp, "\"%s\" (depth %i, m_loc=%x)",
-	     event_desc.m_buffer,
+  pp_character (pp, '"');
+  print_desc (pp);
+  pp_printf (pp, "\" (depth %i, m_loc=%x)",
 	     get_stack_depth (),
 	     get_location ());
-  event_desc.maybe_free ();
 }
 
 /* Hook for being notified when this event has its final id EMISSION_ID
@@ -120,11 +120,11 @@ checker_event::dump (pretty_printer *pp) const
    Base implementation of checker_event::prepare_for_emission vfunc;
    subclasses that override this should chain up to it.
 
-   Record PD and EMISSION_ID, and call the get_desc vfunc, so that any
-   side-effects of the call to get_desc take place before
+   Record PD and EMISSION_ID, and call the print_desc vfunc, so that any
+   side-effects of the call to print_desc take place before
    pending_diagnostic::emit is called.
 
-   For example, state_change_event::get_desc can call
+   For example, state_change_event::print_desc can call
    pending_diagnostic::describe_state_change; free_of_non_heap can use this
    to tweak the message (TODO: would be neater to simply capture the
    pertinent data within the sm-state).  */
@@ -137,32 +137,32 @@ checker_event::prepare_for_emission (checker_path *,
   m_pending_diagnostic = pd;
   m_emission_id = emission_id;
 
-  label_text desc = get_desc (false);
-  desc.maybe_free ();
+  char *desc = get_desc ();
+  free (desc);
 }
 
 /* class debug_event : public checker_event.  */
 
-/* Implementation of diagnostic_event::get_desc vfunc for
+/* Implementation of diagnostic_event::print_desc vfunc for
    debug_event.
    Use the saved string as the event's description.  */
 
-label_text
-debug_event::get_desc (bool) const
+void
+debug_event::print_desc (pretty_printer *pp) const
 {
-  return label_text::borrow (m_desc);
+  pp_string (pp, m_desc);
 }
 
 /* class custom_event : public checker_event.  */
 
-/* Implementation of diagnostic_event::get_desc vfunc for
+/* Implementation of diagnostic_event::print_desc vfunc for
    custom_event.
    Use the saved string as the event's description.  */
 
-label_text
-custom_event::get_desc (bool) const
+void
+custom_event::print_desc (pretty_printer *pp) const
 {
-  return label_text::borrow (m_desc);
+  pp_string (pp, m_desc);
 }
 
 /* class statement_event : public checker_event.  */
@@ -177,30 +177,28 @@ statement_event::statement_event (const gimple *stmt, tree fndecl, int depth,
 {
 }
 
-/* Implementation of diagnostic_event::get_desc vfunc for
+/* Implementation of diagnostic_event::print_desc vfunc for
    statement_event.
    Use the statement's dump form as the event's description.  */
 
-label_text
-statement_event::get_desc (bool) const
+void
+statement_event::print_desc (pretty_printer *pp) const
 {
-  pretty_printer pp;
-  pp_string (&pp, "stmt: ");
-  pp_gimple_stmt_1 (&pp, m_stmt, 0, (dump_flags_t)0);
-  return label_text::take (xstrdup (pp_formatted_text (&pp)));
+  pp_string (pp, "stmt: ");
+  pp_gimple_stmt_1 (pp, m_stmt, 0, (dump_flags_t)0);
 }
 
 /* class function_entry_event : public checker_event.  */
 
-/* Implementation of diagnostic_event::get_desc vfunc for
+/* Implementation of diagnostic_event::print_desc vfunc for
    function_entry_event.
 
    Use a string such as "entry to 'foo'" as the event's description.  */
 
-label_text
-function_entry_event::get_desc (bool can_colorize) const
+void
+function_entry_event::print_desc (pretty_printer *pp) const
 {
-  return make_label_text (can_colorize, "entry to %qE", m_fndecl);
+  pp_printf (pp, _("entry to %qE"), m_fndecl);
 }
 
 /* class state_change_event : public checker_event.  */
@@ -226,7 +224,7 @@ state_change_event::state_change_event (const supernode *node,
 {
 }
 
-/* Implementation of diagnostic_event::get_desc vfunc for
+/* Implementation of diagnostic_event::print_desc vfunc for
    state_change_event.
 
    Attempt to generate a nicer human-readable description.
@@ -237,73 +235,53 @@ state_change_event::state_change_event (const supernode *node,
    the diagnostic is about to being emitted, so the description for
    an event can change.  */
 
-label_text
-state_change_event::get_desc (bool can_colorize) const
+void
+state_change_event::print_desc (pretty_printer *pp) const
 {
-  if (m_pending_diagnostic)
+  if (m_pending_diagnostic
+      && m_pending_diagnostic->describe_state_change
+	   (evdesc::state_change (pp, m_var, m_origin,
+				  m_from, m_to, m_emission_id, *this)))
     {
-      label_text custom_desc
-	= m_pending_diagnostic->describe_state_change
-	    (evdesc::state_change (can_colorize, m_var, m_origin,
-				   m_from, m_to, m_emission_id, *this));
-      if (custom_desc.m_buffer)
+      if (flag_analyzer_verbose_state_changes)
 	{
-	  if (flag_analyzer_verbose_state_changes)
-	    {
-	      /* Append debug version.  */
-	      label_text result;
-	      if (m_origin)
-		result = make_label_text
-		  (can_colorize,
-		   "%s (state of %qE: %qs -> %qs, origin: %qE)",
-		   custom_desc.m_buffer,
-		   m_var,
-		   m_sm.get_state_name (m_from),
-		   m_sm.get_state_name (m_to),
-		   m_origin);
-	      else
-		result = make_label_text
-		  (can_colorize,
-		   "%s (state of %qE: %qs -> %qs, origin: NULL)",
-		   custom_desc.m_buffer,
-		   m_var,
-		   m_sm.get_state_name (m_from),
-		   m_sm.get_state_name (m_to));
-	      custom_desc.maybe_free ();
-	      return result;
-	    }
+	  /* Append debug version.  */
+	  if (m_origin)
+	    pp_printf (pp, " (state of %qE: %qs -> %qs, origin: %qE)",
+		       m_var,
+		       m_sm.get_state_name (m_from),
+		       m_sm.get_state_name (m_to),
+		       m_origin);
 	  else
-	    return custom_desc;
+	    pp_printf (pp, " (state of %qE: %qs -> %qs, origin: NULL)",
+		       m_var,
+		       m_sm.get_state_name (m_from),
+		       m_sm.get_state_name (m_to));
 	}
+      return;
     }
 
   /* Fallback description.  */
   if (m_var)
     {
       if (m_origin)
-	return make_label_text
-	  (can_colorize,
-	   "state of %qE: %qs -> %qs (origin: %qE)",
-	   m_var,
-	   m_sm.get_state_name (m_from),
-	   m_sm.get_state_name (m_to),
-	   m_origin);
+	pp_printf (pp, "state of %qE: %qs -> %qs (origin: %qE)",
+		   m_var,
+		   m_sm.get_state_name (m_from),
+		   m_sm.get_state_name (m_to),
+		   m_origin);
       else
-	return make_label_text
-	  (can_colorize,
-	   "state of %qE: %qs -> %qs (origin: NULL)",
-	   m_var,
-	   m_sm.get_state_name (m_from),
-	   m_sm.get_state_name (m_to));
+	pp_printf (pp, "state of %qE: %qs -> %qs (origin: NULL)",
+		   m_var,
+		   m_sm.get_state_name (m_from),
+		   m_sm.get_state_name (m_to));
     }
   else
     {
       gcc_assert (m_origin == NULL_TREE);
-      return make_label_text
-	(can_colorize,
-	 "global state: %qs -> %qs",
-	 m_sm.get_state_name (m_from),
-	 m_sm.get_state_name (m_to));
+      pp_printf (pp, "global state: %qs -> %qs",
+		 m_sm.get_state_name (m_from),
+		 m_sm.get_state_name (m_to));
     }
 }
 
@@ -336,11 +314,11 @@ superedge_event::should_filter_p (int verbosity) const
 	  {
 	    /* Filter events with empty descriptions.  This ought to filter
 	       FALLTHRU, but retain true/false/switch edges.  */
-	    label_text desc = get_desc (false);
-	    gcc_assert (desc.m_buffer);
-	    if (desc.m_buffer[0] == '\0')
-	      return true;
-	    desc.maybe_free ();
+	    pretty_printer *pp = global_dc->printer->clone ();
+	    print_desc (pp);
+	    bool empty = pp_formatted_text (pp)[0] == '\0';
+	    delete pp;
+	    return empty;
 	  }
       }
       break;
@@ -384,7 +362,7 @@ cfg_edge_event::cfg_edge_event (enum event_kind kind,
 
 /* class start_cfg_edge_event : public cfg_edge_event.  */
 
-/* Implementation of diagnostic_event::get_desc vfunc for
+/* Implementation of diagnostic_event::print_desc vfunc for
    start_cfg_edge_event.
 
    If -fanalyzer-verbose-edges, then generate low-level descriptions, such
@@ -404,8 +382,8 @@ cfg_edge_event::cfg_edge_event (enum event_kind kind,
    Failing that, return an empty description (which will lead to this event
    being filtered).  */
 
-label_text
-start_cfg_edge_event::get_desc (bool can_colorize) const
+void
+start_cfg_edge_event::print_desc (pretty_printer *pp) const
 {
   bool user_facing = !flag_analyzer_verbose_edges;
   char *edge_desc = m_sedge->get_description (user_facing);
@@ -413,55 +391,32 @@ start_cfg_edge_event::get_desc (bool can_colorize) const
     {
       if (edge_desc && strlen (edge_desc) > 0)
 	{
-	  label_text cond_desc = maybe_describe_condition (can_colorize);
-	  label_text result;
-	  if (cond_desc.m_buffer)
-	    {
-	      result = make_label_text (can_colorize,
-					"following %qs branch (%s)...",
-					edge_desc, cond_desc.m_buffer);
-	      cond_desc.maybe_free ();
-	    }
+	  pretty_printer *cond_pp = pp->clone ();
+	  if (maybe_describe_condition (cond_pp))
+	    pp_printf (pp, "following %qs branch (%s)...",
+		       edge_desc, pp_formatted_text (cond_pp));
 	  else
-	    {
-	      result = make_label_text (can_colorize,
-					"following %qs branch...",
-					edge_desc);
-	    }
-	  free (edge_desc);
-	  return result;
-	}
-      else
-	{
-	  free (edge_desc);
-	  return label_text::borrow ("");
+	    pp_printf (pp, "following %qs branch...", edge_desc);
+	  delete cond_pp;
 	}
     }
   else
     {
       if (strlen (edge_desc) > 0)
-	{
-	  label_text result
-	    = make_label_text (can_colorize,
-			       "taking %qs edge SN:%i -> SN:%i",
-			       edge_desc,
-			       m_sedge->m_src->m_index,
-			       m_sedge->m_dest->m_index);
-	  free (edge_desc);
-	  return result;
-	}
+	pp_printf (pp, "taking %qs edge SN:%i -> SN:%i",
+		   edge_desc,
+		   m_sedge->m_src->m_index,
+		   m_sedge->m_dest->m_index);
       else
-	{
-	  free (edge_desc);
-	  return make_label_text (can_colorize,
-				  "taking edge SN:%i -> SN:%i",
-				  m_sedge->m_src->m_index,
-				  m_sedge->m_dest->m_index);
-	}
+	pp_printf (pp, "taking edge SN:%i -> SN:%i",
+		   m_sedge->m_src->m_index,
+		   m_sedge->m_dest->m_index);
     }
+  free (edge_desc);
 }
 
-/* Attempt to generate a description of any condition that holds at this edge.
+/* Attempt to print a description of any condition that holds at this edge
+   and return true if successful.
 
    The intent is to make the user-facing messages more clear, especially for
    cases where there's a single or double-negative, such as
@@ -486,10 +441,10 @@ start_cfg_edge_event::get_desc (bool can_colorize) const
    string: "when 'ptr' is non-NULL".
 
    If the edge is not a condition, or it's not clear that a description of
-   the condition would be helpful to the user, return NULL.  */
+   the condition would be helpful to the user, return false.  */
 
-label_text
-start_cfg_edge_event::maybe_describe_condition (bool can_colorize) const
+bool
+start_cfg_edge_event::maybe_describe_condition (pretty_printer *pp) const
 {
   const cfg_superedge& cfg_sedge = get_cfg_superedge ();
 
@@ -503,21 +458,20 @@ start_cfg_edge_event::maybe_describe_condition (bool can_colorize) const
 	  tree rhs = gimple_cond_rhs (cond_stmt);
 	  if (cfg_sedge.false_value_p ())
 	    op = invert_tree_comparison (op, false /* honor_nans */);
-	  return maybe_describe_condition (can_colorize,
-					   lhs, op, rhs);
+	  return maybe_describe_condition (pp, lhs, op, rhs);
 	}
     }
-  return label_text::borrow (NULL);
+  return false;
 }
 
 /* Subroutine of maybe_describe_condition above.
 
-   Attempt to generate a user-facing description of the condition
+   Attempt to print a user-facing description of the condition
    LHS OP RHS, but only if it is likely to make it easier for the
    user to understand a condition.  */
 
-label_text
-start_cfg_edge_event::maybe_describe_condition (bool can_colorize,
+bool
+start_cfg_edge_event::maybe_describe_condition (pretty_printer *pp,
 						tree lhs,
 						enum tree_code op,
 						tree rhs)
@@ -536,17 +490,23 @@ start_cfg_edge_event::maybe_describe_condition (bool can_colorize,
 	if (is_special_named_call_p (call, "strcmp", 2))
 	  {
 	    if (op == EQ_EXPR)
-	      return label_text::borrow ("when the strings are equal");
+	      {
+		pp_printf (pp, _("when the strings are equal"));
+		return true;
+	      }
 	    if (op == NE_EXPR)
-	      return label_text::borrow ("when the strings are non-equal");
+	      {
+		pp_printf (pp, _("when the strings are non-equal"));
+		return true;
+	      }
 	  }
     }
 
   /* Only attempt to generate text for sufficiently simple expressions.  */
   if (!should_print_expr_p (lhs))
-    return label_text::borrow (NULL);
+    return false;
   if (!should_print_expr_p (rhs))
-    return label_text::borrow (NULL);
+    return false;
 
   /* Special cases for pointer comparisons against NULL.  */
   if (POINTER_TYPE_P (TREE_TYPE (lhs))
@@ -554,15 +514,20 @@ start_cfg_edge_event::maybe_describe_condition (bool can_colorize,
       && zerop (rhs))
     {
       if (op == EQ_EXPR)
-	return make_label_text (can_colorize, "when %qE is NULL",
-				lhs);
+	{
+	  pp_printf (pp, _("when %qE is NULL"), lhs);
+	  return true;
+	}
       if (op == NE_EXPR)
-	return make_label_text (can_colorize, "when %qE is non-NULL",
-				lhs);
+	{
+	  pp_printf (pp, _("when %qE is non-NULL"), lhs);
+	  return true;
+	}
     }
 
-  return make_label_text (can_colorize, "when %<%E %s %E%>",
-			  lhs, op_symbol_code (op), rhs);
+  pp_printf (pp, _("when %<%E %s %E%>"),
+	     lhs, op_symbol_code (op), rhs);
+  return true;
 }
 
 /* Subroutine of maybe_describe_condition.
@@ -601,7 +566,7 @@ call_event::call_event (const exploded_edge &eedge,
   gcc_assert (eedge.m_sedge->m_kind == SUPEREDGE_CALL);
 }
 
-/* Implementation of diagnostic_event::get_desc vfunc for
+/* Implementation of diagnostic_event::print_desc vfunc for
    call_event.
 
    If this call event passes critical state for an sm-based warning,
@@ -612,27 +577,24 @@ call_event::call_event (const exploded_edge &eedge,
    Otherwise, generate a description of the form
    "calling 'foo' from 'bar'".  */
 
-label_text
-call_event::get_desc (bool can_colorize) const
+void
+call_event::print_desc (pretty_printer *pp) const
 {
   if (m_critical_state && m_pending_diagnostic)
     {
       gcc_assert (m_var);
-      label_text custom_desc
-	= m_pending_diagnostic->describe_call_with_state
-	    (evdesc::call_with_state (can_colorize,
-				      m_sedge->m_src->m_fun->decl,
-				      m_sedge->m_dest->m_fun->decl,
-				      m_var,
-				      m_critical_state));
-      if (custom_desc.m_buffer)
-	return custom_desc;
+      if (m_pending_diagnostic->describe_call_with_state
+	  (evdesc::call_with_state (pp,
+				    m_sedge->m_src->m_fun->decl,
+				    m_sedge->m_dest->m_fun->decl,
+				    m_var,
+				    m_critical_state)))
+	return;
     }
 
-  return make_label_text (can_colorize,
-			  "calling %qE from %qE",
-			  m_sedge->m_dest->m_fun->decl,
-			  m_sedge->m_src->m_fun->decl);
+  pp_printf (pp, _("calling %qE from %qE"),
+	     m_sedge->m_dest->m_fun->decl,
+	     m_sedge->m_src->m_fun->decl);
 }
 
 /* Override of checker_event::is_call_p for calls.  */
@@ -654,7 +616,7 @@ return_event::return_event (const exploded_edge &eedge,
   gcc_assert (eedge.m_sedge->m_kind == SUPEREDGE_RETURN);
 }
 
-/* Implementation of diagnostic_event::get_desc vfunc for
+/* Implementation of diagnostic_event::print_desc vfunc for
    return_event.
 
    If this return event returns critical state for an sm-based warning,
@@ -665,28 +627,23 @@ return_event::return_event (const exploded_edge &eedge,
    Otherwise, generate a description of the form
    "returning to 'foo' from 'bar'.  */
 
-label_text
-return_event::get_desc (bool can_colorize) const
+void
+return_event::print_desc (pretty_printer *pp) const
 {
   /*  For greatest precision-of-wording, if this is returning the
       state involved in the pending diagnostic, give the pending
       diagnostic a chance to describe this return (in terms of
       itself).  */
   if (m_critical_state && m_pending_diagnostic)
-    {
-      label_text custom_desc
-	= m_pending_diagnostic->describe_return_of_state
-	    (evdesc::return_of_state (can_colorize,
-				      m_sedge->m_dest->m_fun->decl,
-				      m_sedge->m_src->m_fun->decl,
-				      m_critical_state));
-      if (custom_desc.m_buffer)
-	return custom_desc;
-    }
-  return make_label_text (can_colorize,
-			  "returning to %qE from %qE",
-			  m_sedge->m_dest->m_fun->decl,
-			  m_sedge->m_src->m_fun->decl);
+    if (m_pending_diagnostic->describe_return_of_state
+	(evdesc::return_of_state (pp,
+				  m_sedge->m_dest->m_fun->decl,
+				  m_sedge->m_src->m_fun->decl,
+				  m_critical_state)))
+	return;
+  pp_printf (pp, _("returning to %qE from %qE"),
+	     m_sedge->m_dest->m_fun->decl,
+	     m_sedge->m_src->m_fun->decl);
 }
 
 /* Override of checker_event::is_return_p for returns.  */
@@ -699,15 +656,13 @@ return_event::is_return_p () const
 
 /* class setjmp_event : public checker_event.  */
 
-/* Implementation of diagnostic_event::get_desc vfunc for
+/* Implementation of diagnostic_event::print_desc vfunc for
    setjmp_event.  */
 
-label_text
-setjmp_event::get_desc (bool can_colorize) const
+void
+setjmp_event::print_desc (pretty_printer *pp) const
 {
-  return make_label_text (can_colorize,
-			  "%qs called here",
-			  "setjmp");
+  pp_printf (pp, _("%qs called here"), "setjmp");
 }
 
 /* Implementation of checker_event::prepare_for_emission vfunc for setjmp_event.
@@ -755,34 +710,32 @@ rewind_event::rewind_event (const exploded_edge *eedge,
 
 /* class rewind_from_longjmp_event : public rewind_event.  */
 
-/* Implementation of diagnostic_event::get_desc vfunc for
+/* Implementation of diagnostic_event::print_desc vfunc for
    rewind_from_longjmp_event.  */
 
-label_text
-rewind_from_longjmp_event::get_desc (bool can_colorize) const
+void
+rewind_from_longjmp_event::print_desc (pretty_printer *pp) const
 {
   const char *src_name = "longjmp";
 
   if (get_longjmp_caller () == get_setjmp_caller ())
     /* Special-case: purely intraprocedural rewind.  */
-    return make_label_text (can_colorize,
-			    "rewinding within %qE from %qs...",
-			    get_longjmp_caller (),
-			    src_name);
+    pp_printf (pp, _("rewinding within %qE from %qs..."),
+	       get_longjmp_caller (),
+	       src_name);
   else
-    return make_label_text (can_colorize,
-			    "rewinding from %qs in %qE...",
-			    src_name,
-			    get_longjmp_caller ());
+    pp_printf (pp, _("rewinding from %qs in %qE..."),
+	       src_name,
+	       get_longjmp_caller ());
 }
 
 /* class rewind_to_setjmp_event : public rewind_event.  */
 
-/* Implementation of diagnostic_event::get_desc vfunc for
+/* Implementation of diagnostic_event::print_desc vfunc for
    rewind_to_setjmp_event.  */
 
-label_text
-rewind_to_setjmp_event::get_desc (bool can_colorize) const
+void
+rewind_to_setjmp_event::print_desc (pretty_printer *pp) const
 {
   const char *dst_name = "setjmp";
 
@@ -791,30 +744,26 @@ rewind_to_setjmp_event::get_desc (bool can_colorize) const
     {
       if (get_longjmp_caller () == get_setjmp_caller ())
 	/* Special-case: purely intraprocedural rewind.  */
-	return make_label_text (can_colorize,
-				"...to %qs (saved at %@)",
-				dst_name,
-				&m_original_setjmp_event_id);
+	pp_printf (pp, _("...to %qs (saved at %@)"),
+		   dst_name,
+		   &m_original_setjmp_event_id);
       else
-	return make_label_text (can_colorize,
-				"...to %qs in %qE (saved at %@)",
-				dst_name,
-				get_setjmp_caller (),
-				&m_original_setjmp_event_id);
+	pp_printf (pp, _("...to %qs in %qE (saved at %@)"),
+		   dst_name,
+		   get_setjmp_caller (),
+		   &m_original_setjmp_event_id);
     }
   else
     {
       if (get_longjmp_caller () == get_setjmp_caller ())
 	/* Special-case: purely intraprocedural rewind.  */
-	return make_label_text (can_colorize,
-				"...to %qs",
-				dst_name,
-				get_setjmp_caller ());
+	pp_printf (pp, _("...to %qs"),
+		   dst_name,
+		   get_setjmp_caller ());
       else
-	return make_label_text (can_colorize,
-				"...to %qs in %qE",
-				dst_name,
-				get_setjmp_caller ());
+	pp_printf (pp, _("...to %qs in %qE"),
+		   dst_name,
+		   get_setjmp_caller ());
     }
 }
 
@@ -836,7 +785,7 @@ rewind_to_setjmp_event::prepare_for_emission (checker_path *path,
 
 /* class warning_event : public checker_event.  */
 
-/* Implementation of diagnostic_event::get_desc vfunc for
+/* Implementation of diagnostic_event::print_desc vfunc for
    warning_event.
 
    If the pending diagnostic implements describe_final_event, use it,
@@ -845,38 +794,27 @@ rewind_to_setjmp_event::prepare_for_emission (checker_path *path,
 
    Otherwise generate a generic description.  */
 
-label_text
-warning_event::get_desc (bool can_colorize) const
+void
+warning_event::print_desc (pretty_printer *pp) const
 {
   if (m_pending_diagnostic)
     {
-      label_text ev_desc
-	= m_pending_diagnostic->describe_final_event
-	    (evdesc::final_event (can_colorize, m_var, m_state));
-      if (ev_desc.m_buffer)
+      if (m_pending_diagnostic->describe_final_event
+	  (evdesc::final_event (pp, m_var, m_state)))
 	{
 	  if (m_sm && flag_analyzer_verbose_state_changes)
-	    {
-	      label_text result
-		= make_label_text (can_colorize,
-				   "%s (%qE is in state %qs)",
-				   ev_desc.m_buffer,
-				   m_var,m_sm->get_state_name (m_state));
-	      ev_desc.maybe_free ();
-	      return result;
-	    }
-	  else
-	    return ev_desc;
+	    pp_printf (pp, _(" (%qE is in state %qs)"),
+		       m_var, m_sm->get_state_name (m_state));
+	  return;
 	}
     }
 
   if (m_sm)
-    return make_label_text (can_colorize,
-			    "here (%qE is in state %qs)",
-			    m_var,
-			    m_sm->get_state_name (m_state));
+    pp_printf (pp, _("here (%qE is in state %qs)"),
+	       m_var,
+	       m_sm->get_state_name (m_state));
   else
-    return label_text::borrow ("here");
+    pp_string (pp, _("here"));
 }
 
 /* Print a single-line representation of this path to PP.  */
@@ -892,9 +830,9 @@ checker_path::dump (pretty_printer *pp) const
     {
       if (i > 0)
 	pp_string (pp, ", ");
-      label_text event_desc (e->get_desc (false));
-      pp_printf (pp, "\"%s\"", event_desc.m_buffer);
-      event_desc.maybe_free ();
+      pp_character (pp, '"');
+      e->print_desc (pp);
+      pp_character (pp, '"');
     }
   pp_character (pp, ']');
 }
@@ -929,13 +867,13 @@ checker_path::debug () const
   int i;
   FOR_EACH_VEC_ELT (m_events, i, e)
     {
-      label_text event_desc (e->get_desc (false));
+      char *event_desc (e->get_desc ());
       fprintf (stderr,
 	       "[%i]: %s \"%s\"\n",
 	       i,
 	       event_kind_to_string (m_events[i]->m_kind),
-	       event_desc.m_buffer);
-      event_desc.maybe_free ();
+	       event_desc);
+      free (event_desc);
     }
 }
 
diff --git a/gcc/analyzer/checker-path.h b/gcc/analyzer/checker-path.h
index 20bab77b719..a205a49b2e3 100644
--- a/gcc/analyzer/checker-path.h
+++ b/gcc/analyzer/checker-path.h
@@ -124,7 +124,7 @@ public:
     free (m_desc);
   }
 
-  label_text get_desc (bool) const FINAL OVERRIDE;
+  void print_desc (pretty_printer *) const FINAL OVERRIDE;
 
 private:
   char *m_desc;
@@ -147,7 +147,7 @@ public:
     free (m_desc);
   }
 
-  label_text get_desc (bool) const FINAL OVERRIDE;
+  void print_desc (pretty_printer *) const FINAL OVERRIDE;
 
 private:
   char *m_desc;
@@ -162,7 +162,7 @@ public:
   statement_event (const gimple *stmt, tree fndecl, int depth,
 		   const program_state &dst_state);
 
-  label_text get_desc (bool) const FINAL OVERRIDE;
+  void print_desc (pretty_printer *) const FINAL OVERRIDE;
 
   const gimple * const m_stmt;
   const program_state m_dst_state;
@@ -178,7 +178,7 @@ public:
   {
   }
 
-  label_text get_desc (bool can_colorize) const FINAL OVERRIDE;
+  void print_desc (pretty_printer *) const FINAL OVERRIDE;
 
   bool is_function_entry_p () const FINAL OVERRIDE { return true; }
 };
@@ -197,7 +197,7 @@ public:
 		      tree origin,
 		      const program_state &dst_state);
 
-  label_text get_desc (bool can_colorize) const FINAL OVERRIDE;
+  void print_desc (pretty_printer *) const FINAL OVERRIDE;
 
   region_id get_lvalue (tree expr) const
   {
@@ -271,15 +271,15 @@ public:
   {
   }
 
-  label_text get_desc (bool can_colorize) const FINAL OVERRIDE;
+  void print_desc (pretty_printer *) const FINAL OVERRIDE;
 
  private:
-  label_text maybe_describe_condition (bool can_colorize) const;
+  bool maybe_describe_condition (pretty_printer *pp) const;
 
-  static label_text maybe_describe_condition (bool can_colorize,
-					      tree lhs,
-					      enum tree_code op,
-					      tree rhs);
+  static bool maybe_describe_condition (pretty_printer *pp,
+					tree lhs,
+					enum tree_code op,
+					tree rhs);
   static bool should_print_expr_p (tree);
 };
 
@@ -295,9 +295,9 @@ public:
   {
   }
 
-  label_text get_desc (bool /*can_colorize*/) const FINAL OVERRIDE
+  void print_desc (pretty_printer *pp) const FINAL OVERRIDE
   {
-    return label_text::borrow ("...to here");
+    pp_string (pp, _("...to here"));
   }
 };
 
@@ -309,7 +309,7 @@ public:
   call_event (const exploded_edge &eedge,
 	      location_t loc, tree fndecl, int depth);
 
-  label_text get_desc (bool can_colorize) const FINAL OVERRIDE;
+  void print_desc (pretty_printer *) const FINAL OVERRIDE;
 
   bool is_call_p () const FINAL OVERRIDE;
 };
@@ -322,7 +322,7 @@ public:
   return_event (const exploded_edge &eedge,
 		location_t loc, tree fndecl, int depth);
 
-  label_text get_desc (bool can_colorize) const FINAL OVERRIDE;
+  void print_desc (pretty_printer *) const FINAL OVERRIDE;
 
   bool is_return_p () const FINAL OVERRIDE;
 };
@@ -339,7 +339,7 @@ public:
   {
   }
 
-  label_text get_desc (bool can_colorize) const FINAL OVERRIDE;
+  void print_desc (pretty_printer *) const FINAL OVERRIDE;
 
   void prepare_for_emission (checker_path *path,
 			     pending_diagnostic *pd,
@@ -381,7 +381,7 @@ public:
   {
   }
 
-  label_text get_desc (bool can_colorize) const FINAL OVERRIDE;
+  void print_desc (pretty_printer *) const FINAL OVERRIDE;
 };
 
 /* A concrete event subclass for rewinding from a longjmp to a setjmp,
@@ -398,7 +398,7 @@ public:
   {
   }
 
-  label_text get_desc (bool can_colorize) const FINAL OVERRIDE;
+  void print_desc (pretty_printer *) const FINAL OVERRIDE;
 
   void prepare_for_emission (checker_path *path,
 			     pending_diagnostic *pd,
@@ -425,7 +425,7 @@ public:
   {
   }
 
-  label_text get_desc (bool can_colorize) const FINAL OVERRIDE;
+  void print_desc (pretty_printer *) const FINAL OVERRIDE;
 
 private:
   const state_machine *m_sm;
diff --git a/gcc/analyzer/diagnostic-manager.cc b/gcc/analyzer/diagnostic-manager.cc
index 2b77065ce6f..38ede67f77a 100644
--- a/gcc/analyzer/diagnostic-manager.cc
+++ b/gcc/analyzer/diagnostic-manager.cc
@@ -54,6 +54,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "analyzer/program-point.h"
 #include "analyzer/program-state.h"
 #include "analyzer/exploded-graph.h"
+#include "intl.h"
 #include "analyzer/checker-path.h"
 
 #if ENABLE_ANALYZER
@@ -1205,12 +1206,11 @@ diagnostic_manager::prune_interproc_events (checker_path *path) const
 	    {
 	      if (get_logger ())
 		{
-		  label_text desc
-		    (path->get_checker_event (idx)->get_desc (false));
+		  char *desc = path->get_checker_event (idx)->get_desc ();
 		  log ("filtering events %i-%i:"
 		       " irrelevant call/entry/return: %s",
-		       idx, idx + 2, desc.m_buffer);
-		  desc.maybe_free ();
+		       idx, idx + 2, desc);
+		  free (desc);
 		}
 	      path->delete_event (idx + 2);
 	      path->delete_event (idx + 1);
@@ -1228,12 +1228,11 @@ diagnostic_manager::prune_interproc_events (checker_path *path) const
 	    {
 	      if (get_logger ())
 		{
-		  label_text desc
-		    (path->get_checker_event (idx)->get_desc (false));
+		  char *desc = path->get_checker_event (idx)->get_desc ();
 		  log ("filtering events %i-%i:"
 		       " irrelevant call/return: %s",
-		       idx, idx + 1, desc.m_buffer);
-		  desc.maybe_free ();
+		       idx, idx + 1, desc);
+		  free (desc);
 		}
 	      path->delete_event (idx + 1);
 	      path->delete_event (idx);
diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc
index ac6d1a99c72..0736428906e 100644
--- a/gcc/analyzer/engine.cc
+++ b/gcc/analyzer/engine.cc
@@ -55,6 +55,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "analyzer/program-state.h"
 #include "analyzer/exploded-graph.h"
 #include "analyzer/analysis-plan.h"
+#include "intl.h"
 #include "analyzer/checker-path.h"
 #include "analyzer/state-purge.h"
 
@@ -3054,9 +3055,9 @@ class enode_label : public range_label
   {
     pretty_printer pp;
     pp_format_decoder (&pp) = default_tree_printer;
+    pp_printf (&pp, "EN: %i: ", m_enode->m_index);
     m_enode->get_state ().dump_to_pp (m_ext_state, true, &pp);
-    return make_label_text (false, "EN: %i: %s",
-			    m_enode->m_index, pp_formatted_text (&pp));
+    return label_text::take (xstrdup (pp_formatted_text (&pp)));
   }
 
 private:
diff --git a/gcc/analyzer/pending-diagnostic.cc b/gcc/analyzer/pending-diagnostic.cc
index b692c98304f..b47e750512f 100644
--- a/gcc/analyzer/pending-diagnostic.cc
+++ b/gcc/analyzer/pending-diagnostic.cc
@@ -35,6 +35,7 @@ along with GCC; see the file COPYING3.  If not see
 
 #if ENABLE_ANALYZER
 
+// FIXME:
 /* Generate a label_text by printing FMT.
 
    Use a clone of the global_dc for formatting callbacks.
@@ -42,13 +43,9 @@ along with GCC; see the file COPYING3.  If not see
    Use this evdesc::event_desc's m_colorize flag to control colorization
    (so that e.g. we can disable it for JSON output).  */
 
-label_text
-evdesc::event_desc::formatted_print (const char *fmt, ...) const
+void
+evdesc::event_desc::printf (const char *fmt, ...) const
 {
-  pretty_printer *pp = global_dc->printer->clone ();
-
-  pp_show_color (pp) = m_colorize;
-
   text_info ti;
   rich_location rich_loc (line_table, UNKNOWN_LOCATION);
   va_list ap;
@@ -58,13 +55,9 @@ evdesc::event_desc::formatted_print (const char *fmt, ...) const
   ti.err_no = 0;
   ti.x_data = NULL;
   ti.m_richloc = &rich_loc;
-  pp_format (pp, &ti);
-  pp_output_formatted_text (pp);
+  pp_format (m_pp, &ti);
+  pp_output_formatted_text (m_pp);
   va_end (ap);
-
-  label_text result = label_text::take (xstrdup (pp_formatted_text (pp)));
-  delete pp;
-  return result;
 }
 
 /* Return true if T1 and T2 are "the same" for the purposes of
diff --git a/gcc/analyzer/pending-diagnostic.h b/gcc/analyzer/pending-diagnostic.h
index 6132fcfa481..2f229980155 100644
--- a/gcc/analyzer/pending-diagnostic.h
+++ b/gcc/analyzer/pending-diagnostic.h
@@ -28,28 +28,30 @@ along with GCC; see the file COPYING3.  If not see
 
 namespace evdesc {
 
-struct event_desc
+class event_desc
 {
-  event_desc (bool colorize) : m_colorize (colorize) {}
+public:
+  event_desc (pretty_printer *pp): m_pp (pp) {}
 
-  label_text formatted_print (const char *fmt, ...) const
+  void printf (const char *gmsgid, ...) const
     ATTRIBUTE_GCC_DIAG(2,3);
 
-  bool m_colorize;
+private:
+  pretty_printer *m_pp;
 };
 
 /* For use by pending_diagnostic::describe_state_change.  */
 
 struct state_change : public event_desc
 {
-  state_change (bool colorize,
+  state_change (pretty_printer *pp,
 		tree expr,
 		tree origin,
 		state_machine::state_t old_state,
 		state_machine::state_t new_state,
 		diagnostic_event_id_t event_id,
 		const state_change_event &event)
-  : event_desc (colorize),
+    : event_desc (pp),
     m_expr (expr), m_origin (origin),
     m_old_state (old_state), m_new_state (new_state),
     m_event_id (event_id), m_event (event)
@@ -69,10 +71,10 @@ struct state_change : public event_desc
 
 struct call_with_state : public event_desc
 {
-  call_with_state (bool colorize,
+  call_with_state (pretty_printer *pp,
 		   tree caller_fndecl, tree callee_fndecl,
 		   tree expr, state_machine::state_t state)
-  : event_desc (colorize),
+  : event_desc (pp),
     m_caller_fndecl (caller_fndecl),
     m_callee_fndecl (callee_fndecl),
     m_expr (expr),
@@ -90,10 +92,10 @@ struct call_with_state : public event_desc
 
 struct return_of_state : public event_desc
 {
-  return_of_state (bool colorize,
+  return_of_state (pretty_printer *pp,
 		   tree caller_fndecl, tree callee_fndecl,
 		   state_machine::state_t state)
-  : event_desc (colorize),
+  : event_desc (pp),
     m_caller_fndecl (caller_fndecl),
     m_callee_fndecl (callee_fndecl),
     m_state (state)
@@ -109,9 +111,9 @@ struct return_of_state : public event_desc
 
 struct final_event : public event_desc
 {
-  final_event (bool colorize,
+  final_event (pretty_printer *pp,
 	       tree expr, state_machine::state_t state)
-  : event_desc (colorize),
+  : event_desc (pp),
     m_expr (expr), m_state (state)
   {}
 
@@ -177,9 +179,9 @@ class pending_diagnostic
      virtual functions give the pending diagnostic a way to describe events
      in a diagnostic_path in terms that make sense for that diagnostic.
 
-     In each case, return a non-NULL label_text to give the event a custom
-     description; NULL otherwise (falling back on a more generic
-     description).  */
+     In each case, print to PP and return true to give the event a custom
+     description; print nothing and return false otherwise (falling back on
+     a more generic description).  */
 
   /* Precision-of-wording vfunc for describing a critical state change
      within the diagnostic_path.
@@ -194,10 +196,10 @@ class pending_diagnostic
      Note how in both cases the first event is a "free": the best
      description to use depends on the diagnostic.  */
 
-  virtual label_text describe_state_change (const evdesc::state_change &)
+  virtual bool describe_state_change (const evdesc::state_change &)
   {
     /* Default no-op implementation.  */
-    return label_text ();
+    return false;
   }
 
   /* Precision-of-wording vfunc for describing an interprocedural call
@@ -208,10 +210,10 @@ class pending_diagnostic
      to make it clearer how the freed value moves from caller to
      callee.  */
 
-  virtual label_text describe_call_with_state (const evdesc::call_with_state &)
+  virtual bool describe_call_with_state (const evdesc::call_with_state &)
   {
     /* Default no-op implementation.  */
-    return label_text ();
+    return false;
   }
 
   /* Precision-of-wording vfunc for describing an interprocedural return
@@ -223,10 +225,10 @@ class pending_diagnostic
      to make it clearer how the unchecked value moves from callee
      back to caller.  */
 
-  virtual label_text describe_return_of_state (const evdesc::return_of_state &)
+  virtual bool describe_return_of_state (const evdesc::return_of_state &)
   {
     /* Default no-op implementation.  */
-    return label_text ();
+    return false;
   }
 
   /* Precision-of-wording vfunc for describing the final event within a
@@ -237,10 +239,10 @@ class pending_diagnostic
      and a use-after-free might use
       - "use after 'free' here; memory was freed at (2)".  */
 
-  virtual label_text describe_final_event (const evdesc::final_event &)
+  virtual bool describe_final_event (const evdesc::final_event &)
   {
     /* Default no-op implementation.  */
-    return label_text ();
+    return false;
   }
 
   /* End of precision-of-wording vfuncs.  */
diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc
index d6bda5b6642..ccbf85d316b 100644
--- a/gcc/analyzer/region-model.cc
+++ b/gcc/analyzer/region-model.cc
@@ -3855,22 +3855,23 @@ public:
       }
   }
 
-  label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
+  bool describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
   {
     switch (m_pkind)
       {
       default:
 	gcc_unreachable ();
       case POISON_KIND_UNINIT:
-	return ev.formatted_print ("use of uninitialized value %qE here",
-				   m_expr);
+	ev.printf ("use of uninitialized value %qE here", m_expr);
+	return true;
+
       case POISON_KIND_FREED:
-	return ev.formatted_print ("use after %<free%> of %qE here",
-				   m_expr);
+	ev.printf ("use after %<free%> of %qE here", m_expr);
+	return true;
+
       case POISON_KIND_POPPED_STACK:
-	return ev.formatted_print
-	  ("use of pointer %qE within stale stack frame here",
-	   m_expr);
+	ev.printf ("use of pointer %qE within stale stack frame here", m_expr);
+	return true;
       }
   }
 
diff --git a/gcc/analyzer/sm-file.cc b/gcc/analyzer/sm-file.cc
index e7fb0f00145..e5c988d1252 100644
--- a/gcc/analyzer/sm-file.cc
+++ b/gcc/analyzer/sm-file.cc
@@ -99,21 +99,30 @@ public:
     return same_tree_p (m_arg, ((const file_diagnostic &)base_other).m_arg);
   }
 
-  label_text describe_state_change (const evdesc::state_change &change)
+  bool describe_state_change (const evdesc::state_change &change)
     OVERRIDE
   {
     if (change.m_old_state == m_sm.m_start
 	&& change.m_new_state == m_sm.m_unchecked)
-      // TODO: verify that it's the fopen stmt, not a copy
-      return label_text::borrow ("opened here");
+      {
+	// TODO: verify that it's the fopen stmt, not a copy
+	change.printf ("opened here");
+	return true;
+      }
     if (change.m_old_state == m_sm.m_unchecked
 	&& change.m_new_state == m_sm.m_nonnull)
-      return change.formatted_print ("assuming %qE is non-NULL",
-				     change.m_expr);
+      {
+	change.printf ("assuming %qE is non-NULL",
+		       change.m_expr);
+	return true;
+      }
     if (change.m_new_state == m_sm.m_null)
-      return change.formatted_print ("assuming %qE is NULL",
-				     change.m_expr);
-    return label_text ();
+      {
+	change.printf ("assuming %qE is NULL",
+		       change.m_expr);
+	return true;
+      }
+    return false;
   }
 
 protected:
@@ -137,24 +146,28 @@ public:
 		       m_arg);
   }
 
-  label_text describe_state_change (const evdesc::state_change &change)
+  bool describe_state_change (const evdesc::state_change &change)
     OVERRIDE
   {
     if (change.m_new_state == m_sm.m_closed)
       {
 	m_first_fclose_event = change.m_event_id;
-	return change.formatted_print ("first %qs here", "fclose");
+	change.printf ("first %qs here", "fclose");
+	return true;
       }
     return file_diagnostic::describe_state_change (change);
   }
 
-  label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
+  bool describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
   {
     if (m_first_fclose_event.known_p ())
-      return ev.formatted_print ("second %qs here; first %qs was at %@",
-				 "fclose", "fclose",
-				 &m_first_fclose_event);
-    return ev.formatted_print ("second %qs here", "fclose");
+      ev.printf ("second %qs here; first %qs was at %@",
+		 "fclose", "fclose",
+		 &m_first_fclose_event);
+    else
+      ev.printf ("second %qs here; first %qs was at %@",
+		 "fclose", "fclose");
+    return true;
   }
 
 private:
@@ -178,23 +191,26 @@ public:
 		       m_arg);
   }
 
-  label_text describe_state_change (const evdesc::state_change &change)
+  bool describe_state_change (const evdesc::state_change &change)
     OVERRIDE
   {
     if (change.m_new_state == m_sm.m_closed)
       {
 	m_fclose_event = change.m_event_id;
-	return change.formatted_print ("file closed here");
+	change.printf ("file closed here");
+	return true;
       }
     return file_diagnostic::describe_state_change (change);
   }
 
-  label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
+  bool describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
   {
     if (m_fclose_event.known_p ())
-      return ev.formatted_print ("use of closed FILE %qE; closed at %@",
-				 ev.m_expr, &m_fclose_event);
-    return ev.formatted_print ("use of closed FILE %qE", ev.m_expr);
+      ev.printf ("use of closed FILE %qE; closed at %@",
+		 ev.m_expr, &m_fclose_event);
+    else
+      ev.printf ("use of closed FILE %qE", ev.m_expr);
+    return true;
   }
 
 private:
@@ -221,24 +237,26 @@ public:
 		       m_arg);
   }
 
-  label_text describe_state_change (const evdesc::state_change &change)
+  bool describe_state_change (const evdesc::state_change &change)
     FINAL OVERRIDE
   {
     if (change.m_new_state == m_sm.m_unchecked)
       {
 	m_fopen_event = change.m_event_id;
-	return label_text::borrow ("opened here");
+	change.printf ("opened here");
+	return true;
       }
     return file_diagnostic::describe_state_change (change);
   }
 
-  label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
+  bool describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
   {
     if (m_fopen_event.known_p ())
-      return ev.formatted_print ("%qE leaks here; was opened at %@",
-				 ev.m_expr, &m_fopen_event);
+      ev.printf ("%qE leaks here; was opened at %@",
+		 ev.m_expr, &m_fopen_event);
     else
-      return ev.formatted_print ("%qE leaks here", ev.m_expr);
+      ev.printf ("%qE leaks here", ev.m_expr);
+    return true;
   }
 
 private:
diff --git a/gcc/analyzer/sm-malloc.cc b/gcc/analyzer/sm-malloc.cc
index 15fa1c76471..9f4cf16cc66 100644
--- a/gcc/analyzer/sm-malloc.cc
+++ b/gcc/analyzer/sm-malloc.cc
@@ -105,21 +105,28 @@ public:
     return same_tree_p (m_arg, ((const malloc_diagnostic &)base_other).m_arg);
   }
 
-  label_text describe_state_change (const evdesc::state_change &change)
+  bool describe_state_change (const evdesc::state_change &change)
     OVERRIDE
   {
     if (change.m_old_state == m_sm.m_start
 	&& change.m_new_state == m_sm.m_unchecked)
-      // TODO: verify that it's the allocation stmt, not a copy
-      return label_text::borrow ("allocated here");
+      {
+	// TODO: verify that it's the allocation stmt, not a copy
+	change.printf ("allocated here");
+	return true;
+      }
     if (change.m_old_state == m_sm.m_unchecked
 	&& change.m_new_state == m_sm.m_nonnull)
-      return change.formatted_print ("assuming %qE is non-NULL",
-				     change.m_expr);
+      {
+	change.printf ("assuming %qE is non-NULL", change.m_expr);
+	return true;
+      }
     if (change.m_new_state == m_sm.m_null)
-      return change.formatted_print ("assuming %qE is NULL",
-				     change.m_expr);
-    return label_text ();
+      {
+	change.printf ("assuming %qE is NULL", change.m_expr);
+	return true;
+      }
+    return false;
   }
 
 protected:
@@ -147,34 +154,39 @@ public:
 		       "double-%<free%> of %qE", m_arg);
   }
 
-  label_text describe_state_change (const evdesc::state_change &change)
+  bool describe_state_change (const evdesc::state_change &change)
     FINAL OVERRIDE
   {
     if (change.m_new_state == m_sm.m_freed)
       {
 	m_first_free_event = change.m_event_id;
-	return change.formatted_print ("first %qs here", "free");
+	change.printf ("first %qs here", "free");
+	return true;
       }
     return malloc_diagnostic::describe_state_change (change);
   }
 
-  label_text describe_call_with_state (const evdesc::call_with_state &info)
+  bool describe_call_with_state (const evdesc::call_with_state &info)
     FINAL OVERRIDE
   {
     if (info.m_state == m_sm.m_freed)
-      return info.formatted_print
-	("passing freed pointer %qE in call to %qE from %qE",
-	 info.m_expr, info.m_callee_fndecl, info.m_caller_fndecl);
-    return label_text ();
+      {
+	info.printf ("passing freed pointer %qE in call to %qE from %qE",
+		     info.m_expr, info.m_callee_fndecl, info.m_caller_fndecl);
+	return true;
+      }
+    return false;
   }
 
-  label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
+  bool describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
   {
     if (m_first_free_event.known_p ())
-      return ev.formatted_print ("second %qs here; first %qs was at %@",
-				 "free", "free",
-				 &m_first_free_event);
-    return ev.formatted_print ("second %qs here", "free");
+      ev.printf ("second %qs here; first %qs was at %@",
+		 "free", "free",
+		 &m_first_free_event);
+    else
+      ev.printf ("second %qs here", "free");
+    return true;
   }
 
 private:
@@ -191,25 +203,29 @@ public:
   : malloc_diagnostic (sm, arg)
   {}
 
-  label_text describe_state_change (const evdesc::state_change &change)
+  bool describe_state_change (const evdesc::state_change &change)
     FINAL OVERRIDE
   {
     if (change.m_old_state == m_sm.m_start
 	&& change.m_new_state == m_sm.m_unchecked)
       {
 	m_origin_of_unchecked_event = change.m_event_id;
-	return label_text::borrow ("this call could return NULL");
+	change.printf ("this call could return NULL");
+	return true;
       }
     return malloc_diagnostic::describe_state_change (change);
   }
 
-  label_text describe_return_of_state (const evdesc::return_of_state &info)
+  bool describe_return_of_state (const evdesc::return_of_state &info)
     FINAL OVERRIDE
   {
     if (info.m_state == m_sm.m_unchecked)
-      return info.formatted_print ("possible return of NULL to %qE from %qE",
-				   info.m_caller_fndecl, info.m_callee_fndecl);
-    return label_text ();
+      {
+	info.printf ("possible return of NULL to %qE from %qE",
+		     info.m_caller_fndecl, info.m_callee_fndecl);
+	return true;
+      }
+    return false;
   }
 
 protected:
@@ -237,16 +253,16 @@ public:
 		       "dereference of possibly-NULL %qE", m_arg);
   }
 
-  label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
+  bool describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
   {
     if (m_origin_of_unchecked_event.known_p ())
-      return ev.formatted_print ("%qE could be NULL: unchecked value from %@",
-				 ev.m_expr,
-				 &m_origin_of_unchecked_event);
+      ev.printf ("%qE could be NULL: unchecked value from %@",
+		 ev.m_expr,
+		 &m_origin_of_unchecked_event);
     else
-      return ev.formatted_print ("%qE could be NULL", ev.m_expr);
+      ev.printf ("%qE could be NULL", ev.m_expr);
+    return true;
   }
-
 };
 
 /* Subroutine for use by possible_null_arg::emit and null_arg::emit.
@@ -303,17 +319,17 @@ public:
     return warned;
   }
 
-  label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
+  bool describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
   {
     if (m_origin_of_unchecked_event.known_p ())
-      return ev.formatted_print ("argument %u (%qE) from %@ could be NULL"
-				 " where non-null expected",
-				 m_arg_idx + 1, ev.m_expr,
-				 &m_origin_of_unchecked_event);
+      ev.printf ("argument %u (%qE) from %@ could be NULL"
+		 " where non-null expected",
+		 m_arg_idx + 1, ev.m_expr,
+		 &m_origin_of_unchecked_event);
     else
-      return ev.formatted_print ("argument %u (%qE) could be NULL"
-				 " where non-null expected",
-				 m_arg_idx + 1, ev.m_expr);
+      ev.printf ("argument %u (%qE) could be NULL where non-null expected",
+		 m_arg_idx + 1, ev.m_expr);
+    return true;
   }
 
 private:
@@ -340,18 +356,22 @@ public:
 		       "dereference of NULL %qE", m_arg);
   }
 
-  label_text describe_return_of_state (const evdesc::return_of_state &info)
+  bool describe_return_of_state (const evdesc::return_of_state &info)
     FINAL OVERRIDE
   {
     if (info.m_state == m_sm.m_null)
-      return info.formatted_print ("return of NULL to %qE from %qE",
-				   info.m_caller_fndecl, info.m_callee_fndecl);
-    return label_text ();
+      {
+	info.printf ("return of NULL to %qE from %qE",
+		     info.m_caller_fndecl, info.m_callee_fndecl);
+	return true;
+      }
+    return false;
   }
 
-  label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
+  bool describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
   {
-    return ev.formatted_print ("dereference of NULL %qE", ev.m_expr);
+    ev.printf ("dereference of NULL %qE", ev.m_expr);
+    return true;
   }
 };
 
@@ -391,11 +411,11 @@ public:
     return warned;
   }
 
-  label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
+  bool describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
   {
-    return ev.formatted_print ("argument %u (%qE) NULL"
-			       " where non-null expected",
-			       m_arg_idx + 1, ev.m_expr);
+    ev.printf ("argument %u (%qE) NULL where non-null expected",
+	       m_arg_idx + 1, ev.m_expr);
+    return true;
   }
 
 private:
@@ -421,24 +441,26 @@ public:
 		       "use after %<free%> of %qE", m_arg);
   }
 
-  label_text describe_state_change (const evdesc::state_change &change)
+  bool describe_state_change (const evdesc::state_change &change)
     FINAL OVERRIDE
   {
     if (change.m_new_state == m_sm.m_freed)
       {
 	m_free_event = change.m_event_id;
-	return label_text::borrow ("freed here");
+	change.printf ("freed here");
+	return true;
       }
     return malloc_diagnostic::describe_state_change (change);
   }
 
-  label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
+  bool describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
   {
     if (m_free_event.known_p ())
-      return ev.formatted_print ("use after %<free%> of %qE; freed at %@",
-				 ev.m_expr, &m_free_event);
+      ev.printf ("use after %<free%> of %qE; freed at %@",
+		 ev.m_expr, &m_free_event);
     else
-      return ev.formatted_print ("use after %<free%> of %qE", ev.m_expr);
+      ev.printf ("use after %<free%> of %qE", ev.m_expr);
+    return true;
   }
 
 private:
@@ -461,24 +483,26 @@ public:
 		       "leak of %qE", m_arg);
   }
 
-  label_text describe_state_change (const evdesc::state_change &change)
+  bool describe_state_change (const evdesc::state_change &change)
     FINAL OVERRIDE
   {
     if (change.m_new_state == m_sm.m_unchecked)
       {
 	m_malloc_event = change.m_event_id;
-	return label_text::borrow ("allocated here");
+	change.printf ("allocated here");
+	return true;
       }
     return malloc_diagnostic::describe_state_change (change);
   }
 
-  label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
+  bool describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
   {
     if (m_malloc_event.known_p ())
-      return ev.formatted_print ("%qE leaks here; was allocated at %@",
-				 ev.m_expr, &m_malloc_event);
+      ev.printf ("%qE leaks here; was allocated at %@",
+		 ev.m_expr, &m_malloc_event);
     else
-      return ev.formatted_print ("%qE leaks here", ev.m_expr);
+      ev.printf ("%qE leaks here", ev.m_expr);
+    return true;
   }
 
 private:
@@ -526,7 +550,7 @@ public:
       }
   }
 
-  label_text describe_state_change (const evdesc::state_change &change)
+  bool describe_state_change (const evdesc::state_change &change)
     FINAL OVERRIDE
   {
     /* Attempt to reconstruct what kind of pointer it is.
@@ -540,17 +564,19 @@ public:
 		|| is_special_named_call_p (call, "__builtin_alloca", 1))
 	      {
 		m_kind = KIND_ALLOCA;
-		return label_text::borrow
-		  ("memory is allocated on the stack here");
+		change.printf ("memory is allocated on the stack here");
+		return true;
 	      }
 	  }
       }
-    return label_text::borrow ("pointer is from here");
+    change.printf ("pointer is from here");
+    return true;
   }
 
-  label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
+  bool describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
   {
-    return ev.formatted_print ("call to %qs here", "free");
+    ev.printf ("call to %qs here", "free");
+    return true;
   }
 
 private:
diff --git a/gcc/analyzer/sm-sensitive.cc b/gcc/analyzer/sm-sensitive.cc
index 16b53e2c9e4..d6b2439d6ba 100644
--- a/gcc/analyzer/sm-sensitive.cc
+++ b/gcc/analyzer/sm-sensitive.cc
@@ -108,45 +108,50 @@ public:
 		       m_arg);
   }
 
-  label_text describe_state_change (const evdesc::state_change &change)
+  bool describe_state_change (const evdesc::state_change &change)
     FINAL OVERRIDE
   {
     if (change.m_new_state == m_sm.m_sensitive)
       {
 	m_first_sensitive_event = change.m_event_id;
-	return change.formatted_print ("sensitive value acquired here");
+	change.printf ("sensitive value acquired here");
+	return true;
       }
-    return label_text ();
+    return false;
   }
 
-  label_text describe_call_with_state (const evdesc::call_with_state &info)
+  bool describe_call_with_state (const evdesc::call_with_state &info)
     FINAL OVERRIDE
   {
     if (info.m_state == m_sm.m_sensitive)
-      return info.formatted_print
-	("passing sensitive value %qE in call to %qE from %qE",
-	 info.m_expr, info.m_callee_fndecl, info.m_caller_fndecl);
-    return label_text ();
+      {
+	info.printf ("passing sensitive value %qE in call to %qE from %qE",
+		     info.m_expr, info.m_callee_fndecl, info.m_caller_fndecl);
+	return true;
+      }
+    return false;
   }
 
-  label_text describe_return_of_state (const evdesc::return_of_state &info)
+  bool describe_return_of_state (const evdesc::return_of_state &info)
     FINAL OVERRIDE
   {
     if (info.m_state == m_sm.m_sensitive)
-      return info.formatted_print ("returning sensitive value to %qE from %qE",
-				   info.m_caller_fndecl, info.m_callee_fndecl);
-    return label_text ();
+      {
+	info.printf ("returning sensitive value to %qE from %qE",
+		     info.m_caller_fndecl, info.m_callee_fndecl);
+	return true;
+      }
+    return false;
   }
 
-  label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
+  bool describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
   {
     if (m_first_sensitive_event.known_p ())
-      return ev.formatted_print ("sensitive value %qE written to output file"
-				 "; acquired at %@",
-				 m_arg, &m_first_sensitive_event);
+      ev.printf ("sensitive value %qE written to output file; acquired at %@",
+		 m_arg, &m_first_sensitive_event);
     else
-      return ev.formatted_print ("sensitive value %qE written to output file",
-				 m_arg);
+      ev.printf ("sensitive value %qE written to output file", m_arg);
+    return true;
   }
 
 private:
diff --git a/gcc/analyzer/sm-signal.cc b/gcc/analyzer/sm-signal.cc
index 0057ab96d44..630a94d609b 100644
--- a/gcc/analyzer/sm-signal.cc
+++ b/gcc/analyzer/sm-signal.cc
@@ -43,6 +43,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "selftest.h"
 #include "analyzer/region-model.h"
 #include "analyzer/program-state.h"
+#include "intl.h"
 #include "analyzer/checker-path.h"
 #include "digraph.h"
 #include "cfg.h"
@@ -130,7 +131,7 @@ public:
 		       m_unsafe_fndecl);
   }
 
-  label_text describe_state_change (const evdesc::state_change &change)
+  bool describe_state_change (const evdesc::state_change &change)
     FINAL OVERRIDE
   {
     if (change.is_global_p ()
@@ -138,16 +139,16 @@ public:
       {
 	function *handler
 	  = change.m_event.m_dst_state.m_region_model->get_current_function ();
-	return change.formatted_print ("registering %qD as signal handler",
-				       handler->decl);
+	change.printf ("registering %qD as signal handler", handler->decl);
+	return true;
       }
-    return label_text ();
+    return false;
   }
 
-  label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
+  bool describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
   {
-    return ev.formatted_print ("call to %qD from within signal handler",
-			       m_unsafe_fndecl);
+    ev.printf ("call to %qD from within signal handler", m_unsafe_fndecl);
+    return true;
   }
 
 private:
diff --git a/gcc/analyzer/sm-taint.cc b/gcc/analyzer/sm-taint.cc
index e454407e7af..09451e930d9 100644
--- a/gcc/analyzer/sm-taint.cc
+++ b/gcc/analyzer/sm-taint.cc
@@ -132,46 +132,57 @@ public:
       }
   }
 
-  label_text describe_state_change (const evdesc::state_change &change)
+  bool describe_state_change (const evdesc::state_change &change)
     FINAL OVERRIDE
   {
     if (change.m_new_state == m_sm.m_tainted)
       {
 	if (change.m_origin)
-	  return change.formatted_print ("%qE has an unchecked value here"
-					 " (from %qE)",
-					 change.m_expr, change.m_origin);
+	  change.printf ("%qE has an unchecked value here (from %qE)",
+			 change.m_expr, change.m_origin);
 	else
-	  return change.formatted_print ("%qE gets an unchecked value here",
-					 change.m_expr);
+	  change.printf ("%qE gets an unchecked value here",
+			 change.m_expr);
+	return true;
       }
     else if (change.m_new_state == m_sm.m_has_lb)
-      return change.formatted_print ("%qE has its lower bound checked here",
-				     change.m_expr);
+      {
+	change.printf ("%qE has its lower bound checked here",
+		       change.m_expr);
+	return true;
+      }
     else if (change.m_new_state == m_sm.m_has_ub)
-      return change.formatted_print ("%qE has its upper bound checked here",
-				     change.m_expr);
-    return label_text ();
+      {
+	change.printf ("%qE has its upper bound checked here",
+		       change.m_expr);
+	return true;
+      }
+    return false;
   }
 
-  label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
+  bool describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
   {
     switch (m_has_bounds)
       {
       default:
 	gcc_unreachable ();
       case BOUNDS_NONE:
-	return ev.formatted_print ("use of tainted value %qE in array lookup"
-				   " without bounds checking",
-				   m_arg);
+	ev.printf ("use of tainted value %qE in array lookup"
+		   " without bounds checking",
+		   m_arg);
+	return true;
+
       case BOUNDS_UPPER:
-	return ev.formatted_print ("use of tainted value %qE in array lookup"
-				   " without lower-bounds checking",
-				   m_arg);
+	ev.printf ("use of tainted value %qE in array lookup"
+		   " without lower-bounds checking",
+		   m_arg);
+	return true;
+
       case BOUNDS_LOWER:
-	return ev.formatted_print ("use of tainted value %qE in array lookup"
-				   " without upper-bounds checking",
-				   m_arg);
+	ev.printf ("use of tainted value %qE in array lookup"
+		   " without upper-bounds checking",
+		   m_arg);
+	return true;
       }
   }
 
diff --git a/gcc/diagnostic-path.h b/gcc/diagnostic-path.h
index d005da351a7..a8ef70aaf27 100644
--- a/gcc/diagnostic-path.h
+++ b/gcc/diagnostic-path.h
@@ -79,8 +79,12 @@ class diagnostic_event
      calls, returns, and frame nesting.  */
   virtual int get_stack_depth () const = 0;
 
-  /* Get a localized (and possibly colorized) description of this event.  */
-  virtual label_text get_desc (bool can_colorize) const = 0;
+  /* Print a localized (and possibly colorized) description of this event.  */
+  virtual void print_desc (pretty_printer *pp) const = 0;
+
+  /* Get a localized but uncolorized description of this event.
+     Caller must free it.  */
+  char *get_desc () const;
 };
 
 /* Abstract base class for getting at a sequence of events.  */
@@ -109,9 +113,9 @@ class simple_diagnostic_event : public diagnostic_event
   location_t get_location () const FINAL OVERRIDE { return m_loc; }
   tree get_fndecl () const FINAL OVERRIDE { return m_fndecl; }
   int get_stack_depth () const FINAL OVERRIDE { return m_depth; }
-  label_text get_desc (bool) const FINAL OVERRIDE
+  void print_desc (pretty_printer *pp) const FINAL OVERRIDE
   {
-    return label_text::borrow (m_desc);
+    pp_string (pp, m_desc);
   }
 
  private:
diff --git a/gcc/tree-diagnostic-path.cc b/gcc/tree-diagnostic-path.cc
index 381ec77bc34..5b4eb529aee 100644
--- a/gcc/tree-diagnostic-path.cc
+++ b/gcc/tree-diagnostic-path.cc
@@ -36,6 +36,20 @@ along with GCC; see the file COPYING3.  If not see
 #include "selftest.h"
 #include "selftest-diagnostic.h"
 
+/* Get a localized but uncolorized description of this event.
+   Caller must free it.  */
+
+char *
+diagnostic_event::get_desc () const
+{
+  pretty_printer *pp = global_dc->printer->clone ();
+  print_desc (pp);
+  char *result = xstrdup (pp_formatted_text (pp));
+  delete pp;
+  return result;
+}
+
+
 /* Anonymous namespace for path-printing code.  */
 
 namespace {
@@ -56,18 +70,20 @@ class path_label : public range_label
     unsigned event_idx = m_start_idx + range_idx;
     const diagnostic_event &event = m_path->get_event (event_idx);
 
-    /* Get the description of the event, perhaps with colorization:
+    /* Print the description of the event, perhaps with colorization:
        normally, we don't colorize within a range_label, but this
-       is special-cased for diagnostic paths.  */
-    bool colorize = pp_show_color (global_dc->printer);
-    label_text event_text (event.get_desc (colorize));
-    gcc_assert (event_text.m_buffer);
-    pretty_printer pp;
-    pp_show_color (&pp) = pp_show_color (global_dc->printer);
+       is special-cased for diagnostic paths.
+       We need to use a clone of the dc's printer here as getting the
+       label is called from within a call to diagnostic_show_locus,
+       which will already have buffered text, and we don't want to
+       mix up the label text with that.  */
+    pretty_printer *pp = global_dc->printer->clone ();
     diagnostic_event_id_t event_id (event_idx);
-    pp_printf (&pp, "%@ %s", &event_id, event_text.m_buffer);
-    event_text.maybe_free ();
-    label_text result = label_text::take (xstrdup (pp_formatted_text (&pp)));
+    pp_printf (pp, "%@ ", &event_id);
+    event.print_desc (pp);
+    label_text result = label_text::take (xstrdup (pp_formatted_text (pp)));
+    delete pp;
+
     return result;
   }
 
@@ -178,11 +194,10 @@ class path_summary
 	    {
 	      const diagnostic_event &iter_event = m_path->get_event (i);
 	      diagnostic_event_id_t event_id (i);
-	      label_text event_text (iter_event.get_desc (true));
 	      pretty_printer *pp = dc->printer;
-	      pp_printf (pp, " %@: %s", &event_id, event_text.m_buffer);
+	      pp_printf (pp, " %@: ", &event_id);
+	      iter_event.print_desc (pp);
 	      pp_newline (pp);
-	      event_text.maybe_free ();
 	    }
 	  return;
 	}
@@ -515,16 +530,20 @@ public:
 	  const unsigned num_events = path->num_events ();
 
 	  /* A note per event.  */
+
+	  /* Use a clone of the printer, to get event descriptions with
+	     the same colorization rules as CONTEXT's printer.  */
+	  pretty_printer *pp = context->printer->clone ();
 	  for (unsigned i = 0; i < num_events; i++)
 	    {
 	      const diagnostic_event &event = path->get_event (i);
-	      label_text event_text (event.get_desc (false));
-	      gcc_assert (event_text.m_buffer);
 	      diagnostic_event_id_t event_id (i);
+	      event.print_desc (pp);
 	      inform (event.get_location (),
-		      "%@ %s", &event_id, event_text.m_buffer);
-	      event_text.maybe_free ();
+		      "%@ %s", &event_id, pp_formatted_text (pp));
+	      pp_clear_output_area (pp);
 	    }
+	  delete pp;
 	}
 	break;
 
@@ -581,9 +600,11 @@ path_printer::make_tree_default ()
    doesn't have access to trees (for m_fndecl).  */
 
 json::value *
-default_tree_make_json_for_path (diagnostic_context *,
+default_tree_make_json_for_path (diagnostic_context *dc,
 				 const diagnostic_path *path)
 {
+  pretty_printer *pp = dc->printer->clone ();
+  pp_show_color (pp) = false;
   json::array *path_array = new json::array ();
   for (unsigned i = 0; i < path->num_events (); i++)
     {
@@ -593,9 +614,9 @@ default_tree_make_json_for_path (diagnostic_context *,
       if (event.get_location ())
 	event_obj->set ("location",
 			json_from_expanded_location (event.get_location ()));
-      label_text event_text (event.get_desc (false));
-      event_obj->set ("description", new json::string (event_text.m_buffer));
-      event_text.maybe_free ();
+      event.print_desc (pp);
+      event_obj->set ("description", new json::string (pp_formatted_text (pp)));
+      pp_clear_output_area (pp);
       if (tree fndecl = event.get_fndecl ())
 	{
 	  const char *function
@@ -606,6 +627,7 @@ default_tree_make_json_for_path (diagnostic_context *,
 		      new json::integer_number (event.get_stack_depth ()));
       path_array->append (event_obj);
     }
+  delete pp;
   return path_array;
 }
 
-- 
2.21.0

