[2/2] libstdc++: Add pretty printer for std::stringstream

Message ID 20220904184735.177348-2-fent@in.tum.de
State New, archived
Headers
Series [1/2] libstdc++: Fix pretty printer tests of tuple indexes |

Commit Message

Philipp Fent Sept. 4, 2022, 6:47 p.m. UTC
  Signed-off-by: Philipp Fent <fent@in.tum.de>
---
 libstdc++-v3/python/libstdcxx/v6/printers.py  | 37 +++++++++++++++++++
 .../libstdc++-prettyprinters/debug.cc         |  5 +++
 .../libstdc++-prettyprinters/simple.cc        |  5 +++
 .../libstdc++-prettyprinters/simple11.cc      |  5 +++
 4 files changed, 52 insertions(+)
  

Comments

Jonathan Wakely Sept. 6, 2022, 11:27 a.m. UTC | #1
Thanks for the patch. Comments below.


On Sun, 4 Sept 2022 at 19:48, Philipp Fent via Libstdc++
<libstdc++@gcc.gnu.org> wrote:
>
> Signed-off-by: Philipp Fent <fent@in.tum.de>
> ---
>  libstdc++-v3/python/libstdcxx/v6/printers.py  | 37 +++++++++++++++++++
>  .../libstdc++-prettyprinters/debug.cc         |  5 +++
>  .../libstdc++-prettyprinters/simple.cc        |  5 +++
>  .../libstdc++-prettyprinters/simple11.cc      |  5 +++
>  4 files changed, 52 insertions(+)
>
> diff --git a/libstdc++-v3/python/libstdcxx/v6/printers.py b/libstdc++-v3/python/libstdcxx/v6/printers.py
> index d70c8d5d616..5083f693387 100644
> --- a/libstdc++-v3/python/libstdcxx/v6/printers.py
> +++ b/libstdc++-v3/python/libstdcxx/v6/printers.py
> @@ -969,6 +969,39 @@ class StdStringPrinter:
>      def display_hint (self):
>          return 'string'
>
> +class StdStringBufPrinter:
> +    "Print a std::basic_stringbuf"
> +
> +    def __init__(self, _, val):
> +        self.val = val
> +
> +    def to_string(self):
> +        pbase = self.val['_M_out_beg']
> +        pptr = self.val['_M_out_cur']
> +        egptr = self.val['_M_in_end']

It would be nice to encapsulate this into a generic helper for all
streambufs, as this part isn't specific to basic_stringbuf. We could
then reuse it for printing spanbufs and other types in future. It
could be a function that takes a streambuf and returns a tuple of
(pbase, pptr, egptr).

> +        # Logic from basic_stringbuf::_M_high_mark()
> +        if pptr:
> +            if not egptr or pptr > egptr:
> +                return pbase.string(length = pptr - pbase)
> +            else:
> +                return pbase.string(length = pptr - egptr)

Shouldn't this be egptr - pbase?

This suggests the tests are inadequate. I think this bug would have
been found by a test something like:

std::stringstream ssin("input", std::ios::in);
// { dg-final { note-test ssin "\"input\"" } }

The (egptr - pbase) case is also needed for printing an istringstream.

> +        return self.val['_M_string']
> +
> +    def display_hint(self):
> +        return 'string'
> +
> +class StdStringStreamPrinter:
> +    "Print a std::basic_stringstream"
> +
> +    def __init__(self, _, val):
> +        self.val = val
> +
> +    def to_string(self):
> +        return self.val['_M_stringbuf']

This assumes that the stream is still using its stringbuf, which is
probably true 99% of the time, but isn't guaranteed. In the following
situation, the printer would give potentially misleading output:

std::stringstream ss("xxx");
ss.rdbuf(std::cin.rdbuf());

I wonder if we want to have a check that self.val['_M_streambuf'] ==
self.val['_M_stringbuf'].address

Alternatively, don't provide a printer for the stringstream at all,
just for the stringbuf, and then when GDB uses its default display for
the stringstream it will show that member.


> +
> +    def display_hint(self):
> +        return 'string'
> +
>  class Tr1HashtableIterator(Iterator):
>      def __init__ (self, hashtable):
>          self.buckets = hashtable['_M_buckets']
> @@ -2232,6 +2265,10 @@ def build_libstdcxx_dictionary ():
>      libstdcxx_printer.add_version('std::', 'initializer_list',
>                                    StdInitializerListPrinter)
>      libstdcxx_printer.add_version('std::', 'atomic', StdAtomicPrinter)
> +    libstdcxx_printer.add_version('std::', 'basic_stringbuf', StdStringBufPrinter)
> +    libstdcxx_printer.add_version('std::__cxx11::', 'basic_stringbuf', StdStringBufPrinter)
> +    libstdcxx_printer.add_version('std::', 'basic_stringstream', StdStringStreamPrinter)
> +    libstdcxx_printer.add_version('std::__cxx11::', 'basic_stringstream', StdStringStreamPrinter)

