diff mbox

Add libstdc++ type printers for class templates

Message ID 20140714142159.GF4871@redhat.com
State New
Headers show

Commit Message

Jonathan Wakely July 14, 2014, 2:21 p.m. UTC
This defines a new style of Python type printer that recognizes
templates and can be used to omit default template arguments from the
typename GDB prints, e.g. showing std::vector<T, std::allocator<T>> as
simply std::vector<T>.  Additionally, T will get processed by the type
recognizers so if it's a standard typedef or template it will also get
displayed in its abbreviated form.

e.g. with current trunk:

Breakpoint 1, main () at p.cc:45
45        std::vector<std::deque<std::list<int>>> nested;
(gdb) whatis nested
type = std::vector<std::deque<std::list<int, std::allocator<int> >, std::allocator<std::list<int, std::allocator<int> > > >, std::allocator<std::deque<std::list<int, std::allocator<int> >, std::allocator<std::list<int, std::allocator<int> > > > > >

and with this patch:

(gdb) whatis nested
type = std::vector<std::deque<std::list<int>>>

N.B. I am not printing spaces between the closing angle brackets. If
people prefer I can put them in, or only do it for C++11 types, so
that copying and pasting types from GDB will always work (if you're
copying a C++11 type then you must be planning to use it with a C++11
compiler, which will handle >> without spaces).

This passes the python testsuite but I'll wait for comments before
committing, in case my use of the GDB API or Python can be improved by
anyone.

Comments

Jonathan Wakely July 14, 2014, 7:22 p.m. UTC | #1
On 14/07/14 15:21 +0100, Jonathan Wakely wrote:
>This defines a new style of Python type printer that recognizes
>templates and can be used to omit default template arguments from the
>typename GDB prints, e.g. showing std::vector<T, std::allocator<T>> as
>simply std::vector<T>.  Additionally, T will get processed by the type
>recognizers so if it's a standard typedef or template it will also get
>displayed in its abbreviated form.
>
>e.g. with current trunk:
>
>Breakpoint 1, main () at p.cc:45
>45        std::vector<std::deque<std::list<int>>> nested;
>(gdb) whatis nested
>type = std::vector<std::deque<std::list<int, std::allocator<int> >, std::allocator<std::list<int, std::allocator<int> > > >, std::allocator<std::deque<std::list<int, std::allocator<int> >, std::allocator<std::list<int, std::allocator<int> > > > > >
>
>and with this patch:
>
>(gdb) whatis nested
>type = std::vector<std::deque<std::list<int>>>
>
>N.B. I am not printing spaces between the closing angle brackets. If
>people prefer I can put them in, or only do it for C++11 types, so
>that copying and pasting types from GDB will always work (if you're
>copying a C++11 type then you must be planning to use it with a C++11
>compiler, which will handle >> without spaces).
>
>This passes the python testsuite but I'll wait for comments before
>committing, in case my use of the GDB API or Python can be improved by
>anyone.

I forgot to say that with these type recognizers we might want to
revisit the output of the existing printers for the containers, so
that instead of:

  std::map with 3 elements = { ... }

GDB could print the map type:

  std::map<int, int> with 3 elements = { ... }
Tom Tromey July 14, 2014, 8:08 p.m. UTC | #2
>>>>> "Jonathan" == Jonathan Wakely <jwakely@redhat.com> writes:

Jonathan> This defines a new style of Python type printer that
Jonathan> recognizes templates and can be used to omit default template
Jonathan> arguments from the typename GDB prints, e.g. showing
Jonathan> std::vector<T, std::allocator<T>> as simply std::vector<T>.
Jonathan> Additionally, T will get processed by the type recognizers so
Jonathan> if it's a standard typedef or template it will also get
Jonathan> displayed in its abbreviated form.

Nice.

I'd hoped to eventually implement this automatically using some
information from gcc; see:

    https://gcc.gnu.org/bugzilla/show_bug.cgi?id=49090
    https://sourceware.org/bugzilla/show_bug.cgi?id=15251

However I haven't gotten around to it; and anyway your approach is nice
and not only doesn't conflict with the above, but may continue to be
useful even if the above is implemented.

Jonathan> This passes the python testsuite but I'll wait for comments before
Jonathan> committing, in case my use of the GDB API or Python can be improved by
Jonathan> anyone.

It looked fine to me.

Tom
Tom Tromey July 14, 2014, 8:11 p.m. UTC | #3
>>>>> "Jonathan" == Jonathan Wakely <jwakely@redhat.com> writes:

Jonathan> I forgot to say that with these type recognizers we might want to
Jonathan> revisit the output of the existing printers for the containers, so
Jonathan> that instead of:
Jonathan>  std::map with 3 elements = { ... }
Jonathan> GDB could print the map type:
Jonathan>  std::map<int, int> with 3 elements = { ... }

