[v3,3/8] Introduce character-printing class

Message ID 20251228-string-printing-2-v3-3-ec5cc9dd0c71@tromey.com
State New
Headers
Series Refactor character printing |

Commit Message

Tom Tromey Dec. 28, 2025, 8:13 p.m. UTC
  This patch introduces a new class for printing character and string
literals by rewriting some code in valprint.  The new class is
designed to be subclassed to provide language-specific character
printing.
---
 gdb/char-print.h         | 199 ++++++++++++++++++++++
 gdb/valprint.c           | 428 +++++++++++++++++++++--------------------------
 gdbsupport/gdb_obstack.h |   3 -
 3 files changed, 394 insertions(+), 236 deletions(-)
  

Comments

Simon Marchi Jan. 7, 2026, 9:09 p.m. UTC | #1
On 2025-12-28 15:13, Tom Tromey wrote:
> This patch introduces a new class for printing character and string
> literals by rewriting some code in valprint.  The new class is
> designed to be subclassed to provide language-specific character
> printing.

It's quite a big patch and I am not knowledgeable in this area, so I
just did a quick pass, I did not find anything troubling.  Eventually,
I'd like if the implementation of the methods could move to
char-print.c.

Some minor (or even silly) comments below.

> +/* A class that handles display of a character or string.  The object
> +   is printed as if it were a literal in the source language.  This
> +   class implements C-like syntax, but can be subclassed for other
> +   languages.  */
> +class wchar_printer
> +{
> +private:
> +
> +  /* Helper function to get a default encoding based on CHTYPE.  */
> +  static const char *get_default_encoding (type *chtype);

For completeness, this would require a forward declaration of
`struct type`.

> +
> +public:
> +
> +  /* Constructor.  CH_TYPE is the type of the underlying character.
> +     QUOTER is a (narrow) character that is the quote to print,
> +     e.g. '\'' or '"' for C.  ENCODING is the encoding to use for the
> +     contents; if NULL then a default is chosen based on CH_TYPE.  */
> +  wchar_printer (type *ch_type, int quoter, const char *encoding = nullptr)
> +    : m_wchar_buf (),

The explicit initialization of m_wchar_buf is unnecessary.

> +      m_encoding (encoding == nullptr
> +		  ? get_default_encoding (ch_type)
> +		  : encoding),
> +      m_byte_order (type_byte_order (ch_type)),

Need to include gdbtypes.h for type_byte_order.

> +private:
> +
> +  /* Intermediate output is stored here.  */
> +  auto_obstack m_wchar_buf;

Need to include gdbsupport/gdb_obstack.h.

> +wchar_printer::print (int c, ui_file *stream)
>  {
> -  enum bfd_endian byte_order
> -    = type_byte_order (type);
>    gdb_byte *c_buf;
> -  bool need_escape = false;
>  
> -  c_buf = (gdb_byte *) alloca (type->length ());
> -  pack_long (c_buf, type, c);
> +  c_buf = (gdb_byte *) alloca (m_width);

You can move the declaration of c_buf here.

> +void
> +generic_emit_char (int c, struct type *type, struct ui_file *stream,
> +		   int quoter, const char *encoding)
> +{
> +  wchar_printer printer (type, quoter, encoding);
> +  printer.print (c, stream);

IMO you can get rid of the named variable here.

Simon
  

Patch

diff --git a/gdb/char-print.h b/gdb/char-print.h
new file mode 100644
index 0000000000000000000000000000000000000000..1c3aa6b2f973a7b86b9eccd1ae6f1e156b166ec2
--- /dev/null
+++ b/gdb/char-print.h
@@ -0,0 +1,199 @@ 
+/* Character and string printing
+
+   Copyright (C) 2025 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#ifndef GDB_CHAR_PRINT_H
+#define GDB_CHAR_PRINT_H
+
+#include "gdb_wchar.h"
+#include "ui-file.h"
+#include "charset.h"
+
+/* A ui_file that writes wide characters to an obstack.  */
+class obstack_wide_file : public ui_file
+{
+public:
+  explicit obstack_wide_file (struct obstack *output)
+    : m_output (output)
+  {
+  }
+
+  ~obstack_wide_file () = default;
+
+  void write (const char *buf, long length_buf) override
+  {
+    for (long i = 0; i < length_buf; ++i)
+      {
+	gdb_wchar_t w = gdb_btowc (buf[i]);
+	obstack_grow (m_output, &w, sizeof (gdb_wchar_t));
+      }
+  }
+
+  void write (gdb_wchar_t w)
+  {
+    obstack_grow (m_output, &w, sizeof (gdb_wchar_t));
+  }
+
+  void write (const gdb_wchar_t *str)
+  {
+    obstack_grow (m_output, str, sizeof (gdb_wchar_t) * gdb_wcslen (str));
+  }
+
+private:
+  struct obstack *m_output;
+};
+
+/* A class that handles display of a character or string.  The object
+   is printed as if it were a literal in the source language.  This
+   class implements C-like syntax, but can be subclassed for other
+   languages.  */
+class wchar_printer
+{
+private:
+
+  /* Helper function to get a default encoding based on CHTYPE.  */
+  static const char *get_default_encoding (type *chtype);
+
+public:
+
+  /* Constructor.  CH_TYPE is the type of the underlying character.
+     QUOTER is a (narrow) character that is the quote to print,
+     e.g. '\'' or '"' for C.  ENCODING is the encoding to use for the
+     contents; if NULL then a default is chosen based on CH_TYPE.  */
+  wchar_printer (type *ch_type, int quoter, const char *encoding = nullptr)
+    : m_wchar_buf (),
+      m_encoding (encoding == nullptr
+		  ? get_default_encoding (ch_type)
+		  : encoding),
+      m_byte_order (type_byte_order (ch_type)),
+      m_file (&m_wchar_buf),
+      m_quoter (quoter),
+      m_width (ch_type->length ()),
+      m_type (ch_type)
+  {
+  }
+
+  /* Print a target character, C, to STREAM.  The character is printed
+     as if it were a character literal; that is, surrounded by the
+     appropriate quotes for the language, and escaped as needed.  */
+  void print (int c, ui_file *stream);
+
+  /* Print the character string STRING, printing at most LENGTH
+     characters.  LENGTH is -1 if the string is nul terminated.
+     OPTIONS holds the printing options; printing stops early if the
+     number hits print_max_chars; repeat counts are printed as
+     appropriate.  Print ellipses at the end if we had to stop before
+     printing LENGTH characters, or if FORCE_ELLIPSES.  If
+     C_STYLE_TERMINATOR is true, and the last character is 0, then it
+     is omitted.  */
+  void print (struct ui_file *stream, const gdb_byte *string,
+	      unsigned int length, int force_ellipses,
+	      int c_style_terminator,
+	      const struct value_print_options *options);
+
+protected:
+
+  /* Return true if W is printable.  This must agree with do_print in
+     the sense that if printable returns true, then do_print is
+     assumed to not emit an escape sequence for that character.
+     "Printable" means that either the character is printed as-is, or
+     that it is printed as a known short escape sequence, like
+     "\\".  */
+  virtual bool printable (gdb_wchar_t w) const;
+
+  /* Print a wide character W.  This is called for characters where
+     'printable' returns true.  */
+  virtual void print_char (gdb_wchar_t w);
+
+  /* Print an escape sequence for a character.  ORIG is a pointer to
+     the original (target) bytes representing the character, ORIG_LEN
+     is the number of valid bytes.  W might be gdb_WEOF, in which case
+     an escape sequence for the bytes given by ORIG must be
+     printed.  */
+  virtual void print_escape (const gdb_byte *orig, int orig_len);
+
+  /* Maximum number of wchars returned from wchar_iterate.  */
+  static constexpr int MAX_WCHARS = 4;
+
+  /* A structure to encapsulate state information from iterated
+     character conversions.  */
+  struct converted_character
+  {
+    /* The number of characters converted.  */
+    int num_chars;
+
+    /* The result of the conversion.  See charset.h for more.  */
+    enum wchar_iterate_result result;
+
+    /* The (saved) converted character(s).  */
+    gdb_wchar_t chars[MAX_WCHARS];
+
+    /* The first converted target byte.  */
+    const gdb_byte *buf;
+
+    /* The number of bytes converted.  */
+    size_t buflen;
+
+    /* How many times this character(s) is repeated.  */
+    int repeat_count;
+  };
+
+  /* Return the repeat count of the next character/byte in ITER,
+     storing the result in VEC.  */
+  int count_next_character (wchar_iterator *iter,
+			    std::vector<converted_character> *vec);
+
+  /* Print the characters in CHARS.  OPTIONS is the user's print
+     options.  *FINISHED is set to 0 if we didn't print all the
+     elements in CHARS.  */
+  void print_converted_chars_to_obstack
+       (const std::vector<converted_character> &chars,
+	const struct value_print_options *options,
+	int *finished);
+
+private:
+
+  /* Intermediate output is stored here.  */
+  auto_obstack m_wchar_buf;
+  /* The encoding.  */
+  const char *m_encoding;
+
+protected:
+
+  /* Byte order for the character type.  */
+  bfd_endian m_byte_order;
+  /* The intermediate output file.  "print" implementations should
+     write to this file.  */
+  obstack_wide_file m_file;
+  /* The current quote character.  */
+  int m_quoter;
+  /* The width of a single character.  Note that for multi-byte
+     encodings, this is the width of a 'base' character; e.g., for
+     UTF-8 it would be 1.  */
+  int m_width;
+  /* The type of character being processed.  */
+  type *m_type;
+
+  /* If a character was printed as an escape sequence, and this might
+     force the next character to be an escape sequence as well, then
+     this can be set.  This happens in C syntax when a hex escape is
+     followed by a hex digit.  */
+  bool m_need_escape = false;
+};
+
+#endif /* GDB_CHAR_PRINT_H */
diff --git a/gdb/valprint.c b/gdb/valprint.c
index b808a3684dc79bee277a2d4a139903cfd7ee16a5..83a9a3a21e6132fea009eada76612b38ed9aa7c3 100644
--- a/gdb/valprint.c
+++ b/gdb/valprint.c
@@ -45,39 +45,7 @@ 
 #include "inferior.h"
 #include "gdbsupport/selftest.h"
 #include "selftest-arch.h"
-
-/* Maximum number of wchars returned from wchar_iterate.  */
-#define MAX_WCHARS 4
-
-/* A convenience macro to compute the size of a wchar_t buffer containing X
-   characters.  */
-#define WCHAR_BUFLEN(X) ((X) * sizeof (gdb_wchar_t))
-
-/* Character buffer size saved while iterating over wchars.  */
-#define WCHAR_BUFLEN_MAX WCHAR_BUFLEN (MAX_WCHARS)
-
-/* A structure to encapsulate state information from iterated
-   character conversions.  */
-struct converted_character
-{
-  /* The number of characters converted.  */
-  int num_chars;
-
-  /* The result of the conversion.  See charset.h for more.  */
-  enum wchar_iterate_result result;
-
-  /* The (saved) converted character(s).  */
-  gdb_wchar_t chars[WCHAR_BUFLEN_MAX];
-
-  /* The first converted target byte.  */
-  const gdb_byte *buf;
-
-  /* The number of bytes converted.  */
-  size_t buflen;
-
-  /* How many times this character(s) is repeated.  */
-  int repeat_count;
-};
+#include "char-print.h"
 
 /* Command lists for set/show print raw.  */
 struct cmd_list_element *setprintrawlist;
@@ -2141,151 +2109,141 @@  value_print_array_elements (struct value *val, struct ui_file *stream,
 /* Return true if print_wchar can display W without resorting to a
    numeric escape, false otherwise.  */
 
-static int
-wchar_printable (gdb_wchar_t w)
-{
-  return (gdb_iswprint (w)
-	  || w == LCST ('\a') || w == LCST ('\b')
-	  || w == LCST ('\f') || w == LCST ('\n')
-	  || w == LCST ('\r') || w == LCST ('\t')
-	  || w == LCST ('\v'));
-}
-
-/* A helper function that converts the contents of STRING to wide
-   characters and then appends them to OUTPUT.  */
-
-static void
-append_string_as_wide (const char *string,
-		       struct obstack *output)
-{
-  for (; *string; ++string)
-    {
-      gdb_wchar_t w = gdb_btowc (*string);
-      obstack_grow (output, &w, sizeof (gdb_wchar_t));
-    }
+bool
+wchar_printer::printable (gdb_wchar_t w) const
+{
+  if (w == LCST ('\a') || w == LCST ('\b')
+      || w == LCST ('\f') || w == LCST ('\n')
+      || w == LCST ('\r') || w == LCST ('\t')
+      || w == LCST ('\v'))
+    return true;
+  if (!gdb_iswprint (w))
+    return false;
+  /* If we previously emitted a hex escape, then we may need to emit
+     an escape again, if W is a hex digit.  */
+  if (!m_need_escape)
+    return true;
+  return !gdb_iswxdigit (w);
 }
 
-/* Print a wide character W to OUTPUT.  ORIG is a pointer to the
-   original (target) bytes representing the character, ORIG_LEN is the
-   number of valid bytes.  WIDTH is the number of bytes in a base
-   characters of the type.  OUTPUT is an obstack to which wide
-   characters are emitted.  QUOTER is a (narrow) character indicating
-   the style of quotes surrounding the character to be printed.
-   NEED_ESCAPE is an in/out flag which is used to track numeric
-   escapes across calls.  */
+/* See char-print.h.  */
 
-static void
-print_wchar (gdb_wint_t w, const gdb_byte *orig,
-	     int orig_len, int width,
-	     enum bfd_endian byte_order,
-	     struct obstack *output,
-	     int quoter, bool *need_escapep)
+void
+wchar_printer::print_char (gdb_wchar_t w)
 {
-  bool need_escape = *need_escapep;
+  m_need_escape = false;
 
-  *need_escapep = false;
-
-  /* If any additional cases are added to this switch block, then the
-     function wchar_printable will likely need updating too.  */
   switch (w)
     {
       case LCST ('\a'):
-	obstack_grow_wstr (output, LCST ("\\a"));
+	m_file.write (LCST ("\\a"));
 	break;
       case LCST ('\b'):
-	obstack_grow_wstr (output, LCST ("\\b"));
+	m_file.write (LCST ("\\b"));
 	break;
       case LCST ('\f'):
-	obstack_grow_wstr (output, LCST ("\\f"));
+	m_file.write (LCST ("\\f"));
 	break;
       case LCST ('\n'):
-	obstack_grow_wstr (output, LCST ("\\n"));
+	m_file.write (LCST ("\\n"));
 	break;
       case LCST ('\r'):
-	obstack_grow_wstr (output, LCST ("\\r"));
+	m_file.write (LCST ("\\r"));
 	break;
       case LCST ('\t'):
-	obstack_grow_wstr (output, LCST ("\\t"));
+	m_file.write (LCST ("\\t"));
 	break;
       case LCST ('\v'):
-	obstack_grow_wstr (output, LCST ("\\v"));
+	m_file.write (LCST ("\\v"));
 	break;
       default:
-	{
-	  if (gdb_iswprint (w) && !(need_escape && gdb_iswxdigit (w)))
-	    {
-	      gdb_wchar_t wchar = w;
+	if (w == gdb_btowc (m_quoter) || w == LCST ('\\'))
+	  m_file.write (LCST ("\\"));
+	m_file.write (w);
+	break;
+    }
+}
 
-	      if (w == gdb_btowc (quoter) || w == LCST ('\\'))
-		obstack_grow_wstr (output, LCST ("\\"));
-	      obstack_grow (output, &wchar, sizeof (gdb_wchar_t));
-	    }
-	  else
-	    {
-	      int i;
+/* See char-print.h.  */
 
-	      for (i = 0; i + width <= orig_len; i += width)
-		{
-		  char octal[30];
-		  ULONGEST value;
-
-		  value = extract_unsigned_integer (&orig[i], width,
-						  byte_order);
-		  /* If the value fits in 3 octal digits, print it that
-		     way.  Otherwise, print it as a hex escape.  */
-		  if (value <= 0777)
-		    {
-		      xsnprintf (octal, sizeof (octal), "\\%.3o",
-				 (int) (value & 0777));
-		      *need_escapep = false;
-		    }
-		  else
-		    {
-		      xsnprintf (octal, sizeof (octal), "\\x%lx", (long) value);
-		      /* A hex escape might require the next character
-			 to be escaped, because, unlike with octal,
-			 hex escapes have no length limit.  */
-		      *need_escapep = true;
-		    }
-		  append_string_as_wide (octal, output);
-		}
-	      /* If we somehow have extra bytes, print them now.  */
-	      while (i < orig_len)
-		{
-		  char octal[5];
+void
+wchar_printer::print_escape (const gdb_byte *orig, int orig_len)
+{
+  m_need_escape = false;
 
-		  xsnprintf (octal, sizeof (octal), "\\%.3o", orig[i] & 0xff);
-		  *need_escapep = false;
-		  append_string_as_wide (octal, output);
-		  ++i;
-		}
-	    }
-	  break;
+  int i;
+  for (i = 0; i + m_width <= orig_len; i += m_width)
+    {
+      ULONGEST value;
+
+      value = extract_unsigned_integer (&orig[i], m_width,
+					m_byte_order);
+      /* If the value fits in 3 octal digits, print it that
+	 way.  Otherwise, print it as a hex escape.  */
+      if (value <= 0777)
+	{
+	  gdb_printf (&m_file, "\\%.3o", (int) (value & 0777));
+	  m_need_escape = false;
+	}
+      else
+	{
+	  gdb_printf (&m_file, "\\x%lx", (long) value);
+	  /* A hex escape might require the next character
+	     to be escaped, because, unlike with octal,
+	     hex escapes have no length limit.  */
+	  m_need_escape = true;
 	}
     }
+
+  /* If we somehow have extra bytes, print them now.  */
+  while (i < orig_len)
+    {
+      gdb_printf (&m_file, "\\%.3o", orig[i] & 0xff);
+      m_need_escape = false;
+      ++i;
+    }
 }
 
-/* Print the character C on STREAM as part of the contents of a
-   literal string whose delimiter is QUOTER.  ENCODING names the
-   encoding of C.  */
+const char *
+wchar_printer::get_default_encoding (type *chtype)
+{
+  const char *encoding;
+  if (chtype->length () == 1)
+    encoding = target_charset (chtype->arch ());
+  else if (streq (chtype->name (), "wchar_t"))
+    encoding = target_wide_charset (chtype->arch ());
+  else if (chtype->length () == 2)
+    {
+      if (type_byte_order (chtype) == BFD_ENDIAN_BIG)
+	encoding = "UTF-16BE";
+      else
+	encoding = "UTF-16LE";
+    }
+  else if (chtype->length () == 4)
+    {
+      if (type_byte_order (chtype) == BFD_ENDIAN_BIG)
+	encoding = "UTF-32BE";
+      else
+	encoding = "UTF-32LE";
+    }
+  else
+    {
+      /* No idea.  */
+      encoding = target_charset (chtype->arch ());
+    }
+  return encoding;
+}
 
 void
-generic_emit_char (int c, struct type *type, struct ui_file *stream,
-		   int quoter, const char *encoding)
+wchar_printer::print (int c, ui_file *stream)
 {
-  enum bfd_endian byte_order
-    = type_byte_order (type);
   gdb_byte *c_buf;
-  bool need_escape = false;
 
-  c_buf = (gdb_byte *) alloca (type->length ());
-  pack_long (c_buf, type, c);
+  c_buf = (gdb_byte *) alloca (m_width);
+  pack_long (c_buf, m_type, c);
 
-  gdb_putc (quoter, stream);
-  wchar_iterator iter (c_buf, type->length (), encoding, type->length ());
-
-  /* This holds the printable form of the wchar_t data.  */
-  auto_obstack wchar_buf;
+  gdb_putc (m_quoter, stream);
+  wchar_iterator iter (c_buf, m_width, m_encoding, m_width);
 
   while (1)
     {
@@ -2293,7 +2251,7 @@  generic_emit_char (int c, struct type *type, struct ui_file *stream,
       gdb_wchar_t *chars;
       const gdb_byte *buf;
       size_t buflen;
-      int print_escape = 1;
+      bool need_escape = true;
       enum wchar_iterate_result result;
 
       num_chars = iter.iterate (&result, &chars, &buf, &buflen);
@@ -2308,48 +2266,56 @@  generic_emit_char (int c, struct type *type, struct ui_file *stream,
 	     boundaries there.  */
 	  int i;
 
-	  print_escape = 0;
+	  need_escape = false;
 	  for (i = 0; i < num_chars; ++i)
-	    if (!wchar_printable (chars[i]))
+	    if (!printable (chars[i]))
 	      {
-		print_escape = 1;
+		need_escape = true;
 		break;
 	      }
 
-	  if (!print_escape)
+	  if (!need_escape)
 	    {
 	      for (i = 0; i < num_chars; ++i)
-		print_wchar (chars[i], buf, buflen,
-			     type->length (), byte_order,
-			     &wchar_buf, quoter, &need_escape);
+		print_char (chars[i]);
 	    }
 	}
 
       /* This handles the NUM_CHARS == 0 case as well.  */
-      if (print_escape)
-	print_wchar (gdb_WEOF, buf, buflen, type->length (),
-		     byte_order, &wchar_buf, quoter, &need_escape);
+      if (need_escape)
+	print_escape (buf, buflen);
     }
 
   /* The output in the host encoding.  */
   auto_obstack output;
 
   convert_between_encodings (INTERMEDIATE_ENCODING, host_charset (),
-			     (gdb_byte *) obstack_base (&wchar_buf),
-			     obstack_object_size (&wchar_buf),
+			     (gdb_byte *) obstack_base (&m_wchar_buf),
+			     obstack_object_size (&m_wchar_buf),
 			     sizeof (gdb_wchar_t), &output, translit_char);
   obstack_1grow (&output, '\0');
 
   gdb_puts ((const char *) obstack_base (&output), stream);
-  gdb_putc (quoter, stream);
+  gdb_putc (m_quoter, stream);
 }
 
-/* Return the repeat count of the next character/byte in ITER,
-   storing the result in VEC.  */
+/* Print the character C on STREAM as part of the contents of a
+   literal string whose delimiter is QUOTER.  ENCODING names the
+   encoding of C.  */
 
-static int
-count_next_character (wchar_iterator *iter,
-		      std::vector<converted_character> *vec)
+void
+generic_emit_char (int c, struct type *type, struct ui_file *stream,
+		   int quoter, const char *encoding)
+{
+  wchar_printer printer (type, quoter, encoding);
+  printer.print (c, stream);
+}
+
+/* See char-print.h.  */
+
+int
+wchar_printer::count_next_character (wchar_iterator *iter,
+				     std::vector<converted_character> *vec)
 {
   struct converted_character *current;
 
@@ -2395,7 +2361,7 @@  count_next_character (wchar_iterator *iter,
 	  if (d.num_chars > 0)
 	    {
 	      gdb_assert (d.num_chars < MAX_WCHARS);
-	      memcpy (d.chars, chars, WCHAR_BUFLEN (d.num_chars));
+	      memcpy (d.chars, chars, d.num_chars * sizeof (gdb_wchar_t));
 	    }
 
 	  /* Determine if the current character is the same as this
@@ -2408,7 +2374,7 @@  count_next_character (wchar_iterator *iter,
 		 2) Equality of non-converted character (num_chars == 0)  */
 	      if ((current->num_chars > 0
 		   && memcmp (current->chars, d.chars,
-			      WCHAR_BUFLEN (current->num_chars)) == 0)
+			      current->num_chars * sizeof (gdb_wchar_t)) == 0)
 		  || (current->num_chars == 0
 		      && current->buflen == d.buflen
 		      && memcmp (current->buf, d.buf, current->buflen) == 0))
@@ -2427,25 +2393,18 @@  count_next_character (wchar_iterator *iter,
     }
 }
 
-/* Print the characters in CHARS to the OBSTACK.  QUOTE_CHAR is the quote
-   character to use with string output.  WIDTH is the size of the output
-   character type.  BYTE_ORDER is the target byte order.  OPTIONS
-   is the user's print options.  *FINISHED is set to 0 if we didn't print
-   all the elements in CHARS.  */
+/* See char-print.h.  */
 
-static void
-print_converted_chars_to_obstack (struct obstack *obstack,
-				  const std::vector<converted_character> &chars,
-				  int quote_char, int width,
-				  enum bfd_endian byte_order,
-				  const struct value_print_options *options,
-				  int *finished)
+void
+wchar_printer::print_converted_chars_to_obstack
+     (const std::vector<converted_character> &chars,
+      const struct value_print_options *options,
+      int *finished)
 {
   unsigned int idx, num_elements;
   const converted_character *elem;
   enum {START, SINGLE, REPEAT, INCOMPLETE, FINISH} state, last;
-  gdb_wchar_t wide_quote_char = gdb_btowc (quote_char);
-  bool need_escape = false;
+  gdb_wchar_t wide_quote_char = gdb_btowc (m_quoter);
   const int print_max = options->print_max_chars > 0
       ? options->print_max_chars : options->print_max;
 
@@ -2474,8 +2433,8 @@  print_converted_chars_to_obstack (struct obstack *obstack,
 		/* We were outputting some other type of content, so we
 		   must output and a comma and a quote.  */
 		if (last != START)
-		  obstack_grow_wstr (obstack, LCST (", "));
-		obstack_grow (obstack, &wide_quote_char, sizeof (gdb_wchar_t));
+		  m_file.write (LCST (", "));
+		m_file.write (wide_quote_char);
 	      }
 	    /* Output the character.  */
 	    int repeat_count = elem->repeat_count;
@@ -2486,12 +2445,11 @@  print_converted_chars_to_obstack (struct obstack *obstack,
 	      }
 	    for (j = 0; j < repeat_count; ++j)
 	      {
-		if (elem->result == wchar_iterate_ok)
-		  print_wchar (elem->chars[0], elem->buf, elem->buflen, width,
-			       byte_order, obstack, quote_char, &need_escape);
+		if (elem->result == wchar_iterate_ok
+		    && printable (elem->chars[0]))
+		  print_char (elem->chars[0]);
 		else
-		  print_wchar (gdb_WEOF, elem->buf, elem->buflen, width,
-			       byte_order, obstack, quote_char, &need_escape);
+		  print_escape (elem->buf, elem->buflen);
 		num_elements += 1;
 	      }
 	  }
@@ -2508,27 +2466,26 @@  print_converted_chars_to_obstack (struct obstack *obstack,
 	      {
 		/* We were outputting a single string.  Terminate the
 		   string.  */
-		obstack_grow (obstack, &wide_quote_char, sizeof (gdb_wchar_t));
+		m_file.write (wide_quote_char);
 	      }
 	    if (last != START)
-	      obstack_grow_wstr (obstack, LCST (", "));
+	      m_file.write (LCST (", "));
 
 	    /* Output the character and repeat string.  */
-	    obstack_grow_wstr (obstack, LCST ("'"));
-	    if (elem->result == wchar_iterate_ok)
-	      print_wchar (elem->chars[0], elem->buf, elem->buflen, width,
-			   byte_order, obstack, quote_char, &need_escape);
+	    m_file.write (LCST ("'"));
+	    if (elem->result == wchar_iterate_ok
+		&& printable (elem->chars[0]))
+	      print_char (elem->chars[0]);
 	    else
-	      print_wchar (gdb_WEOF, elem->buf, elem->buflen, width,
-			   byte_order, obstack, quote_char, &need_escape);
-	    obstack_grow_wstr (obstack, LCST ("'"));
+	      print_escape (elem->buf, elem->buflen);
+	    m_file.write (LCST ("'"));
 	    std::string s = string_printf (_(" <repeats %u times>"),
 					   elem->repeat_count);
 	    num_elements += elem->repeat_count;
 	    for (j = 0; s[j]; ++j)
 	      {
 		gdb_wchar_t w = gdb_btowc (s[j]);
-		obstack_grow (obstack, &w, sizeof (gdb_wchar_t));
+		m_file.write (w);
 	      }
 	  }
 	  break;
@@ -2539,16 +2496,15 @@  print_converted_chars_to_obstack (struct obstack *obstack,
 	    {
 	      /* If we were outputting a string of SINGLE characters,
 		 terminate the quote.  */
-	      obstack_grow (obstack, &wide_quote_char, sizeof (gdb_wchar_t));
+	      m_file.write (wide_quote_char);
 	    }
 	  if (last != START)
-	    obstack_grow_wstr (obstack, LCST (", "));
+	    m_file.write (LCST (", "));
 
 	  /* Output the incomplete sequence string.  */
-	  obstack_grow_wstr (obstack, LCST ("<incomplete sequence "));
-	  print_wchar (gdb_WEOF, elem->buf, elem->buflen, width, byte_order,
-		       obstack, 0, &need_escape);
-	  obstack_grow_wstr (obstack, LCST (">"));
+	  m_file.write (LCST ("<incomplete sequence "));
+	  print_escape (elem->buf, elem->buflen);
+	  m_file.write (LCST (">"));
 	  num_elements += 1;
 
 	  /* We do not attempt to output anything after this.  */
@@ -2560,7 +2516,7 @@  print_converted_chars_to_obstack (struct obstack *obstack,
 	     characters, the string must be terminated.  Otherwise,
 	     REPEAT and INCOMPLETE are always left properly terminated.  */
 	  if (last == SINGLE)
-	    obstack_grow (obstack, &wide_quote_char, sizeof (gdb_wchar_t));
+	    m_file.write (wide_quote_char);
 
 	  return;
 	}
@@ -2592,26 +2548,15 @@  print_converted_chars_to_obstack (struct obstack *obstack,
     }
 }
 
-/* Print the character string STRING, printing at most LENGTH
-   characters.  LENGTH is -1 if the string is nul terminated.  TYPE is
-   the type of each character.  OPTIONS holds the printing options;
-   printing stops early if the number hits print_max_chars; repeat
-   counts are printed as appropriate.  Print ellipses at the end if we
-   had to stop before printing LENGTH characters, or if FORCE_ELLIPSES.
-   QUOTE_CHAR is the character to print at each end of the string.  If
-   C_STYLE_TERMINATOR is true, and the last character is 0, then it is
-   omitted.  */
+/* See char-print.h.  */
 
 void
-generic_printstr (struct ui_file *stream, struct type *type,
-		  const gdb_byte *string, unsigned int length,
-		  const char *encoding, int force_ellipses,
-		  int quote_char, int c_style_terminator,
-		  const struct value_print_options *options)
+wchar_printer::print (struct ui_file *stream, const gdb_byte *string,
+		      unsigned int length, int force_ellipses,
+		      int c_style_terminator,
+		      const struct value_print_options *options)
 {
-  enum bfd_endian byte_order = type_byte_order (type);
   unsigned int i;
-  int width = type->length ();
   int finished = 0;
   struct converted_character *last;
 
@@ -2622,8 +2567,8 @@  generic_printstr (struct ui_file *stream, struct type *type,
       for (i = 0; current_char; ++i)
 	{
 	  QUIT;
-	  current_char = extract_unsigned_integer (string + i * width,
-						   width, byte_order);
+	  current_char = extract_unsigned_integer (string + i * m_width,
+						   m_width, m_byte_order);
 	}
       length = i;
     }
@@ -2634,18 +2579,18 @@  generic_printstr (struct ui_file *stream, struct type *type,
   if (c_style_terminator
       && !force_ellipses
       && length > 0
-      && (extract_unsigned_integer (string + (length - 1) * width,
-				    width, byte_order) == 0))
+      && (extract_unsigned_integer (string + (length - 1) * m_width,
+				    m_width, m_byte_order) == 0))
     length--;
 
   if (length == 0)
     {
-      gdb_printf (stream, "%c%c", quote_char, quote_char);
+      gdb_printf (stream, "%c%c", m_quoter, m_quoter);
       return;
     }
 
   /* Arrange to iterate over the characters, in wchar_t form.  */
-  wchar_iterator iter (string, length * width, encoding, width);
+  wchar_iterator iter (string, length * m_width, m_encoding, m_width);
   std::vector<converted_character> converted_chars;
 
   /* Convert characters until the string is over or the maximum
@@ -2678,29 +2623,46 @@  generic_printstr (struct ui_file *stream, struct type *type,
   /* Ensure that CONVERTED_CHARS is terminated.  */
   last->result = wchar_iterate_eof;
 
-  /* WCHAR_BUF is the obstack we use to represent the string in
-     wchar_t form.  */
-  auto_obstack wchar_buf;
-
   /* Print the output string to the obstack.  */
-  print_converted_chars_to_obstack (&wchar_buf, converted_chars, quote_char,
-				    width, byte_order, options, &finished);
+  print_converted_chars_to_obstack (converted_chars, options, &finished);
 
   if (force_ellipses || !finished)
-    obstack_grow_wstr (&wchar_buf, LCST ("..."));
+    m_file.write (LCST ("..."));
 
   /* OUTPUT is where we collect `char's for printing.  */
   auto_obstack output;
 
   convert_between_encodings (INTERMEDIATE_ENCODING, host_charset (),
-			     (gdb_byte *) obstack_base (&wchar_buf),
-			     obstack_object_size (&wchar_buf),
+			     (gdb_byte *) obstack_base (&m_wchar_buf),
+			     obstack_object_size (&m_wchar_buf),
 			     sizeof (gdb_wchar_t), &output, translit_char);
   obstack_1grow (&output, '\0');
 
   gdb_puts ((const char *) obstack_base (&output), stream);
 }
 
+/* Print the character string STRING, printing at most LENGTH
+   characters.  LENGTH is -1 if the string is nul terminated.  TYPE is
+   the type of each character.  OPTIONS holds the printing options;
+   printing stops early if the number hits print_max_chars; repeat
+   counts are printed as appropriate.  Print ellipses at the end if we
+   had to stop before printing LENGTH characters, or if FORCE_ELLIPSES.
+   QUOTE_CHAR is the character to print at each end of the string.  If
+   C_STYLE_TERMINATOR is true, and the last character is 0, then it is
+   omitted.  */
+
+void
+generic_printstr (struct ui_file *stream, struct type *type,
+		  const gdb_byte *string, unsigned int length,
+		  const char *encoding, int force_ellipses,
+		  int quote_char, int c_style_terminator,
+		  const struct value_print_options *options)
+{
+  wchar_printer printer (type, quote_char, encoding);
+  printer.print (stream, string, length, force_ellipses,
+		 c_style_terminator, options);
+}
+
 /* Print a string from the inferior, starting at ADDR and printing up to LEN
    characters, of WIDTH bytes a piece, to STREAM.  If LEN is -1, printing
    stops at the first null byte, otherwise printing proceeds (including null
diff --git a/gdbsupport/gdb_obstack.h b/gdbsupport/gdb_obstack.h
index 755b90767aa09563df44a187a67edb90fed1d125..7893439cfe560617c0981c612ac38080c2f3c8f5 100644
--- a/gdbsupport/gdb_obstack.h
+++ b/gdbsupport/gdb_obstack.h
@@ -76,9 +76,6 @@  obstack_new (struct obstack *ob, Args&&... args)
 #define obstack_grow_str0(OBSTACK,STRING) \
   obstack_grow0 (OBSTACK, STRING, strlen (STRING))
 
-#define obstack_grow_wstr(OBSTACK, WSTRING) \
-  obstack_grow (OBSTACK, WSTRING, sizeof (gdb_wchar_t) * gdb_wcslen (WSTRING))
-
 /* Concatenate NULL terminated variable argument list of `const char
    *' strings; return the new string.  Space is found in the OBSTACKP.
    Argument list must be terminated by a sentinel expression `(char *)