Wouldn't we want it for ostringstream and istringstream too?
  
Philipp Fent Sept. 12, 2022, 8:37 a.m. UTC | #2
Hi Jonathan,

I've sent an updated patch addressing your comments here:
https://gcc.gnu.org/pipermail/libstdc++/2022-September/054587.html

Details below.

On 06.09.22 13:27, Jonathan Wakely wrote:
>> +        pbase = self.val['_M_out_beg']
>> +        pptr = self.val['_M_out_cur']
>> +        egptr = self.val['_M_in_end']
> 
> It would be nice to encapsulate this into a generic helper for all
> streambufs, as this part isn't specific to basic_stringbuf. We could
> then reuse it for printing spanbufs and other types in future. It
> could be a function that takes a streambuf and returns a tuple of
> (pbase, pptr, egptr).

I factored this into a reusable access_streambuf_ptrs().

>> +        # Logic from basic_stringbuf::_M_high_mark()
>> +        if pptr:
>> +            if not egptr or pptr > egptr:
>> +                return pbase.string(length = pptr - pbase)
>> +            else:
>> +                return pbase.string(length = pptr - egptr)
> 
> Shouldn't this be egptr - pbase?
> 
> This suggests the tests are inadequate. I think this bug would have
> been found by a test something like:
> 
> std::stringstream ssin("input", std::ios::in);
> // { dg-final { note-test ssin "\"input\"" } }
> 
> The (egptr - pbase) case is also needed for printing an istringstream.

Good catch! I fixed that bug and added a test for this in the testsuite.

> 
>> +        return self.val['_M_string']
>> +
>> +    def display_hint(self):
>> +        return 'string'
>> +
>> +class StdStringStreamPrinter:
>> +    "Print a std::basic_stringstream"
>> +
>> +    def __init__(self, _, val):
>> +        self.val = val
>> +
>> +    def to_string(self):
>> +        return self.val['_M_stringbuf']
> 
> This assumes that the stream is still using its stringbuf, which is
> probably true 99% of the time, but isn't guaranteed. In the following
> situation, the printer would give potentially misleading output:
> 
> std::stringstream ss("xxx");
> ss.rdbuf(std::cin.rdbuf());
> 
> I wonder if we want to have a check that self.val['_M_streambuf'] ==
> self.val['_M_stringbuf'].address
> 
> Alternatively, don't provide a printer for the stringstream at all,
> just for the stringbuf, and then when GDB uses its default display for
> the stringstream it will show that member.

I didn't know one could redirect a stringstream, since its rdbuf() 
method hides the basic_ios rdbuf() methods. But of course, that's still 
possible via the base class...
The Patch v2 checks that case in the StdStringStreamPrinter constructor, 
and follows that redirect in to_string().

> 
>> +
>> +    def display_hint(self):
>> +        return 'string'
>> +
>>   class Tr1HashtableIterator(Iterator):
>>       def __init__ (self, hashtable):
>>           self.buckets = hashtable['_M_buckets']
>> @@ -2232,6 +2265,10 @@ def build_libstdcxx_dictionary ():
>>       libstdcxx_printer.add_version('std::', 'initializer_list',
>>                                     StdInitializerListPrinter)
>>       libstdcxx_printer.add_version('std::', 'atomic', StdAtomicPrinter)
>> +    libstdcxx_printer.add_version('std::', 'basic_stringbuf', StdStringBufPrinter)
>> +    libstdcxx_printer.add_version('std::__cxx11::', 'basic_stringbuf', StdStringBufPrinter)
>> +    libstdcxx_printer.add_version('std::', 'basic_stringstream', StdStringStreamPrinter)
>> +    libstdcxx_printer.add_version('std::__cxx11::', 'basic_stringstream', StdStringStreamPrinter)
> 
> Wouldn't we want it for ostringstream and istringstream too?

Indeed. The updated patch registers the pretty printer for all flavors.
  

Patch

diff --git a/libstdc++-v3/python/libstdcxx/v6/printers.py b/libstdc++-v3/python/libstdcxx/v6/printers.py
index d70c8d5d616..5083f693387 100644
--- a/libstdc++-v3/python/libstdcxx/v6/printers.py
+++ b/libstdc++-v3/python/libstdcxx/v6/printers.py
@@ -969,6 +969,39 @@  class StdStringPrinter:
     def display_hint (self):
         return 'string'
 