I think the reason for the brevity right now is just that, even eliding
the defaults, the type names can get rather long.  It's not absurd
though, just maybe not always desirable.  One possible approach would be
to make a new gdb.Parameter in the libstdc++ code to control it.

Tom
Jonathan Wakely July 14, 2014, 11:40 p.m. UTC | #4
On 14/07/14 14:08 -0600, Tom Tromey wrote:
>Jonathan> This passes the python testsuite but I'll wait for comments before
>Jonathan> committing, in case my use of the GDB API or Python can be improved by
>Jonathan> anyone.
>
>It looked fine to me.

Thanks for checking it. One thing I should have mentioned is the
inconsistency between regex subgroups and string replacement fields:

    add_one_template_type_printer(obj, 'unique_ptr<T>',
            'unique_ptr<(.*), std::default_delete<\\1 ?> >',
            'unique_ptr<{1}>')

It might seem odd that the last argument uses {1} not \\1 but the
reason is that what gets substituted there is not actually \\1 but
rather the result of applying type printers to \\1, and I implemented
the substitution via string.format(), rather than reimplementing most
of re.sub() by hand.
Jonathan Wakely July 14, 2014, 11:50 p.m. UTC | #5
On 14/07/14 14:11 -0600, Tom Tromey wrote:
>Jonathan> I forgot to say that with these type recognizers we might want to
>Jonathan> revisit the output of the existing printers for the containers, so
>Jonathan> that instead of:
>Jonathan>  std::map with 3 elements = { ... }
>Jonathan> GDB could print the map type:
>Jonathan>  std::map<int, int> with 3 elements = { ... }
>
>I think the reason for the brevity right now is just that, even eliding
>the defaults, the type names can get rather long.  It's not absurd
>though, just maybe not always desirable.  One possible approach would be
>to make a new gdb.Parameter in the libstdc++ code to control it.

I think a parameter would make sense. I can look into that some time.

One part of the patch I wasn't sure about was this, where 'mgr' is a
function pointer:

  func = gdb.block_for_pc(int(mgr.cast(gdb.lookup_type('intptr_t'))))

Is there a better way to get a pc from the function pointer?
I tried simply int(mgr) but it didn't work.
Jonathan Wakely July 15, 2014, 11:57 a.m. UTC | #6
On 15/07/14 00:50 +0100, Jonathan Wakely wrote:
>One part of the patch I wasn't sure about was this, where 'mgr' is a
>function pointer:
>
> func = gdb.block_for_pc(int(mgr.cast(gdb.lookup_type('intptr_t'))))
>
>Is there a better way to get a pc from the function pointer?
>I tried simply int(mgr) but it didn't work.

I've committed the patch, but would still like to improve the cast
above if there's a better way to do it.
Samuel Bronson Aug. 5, 2014, 6:23 a.m. UTC | #7
Jonathan Wakely <jwakely@redhat.com> writes:

> One part of the patch I wasn't sure about was this, where 'mgr' is a
> function pointer:
>
>  func = gdb.block_for_pc(int(mgr.cast(gdb.lookup_type('intptr_t'))))
>
> Is there a better way to get a pc from the function pointer?
> I tried simply int(mgr) but it didn't work.

Well, long() WFM; e.g. with Python 2 and gdb 7.8.50.20140706-cvs I can do:

(gdb) python print(long(gdb.parse_and_eval("(void(*)())-1")))
4294967295

The fact that int() does not work the same way may indicate a
shortcoming in gdb.Value.__int__(); I'm told that in recent 2.x
versions, int() is perfectly happy returning a long instead of an int.

Of course, this still presumably won't work on architectures where
function pointers don't point straight to the code, e.g. most PowerPC,
where as I understand things it actually points at a "function
descriptor", which in turn references the actual code.

So it seems we're missing an API for this.
diff mbox

Patch

commit 538e6eddc52681d9b3ca8fb5d97f194492ee68da
Author: Jonathan Wakely <jwakely@redhat.com>
Date:   Thu Jul 10 20:31:11 2014 +0100

    	* python/libstdcxx/v6/printers.py (TemplateTypePrinter): Add type
    	printer for class templates.
    	(register_type_printers): Use TemplateTypePrinter for containers
    	and other class templates with default template arguments.
    	* testsuite/libstdc++-prettyprinters/whatis.cc: Test new recognizers.

diff --git a/libstdc++-v3/python/libstdcxx/v6/printers.py b/libstdc++-v3/python/libstdcxx/v6/printers.py
index 1fa08fb..ea34f22 100644
--- a/libstdc++-v3/python/libstdcxx/v6/printers.py
+++ b/libstdc++-v3/python/libstdcxx/v6/printers.py
@@ -922,6 +922,57 @@  class Printer(object):
 
 libstdcxx_printer = None
 
+class TemplateTypePrinter(object):
+    """A type printer for class templates.
+
+    Recognizes type names that match a regular expression.
+    Replaces them with a formatted string which can use replacement field
+    {N} to refer to the \N subgroup of the regex match.
+    Type printers are recusively applied to the subgroups.
+
+    This allows recognizing e.g. "std::vector<(.*), std::allocator<\\1> >"
+    and replacing it with "std::vector<{1}>", omitting the template argument
+    that uses the default type.
+    """
+
+    def __init__(self, name, pattern, subst):
+        self.name = name
+        self.pattern = re.compile(pattern)
+        self.subst = subst
+        self.enabled = True
+
+    class _recognizer(object):
+        def __init__(self, pattern, subst):
+            self.pattern = pattern
+            self.subst = subst
+            self.type_obj = None
+
+        def recognize(self, type_obj):
+            if type_obj.tag is None:
+                return None
+
+            m = self.pattern.match(type_obj.tag)
+            if m:
+                subs = list(m.groups())
+                for i, sub in enumerate(subs):
+                    if ('{%d}' % (i+1)) in self.subst:
+                        # apply recognizers to subgroup
+                        rep = gdb.types.apply_type_recognizers(
+                                gdb.types.get_type_recognizers(),
+                                gdb.lookup_type(sub))
+                        if rep:
+                            subs[i] = rep
+                subs = [None] + subs
+                return self.subst.format(*subs)
+            return None
+
+    def instantiate(self):
+        return self._recognizer(self.pattern, self.subst)
+
+def add_one_template_type_printer(obj, name, match, subst):
+    printer = TemplateTypePrinter(name, '^std::' + match + '$', 'std::' + subst)
+    gdb.types.register_type_printer(obj, printer)
+
 class FilteringTypePrinter(object):
     def __init__(self, match, name):
         self.match = match
@@ -1013,6 +1064,56 @@  def register_type_printers(obj):
     add_one_type_printer(obj, 'discard_block_engine', 'ranlux48')
     add_one_type_printer(obj, 'shuffle_order_engine', 'knuth_b')
 
+    # Do not show defaulted template arguments in class templates
+    add_one_template_type_printer(obj, 'unique_ptr<T>',
+            'unique_ptr<(.*), std::default_delete<\\1 ?> >',
+            'unique_ptr<{1}>')
+
+    add_one_template_type_printer(obj, 'deque<T>',
+            'deque<(.*), std::allocator<\\1 ?> >',
+            'deque<{1}>')
+    add_one_template_type_printer(obj, 'forward_list<T>',
+            'forward_list<(.*), std::allocator<\\1 ?> >',
+            'forward_list<{1}>')
+    add_one_template_type_printer(obj, 'list<T>',
+            'list<(.*), std::allocator<\\1 ?> >',
+            'list<{1}>')
+    add_one_template_type_printer(obj, 'vector<T>',
+            'vector<(.*), std::allocator<\\1 ?> >',
+            'vector<{1}>')
+    add_one_template_type_printer(obj, 'map<Key, T>',
+            'map<(.*), (.*), std::less<\\1 ?>, std::allocator<std::pair<\\1 const, \\2 ?> > >',
+            'map<{1}, {2}>')
+    add_one_template_type_printer(obj, 'multimap<Key, T>',
+            'multimap<(.*), (.*), std::less<\\1 ?>, std::allocator<std::pair<\\1 const, \\2 ?> > >',
+            'multimap<{1}, {2}>')
+    add_one_template_type_printer(obj, 'set<T>',
+            'set<(.*), std::less<\\1 ?>, std::allocator<\\1 ?> >',
+            'set<{1}>')
+    add_one_template_type_printer(obj, 'multiset<T>',
+            'multiset<(.*), std::less<\\1 ?>, std::allocator<\\1 ?> >',
+            'multiset<{1}>')
+    add_one_template_type_printer(obj, 'unordered_map<Key, T>',
+            'unordered_map<(.*), (.*), std::hash<\\1 ?>, std::equal_to<\\1 ?>, std::allocator<std::pair<\\1 const, \\2 ?> > >',
+            'unordered_map<{1}, {2}>')
+    add_one_template_type_printer(obj, 'unordered_multimap<Key, T>',
+            'unordered_multimap<(.*), (.*), std::hash<\\1 ?>, std::equal_to<\\1 ?>, std::allocator<std::pair<\\1 const, \\2 ?> > >',
+            'unordered_multimap<{1}, {2}>')
+    add_one_template_type_printer(obj, 'unordered_set<T>',
+            'unordered_set<(.*), std::hash<\\1 ?>, std::equal_to<\\1 ?>, std::allocator<\\1 ?> >',
+            'unordered_set<{1}>')
+    add_one_template_type_printer(obj, 'unordered_multiset<T>',
+            'unordered_multiset<(.*), std::hash<\\1 ?>, std::equal_to<\\1 ?>, std::allocator<\\1 ?> >',
+            'unordered_multiset<{1}>')
+
+    # strip the "fundamentals_v1" inline namespace from these types
+    add_one_template_type_printer(obj, 'optional<T>',
+            'experimental::fundamentals_v1::optional<(.*)>',
+            'experimental::optional<\\1>')
+    add_one_template_type_printer(obj, 'basic_string_view<C>',
+            'experimental::fundamentals_v1::basic_string_view<(.*), std::char_traits<\\1> >',
+            'experimental::basic_string_view<\\1>')
+
 def register_libstdcxx_printers (obj):
     "Register libstdc++ pretty-printers with objfile Obj."
 