+class StdStringBufPrinter:
+    "Print a std::basic_stringbuf"
+
+    def __init__(self, _, val):
+        self.val = val
+
+    def to_string(self):
+        pbase = self.val['_M_out_beg']
+        pptr = self.val['_M_out_cur']
+        egptr = self.val['_M_in_end']
+        # Logic from basic_stringbuf::_M_high_mark()
+        if pptr:
+            if not egptr or pptr > egptr:
+                return pbase.string(length = pptr - pbase)
+            else:
+                return pbase.string(length = pptr - egptr)
+        return self.val['_M_string']
+
+    def display_hint(self):
+        return 'string'
+
+class StdStringStreamPrinter:
+    "Print a std::basic_stringstream"
+
+    def __init__(self, _, val):
+        self.val = val
+
+    def to_string(self):
+        return self.val['_M_stringbuf']
+
+    def display_hint(self):
+        return 'string'
+
 class Tr1HashtableIterator(Iterator):
     def __init__ (self, hashtable):
         self.buckets = hashtable['_M_buckets']
@@ -2232,6 +2265,10 @@  def build_libstdcxx_dictionary ():
     libstdcxx_printer.add_version('std::', 'initializer_list',
                                   StdInitializerListPrinter)
     libstdcxx_printer.add_version('std::', 'atomic', StdAtomicPrinter)
+    libstdcxx_printer.add_version('std::', 'basic_stringbuf', StdStringBufPrinter)
+    libstdcxx_printer.add_version('std::__cxx11::', 'basic_stringbuf', StdStringBufPrinter)
+    libstdcxx_printer.add_version('std::', 'basic_stringstream', StdStringStreamPrinter)
+    libstdcxx_printer.add_version('std::__cxx11::', 'basic_stringstream', StdStringStreamPrinter)
 
     # std::regex components
     libstdcxx_printer.add_version('std::__detail::', '_State',
diff --git a/libstdc++-v3/testsuite/libstdc++-prettyprinters/debug.cc b/libstdc++-v3/testsuite/libstdc++-prettyprinters/debug.cc
index 98bbc182551..7efec6d0f8b 100644
--- a/libstdc++-v3/testsuite/libstdc++-prettyprinters/debug.cc
+++ b/libstdc++-v3/testsuite/libstdc++-prettyprinters/debug.cc
@@ -29,6 +29,7 @@ 
 #include <list>
 #include <map>
 #include <set>
+#include <sstream>
 #include <vector>
 #include <ext/slist>
 
@@ -110,6 +111,10 @@  main()
   __gnu_cxx::slist<int>::iterator slliter = sll.begin();
 // { dg-final { note-test slliter {47} } }
 
+  std::stringstream sstream;
+  sstream << "abc";
+// { dg-final { note-test sstream "\"abc\"" } }
+
   std::cout << "\n";
   return 0;			// Mark SPOT
 }
diff --git a/libstdc++-v3/testsuite/libstdc++-prettyprinters/simple.cc b/libstdc++-v3/testsuite/libstdc++-prettyprinters/simple.cc
index 1f85775bff0..584989ce09f 100644
--- a/libstdc++-v3/testsuite/libstdc++-prettyprinters/simple.cc
+++ b/libstdc++-v3/testsuite/libstdc++-prettyprinters/simple.cc
@@ -30,6 +30,7 @@ 
 #include <list>
 #include <map>
 #include <set>
+#include <sstream>
 #include <vector>
 #include <ext/slist>
 
@@ -169,6 +170,10 @@  main()
   __gnu_cxx::slist<int>::iterator slliter0;
 // { dg-final { note-test slliter0 {non-dereferenceable iterator for __gnu_cxx::slist} } }
 
+  std::stringstream sstream;
+  sstream << "abc";
+// { dg-final { note-test sstream "\"abc\"" } }
+
   std::cout << "\n";
   return 0;			// Mark SPOT
 }
diff --git a/libstdc++-v3/testsuite/libstdc++-prettyprinters/simple11.cc b/libstdc++-v3/testsuite/libstdc++-prettyprinters/simple11.cc
index 6f21675cf41..6edd7e929fe 100644
--- a/libstdc++-v3/testsuite/libstdc++-prettyprinters/simple11.cc
+++ b/libstdc++-v3/testsuite/libstdc++-prettyprinters/simple11.cc
@@ -30,6 +30,7 @@ 
 #include <list>
 #include <map>
 #include <set>
+#include <sstream>
 #include <vector>
 #include <ext/slist>
 
@@ -162,6 +163,10 @@  main()
   __gnu_cxx::slist<int>::iterator slliter0;
 // { dg-final { note-test slliter0 {non-dereferenceable iterator for __gnu_cxx::slist} } }
 
+  std::stringstream sstream;
+  sstream << "abc";
+// { dg-final { note-test sstream "\"abc\"" } }
+
   std::cout << "\n";
   return 0;			// Mark SPOT
 }