diff --git a/libstdc++-v3/testsuite/libstdc++-prettyprinters/whatis.cc b/libstdc++-v3/testsuite/libstdc++-prettyprinters/whatis.cc
index fbbb772..b398972 100644
--- a/libstdc++-v3/testsuite/libstdc++-prettyprinters/whatis.cc
+++ b/libstdc++-v3/testsuite/libstdc++-prettyprinters/whatis.cc
@@ -21,6 +21,15 @@ 
 #include <string>
 #include <iostream>
 #include <regex>
+#include <memory>
+#include <deque>
+#include <forward_list>
+#include <list>
+#include <vector>
+#include <map>
+#include <set>
+#include <unordered_map>
+#include <unordered_set>
 
 template<class T>
 void
@@ -159,6 +168,31 @@  std::basic_string<signed char> *sstring_ptr;
 holder< std::basic_string<signed char> > sstring_holder;
 // { dg-final { whatis-test sstring_holder "holder<std::basic_string<signed char, std::char_traits<signed char>, std::allocator<signed char> > >" } }
 
+std::vector<std::deque<std::unique_ptr<char>>> *seq1_ptr;
+holder< std::vector<std::deque<std::unique_ptr<char>>> > seq1_holder;
+// { dg-final { whatis-test seq1_holder "holder<std::vector<std::deque<std::unique_ptr<char>>> >" } }
+
+std::list<std::forward_list<std::unique_ptr<char>>> *seq2_ptr;
+holder< std::list<std::forward_list<std::unique_ptr<char>>> > seq2_holder;
+// { dg-final { whatis-test seq2_holder "holder<std::list<std::forward_list<std::unique_ptr<char>>> >" } }
+
+std::map<int, std::set<int>> *assoc1_ptr;
+holder< std::map<int, std::set<int>> > assoc1_holder;
+// { dg-final { whatis-test assoc1_holder "holder<std::map<int, std::set<int>> >" } }
+
+std::multimap<int, std::multiset<int>> *assoc2_ptr;
+holder< std::multimap<int, std::multiset<int>> > assoc2_holder;
+// { dg-final { whatis-test assoc2_holder "holder<std::multimap<int, std::multiset<int>> >" } }
+
+std::unordered_map<int, std::unordered_set<int>> *unord1_ptr;
+holder< std::unordered_map<int, std::unordered_set<int>> > unord1_holder;
+// { dg-final { whatis-test unord1_holder "holder<std::unordered_map<int, std::unordered_set<int>> >" } }
+
+std::unordered_multimap<int, std::unordered_multiset<int>> *unord2_ptr;
+holder< std::unordered_multimap<int, std::unordered_multiset<int>> > unord2_holder;
+// { dg-final { whatis-test unord2_holder "holder<std::unordered_multimap<int, std::unordered_multiset<int>> >" } }
+
+
 int
 main()
 {
@@ -236,6 +270,18 @@  main()
   placeholder(&ustring_holder);
   placeholder(&sstring_ptr);
   placeholder(&sstring_holder);
+  placeholder(&seq1_ptr);
+  placeholder(&seq1_holder);
+  placeholder(&seq2_ptr);
+  placeholder(&seq2_holder);
+  placeholder(&assoc1_ptr);
+  placeholder(&assoc1_holder);
+  placeholder(&assoc2_ptr);
+  placeholder(&assoc2_holder);
+  placeholder(&unord1_ptr);
+  placeholder(&unord1_holder);
+  placeholder(&unord2_ptr);
+  placeholder(&unord2_holder);
 
   return 0;
 }