From 0234020f718829f13f2645d5057b51ebcbbff151 Mon Sep 17 00:00:00 2001 From: Tom de Vries Date: Wed, 24 Sep 2025 13:45:38 +0200 Subject: [PATCH] [gdb/c++] Fix hang on whatis std::string::npos Consider the following scenario, exercising "whatis std::string::npos": ... $ cat test.cc int main (void) { std::string foo = "bar"; return foo.size (); } $ g++ test.cc -g $ gdb -q -batch -iex "set trace-commands on" a.out -x gdb.in +start Temporary breakpoint 1 at 0x4021c7: file test.cc, line 3. [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib64/libthread_db.so.1". Temporary breakpoint 1, main () at test.cc:3 3 std::string foo = "bar"; +info auto-load python-scripts No auto-load scripts. +whatis std::string type = std::__cxx11::basic_string, \ std::allocator > +whatis std::string::npos type = const std::__cxx11::basic_string, \ std::allocator >::size_type ... After installing the package containing the pretty-printers: ... $ zypper install libstdc++6-pp ... and adding some commands to use them, we get instead: ... $ gdb -q -batch -iex "set trace-commands on" a.out -x gdb.in +add-auto-load-safe-path /usr/share/gdb/auto-load +add-auto-load-scripts-directory /usr/share/gdb/auto-load +start ... +info auto-load python-scripts Loaded Script Yes /usr/share/gdb/auto-load/usr/lib64/libstdc++.so.6.0.34-gdb.py +whatis std::string type = std::string +whatis std::string::npos type = const std::__cxx11::basic_string, \ std::allocator >::size_type ... Note that "whatis std::string" now prints "std::string", but that "whatis std::string::npos" still uses the longer name for std::string. This is when compiling gdb with -O0. With -O2 -fstack-protector-strong, we have a hang instead: ... +whatis std::string type = std::string +whatis std::string::npos ... Valgrind complains about an uninitialized field demangle_component::d_counting, which is fixed by using cplus_demangle_fill_name in replace_typedefs_qualified_name. After fixing that, the hang is also reproducible at -O0. The hang happens because we're stuck in the while loop in replace_typedefs_qualified_name, replacing "std::string::size_type" with "std::string::size_type". Fix this in inspect_type by checking for this situation, getting us instead: ... +whatis std::string type = std::string +whatis std::string::npos type = const std::string::size_type $ ... The test-case is a bit unusual: - pretty-print.cc is a preprocessed c++ source, reduced using cvise [1]. - pretty-print.cc doesn't compile with clang. - the pretty-printer .py file is a complete copy of /usr/share/gcc-15/python/libstdcxx/v6/printers.py, modified slightly by pre-commit checks. Reducing it to the minimum has the upside of a smaller file, but the downside of limited reusability for other test-cases, so I've left it as is. Using the test-case (and the cplus_demangle_fill_name fix), I managed to reproduce the hang on both: - openSUSE Leap 15.6 with gcc 7, and - openSUSE Tumbleweed with gcc 15. Tested on x86_64-linux. PR testsuite/33480 Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=33480 [1] https://github.com/marxin/cvise --- gdb/cp-support.c | 9 +- .../gdb.cp/gcc-15-libstdcxx-v6-printers.py | 3115 +++++++++++++++++ gdb/testsuite/gdb.cp/pretty-print.cc | 80 + gdb/testsuite/gdb.cp/pretty-print.exp | 41 + 4 files changed, 3241 insertions(+), 4 deletions(-) create mode 100644 gdb/testsuite/gdb.cp/gcc-15-libstdcxx-v6-printers.py create mode 100644 gdb/testsuite/gdb.cp/pretty-print.cc create mode 100644 gdb/testsuite/gdb.cp/pretty-print.exp diff --git a/gdb/cp-support.c b/gdb/cp-support.c index 91b024d7fbe..89f2df69793 100644 --- a/gdb/cp-support.c +++ b/gdb/cp-support.c @@ -165,7 +165,7 @@ inspect_type (struct demangle_parse_info *info, { const char *new_name = (*finder) (otype, data); - if (new_name != NULL) + if (new_name != nullptr && strcmp (new_name, name) != 0) { ret_comp->u.s_name.s = new_name; ret_comp->u.s_name.len = strlen (new_name); @@ -378,9 +378,10 @@ replace_typedefs_qualified_name (struct demangle_parse_info *info, struct demangle_component newobj; buf.write (d_left (comp)->u.s_name.s, d_left (comp)->u.s_name.len); - newobj.type = DEMANGLE_COMPONENT_NAME; - newobj.u.s_name.s = obstack_strdup (&info->obstack, buf.string ()); - newobj.u.s_name.len = buf.size (); + cplus_demangle_fill_name (&newobj, + obstack_strdup (&info->obstack, + buf.string ()), + buf.size ()); if (inspect_type (info, &newobj, finder, data)) { char *s; diff --git a/gdb/testsuite/gdb.cp/gcc-15-libstdcxx-v6-printers.py b/gdb/testsuite/gdb.cp/gcc-15-libstdcxx-v6-printers.py new file mode 100644 index 00000000000..75d0f808a01 --- /dev/null +++ b/gdb/testsuite/gdb.cp/gcc-15-libstdcxx-v6-printers.py @@ -0,0 +1,3115 @@ +# Pretty-printers for libstdc++. + +# Copyright (C) 2008-2025 Free Software Foundation, Inc. + +# 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 . + +import datetime +import errno +import itertools +import re +import sys + +import gdb + +# Python 2 + Python 3 compatibility code + +# Resources about compatibility: +# +# * : Documentation of the "six" module + +# FIXME: The handling of e.g. std::basic_string (at least on char) +# probably needs updating to work with Python 3's new string rules. +# +# In particular, Python 3 has a separate type (called byte) for +# bytestrings, and a special b"" syntax for the byte literals; the old +# str() type has been redefined to always store Unicode text. +# +# We probably can't do much about this until this GDB PR is addressed: +# + +if sys.version_info[0] > 2: + # Python 3 stuff + Iterator = object + # Python 3 folds these into the normal functions. + imap = map + izip = zip + # Also, int subsumes long + long = int + _utc_timezone = datetime.timezone.utc +else: + # Python 2 stuff + class Iterator: + """Compatibility mixin for iterators + + Instead of writing next() methods for iterators, write + __next__() methods and use this mixin to make them work in + Python 2 as well as Python 3. + + Idea stolen from the "six" documentation: + + """ + + def next(self): + return self.__next__() + + # In Python 2, we still need these from itertools + from itertools import imap, izip + + # Python 2 does not provide the datetime.UTC singleton. + class UTC(datetime.tzinfo): + """Concrete tzinfo class representing the UTC time zone.""" + + def utcoffset(self, dt): + return datetime.timedelta(0) + + def tzname(self, dt): + return "UTC" + + def dst(self, dt): + return datetime.timedelta(0) + + _utc_timezone = UTC() + +# Try to use the new-style pretty-printing if available. +_use_gdb_pp = True +try: + import gdb.printing +except ImportError: + _use_gdb_pp = False + +# Try to install type-printers. +_use_type_printing = False +try: + import gdb.types + + if hasattr(gdb.types, "TypePrinter"): + _use_type_printing = True +except ImportError: + pass + +# Use the base class if available. +if hasattr(gdb, "ValuePrinter"): + printer_base = gdb.ValuePrinter +else: + printer_base = object + +# Starting with the type ORIG, search for the member type NAME. This +# handles searching upward through superclasses. This is needed to +# work around http://sourceware.org/bugzilla/show_bug.cgi?id=13615. + + +def find_type(orig, name): + typ = orig.strip_typedefs() + while True: + # Use Type.tag to ignore cv-qualifiers. PR 67440. + search = "%s::%s" % (typ.tag, name) + try: + return gdb.lookup_type(search) + except RuntimeError: + pass + # The type was not found, so try the superclass. We only need + # to check the first superclass, so we don't bother with + # anything fancier here. + fields = typ.fields() + if len(fields) and fields[0].is_base_class: + typ = fields[0].type + else: + raise ValueError("Cannot find type %s::%s" % (str(orig), name)) + + +_versioned_namespace = "__8::" + + +def lookup_templ_spec(templ, *args): + """ + Lookup template specialization templ. + """ + t = "{}<{}>".format(templ, ", ".join([str(a) for a in args])) + try: + return gdb.lookup_type(t) + except gdb.error as e: + # Type not found, try again in versioned namespace. + global _versioned_namespace + if _versioned_namespace not in templ: + t = t.replace("::", "::" + _versioned_namespace, 1) + try: + return gdb.lookup_type(t) + except gdb.error: + # If that also fails, rethrow the original exception + pass + raise e + + +# Use this to find container node types instead of find_type, +# see https://gcc.gnu.org/bugzilla/show_bug.cgi?id=91997 for details. +def lookup_node_type(nodename, containertype): + """ + Lookup specialization of template nodename corresponding to containertype. + + nodename - The name of a class template, as a String + containertype - The container, as a gdb.Type + + Return a gdb.Type for the corresponding specialization of nodename, + or None if the type cannot be found. + + e.g. lookup_node_type('_List_node', gdb.lookup_type('std::list')) + will return a gdb.Type for the type std::_List_node. + """ + # If nodename is unqualified, assume it's in namespace std. + if "::" not in nodename: + nodename = "std::" + nodename + # Use either containertype's value_type or its first template argument. + try: + valtype = find_type(containertype, "value_type") + except: + valtype = containertype.template_argument(0) + valtype = valtype.strip_typedefs() + try: + return lookup_templ_spec(nodename, valtype) + except gdb.error: + # For debug mode containers the node is in std::__cxx1998. + if is_member_of_namespace(nodename, "std"): + if is_member_of_namespace( + containertype, "std::__cxx1998", "std::__debug", "__gnu_debug" + ): + nodename = nodename.replace("::", "::__cxx1998::", 1) + try: + return lookup_templ_spec(nodename, valtype) + except gdb.error: + pass + return None + + +def is_member_of_namespace(typ, *namespaces): + """ + Test whether a type is a member of one of the specified namespaces. + The type can be specified as a string or a gdb.Type object. + """ + if isinstance(typ, gdb.Type): + typ = str(typ) + typ = strip_versioned_namespace(typ) + for namespace in namespaces: + if typ.startswith(namespace + "::"): + return True + return False + + +def is_specialization_of(x, template_name): + """ + Test whether a type is a specialization of the named class template. + The type can be specified as a string or a gdb.Type object. + The template should be the name of a class template as a string, + without any 'std' qualification. + """ + global _versioned_namespace + if isinstance(x, gdb.Type): + x = x.tag + template_name = "(%s)?%s" % (_versioned_namespace, template_name) + return re.match("^std::%s<.*>$" % template_name, x) is not None + + +def strip_versioned_namespace(typename): + global _versioned_namespace + return typename.replace(_versioned_namespace, "") + + +def strip_fundts_namespace(typ): + """Remove "fundamentals_vN" inline namespace from qualified type name.""" + pattern = r"^std::experimental::fundamentals_v\d::" + repl = "std::experimental::" + if sys.version_info[0] == 2: + return re.sub(pattern, repl, typ, 1) + else: # Technically this needs Python 3.1 but nobody should be using 3.0 + return re.sub(pattern, repl, typ, count=1) + + +def strip_inline_namespaces(type_str): + """Remove known inline namespaces from the canonical name of a type.""" + type_str = strip_versioned_namespace(type_str) + type_str = type_str.replace("std::__cxx11::", "std::") + expt_ns = "std::experimental::" + for lfts_ns in ("fundamentals_v1", "fundamentals_v2"): + type_str = type_str.replace(expt_ns + lfts_ns + "::", expt_ns) + fs_ns = expt_ns + "filesystem::" + type_str = type_str.replace(fs_ns + "v1::", fs_ns) + return type_str + + +def get_template_arg_list(type_obj): + """Return a type's template arguments as a list.""" + n = 0 + template_args = [] + while True: + try: + template_args.append(type_obj.template_argument(n)) + except: + return template_args + n += 1 + + +class SmartPtrIterator(Iterator): + """An iterator for smart pointer types with a single 'child' value.""" + + def __init__(self, val): + self._val = val + + def __iter__(self): + return self + + def __next__(self): + if self._val is None: + raise StopIteration + self._val, val = None, self._val + return ("get()", val) + + +class SharedPointerPrinter(printer_base): + """ + Print a shared_ptr, weak_ptr, atomic, or atomic. + """ + + def __init__(self, typename, val): + self._typename = strip_versioned_namespace(typename) + self._val = val + self._pointer = val["_M_ptr"] + + def children(self): + return SmartPtrIterator(self._pointer) + + # Return the _Sp_counted_base<>* that holds the refcounts. + def _get_refcounts(self): + if self._typename == "std::atomic": + # A tagged pointer is stored as uintptr_t. + ptr_val = self._val["_M_refcount"]["_M_val"]["_M_i"] + ptr_val = ptr_val - (ptr_val % 2) # clear lock bit + ptr_type = find_type(self._val["_M_refcount"].type, "pointer") + return ptr_val.cast(ptr_type) + return self._val["_M_refcount"]["_M_pi"] + + def to_string(self): + state = "empty" + refcounts = self._get_refcounts() + targ = self._val.type.template_argument(0) + targ = strip_versioned_namespace(str(targ)) + + if refcounts != 0: + usecount = refcounts["_M_use_count"] + weakcount = refcounts["_M_weak_count"] + if usecount == 0: + state = "expired, weak count %d" % weakcount + else: + state = "use count %d, weak count %d" % (usecount, weakcount - 1) + return "%s<%s> (%s)" % (self._typename, targ, state) + + +def _tuple_impl_get(val): + """Return the tuple element stored in a _Tuple_impl base class.""" + bases = val.type.fields() + if not bases[-1].is_base_class: + raise ValueError( + "Unsupported implementation for std::tuple: %s" % str(val.type) + ) + # Get the _Head_base base class: + head_base = val.cast(bases[-1].type) + fields = head_base.type.fields() + if len(fields) == 0: + raise ValueError( + "Unsupported implementation for std::tuple: %s" % str(val.type) + ) + if fields[0].name == "_M_head_impl": + # The tuple element is the _Head_base::_M_head_impl data member. + return head_base["_M_head_impl"] + elif fields[0].is_base_class: + # The tuple element is an empty base class of _Head_base. + # Cast to that empty base class. + return head_base.cast(fields[0].type) + else: + raise ValueError( + "Unsupported implementation for std::tuple: %s" % str(val.type) + ) + + +def tuple_get(n, val): + """Return the result of std::get(val) on a std::tuple.""" + tuple_size = len(get_template_arg_list(val.type)) + if n > tuple_size: + raise ValueError("Out of range index for std::get on std::tuple") + # Get the first _Tuple_impl<0, T...> base class: + node = val.cast(val.type.fields()[0].type) + while n > 0: + # Descend through the base classes until the Nth one. + node = node.cast(node.type.fields()[0].type) + n -= 1 + return _tuple_impl_get(node) + + +def unique_ptr_get(val): + """Return the result of val.get() on a std::unique_ptr.""" + # std::unique_ptr contains a std::tuple, + # either as a direct data member _M_t (the old implementation) + # or within a data member of type __uniq_ptr_data. + impl_type = val.type.fields()[0].type.strip_typedefs() + # Check for new implementations first: + if is_specialization_of(impl_type, "__uniq_ptr_data") or is_specialization_of( + impl_type, "__uniq_ptr_impl" + ): + tuple_member = val["_M_t"]["_M_t"] + elif is_specialization_of(impl_type, "tuple"): + tuple_member = val["_M_t"] + else: + raise ValueError( + "Unsupported implementation for unique_ptr: %s" % str(impl_type) + ) + return tuple_get(0, tuple_member) + + +class UniquePointerPrinter(printer_base): + """Print a unique_ptr.""" + + def __init__(self, typename, val): + self._val = val + + def children(self): + return SmartPtrIterator(unique_ptr_get(self._val)) + + def to_string(self): + t = self._val.type.template_argument(0) + return "std::unique_ptr<{}>".format(str(t)) + + +def get_value_from_aligned_membuf(buf, valtype): + """Return the value held in a __gnu_cxx::__aligned_membuf.""" + return buf["_M_storage"].address.cast(valtype.pointer()).dereference() + + +def get_value_from_list_node(node): + """Return the value held in an _List_node<_Val>.""" + try: + member = node.type.fields()[1].name + if member == "_M_data": + # C++03 implementation, node contains the value as a member + return node["_M_data"] + elif member == "_M_storage": + # C++11 implementation, node stores value in __aligned_membuf + valtype = node.type.template_argument(0) + return get_value_from_aligned_membuf(node["_M_storage"], valtype) + except: + pass + raise ValueError("Unsupported implementation for %s" % str(node.type)) + + +class StdListPrinter(printer_base): + """Print a std::list.""" + + class _iterator(Iterator): + def __init__(self, nodetype, head): + self._nodetype = nodetype + self._base = head["_M_next"] + self._head = head.address + self._count = 0 + + def __iter__(self): + return self + + def __next__(self): + if self._base == self._head: + raise StopIteration + elt = self._base.cast(self._nodetype).dereference() + self._base = elt["_M_next"] + count = self._count + self._count = self._count + 1 + val = get_value_from_list_node(elt) + return ("[%d]" % count, val) + + def __init__(self, typename, val): + self._typename = strip_versioned_namespace(typename) + self._val = val + + def children(self): + nodetype = lookup_node_type("_List_node", self._val.type).pointer() + return self._iterator(nodetype, self._val["_M_impl"]["_M_node"]) + + def to_string(self): + headnode = self._val["_M_impl"]["_M_node"] + if headnode["_M_next"] == headnode.address: + return "empty %s" % (self._typename) + return "%s" % (self._typename) + + +class NodeIteratorPrinter(printer_base): + def __init__(self, typename, val, contname, nodename): + self._val = val + self._typename = typename + self._contname = contname + self._nodetype = lookup_node_type(nodename, val.type) + + def to_string(self): + if not self._val["_M_node"]: + return "non-dereferenceable iterator for std::%s" % (self._contname) + node = self._val["_M_node"].cast(self._nodetype.pointer()).dereference() + return str(get_value_from_list_node(node)) + + +class StdListIteratorPrinter(NodeIteratorPrinter): + """Print std::list::iterator.""" + + def __init__(self, typename, val): + NodeIteratorPrinter.__init__(self, typename, val, "list", "_List_node") + + +class StdFwdListIteratorPrinter(NodeIteratorPrinter): + """Print std::forward_list::iterator.""" + + def __init__(self, typename, val): + NodeIteratorPrinter.__init__( + self, typename, val, "forward_list", "_Fwd_list_node" + ) + + +class StdSlistPrinter(printer_base): + """Print a __gnu_cxx::slist.""" + + class _iterator(Iterator): + def __init__(self, nodetype, head): + self._nodetype = nodetype + self._base = head["_M_head"]["_M_next"] + self._count = 0 + + def __iter__(self): + return self + + def __next__(self): + if self._base == 0: + raise StopIteration + elt = self._base.cast(self._nodetype).dereference() + self._base = elt["_M_next"] + count = self._count + self._count = self._count + 1 + return ("[%d]" % count, elt["_M_data"]) + + def __init__(self, typename, val): + self._val = val + + def children(self): + nodetype = lookup_node_type("__gnu_cxx::_Slist_node", self._val.type) + return self._iterator(nodetype.pointer(), self._val) + + def to_string(self): + if self._val["_M_head"]["_M_next"] == 0: + return "empty __gnu_cxx::slist" + return "__gnu_cxx::slist" + + +class StdSlistIteratorPrinter(printer_base): + """Print __gnu_cxx::slist::iterator.""" + + def __init__(self, typename, val): + self._val = val + + def to_string(self): + if not self._val["_M_node"]: + return "non-dereferenceable iterator for __gnu_cxx::slist" + nodetype = lookup_node_type("__gnu_cxx::_Slist_node", self._val.type).pointer() + return str(self._val["_M_node"].cast(nodetype).dereference()["_M_data"]) + + +class StdVectorPrinter(printer_base): + """Print a std::vector.""" + + class _iterator(Iterator): + def __init__(self, start, finish, bitvec): + self._bitvec = bitvec + if bitvec: + self._item = start["_M_p"] + self._so = 0 + self._finish = finish["_M_p"] + self._fo = finish["_M_offset"] + itype = self._item.dereference().type + self._isize = 8 * itype.sizeof + else: + self._item = start + self._finish = finish + self._count = 0 + + def __iter__(self): + return self + + def __next__(self): + count = self._count + self._count = self._count + 1 + if self._bitvec: + if self._item == self._finish and self._so >= self._fo: + raise StopIteration + elt = bool(self._item.dereference() & (1 << self._so)) + self._so = self._so + 1 + if self._so >= self._isize: + self._item = self._item + 1 + self._so = 0 + return ("[%d]" % count, elt) + else: + if self._item == self._finish: + raise StopIteration + elt = self._item.dereference() + self._item = self._item + 1 + return ("[%d]" % count, elt) + + def __init__(self, typename, val): + self._typename = strip_versioned_namespace(typename) + self._val = val + self._is_bool = val.type.template_argument(0).code == gdb.TYPE_CODE_BOOL + + def children(self): + return self._iterator( + self._val["_M_impl"]["_M_start"], + self._val["_M_impl"]["_M_finish"], + self._is_bool, + ) + + def to_string(self): + start = self._val["_M_impl"]["_M_start"] + finish = self._val["_M_impl"]["_M_finish"] + end = self._val["_M_impl"]["_M_end_of_storage"] + if self._is_bool: + start = self._val["_M_impl"]["_M_start"]["_M_p"] + finish = self._val["_M_impl"]["_M_finish"]["_M_p"] + fo = self._val["_M_impl"]["_M_finish"]["_M_offset"] + itype = start.dereference().type + bl = 8 * itype.sizeof + length = bl * (finish - start) + fo + capacity = bl * (end - start) + return "%s of length %d, capacity %d" % ( + self._typename, + int(length), + int(capacity), + ) + else: + return "%s of length %d, capacity %d" % ( + self._typename, + int(finish - start), + int(end - start), + ) + + def display_hint(self): + return "array" + + +class StdVectorIteratorPrinter(printer_base): + """Print std::vector::iterator.""" + + def __init__(self, typename, val): + self._val = val + + def to_string(self): + if not self._val["_M_current"]: + return "non-dereferenceable iterator for std::vector" + return str(self._val["_M_current"].dereference()) + + +class StdBitIteratorPrinter(printer_base): + """Print std::vector's _Bit_iterator and _Bit_const_iterator.""" + + def __init__(self, typename, val): + self._val = val + + def to_string(self): + if not self._val["_M_p"]: + return "non-dereferenceable iterator for std::vector" + return bool(self._val["_M_p"].dereference() & (1 << self._val["_M_offset"])) + + +class StdBitReferencePrinter(printer_base): + """Print std::vector::reference.""" + + def __init__(self, typename, val): + self._val = val + + def to_string(self): + if not self._val["_M_p"]: + # PR libstdc++/115098 removed the reference default constructor + # that this case relates to. New code should never need this, + # but we still handle it for compatibility with old binaries. + return "invalid std::vector::reference" + return bool(self._val["_M_p"].dereference() & (self._val["_M_mask"])) + + +class StdTuplePrinter(printer_base): + """Print a std::tuple.""" + + class _iterator(Iterator): + @staticmethod + def _is_nonempty_tuple(nodes): + if len(nodes) == 2: + if is_specialization_of(nodes[1].type, "__tuple_base"): + return True + elif len(nodes) == 1: + return True + elif len(nodes) == 0: + return False + raise ValueError("Top of tuple tree does not consist of a single node.") + + def __init__(self, head): + self._head = head + + # Set the base class as the initial head of the + # tuple. + nodes = self._head.type.fields() + if self._is_nonempty_tuple(nodes): + # Set the actual head to the first pair. + self._head = self._head.cast(nodes[0].type) + self._count = 0 + + def __iter__(self): + return self + + def __next__(self): + # Check for further recursions in the inheritance tree. + # For a GCC 5+ tuple self._head is None after visiting all nodes: + if not self._head: + raise StopIteration + nodes = self._head.type.fields() + # For a GCC 4.x tuple there is a final node with no fields: + if len(nodes) == 0: + raise StopIteration + # Check that this iteration has an expected structure. + if len(nodes) > 2: + raise ValueError("Cannot parse more than 2 nodes in a tuple tree.") + + if len(nodes) == 1: + # This is the last node of a GCC 5+ std::tuple. + impl = self._head.cast(nodes[0].type) + self._head = None + else: + # Either a node before the last node, or the last node of + # a GCC 4.x tuple (which has an empty parent). + + # - Left node is the next recursion parent. + # - Right node is the actual class contained in the tuple. + + # Process right node. + impl = self._head.cast(nodes[1].type) + + # Process left node and set it as head. + self._head = self._head.cast(nodes[0].type) + + self._count = self._count + 1 + + # Finally, check the implementation. If it is + # wrapped in _M_head_impl return that, otherwise return + # the value "as is". + fields = impl.type.fields() + if len(fields) < 1 or fields[0].name != "_M_head_impl": + return ("[%d]" % (self._count - 1), impl) + else: + return ("[%d]" % (self._count - 1), impl["_M_head_impl"]) + + def __init__(self, typename, val): + self._typename = strip_versioned_namespace(typename) + self._val = val + + def children(self): + return self._iterator(self._val) + + def to_string(self): + if len(self._val.type.fields()) == 0: + return "empty %s" % (self._typename) + return "%s containing" % (self._typename) + + +class StdStackOrQueuePrinter(printer_base): + """Print a std::stack or std::queue.""" + + def __init__(self, typename, val): + self._typename = strip_versioned_namespace(typename) + self._visualizer = gdb.default_visualizer(val["c"]) + + def children(self): + return self._visualizer.children() + + def to_string(self): + return "%s wrapping: %s" % (self._typename, self._visualizer.to_string()) + + def display_hint(self): + if hasattr(self._visualizer, "display_hint"): + return self._visualizer.display_hint() + return None + + +class RbtreeIterator(Iterator): + """ + Turn an RB-tree-based container (std::map, std::set etc.) into + a Python iterable object. + """ + + def __init__(self, rbtree): + self._size = rbtree["_M_t"]["_M_impl"]["_M_node_count"] + self._node = rbtree["_M_t"]["_M_impl"]["_M_header"]["_M_left"] + self._count = 0 + + def __iter__(self): + return self + + def __len__(self): + return int(self._size) + + def __next__(self): + if self._count == self._size: + raise StopIteration + result = self._node + self._count = self._count + 1 + if self._count < self._size: + # Compute the next node. + node = self._node + if node.dereference()["_M_right"]: + node = node.dereference()["_M_right"] + while node.dereference()["_M_left"]: + node = node.dereference()["_M_left"] + else: + parent = node.dereference()["_M_parent"] + while node == parent.dereference()["_M_right"]: + node = parent + parent = parent.dereference()["_M_parent"] + if node.dereference()["_M_right"] != parent: + node = parent + self._node = node + return result + + +def get_value_from_Rb_tree_node(node): + """Return the value held in an _Rb_tree_node<_Val>.""" + try: + member = node.type.fields()[1].name + if member == "_M_value_field": + # C++03 implementation, node contains the value as a member + return node["_M_value_field"] + elif member == "_M_storage": + # C++11 implementation, node stores value in __aligned_membuf + valtype = node.type.template_argument(0) + return get_value_from_aligned_membuf(node["_M_storage"], valtype) + except: + pass + raise ValueError("Unsupported implementation for %s" % str(node.type)) + + +# This is a pretty printer for std::_Rb_tree_iterator (which is +# std::map::iterator), and has nothing to do with the RbtreeIterator +# class above. + + +class StdRbtreeIteratorPrinter(printer_base): + """Print std::map::iterator, std::set::iterator, etc.""" + + def __init__(self, typename, val): + self._val = val + nodetype = lookup_node_type("_Rb_tree_node", self._val.type) + self._link_type = nodetype.pointer() + + def to_string(self): + if not self._val["_M_node"]: + return "non-dereferenceable iterator for associative container" + node = self._val["_M_node"].cast(self._link_type).dereference() + return str(get_value_from_Rb_tree_node(node)) + + +class StdDebugIteratorPrinter(printer_base): + """Print a debug enabled version of an iterator.""" + + def __init__(self, typename, val): + self._val = val + + # Just strip away the encapsulating __gnu_debug::_Safe_iterator + # and return the wrapped iterator value. + def to_string(self): + base_type = gdb.lookup_type("__gnu_debug::_Safe_iterator_base") + itype = self._val.type.template_argument(0) + safe_seq = self._val.cast(base_type)["_M_sequence"] + if not safe_seq: + return str(self._val.cast(itype)) + if self._val["_M_version"] != safe_seq["_M_version"]: + return "invalid iterator" + return str(self._val.cast(itype)) + + +def num_elements(num): + """Return either "1 element" or "N elements" depending on the argument.""" + return "1 element" if num == 1 else "%d elements" % num + + +class StdMapPrinter(printer_base): + """Print a std::map or std::multimap.""" + + # Turn an RbtreeIterator into a pretty-print iterator. + class _iter(Iterator): + def __init__(self, rbiter, type): + self._rbiter = rbiter + self._count = 0 + self._type = type + + def __iter__(self): + return self + + def __next__(self): + if self._count % 2 == 0: + n = next(self._rbiter) + n = n.cast(self._type).dereference() + n = get_value_from_Rb_tree_node(n) + self._pair = n + item = n["first"] + else: + item = self._pair["second"] + result = ("[%d]" % self._count, item) + self._count = self._count + 1 + return result + + def __init__(self, typename, val): + self._typename = strip_versioned_namespace(typename) + self._val = val + + def to_string(self): + return "%s with %s" % ( + self._typename, + num_elements(len(RbtreeIterator(self._val))), + ) + + def children(self): + node = lookup_node_type("_Rb_tree_node", self._val.type).pointer() + return self._iter(RbtreeIterator(self._val), node) + + def display_hint(self): + return "map" + + +class StdSetPrinter(printer_base): + """Print a std::set or std::multiset.""" + + # Turn an RbtreeIterator into a pretty-print iterator. + class _iter(Iterator): + def __init__(self, rbiter, type): + self._rbiter = rbiter + self._count = 0 + self._type = type + + def __iter__(self): + return self + + def __next__(self): + item = next(self._rbiter) + item = item.cast(self._type).dereference() + item = get_value_from_Rb_tree_node(item) + # FIXME: this is weird ... what to do? + # Maybe a 'set' display hint? + result = ("[%d]" % self._count, item) + self._count = self._count + 1 + return result + + def __init__(self, typename, val): + self._typename = strip_versioned_namespace(typename) + self._val = val + + def to_string(self): + return "%s with %s" % ( + self._typename, + num_elements(len(RbtreeIterator(self._val))), + ) + + def children(self): + node = lookup_node_type("_Rb_tree_node", self._val.type).pointer() + return self._iter(RbtreeIterator(self._val), node) + + +class StdBitsetPrinter(printer_base): + """Print a std::bitset.""" + + def __init__(self, typename, val): + self._typename = strip_versioned_namespace(typename) + self._val = val + + def to_string(self): + # If template_argument handled values, we could print the + # size. Or we could use a regexp on the type. + return "%s" % (self._typename) + + def children(self): + try: + # An empty bitset may not have any members which will + # result in an exception being thrown. + words = self._val["_M_w"] + except: + return [] + + wtype = words.type + + # The _M_w member can be either an unsigned long, or an + # array. This depends on the template specialization used. + # If it is a single long, convert to a single element list. + if wtype.code == gdb.TYPE_CODE_ARRAY: + tsize = wtype.target().sizeof + else: + words = [words] + tsize = wtype.sizeof + + nwords = wtype.sizeof / tsize + result = [] + byte = 0 + while byte < nwords: + w = words[byte] + bit = 0 + while w != 0: + if (w & 1) != 0: + # Another spot where we could use 'set'? + result.append(("[%d]" % (byte * tsize * 8 + bit), 1)) + bit = bit + 1 + w = w >> 1 + byte = byte + 1 + return result + + +class StdDequePrinter(printer_base): + """Print a std::deque.""" + + class _iter(Iterator): + def __init__(self, node, start, end, last, buffer_size): + self._node = node + self._p = start + self._end = end + self._last = last + self._buffer_size = buffer_size + self._count = 0 + + def __iter__(self): + return self + + def __next__(self): + if self._p == self._last: + raise StopIteration + + result = ("[%d]" % self._count, self._p.dereference()) + self._count = self._count + 1 + + # Advance the 'cur' pointer. + self._p = self._p + 1 + if self._p == self._end: + # If we got to the end of this bucket, move to the + # next bucket. + self._node = self._node + 1 + self._p = self._node[0] + self._end = self._p + self._buffer_size + + return result + + def __init__(self, typename, val): + self._typename = strip_versioned_namespace(typename) + self._val = val + self._elttype = val.type.template_argument(0) + size = self._elttype.sizeof + if size < 512: + self._buffer_size = int(512 / size) + else: + self._buffer_size = 1 + + def to_string(self): + start = self._val["_M_impl"]["_M_start"] + end = self._val["_M_impl"]["_M_finish"] + + delta_n = end["_M_node"] - start["_M_node"] - 1 + delta_s = start["_M_last"] - start["_M_cur"] + delta_e = end["_M_cur"] - end["_M_first"] + + size = self._buffer_size * delta_n + delta_s + delta_e + + return "%s with %s" % (self._typename, num_elements(long(size))) + + def children(self): + start = self._val["_M_impl"]["_M_start"] + end = self._val["_M_impl"]["_M_finish"] + return self._iter( + start["_M_node"], + start["_M_cur"], + start["_M_last"], + end["_M_cur"], + self._buffer_size, + ) + + def display_hint(self): + return "array" + + +class StdDequeIteratorPrinter(printer_base): + """Print std::deque::iterator.""" + + def __init__(self, typename, val): + self._val = val + + def to_string(self): + if not self._val["_M_cur"]: + return "non-dereferenceable iterator for std::deque" + return str(self._val["_M_cur"].dereference()) + + +class StdStringPrinter(printer_base): + """Print a std::basic_string of some kind.""" + + def __init__(self, typename, val): + self._val = val + self._new_string = typename.find("::__cxx11::basic_string") != -1 + + def to_string(self): + # Make sure &string works, too. + type = self._val.type + if type.code == gdb.TYPE_CODE_REF: + type = type.target() + + # Calculate the length of the string so that to_string returns + # the string according to length, not according to first null + # encountered. + ptr = self._val["_M_dataplus"]["_M_p"] + if self._new_string: + length = self._val["_M_string_length"] + # https://sourceware.org/bugzilla/show_bug.cgi?id=17728 + ptr = ptr.cast(ptr.type.strip_typedefs()) + else: + realtype = type.unqualified().strip_typedefs() + reptype = gdb.lookup_type(str(realtype) + "::_Rep").pointer() + header = ptr.cast(reptype) - 1 + length = header.dereference()["_M_length"] + if hasattr(ptr, "lazy_string"): + return ptr.lazy_string(length=length) + return ptr.string(length=length) + + def display_hint(self): + return "string" + + +def access_streambuf_ptrs(streambuf): + """Access the streambuf put area pointers.""" + pbase = streambuf["_M_out_beg"] + pptr = streambuf["_M_out_cur"] + egptr = streambuf["_M_in_end"] + return pbase, pptr, egptr + + +class StdStringBufPrinter(printer_base): + """Print a std::basic_stringbuf.""" + + def __init__(self, _, val): + self._val = val + + def to_string(self): + (pbase, pptr, egptr) = access_streambuf_ptrs(self._val) + # 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=egptr - pbase) + return self._val["_M_string"] + + def display_hint(self): + return "string" + + +class StdStringStreamPrinter(printer_base): + """Print a std::basic_stringstream.""" + + def __init__(self, typename, val): + self._val = val + self._typename = typename + + # Check if the stream was redirected. This is essentially: + # val['_M_streambuf'] != val['_M_stringbuf'].address + # However, GDB can't resolve the virtual inheritance, so we do that + # manually. + basetype = [f.type for f in val.type.fields() if f.is_base_class][0] + gdb.set_convenience_variable("__stream", val.cast(basetype).address) + self._streambuf = gdb.parse_and_eval("$__stream->rdbuf()") + self._was_redirected = self._streambuf != val["_M_stringbuf"].address + + def to_string(self): + if self._was_redirected: + return "%s redirected to %s" % ( + self._typename, + self._streambuf.dereference(), + ) + return self._val["_M_stringbuf"] + + def display_hint(self): + if self._was_redirected: + return None + return "string" + + +class Tr1HashtableIterator(Iterator): + def __init__(self, hashtable): + self._buckets = hashtable["_M_buckets"] + self._bucket = 0 + self._bucket_count = hashtable["_M_bucket_count"] + self._node_type = find_type(hashtable.type, "_Node").pointer() + self._node = 0 + while self._bucket != self._bucket_count: + self._node = self._buckets[self._bucket] + if self._node: + break + self._bucket = self._bucket + 1 + + def __iter__(self): + return self + + def __next__(self): + if self._node == 0: + raise StopIteration + node = self._node.cast(self._node_type) + result = node.dereference()["_M_v"] + self._node = node.dereference()["_M_next"] + if self._node == 0: + self._bucket = self._bucket + 1 + while self._bucket != self._bucket_count: + self._node = self._buckets[self._bucket] + if self._node: + break + self._bucket = self._bucket + 1 + return result + + +class StdHashtableIterator(Iterator): + def __init__(self, hashtable): + self._node = hashtable["_M_before_begin"]["_M_nxt"] + valtype = hashtable.type.template_argument(1) + cached = hashtable.type.template_argument(9).template_argument(0) + node_type = lookup_templ_spec( + "std::__detail::_Hash_node", str(valtype), "true" if cached else "false" + ) + self._node_type = node_type.pointer() + + def __iter__(self): + return self + + def __next__(self): + if self._node == 0: + raise StopIteration + elt = self._node.cast(self._node_type).dereference() + self._node = elt["_M_nxt"] + valptr = elt["_M_storage"].address + valptr = valptr.cast(elt.type.template_argument(0).pointer()) + return valptr.dereference() + + +class Tr1UnorderedSetPrinter(printer_base): + """Print a std::unordered_set or tr1::unordered_set.""" + + def __init__(self, typename, val): + self._typename = strip_versioned_namespace(typename) + self._val = val + + def _hashtable(self): + if self._typename.startswith("std::tr1"): + return self._val + return self._val["_M_h"] + + def to_string(self): + count = self._hashtable()["_M_element_count"] + return "%s with %s" % (self._typename, num_elements(count)) + + @staticmethod + def _format_count(i): + return "[%d]" % i + + def children(self): + counter = imap(self._format_count, itertools.count()) + if self._typename.startswith("std::tr1"): + return izip(counter, Tr1HashtableIterator(self._hashtable())) + return izip(counter, StdHashtableIterator(self._hashtable())) + + +class Tr1UnorderedMapPrinter(printer_base): + """Print a std::unordered_map or tr1::unordered_map.""" + + def __init__(self, typename, val): + self._typename = strip_versioned_namespace(typename) + self._val = val + + def _hashtable(self): + if self._typename.startswith("std::tr1"): + return self._val + return self._val["_M_h"] + + def to_string(self): + count = self._hashtable()["_M_element_count"] + return "%s with %s" % (self._typename, num_elements(count)) + + @staticmethod + def _flatten(list): + for elt in list: + for i in elt: + yield i + + @staticmethod + def _format_one(elt): + return (elt["first"], elt["second"]) + + @staticmethod + def _format_count(i): + return "[%d]" % i + + def children(self): + counter = imap(self._format_count, itertools.count()) + # Map over the hash table and flatten the result. + if self._typename.startswith("std::tr1"): + data = self._flatten( + imap(self._format_one, Tr1HashtableIterator(self._hashtable())) + ) + # Zip the two iterators together. + return izip(counter, data) + data = self._flatten( + imap(self._format_one, StdHashtableIterator(self._hashtable())) + ) + # Zip the two iterators together. + return izip(counter, data) + + def display_hint(self): + return "map" + + +class StdForwardListPrinter(printer_base): + """Print a std::forward_list.""" + + class _iterator(Iterator): + def __init__(self, nodetype, head): + self._nodetype = nodetype + self._base = head["_M_next"] + self._count = 0 + + def __iter__(self): + return self + + def __next__(self): + if self._base == 0: + raise StopIteration + elt = self._base.cast(self._nodetype).dereference() + self._base = elt["_M_next"] + count = self._count + self._count = self._count + 1 + valptr = elt["_M_storage"].address + valptr = valptr.cast(elt.type.template_argument(0).pointer()) + return ("[%d]" % count, valptr.dereference()) + + def __init__(self, typename, val): + self._val = val + self._typename = strip_versioned_namespace(typename) + + def children(self): + nodetype = lookup_node_type("_Fwd_list_node", self._val.type).pointer() + return self._iterator(nodetype, self._val["_M_impl"]["_M_head"]) + + def to_string(self): + if self._val["_M_impl"]["_M_head"]["_M_next"] == 0: + return "empty %s" % self._typename + return "%s" % self._typename + + +class SingleObjContainerPrinter(printer_base): + """Base class for printers of containers of single objects.""" + + def __init__(self, val, viz, hint=None): + self._contained_value = val + self._visualizer = viz + self._hint = hint + + def _recognize(self, type): + """Return type as a string after applying type printers.""" + global _use_type_printing + if not _use_type_printing: + return str(type) + return gdb.types.apply_type_recognizers( + gdb.types.get_type_recognizers(), type + ) or str(type) + + class _contained(Iterator): + def __init__(self, val): + self._val = val + + def __iter__(self): + return self + + def __next__(self): + if self._val is None: + raise StopIteration + retval = self._val + self._val = None + return ("[contained value]", retval) + + def children(self): + if self._contained_value is None: + return self._contained(None) + if hasattr(self._visualizer, "children"): + return self._visualizer.children() + return self._contained(self._contained_value) + + def display_hint(self): + if hasattr(self._visualizer, "children") and hasattr( + self._visualizer, "display_hint" + ): + # If contained value is a map we want to display in the same way. + return self._visualizer.display_hint() + return self._hint + + +def function_pointer_to_name(f): + """Find the name of the function referred to by the gdb.Value f, + which should contain a function pointer from the program.""" + + # Turn the function pointer into an actual address. + # This is needed to unpack ppc64 function descriptors. + f = f.dereference().address + + if sys.version_info[0] == 2: + # Older versions of GDB need to use long for Python 2, + # because int(f) on 64-bit big-endian values raises a + # gdb.error saying "Cannot convert value to int." + f = long(f) + else: + f = int(f) + + try: + # If the function can't be found older versions of GDB raise a + # RuntimeError saying "Cannot locate object file for block." + return gdb.block_for_pc(f).function.name + except: + return None + + +class StdExpAnyPrinter(SingleObjContainerPrinter): + """Print a std::any or std::experimental::any.""" + + def __init__(self, typename, val): + self._typename = strip_versioned_namespace(typename) + self._typename = strip_fundts_namespace(self._typename) + self._val = val + self._contained_type = None + contained_value = None + visualizer = None + mgr = self._val["_M_manager"] + if mgr != 0: + func = function_pointer_to_name(mgr) + if not func: + raise ValueError("Invalid function pointer in %s" % (self._typename)) + # We want to use this regular expression: + # T::_Manager_xxx<.*>::_S_manage\(T::_Op, const T\*, T::_Arg\*\) + # where T is std::any or std::experimental::any. + # But we need to account for variances in demangled names + # between GDB versions, e.g. 'enum T::_Op' instead of 'T::_Op'. + rx = ( + r"({0}::_Manager_\w+<.*>)::_S_manage\(" + r"(enum )?{0}::_Op, (const {0}|{0} const) ?\*, " + r"(union )?{0}::_Arg ?\*\)" + ).format(typename) + m = re.match(rx, func) + if not m: + raise ValueError("Unknown manager function in %s" % self._typename) + + mgrname = m.group(1) + # FIXME need to expand 'std::string' so that gdb.lookup_type works + if "std::string" in mgrname: + mgrtypes = [] + for s in StdExpAnyPrinter._string_types(): + try: + x = re.sub(r"std::string(?!\w)", s, m.group(1)) + # The following lookup might raise gdb.error if the + # manager function was never instantiated for 's' in + # the program, because there will be no such type. + mgrtypes.append(gdb.lookup_type(x)) + except gdb.error: + pass + if len(mgrtypes) != 1: + # FIXME: this is unlikely in practice, but possible for + # programs that use both old and new string types with + # std::any in a single program. Can we do better? + # Maybe find the address of each type's _S_manage and + # compare to the address stored in _M_manager? + raise ValueError( + "Cannot uniquely determine std::string type " "used in std::any" + ) + mgrtype = mgrtypes[0] + else: + mgrtype = gdb.lookup_type(mgrname) + self._contained_type = mgrtype.template_argument(0) + valptr = None + if "::_Manager_internal" in mgrname: + valptr = self._val["_M_storage"]["_M_buffer"].address + elif "::_Manager_external" in mgrname: + valptr = self._val["_M_storage"]["_M_ptr"] + else: + raise ValueError("Unknown manager function in %s" % self._typename) + contained_value = valptr.cast(self._contained_type.pointer()).dereference() + visualizer = gdb.default_visualizer(contained_value) + super(StdExpAnyPrinter, self).__init__(contained_value, visualizer) + + def to_string(self): + if self._contained_type is None: + return "%s [no contained value]" % self._typename + desc = "%s containing " % self._typename + if hasattr(self._visualizer, "children"): + return desc + self._visualizer.to_string() + valtype = self._recognize(self._contained_type) + return desc + strip_versioned_namespace(str(valtype)) + + @staticmethod + def _string_types(): + # This lookup for std::string might return the __cxx11 version, + # but that's not necessarily the one used by the std::any + # manager function we're trying to find. + strings = {str(gdb.lookup_type("std::string").strip_typedefs())} + # So also consider all the other possible std::string types! + s = "basic_string, std::allocator >" + quals = ["std::", "std::__cxx11::", "std::" + _versioned_namespace] + strings |= {q + s for q in quals} # set of unique strings + return strings + + +class StdExpOptionalPrinter(SingleObjContainerPrinter): + """Print a std::optional or std::experimental::optional.""" + + def __init__(self, typename, val): + self._typename = strip_versioned_namespace(typename) + self._typename = strip_fundts_namespace(self._typename) + payload = val["_M_payload"] + if self._typename.startswith("std::experimental"): + engaged = val["_M_engaged"] + contained_value = payload + else: + engaged = payload["_M_engaged"] + contained_value = payload["_M_payload"] + try: + # Since GCC 9 + contained_value = contained_value["_M_value"] + except: + pass + visualizer = gdb.default_visualizer(contained_value) + if not engaged: + contained_value = None + super(StdExpOptionalPrinter, self).__init__(contained_value, visualizer) + + def to_string(self): + if self._contained_value is None: + return "%s [no contained value]" % self._typename + if hasattr(self._visualizer, "children"): + return "%s containing %s" % (self._typename, self._visualizer.to_string()) + return self._typename + + +class StdVariantPrinter(SingleObjContainerPrinter): + """Print a std::variant.""" + + def __init__(self, typename, val): + alternatives = get_template_arg_list(val.type) + self._typename = strip_versioned_namespace(typename) + self._index = val["_M_index"] + if self._index >= len(alternatives): + self._contained_type = None + contained_value = None + visualizer = None + else: + self._contained_type = alternatives[int(self._index)] + addr = val["_M_u"]["_M_first"]["_M_storage"].address + contained_value = addr.cast(self._contained_type.pointer()).dereference() + visualizer = gdb.default_visualizer(contained_value) + super(StdVariantPrinter, self).__init__(contained_value, visualizer, "array") + + def to_string(self): + if self._contained_value is None: + return "%s [no contained value]" % self._typename + if hasattr(self._visualizer, "children"): + return "%s [index %d] containing %s" % ( + self._typename, + self._index, + self._visualizer.to_string(), + ) + return "%s [index %d]" % (self._typename, self._index) + + +class StdNodeHandlePrinter(SingleObjContainerPrinter): + """Print a container node handle.""" + + def __init__(self, typename, val): + self._value_type = val.type.template_argument(1) + nodetype = val.type.template_argument(2).template_argument(0) + self._is_rb_tree_node = is_specialization_of(nodetype.name, "_Rb_tree_node") + self._is_map_node = val.type.template_argument(0) != self._value_type + nodeptr = val["_M_ptr"] + if nodeptr: + if self._is_rb_tree_node: + contained_value = get_value_from_Rb_tree_node(nodeptr.dereference()) + else: + contained_value = get_value_from_aligned_membuf( + nodeptr["_M_storage"], self._value_type + ) + visualizer = gdb.default_visualizer(contained_value) + else: + contained_value = None + visualizer = None + optalloc = val["_M_alloc"] + self._alloc = optalloc["_M_payload"] if optalloc["_M_engaged"] else None + super(StdNodeHandlePrinter, self).__init__(contained_value, visualizer, "array") + + def to_string(self): + desc = "node handle for " + if not self._is_rb_tree_node: + desc += "unordered " + if self._is_map_node: + desc += "map" + else: + desc += "set" + + if self._contained_value: + desc += " with element" + if hasattr(self._visualizer, "children"): + return "%s = %s" % (desc, self._visualizer.to_string()) + return desc + else: + return "empty %s" % desc + + +class StdExpStringViewPrinter(printer_base): + """ + Print a std::basic_string_view or std::experimental::basic_string_view + """ + + def __init__(self, typename, val): + self._val = val + + def to_string(self): + ptr = self._val["_M_str"] + len = self._val["_M_len"] + if hasattr(ptr, "lazy_string"): + return ptr.lazy_string(length=len) + return ptr.string(length=len) + + def display_hint(self): + return "string" + + +class StdExpPathPrinter(printer_base): + """Print a std::experimental::filesystem::path.""" + + def __init__(self, typename, val): + self._val = val + self._typename = typename + start = self._val["_M_cmpts"]["_M_impl"]["_M_start"] + finish = self._val["_M_cmpts"]["_M_impl"]["_M_finish"] + self._num_cmpts = int(finish - start) + + def _path_type(self): + t = str(self._val["_M_type"]) + if t[-9:] == "_Root_dir": + return "root-directory" + if t[-10:] == "_Root_name": + return "root-name" + return None + + def to_string(self): + path = "%s" % self._val["_M_pathname"] + if self._num_cmpts == 0: + t = self._path_type() + if t: + path = "%s [%s]" % (path, t) + return "experimental::filesystem::path %s" % path + + class _iterator(Iterator): + def __init__(self, cmpts, pathtype): + self._pathtype = pathtype + self._item = cmpts["_M_impl"]["_M_start"] + self._finish = cmpts["_M_impl"]["_M_finish"] + self._count = 0 + + def __iter__(self): + return self + + def __next__(self): + if self._item == self._finish: + raise StopIteration + item = self._item.dereference() + count = self._count + self._count = self._count + 1 + self._item = self._item + 1 + path = item["_M_pathname"] + t = StdExpPathPrinter(self._pathtype, item)._path_type() + if not t: + t = count + return ("[%s]" % t, path) + + def children(self): + return self._iterator(self._val["_M_cmpts"], self._typename) + + +class StdPathPrinter(printer_base): + """Print a std::filesystem::path.""" + + def __init__(self, typename, val): + self._val = val + self._typename = typename + impl = unique_ptr_get(self._val["_M_cmpts"]["_M_impl"]) + self._type = impl.cast(gdb.lookup_type("uintptr_t")) & 3 + if self._type == 0: + self._impl = impl + else: + self._impl = None + + def _path_type(self): + t = str(self._type.cast(gdb.lookup_type(self._typename + "::_Type"))) + if t[-9:] == "_Root_dir": + return "root-directory" + if t[-10:] == "_Root_name": + return "root-name" + return None + + def to_string(self): + path = "%s" % self._val["_M_pathname"] + if self._type != 0: + t = self._path_type() + if t: + path = "%s [%s]" % (path, t) + return "filesystem::path %s" % path + + class _iterator(Iterator): + def __init__(self, impl, pathtype): + self._pathtype = pathtype + if impl: + # We can't access _Impl::_M_size because _Impl is incomplete + # so cast to int* to access the _M_size member at offset zero, + int_type = gdb.lookup_type("int") + cmpt_type = gdb.lookup_type(pathtype + "::_Cmpt") + char_type = gdb.lookup_type("char") + impl = impl.cast(int_type.pointer()) + size = impl.dereference() + # self._capacity = (impl + 1).dereference() + if hasattr(gdb.Type, "alignof"): + sizeof_Impl = max(2 * int_type.sizeof, cmpt_type.alignof) + else: + sizeof_Impl = 2 * int_type.sizeof + begin = impl.cast(char_type.pointer()) + sizeof_Impl + self._item = begin.cast(cmpt_type.pointer()) + self._finish = self._item + size + self._count = 0 + else: + self._item = None + self._finish = None + + def __iter__(self): + return self + + def __next__(self): + if self._item == self._finish: + raise StopIteration + item = self._item.dereference() + count = self._count + self._count = self._count + 1 + self._item = self._item + 1 + path = item["_M_pathname"] + t = StdPathPrinter(self._pathtype, item)._path_type() + if not t: + t = count + return ("[%s]" % t, path) + + def children(self): + return self._iterator(self._impl, self._typename) + + +class StdPairPrinter(printer_base): + """Print a std::pair object, with 'first' and 'second' as children.""" + + def __init__(self, typename, val): + self._val = val + + class _iter(Iterator): + """An iterator for std::pair types. Returns 'first' then 'second'.""" + + def __init__(self, val): + self._val = val + self._which = "first" + + def __iter__(self): + return self + + def __next__(self): + if self._which is None: + raise StopIteration + which = self._which + if which == "first": + self._which = "second" + else: + self._which = None + return (which, self._val[which]) + + def children(self): + return self._iter(self._val) + + def to_string(self): + return None + + +class StdCmpCatPrinter(printer_base): + """Print a comparison category object.""" + + def __init__(self, typename, val): + self._typename = typename[typename.rfind(":") + 1 :] + self._val = val["_M_value"] + + def to_string(self): + if self._typename == "strong_ordering" and self._val == 0: + name = "equal" + else: + names = {2: "unordered", -1: "less", 0: "equivalent", 1: "greater"} + name = names[int(self._val)] + return "std::{}::{}".format(self._typename, name) + + +class StdErrorCodePrinter(printer_base): + """Print a std::error_code or std::error_condition.""" + + _system_is_posix = None # Whether std::system_category() use errno values. + + def __init__(self, typename, val): + self._val = val + self._typename = strip_versioned_namespace(typename) + # Do this only once ... + if StdErrorCodePrinter._system_is_posix is None: + try: + import posix + + StdErrorCodePrinter._system_is_posix = True + except ImportError: + StdErrorCodePrinter._system_is_posix = False + + @staticmethod + def _find_errc_enum(name): + typ = gdb.lookup_type(name) + if typ is not None and typ.code == gdb.TYPE_CODE_ENUM: + return typ + return None + + @classmethod + def _find_standard_errc_enum(cls, name): + for ns in ["", _versioned_namespace]: + try: + qname = "std::{}{}".format(ns, name) + return cls._find_errc_enum(qname) + except RuntimeError: + pass + + @classmethod + def _match_net_ts_category(cls, cat): + net_cats = ["stream", "socket", "ip::resolver"] + for c in net_cats: + func = c + "_category()" + for ns in ["", _versioned_namespace]: + ns = "std::{}experimental::net::v1".format(ns) + sym = gdb.lookup_symbol("{}::{}::__c".format(ns, func))[0] + if sym is not None: + if cat == sym.value().address: + name = "net::" + func + enum = cls._find_errc_enum("{}::{}_errc".format(ns, c)) + return (name, enum) + return (None, None) + + @classmethod + def _category_info(cls, cat): + """Return details of a std::error_category.""" + + name = None + enum = None + is_errno = False + + # Try these first, or we get "warning: RTTI symbol not found" when + # using cat.dynamic_type on the local class types for Net TS + # categories. + func, enum = cls._match_net_ts_category(cat) + if func is not None: + return (None, func, enum, is_errno) + + # This might give a warning for a program-defined category defined as + # a local class, but there doesn't seem to be any way to avoid that. + typ = cat.dynamic_type.target() + # Shortcuts for the known categories defined by libstdc++. + if typ.tag.endswith("::generic_error_category"): + name = "generic" + is_errno = True + if typ.tag.endswith("::system_error_category"): + name = "system" + is_errno = cls._system_is_posix + if typ.tag.endswith("::future_error_category"): + name = "future" + enum = cls._find_standard_errc_enum("future_errc") + if typ.tag.endswith("::io_error_category"): + name = "io" + enum = cls._find_standard_errc_enum("io_errc") + + if name is None: + try: + # Want to call std::error_category::name() override, but it's + # unsafe: https://sourceware.org/bugzilla/show_bug.cgi?id=28856 + # gdb.set_convenience_variable('__cat', cat) + # return '"%s"' % gdb.parse_and_eval('$__cat->name()').string() + pass + except: + pass + return (name, typ.tag, enum, is_errno) + + @staticmethod + def _unqualified_name(name): + """ + Strip any nested-name-specifier from name to give an unqualified name. + """ + return name.split("::")[-1] + + def to_string(self): + value = self._val["_M_value"] + cat = self._val["_M_cat"] + name, alt_name, enum, is_errno = self._category_info(cat) + if value == 0: + default_cats = {"error_code": "system", "error_condition": "generic"} + if name == default_cats[self._unqualified_name(self._typename)]: + return self._typename + " = { }" # default-constructed value + + strval = str(value) + if is_errno and value != 0: + try: + strval = errno.errorcode[int(value)] + except: + pass + elif enum is not None: + strval = self._unqualified_name(str(value.cast(enum))) + + if name is not None: + name = '"%s"' % name + else: + name = alt_name + return "%s = {%s: %s}" % (self._typename, name, strval) + + +class StdRegexStatePrinter(printer_base): + """Print a state node in the NFA for a std::regex.""" + + def __init__(self, typename, val): + self._val = val + self._typename = typename + + def to_string(self): + opcode = str(self._val["_M_opcode"]) + if opcode: + opcode = opcode[25:] + next_id = self._val["_M_next"] + + variants = { + "repeat": "alt", + "alternative": "alt", + "subexpr_begin": "subexpr", + "subexpr_end": "subexpr", + "line_begin_assertion": None, + "line_end_assertion": None, + "word_boundary": "neg", + "subexpr_lookahead": "neg", + "backref": "backref_index", + "match": None, + "accept": None, + "dummy": None, + "unknown": None, + } + v = variants[opcode] + + s = "opcode={}, next={}".format(opcode, next_id) + if v is not None and self._val["_M_" + v] is not None: + s = "{}, {}={}".format(s, v, self._val["_M_" + v]) + return "{%s}" % (s) + + +class StdSpanPrinter(printer_base): + """Print a std::span.""" + + class _iterator(Iterator): + def __init__(self, begin, size): + self._count = 0 + self._begin = begin + self._size = size + + def __iter__(self): + return self + + def __next__(self): + if self._count == self._size: + raise StopIteration + + count = self._count + self._count = self._count + 1 + return "[%d]" % count, (self._begin + count).dereference() + + def __init__(self, typename, val): + self._typename = strip_versioned_namespace(typename) + self._val = val + size_max = gdb.parse_and_eval("static_cast(-1)") + if val.type.template_argument(1) == size_max: + self._size = val["_M_extent"]["_M_extent_value"] + else: + self._size = val.type.template_argument(1) + + def to_string(self): + return "%s of length %d" % (self._typename, self._size) + + def children(self): + return self._iterator(self._val["_M_ptr"], self._size) + + def display_hint(self): + return "array" + + +class StdInitializerListPrinter(printer_base): + """Print a std::initializer_list.""" + + def __init__(self, typename, val): + self._typename = typename + self._val = val + self._size = val["_M_len"] + + def to_string(self): + return "%s of length %d" % (self._typename, self._size) + + def children(self): + return StdSpanPrinter._iterator(self._val["_M_array"], self._size) + + def display_hint(self): + return "array" + + +class StdAtomicPrinter(printer_base): + """Print a std:atomic.""" + + def __init__(self, typename, val): + self._typename = strip_versioned_namespace(typename) + self._val = val + self._shptr_printer = None + self._value_type = self._val.type.template_argument(0) + if self._value_type.tag is not None: + typ = strip_versioned_namespace(self._value_type.tag) + if typ.startswith("std::shared_ptr<") or typ.startswith("std::weak_ptr<"): + impl = val["_M_impl"] + self._shptr_printer = SharedPointerPrinter(typename, impl) + self.children = self._shptr_children + + def _shptr_children(self): + return SmartPtrIterator(self._shptr_printer._pointer) + + def to_string(self): + if self._shptr_printer is not None: + return self._shptr_printer.to_string() + + if self._value_type.code == gdb.TYPE_CODE_INT: + val = self._val["_M_i"] + elif self._value_type.code == gdb.TYPE_CODE_FLT: + val = self._val["_M_fp"] + elif self._value_type.code == gdb.TYPE_CODE_PTR: + val = self._val["_M_b"]["_M_p"] + elif self._value_type.code == gdb.TYPE_CODE_BOOL: + val = self._val["_M_base"]["_M_i"] + else: + val = self._val["_M_i"] + return "%s<%s> = { %s }" % (self._typename, str(self._value_type), val) + + +class StdFormatArgsPrinter(printer_base): + """Print a std::basic_format_args.""" + + # TODO: add printer for basic_format_arg and print out children. + # TODO: add printer for __format::_ArgStore. + + def __init__(self, typename, val): + self._typename = strip_versioned_namespace(typename) + self._val = val + + def to_string(self): + targs = get_template_arg_list(self._val.type) + char_type = get_template_arg_list(targs[0])[1] + if char_type == gdb.lookup_type("char"): + typ = "std::format_args" + elif char_type == gdb.lookup_type("wchar_t"): + typ = "std::wformat_args" + else: + typ = "std::basic_format_args" + + size = self._val["_M_packed_size"] + if size == 1: + return "%s with 1 argument" % (typ) + if size == 0: + size = self._val["_M_unpacked_size"] + return "%s with %d arguments" % (typ, size) + + +class StdChronoDurationPrinter(printer_base): + """Print a std::chrono::duration.""" + + def __init__(self, typename, val): + self._typename = strip_versioned_namespace(typename) + self._val = val + + def _ratio(self): + # TODO use reduced period i.e. duration::period + period = self._val.type.template_argument(1) + num = period.template_argument(0) + den = period.template_argument(1) + return (num, den) + + def _suffix(self): + num, den = self._ratio() + if num == 1: + if den == 1: + return "s" + if den == 1000: + return "ms" + if den == 1000000: + return "us" + if den == 1000000000: + return "ns" + elif den == 1: + if num == 60: + return "min" + if num == 3600: + return "h" + if num == 86400: + return "d" + return "[{}]s".format(num) + return "[{}/{}]s".format(num, den) + + def to_string(self): + r = self._val["__r"] + if r.type.strip_typedefs().code == gdb.TYPE_CODE_FLT: + r = "%g" % r + return "std::chrono::duration = {{ {}{} }}".format(r, self._suffix()) + + +class StdChronoTimePointPrinter(printer_base): + """Print a std::chrono::time_point.""" + + def __init__(self, typename, val): + self._typename = strip_versioned_namespace(typename) + self._val = val + + def _clock(self): + clock = self._val.type.template_argument(0) + name = strip_versioned_namespace(clock.name) + if ( + name == "std::chrono::_V2::system_clock" + or name == "std::chrono::system_clock" + ): + return ("std::chrono::sys_time", 0) + # XXX need to remove leap seconds from utc, gps, and tai + if name == "std::chrono::utc_clock": + return ("std::chrono::utc_time", None) # XXX + if name == "std::chrono::gps_clock": + return ("std::chrono::gps_time", None) # XXX 315964809 + if name == "std::chrono::tai_clock": + return ("std::chrono::tai_time", None) # XXX -378691210 + if name == "std::filesystem::__file_clock": + return ("std::chrono::file_time", 6437664000) + if name == "std::chrono::local_t": + return ("std::chrono::local_time", 0) + return ("{} time_point".format(name), None) + + def to_string(self, abbrev=False): + clock, offset = self._clock() + d = self._val["__d"] + r = d["__r"] + printer = StdChronoDurationPrinter(d.type.name, d) + suffix = printer._suffix() + time = "" + if offset is not None: + num, den = printer._ratio() + secs = (r * num / den) + offset + try: + dt = datetime.datetime.fromtimestamp(secs, _utc_timezone) + time = " [{:%Y-%m-%d %H:%M:%S}]".format(dt) + except: + pass + s = "%d%s%s" % (r, suffix, time) + if abbrev: + return s + return "%s = { %s }" % (clock, s) + + +class StdChronoZonedTimePrinter(printer_base): + """Print a std::chrono::zoned_time.""" + + def __init__(self, typename, val): + self._typename = strip_versioned_namespace(typename) + self._val = val + + def to_string(self): + zone = self._val["_M_zone"].dereference()["_M_name"] + time = self._val["_M_tp"] + printer = StdChronoTimePointPrinter(time.type.name, time) + time = printer.to_string(True) + return "std::chrono::zoned_time = {{ {} {} }}".format(zone, time) + + +months = [ + None, + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December", +] + +weekdays = [ + "Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday", + "Sunday", +] + + +class StdChronoCalendarPrinter(printer_base): + """Print a std::chrono::day, std::chrono::month, std::chrono::year etc.""" + + def __init__(self, typename, val): + self._typename = strip_versioned_namespace(typename) + self._val = val + + def to_string(self): + val = self._val + typ = self._typename + if "month" in typ and typ != "std::chrono::year_month_day_last": + m = val["_M_m"] + if typ.startswith("std::chrono::year"): + y = val["_M_y"] + + if typ == "std::chrono::day": + return "{}".format(int(val["_M_d"])) + if typ == "std::chrono::month": + if m < 1 or m >= len(months): + return "%d is not a valid month" % m + return months[m] + if typ == "std::chrono::year": + return "{}y".format(y) + if typ == "std::chrono::weekday": + wd = val["_M_wd"] + if wd < 0 or wd >= len(weekdays): + return "%d is not a valid weekday" % wd + return "{}".format(weekdays[wd]) + if typ == "std::chrono::weekday_indexed": + return "{}[{}]".format(val["_M_wd"], int(val["_M_index"])) + if typ == "std::chrono::weekday_last": + return "{}[last]".format(val["_M_wd"]) + if typ == "std::chrono::month_day": + return "{}/{}".format(m, val["_M_d"]) + if typ == "std::chrono::month_day_last": + return "{}/last".format(m) + if typ == "std::chrono::month_weekday": + return "{}/{}".format(m, val["_M_wdi"]) + if typ == "std::chrono::month_weekday_last": + return "{}/{}".format(m, val["_M_wdl"]) + if typ == "std::chrono::year_month": + return "{}/{}".format(y, m) + if typ == "std::chrono::year_month_day": + return "{}/{}/{}".format(y, m, val["_M_d"]) + if typ == "std::chrono::year_month_day_last": + return "{}/{}".format(y, val["_M_mdl"]) + if typ == "std::chrono::year_month_weekday": + return "{}/{}/{}".format(y, m, val["_M_wdi"]) + if typ == "std::chrono::year_month_weekday_last": + return "{}/{}/{}".format(y, m, val["_M_wdl"]) + if typ.startswith("std::chrono::hh_mm_ss"): + fract = "" + if val["fractional_width"] != 0: + fract = ".{:0{}d}".format( + int(val["_M_ss"]["_M_r"]), int(val["fractional_width"]) + ) + h = int(val["_M_h"]["__r"]) + m = int(val["_M_m"]["__r"]) + s = int(val["_M_s"]["__r"]) + if val["_M_is_neg"]: + h = -h + return "{:02}:{:02}:{:02}{}".format(h, m, s, fract) + + +class StdChronoTimeZonePrinter(printer_base): + """Print a chrono::time_zone or chrono::time_zone_link.""" + + def __init__(self, typename, val): + self._typename = strip_versioned_namespace(typename) + self._val = val + + def to_string(self): + str = "%s = %s" % (self._typename, self._val["_M_name"]) + if self._typename.endswith("_link"): + str += " -> %s" % (self._val["_M_target"]) + return str + + +class StdChronoLeapSecondPrinter(printer_base): + """Print a chrono::leap_second.""" + + def __init__(self, typename, val): + self._typename = strip_versioned_namespace(typename) + self._val = val + + def to_string(self): + date = self._val["_M_s"]["__r"] + neg = "+-"[date < 0] + return "%s %d (%c)" % (self._typename, abs(date), neg) + + +class StdChronoTzdbPrinter(printer_base): + """Print a chrono::tzdb.""" + + def __init__(self, typename, val): + self._typename = strip_versioned_namespace(typename) + self._val = val + + def to_string(self): + return "%s %s" % (self._typename, self._val["version"]) + + +class StdChronoTimeZoneRulePrinter(printer_base): + """Print a chrono::time_zone rule.""" + + def __init__(self, typename, val): + self._typename = strip_versioned_namespace(typename) + self._val = val + + def to_string(self): + on = self._val["on"] + kind = on["kind"] + month = months[on["month"]] + suffixes = {1: "st", 2: "nd", 3: "rd", 21: "st", 22: "nd", 23: "rd", 31: "st"} + day = on["day_of_month"] + ordinal_day = "{}{}".format(day, suffixes.get(day, "th")) + if kind == 0: # DayOfMonth + start = "{} {}".format(month, ordinal_day) + else: + weekday = weekdays[on["day_of_week"]] + if kind == 1: # LastWeekDay + start = "last {} in {}".format(weekday, month) + else: + if kind == 2: # LessEq + direction = ("last", "<=") + else: + direction = ("first", ">=") + day = on["day_of_month"] + start = "{} {} {} {} {}".format( + direction[0], weekday, direction[1], month, ordinal_day + ) + return "time_zone rule {} from {} to {} starting on {}".format( + self._val["name"], self._val["from"], self._val["to"], start + ) + + +class StdLocalePrinter(printer_base): + """Print a std::locale.""" + + def __init__(self, typename, val): + self._val = val + self._typename = typename + + def to_string(self): + names = self._val["_M_impl"]["_M_names"] + mod = "" + if names[0] == 0: + name = "*" + else: + cats = gdb.parse_and_eval(self._typename + "::_S_categories") + ncat = gdb.parse_and_eval(self._typename + "::_S_categories_size") + n = names[0].string() + cat = cats[0].string() + name = "{}={}".format(cat, n) + cat_names = {cat: n} + i = 1 + while i < ncat and names[i] != 0: + n = names[i].string() + cat = cats[i].string() + name = "{};{}={}".format(name, cat, n) + cat_names[cat] = n + i = i + 1 + uniq_names = set(cat_names.values()) + if len(uniq_names) == 1: + name = n + elif len(uniq_names) == 2: + n1, n2 = uniq_names + name_list = list(cat_names.values()) + other = None + if name_list.count(n1) == 1: + name = n2 + other = n1 + elif name_list.count(n2) == 1: + name = n1 + other = n2 + if other is not None: + cat = next(c for c, n in cat_names.items() if n == other) + mod = ' with "{}={}"'.format(cat, other) + return 'std::locale = "{}"{}'.format(name, mod) + + +class StdIntegralConstantPrinter(printer_base): + """Print a std::true_type or std::false_type.""" + + def __init__(self, typename, val): + self._val = val + self._typename = typename + + def to_string(self): + value_type = self._val.type.template_argument(0) + value = self._val.type.template_argument(1) + if value_type.code == gdb.TYPE_CODE_BOOL: + if value: + return "std::true_type" + else: + return "std::false_type" + typename = strip_versioned_namespace(self._typename) + return "{}<{}, {}>".format(typename, value_type, value) + + +class StdTextEncodingPrinter(printer_base): + """Print a std::text_encoding.""" + + def __init__(self, typename, val): + self._val = val + self._typename = typename + + def to_string(self): + rep = self._val["_M_rep"].dereference() + if rep["_M_id"] == 1: + return self._val["_M_name"] + if rep["_M_id"] == 2: + return "unknown" + return rep["_M_name"] + + +# A "regular expression" printer which conforms to the +# "SubPrettyPrinter" protocol from gdb.printing. +class RxPrinter(object): + def __init__(self, name, function): + super(RxPrinter, self).__init__() + self.name = name + self._function = function + self.enabled = True + + def invoke(self, value): + if not self.enabled: + return None + + if value.type.code == gdb.TYPE_CODE_REF: + if hasattr(gdb.Value, "referenced_value"): + value = value.referenced_value() + + return self._function(self.name, value) + + +# A pretty-printer that conforms to the "PrettyPrinter" protocol from +# gdb.printing. It can also be used directly as an old-style printer. + + +class Printer(object): + def __init__(self, name): + super(Printer, self).__init__() + self.name = name + self._subprinters = [] + self._lookup = {} + self.enabled = True + self._compiled_rx = re.compile("^([a-zA-Z0-9_:]+)(<.*>)?$") + + def add(self, name, function): + # A small sanity check. + # FIXME + if not self._compiled_rx.match(name): + raise ValueError('libstdc++ programming error: "%s" does not match' % name) + printer = RxPrinter(name, function) + self._subprinters.append(printer) + self._lookup[name] = printer + + # Add a name using _GLIBCXX_BEGIN_NAMESPACE_VERSION. + def add_version(self, base, name, function): + self.add(base + name, function) + if "__cxx11" not in base: + vbase = re.sub( + "^(std|__gnu_cxx)::", r"\g<0>%s" % _versioned_namespace, base + ) + self.add(vbase + name, function) + + # Add a name using _GLIBCXX_BEGIN_NAMESPACE_CONTAINER. + def add_container(self, base, name, function): + self.add_version(base, name, function) + self.add_version(base + "__cxx1998::", name, function) + + @staticmethod + def get_basic_type(type): + # If it points to a reference, get the reference. + if type.code == gdb.TYPE_CODE_REF: + type = type.target() + + # Get the unqualified type, stripped of typedefs. + type = type.unqualified().strip_typedefs() + + return type.tag + + def __call__(self, val): + typename = self.get_basic_type(val.type) + if not typename: + return None + + # All the types we match are template types, so we can use a + # dictionary. + match = self._compiled_rx.match(typename) + if not match: + return None + + basename = match.group(1) + + if val.type.code == gdb.TYPE_CODE_REF: + if hasattr(gdb.Value, "referenced_value"): + val = val.referenced_value() + + if basename in self._lookup: + return self._lookup[basename].invoke(val) + + # Cannot find a pretty printer. Return None. + return None + + +libstdcxx_printer = None + + +class TemplateTypePrinter(object): + """ + A type printer for class templates with default template arguments. + + Recognizes specializations of class templates and prints them without + any template arguments that use a default template argument. + Type printers are recursively applied to the template arguments. + + e.g. replace 'std::vector >' with 'std::vector'. + """ + + def __init__(self, name, defargs): + self.name = name + self._defargs = defargs + self.enabled = True + + class _recognizer(object): + """The recognizer class for TemplateTypePrinter.""" + + def __init__(self, name, defargs): + self.name = name + self._defargs = defargs + # self._type_obj = None + + def recognize(self, type_obj): + """ + If type_obj is a specialization of self.name that uses all the + default template arguments for the class template, then return + a string representation of the type without default arguments. + Otherwise, return None. + """ + + if type_obj.tag is None: + return None + + if not type_obj.tag.startswith(self.name): + return None + + template_args = get_template_arg_list(type_obj) + displayed_args = [] + require_defaulted = False + for n in range(len(template_args)): + # The actual template argument in the type: + targ = template_args[n] + # The default template argument for the class template: + defarg = self._defargs.get(n) + if defarg is not None: + # Substitute other template arguments into the default: + defarg = defarg.format(*template_args) + # Fail to recognize the type (by returning None) + # unless the actual argument is the same as the default. + try: + if targ != gdb.lookup_type(defarg): + return None + except gdb.error: + # Type lookup failed, just use string comparison: + if targ.tag != defarg: + return None + # All subsequent args must have defaults: + require_defaulted = True + elif require_defaulted: + return None + else: + # Recursively apply recognizers to the template argument + # and add it to the arguments that will be displayed: + displayed_args.append(self._recognize_subtype(targ)) + + # This assumes no class templates in the nested-name-specifier: + template_name = type_obj.tag[0 : type_obj.tag.find("<")] + template_name = strip_inline_namespaces(template_name) + + return template_name + "<" + ", ".join(displayed_args) + ">" + + def _recognize_subtype(self, type_obj): + """Convert a gdb.Type to a string by applying recognizers, + or if that fails then simply converting to a string.""" + + if type_obj.code == gdb.TYPE_CODE_PTR: + return self._recognize_subtype(type_obj.target()) + "*" + if type_obj.code == gdb.TYPE_CODE_ARRAY: + type_str = self._recognize_subtype(type_obj.target()) + if str(type_obj.strip_typedefs()).endswith("[]"): + return type_str + "[]" # array of unknown bound + return "%s[%d]" % (type_str, type_obj.range()[1] + 1) + if type_obj.code == gdb.TYPE_CODE_REF: + return self._recognize_subtype(type_obj.target()) + "&" + if hasattr(gdb, "TYPE_CODE_RVALUE_REF"): + if type_obj.code == gdb.TYPE_CODE_RVALUE_REF: + return self._recognize_subtype(type_obj.target()) + "&&" + + type_str = gdb.types.apply_type_recognizers( + gdb.types.get_type_recognizers(), type_obj + ) + if type_str: + return type_str + return str(type_obj) + + def instantiate(self): + """Return a recognizer object for this type printer.""" + return self._recognizer(self.name, self._defargs) + + +def add_one_template_type_printer(obj, name, defargs): + """ + Add a type printer for a class template with default template arguments. + + Args: + name (str): The template-name of the class template. + defargs (dict int:string) The default template arguments. + + Types in defargs can refer to the Nth template-argument using {N} + (with zero-based indices). + + e.g. 'unordered_map' has these defargs: + { 2: 'std::hash<{0}>', + 3: 'std::equal_to<{0}>', + 4: 'std::allocator >' } + """ + printer = TemplateTypePrinter("std::" + name, defargs) + gdb.types.register_type_printer(obj, printer) + + # Add type printer for same type in debug namespace: + printer = TemplateTypePrinter("std::__debug::" + name, defargs) + gdb.types.register_type_printer(obj, printer) + + if "__cxx11" not in name: + # Add second type printer for same type in versioned namespace: + ns = "std::" + _versioned_namespace + # PR 86112 Cannot use dict comprehension here: + defargs = dict((n, d.replace("std::", ns)) for (n, d) in defargs.items()) + printer = TemplateTypePrinter(ns + name, defargs) + gdb.types.register_type_printer(obj, printer) + + # Add type printer for same type in debug namespace: + printer = TemplateTypePrinter("std::__debug::" + name, defargs) + gdb.types.register_type_printer(obj, printer) + + +class FilteringTypePrinter(object): + """ + A type printer that uses typedef names for common template specializations. + + Args: + template (str): The class template to recognize. + name (str): The typedef-name that will be used instead. + targ1 (str, optional): The first template argument. Defaults to None. + + Checks if a specialization of the class template 'template' is the same type + as the typedef 'name', and prints it as 'name' instead. + + e.g. if an instantiation of std::basic_istream is the same type as + std::istream then print it as std::istream. + + If targ1 is provided (not None), match only template specializations with + this type as the first template argument, e.g. if template='basic_string' + and targ1='char' then only match 'basic_string' and not + 'basic_string'. This rejects non-matching specializations + more quickly, without needing to do GDB type lookups. + """ + + def __init__(self, template, name, targ1=None): + self._template = template + self.name = name + self._targ1 = targ1 + self.enabled = True + + class _recognizer(object): + """The recognizer class for FilteringTypePrinter.""" + + def __init__(self, template, name, targ1): + self._template = template + self.name = name + self._targ1 = targ1 + self._type_obj = None + + def recognize(self, type_obj): + """ + If type_obj starts with self._template and is the same type as + self.name then return self.name, otherwise None. + """ + if type_obj.tag is None: + return None + + if self._type_obj is None: + if self._targ1 is not None: + s = "{}<{}".format(self._template, self._targ1) + if not type_obj.tag.startswith(s): + # Filter didn't match. + return None + elif not type_obj.tag.startswith(self._template): + # Filter didn't match. + return None + + try: + self._type_obj = gdb.lookup_type(self.name).strip_typedefs() + except: + pass + + if self._type_obj is None: + return None + + t1 = gdb.types.get_basic_type(self._type_obj) + t2 = gdb.types.get_basic_type(type_obj) + if t1 == t2: + return strip_inline_namespaces(self.name) + + # Workaround ambiguous typedefs matching both std:: and + # std::__cxx11:: symbols. + if self._template.split("::")[-1] == "basic_string": + s1 = self._type_obj.tag.replace("__cxx11::", "") + s2 = type_obj.tag.replace("__cxx11::", "") + if s1 == s2: + return strip_inline_namespaces(self.name) + + return None + + def instantiate(self): + """Return a recognizer object for this type printer.""" + return self._recognizer(self._template, self.name, self._targ1) + + +def add_one_type_printer(obj, template, name, targ1=None): + printer = FilteringTypePrinter("std::" + template, "std::" + name, targ1) + gdb.types.register_type_printer(obj, printer) + if "__cxx11" not in template: + ns = "std::" + _versioned_namespace + printer = FilteringTypePrinter(ns + template, ns + name, targ1) + gdb.types.register_type_printer(obj, printer) + + +def register_type_printers(obj): + global _use_type_printing + + if not _use_type_printing: + return + + # Add type printers for typedefs std::string, std::wstring etc. + for ch in ( + ("", "char"), + ("w", "wchar_t"), + ("u8", "char8_t"), + ("u16", "char16_t"), + ("u32", "char32_t"), + ): + add_one_type_printer(obj, "basic_string", ch[0] + "string", ch[1]) + add_one_type_printer(obj, "__cxx11::basic_string", ch[0] + "string", ch[1]) + # Typedefs for __cxx11::basic_string used to be in namespace __cxx11: + add_one_type_printer( + obj, "__cxx11::basic_string", "__cxx11::" + ch[0] + "string", ch[1] + ) + add_one_type_printer(obj, "basic_string_view", ch[0] + "string_view", ch[1]) + + # Add type printers for typedefs std::istream, std::wistream etc. + for ch in (("", "char"), ("w", "wchar_t")): + for x in ( + "ios", + "streambuf", + "istream", + "ostream", + "iostream", + "filebuf", + "ifstream", + "ofstream", + "fstream", + ): + add_one_type_printer(obj, "basic_" + x, ch[0] + x, ch[1]) + for x in ("stringbuf", "istringstream", "ostringstream", "stringstream"): + add_one_type_printer(obj, "basic_" + x, ch[0] + x, ch[1]) + # types are in __cxx11 namespace, but typedefs aren't: + add_one_type_printer(obj, "__cxx11::basic_" + x, ch[0] + x, ch[1]) + + # Add type printers for typedefs regex, wregex, cmatch, wcmatch etc. + for abi in ("", "__cxx11::"): + for ch in (("", "char"), ("w", "wchar_t")): + add_one_type_printer(obj, abi + "basic_regex", abi + ch[0] + "regex", ch[1]) + for ch in ("c", "s", "wc", "ws"): + add_one_type_printer(obj, abi + "match_results", abi + ch + "match") + for x in ("sub_match", "regex_iterator", "regex_token_iterator"): + add_one_type_printer(obj, abi + x, abi + ch + x) + + # Note that we can't have a printer for std::wstreampos, because + # it is the same type as std::streampos. + add_one_type_printer(obj, "fpos", "streampos") + + # Add type printers for typedefs. + for dur in ( + "nanoseconds", + "microseconds", + "milliseconds", + "seconds", + "minutes", + "hours", + "days", + "weeks", + "years", + "months", + ): + add_one_type_printer(obj, "chrono::duration", "chrono::" + dur) + + # Add type printers for typedefs. + add_one_type_printer(obj, "linear_congruential_engine", "minstd_rand0") + add_one_type_printer(obj, "linear_congruential_engine", "minstd_rand") + add_one_type_printer(obj, "mersenne_twister_engine", "mt19937") + add_one_type_printer(obj, "mersenne_twister_engine", "mt19937_64") + add_one_type_printer(obj, "subtract_with_carry_engine", "ranlux24_base") + add_one_type_printer(obj, "subtract_with_carry_engine", "ranlux48_base") + add_one_type_printer(obj, "discard_block_engine", "ranlux24") + add_one_type_printer(obj, "discard_block_engine", "ranlux48") + add_one_type_printer(obj, "shuffle_order_engine", "knuth_b") + + # Add type printers for experimental::basic_string_view typedefs. + ns = "experimental::fundamentals_v1::" + for ch in ( + ("", "char"), + ("w", "wchar_t"), + ("u8", "char8_t"), + ("u16", "char16_t"), + ("u32", "char32_t"), + ): + add_one_type_printer( + obj, ns + "basic_string_view", ns + ch[0] + "string_view", ch[1] + ) + + # Do not show defaulted template arguments in class templates. + add_one_template_type_printer(obj, "unique_ptr", {1: "std::default_delete<{0}>"}) + add_one_template_type_printer(obj, "deque", {1: "std::allocator<{0}>"}) + add_one_template_type_printer(obj, "forward_list", {1: "std::allocator<{0}>"}) + add_one_template_type_printer(obj, "list", {1: "std::allocator<{0}>"}) + add_one_template_type_printer(obj, "__cxx11::list", {1: "std::allocator<{0}>"}) + add_one_template_type_printer(obj, "vector", {1: "std::allocator<{0}>"}) + add_one_template_type_printer( + obj, + "map", + {2: "std::less<{0}>", 3: "std::allocator>"}, + ) + add_one_template_type_printer( + obj, + "multimap", + {2: "std::less<{0}>", 3: "std::allocator>"}, + ) + add_one_template_type_printer( + obj, "set", {1: "std::less<{0}>", 2: "std::allocator<{0}>"} + ) + add_one_template_type_printer( + obj, "multiset", {1: "std::less<{0}>", 2: "std::allocator<{0}>"} + ) + add_one_template_type_printer( + obj, + "unordered_map", + { + 2: "std::hash<{0}>", + 3: "std::equal_to<{0}>", + 4: "std::allocator>", + }, + ) + add_one_template_type_printer( + obj, + "unordered_multimap", + { + 2: "std::hash<{0}>", + 3: "std::equal_to<{0}>", + 4: "std::allocator>", + }, + ) + add_one_template_type_printer( + obj, + "unordered_set", + {1: "std::hash<{0}>", 2: "std::equal_to<{0}>", 3: "std::allocator<{0}>"}, + ) + add_one_template_type_printer( + obj, + "unordered_multiset", + {1: "std::hash<{0}>", 2: "std::equal_to<{0}>", 3: "std::allocator<{0}>"}, + ) + + +def register_libstdcxx_printers(obj): + """Register libstdc++ pretty-printers with objfile Obj.""" + + global _use_gdb_pp + global libstdcxx_printer + + if _use_gdb_pp: + gdb.printing.register_pretty_printer(obj, libstdcxx_printer) + else: + if obj is None: + obj = gdb + obj.pretty_printers.append(libstdcxx_printer) + + register_type_printers(obj) + + +def build_libstdcxx_dictionary(): + global libstdcxx_printer + + libstdcxx_printer = Printer("libstdc++-v6") + + # libstdc++ objects requiring pretty-printing. + # In order from: + # http://gcc.gnu.org/onlinedocs/libstdc++/latest-doxygen/a01847.html + libstdcxx_printer.add_version("std::", "basic_string", StdStringPrinter) + libstdcxx_printer.add_version("std::__cxx11::", "basic_string", StdStringPrinter) + libstdcxx_printer.add_container("std::", "bitset", StdBitsetPrinter) + libstdcxx_printer.add_container("std::", "deque", StdDequePrinter) + libstdcxx_printer.add_container("std::", "list", StdListPrinter) + libstdcxx_printer.add_container("std::__cxx11::", "list", StdListPrinter) + libstdcxx_printer.add_container("std::", "map", StdMapPrinter) + libstdcxx_printer.add_container("std::", "multimap", StdMapPrinter) + libstdcxx_printer.add_container("std::", "multiset", StdSetPrinter) + libstdcxx_printer.add_version("std::", "pair", StdPairPrinter) + libstdcxx_printer.add_version("std::", "priority_queue", StdStackOrQueuePrinter) + libstdcxx_printer.add_version("std::", "queue", StdStackOrQueuePrinter) + libstdcxx_printer.add_version("std::", "tuple", StdTuplePrinter) + libstdcxx_printer.add_container("std::", "set", StdSetPrinter) + libstdcxx_printer.add_version("std::", "stack", StdStackOrQueuePrinter) + libstdcxx_printer.add_version("std::", "unique_ptr", UniquePointerPrinter) + libstdcxx_printer.add_container("std::", "vector", StdVectorPrinter) + # vector + libstdcxx_printer.add_version("std::", "locale", StdLocalePrinter) + + if hasattr(gdb.Value, "dynamic_type"): + libstdcxx_printer.add_version("std::", "error_code", StdErrorCodePrinter) + libstdcxx_printer.add_version("std::", "error_condition", StdErrorCodePrinter) + + # Printer registrations for classes compiled with -D_GLIBCXX_DEBUG. + libstdcxx_printer.add("std::__debug::bitset", StdBitsetPrinter) + libstdcxx_printer.add("std::__debug::deque", StdDequePrinter) + libstdcxx_printer.add("std::__debug::list", StdListPrinter) + libstdcxx_printer.add("std::__debug::map", StdMapPrinter) + libstdcxx_printer.add("std::__debug::multimap", StdMapPrinter) + libstdcxx_printer.add("std::__debug::multiset", StdSetPrinter) + libstdcxx_printer.add("std::__debug::set", StdSetPrinter) + libstdcxx_printer.add("std::__debug::vector", StdVectorPrinter) + + # These are the TR1 and C++11 printers. + # For array - the default GDB pretty-printer seems reasonable. + libstdcxx_printer.add_version("std::", "shared_ptr", SharedPointerPrinter) + libstdcxx_printer.add_version("std::", "weak_ptr", SharedPointerPrinter) + libstdcxx_printer.add_container("std::", "unordered_map", Tr1UnorderedMapPrinter) + libstdcxx_printer.add_container("std::", "unordered_set", Tr1UnorderedSetPrinter) + libstdcxx_printer.add_container( + "std::", "unordered_multimap", Tr1UnorderedMapPrinter + ) + libstdcxx_printer.add_container( + "std::", "unordered_multiset", Tr1UnorderedSetPrinter + ) + libstdcxx_printer.add_container("std::", "forward_list", StdForwardListPrinter) + + libstdcxx_printer.add_version("std::tr1::", "shared_ptr", SharedPointerPrinter) + libstdcxx_printer.add_version("std::tr1::", "weak_ptr", SharedPointerPrinter) + libstdcxx_printer.add_version("std::tr1::", "unordered_map", Tr1UnorderedMapPrinter) + libstdcxx_printer.add_version("std::tr1::", "unordered_set", Tr1UnorderedSetPrinter) + libstdcxx_printer.add_version( + "std::tr1::", "unordered_multimap", Tr1UnorderedMapPrinter + ) + libstdcxx_printer.add_version( + "std::tr1::", "unordered_multiset", Tr1UnorderedSetPrinter + ) + + 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 + ) + for sstream in ("istringstream", "ostringstream", "stringstream"): + libstdcxx_printer.add_version( + "std::", "basic_" + sstream, StdStringStreamPrinter + ) + libstdcxx_printer.add_version( + "std::__cxx11::", "basic_" + sstream, StdStringStreamPrinter + ) + + libstdcxx_printer.add_version("std::chrono::", "duration", StdChronoDurationPrinter) + libstdcxx_printer.add_version( + "std::chrono::", "time_point", StdChronoTimePointPrinter + ) + libstdcxx_printer.add_version( + "std::", "integral_constant", StdIntegralConstantPrinter + ) + + # std::regex components + libstdcxx_printer.add_version("std::__detail::", "_State", StdRegexStatePrinter) + + # These are the C++11 printer registrations for -D_GLIBCXX_DEBUG cases. + # The tr1 namespace containers do not have any debug equivalents, + # so do not register printers for them. + libstdcxx_printer.add("std::__debug::unordered_map", Tr1UnorderedMapPrinter) + libstdcxx_printer.add("std::__debug::unordered_set", Tr1UnorderedSetPrinter) + libstdcxx_printer.add("std::__debug::unordered_multimap", Tr1UnorderedMapPrinter) + libstdcxx_printer.add("std::__debug::unordered_multiset", Tr1UnorderedSetPrinter) + libstdcxx_printer.add("std::__debug::forward_list", StdForwardListPrinter) + + # Library Fundamentals TS components + libstdcxx_printer.add_version( + "std::experimental::fundamentals_v1::", "any", StdExpAnyPrinter + ) + libstdcxx_printer.add_version( + "std::experimental::fundamentals_v1::", "optional", StdExpOptionalPrinter + ) + libstdcxx_printer.add_version( + "std::experimental::fundamentals_v1::", + "basic_string_view", + StdExpStringViewPrinter, + ) + # Filesystem TS components + libstdcxx_printer.add_version( + "std::experimental::filesystem::v1::", "path", StdExpPathPrinter + ) + libstdcxx_printer.add_version( + "std::experimental::filesystem::v1::__cxx11::", "path", StdExpPathPrinter + ) + libstdcxx_printer.add_version("std::filesystem::", "path", StdPathPrinter) + libstdcxx_printer.add_version("std::filesystem::__cxx11::", "path", StdPathPrinter) + + # C++17 components + libstdcxx_printer.add_version("std::", "any", StdExpAnyPrinter) + libstdcxx_printer.add_version("std::", "optional", StdExpOptionalPrinter) + libstdcxx_printer.add_version("std::", "basic_string_view", StdExpStringViewPrinter) + libstdcxx_printer.add_version("std::", "variant", StdVariantPrinter) + libstdcxx_printer.add_version("std::", "_Node_handle", StdNodeHandlePrinter) + + # C++20 components + libstdcxx_printer.add_version("std::", "partial_ordering", StdCmpCatPrinter) + libstdcxx_printer.add_version("std::", "weak_ordering", StdCmpCatPrinter) + libstdcxx_printer.add_version("std::", "strong_ordering", StdCmpCatPrinter) + libstdcxx_printer.add_version("std::", "span", StdSpanPrinter) + libstdcxx_printer.add_version("std::", "basic_format_args", StdFormatArgsPrinter) + for c in [ + "day", + "month", + "year", + "weekday", + "weekday_indexed", + "weekday_last", + "month_day", + "month_day_last", + "month_weekday", + "month_weekday_last", + "year_month", + "year_month_day", + "year_month_day_last", + "year_month_weekday", + "year_month_weekday_last", + "hh_mm_ss", + ]: + libstdcxx_printer.add_version("std::chrono::", c, StdChronoCalendarPrinter) + libstdcxx_printer.add_version( + "std::chrono::", "time_zone", StdChronoTimeZonePrinter + ) + libstdcxx_printer.add_version( + "std::chrono::", "time_zone_link", StdChronoTimeZonePrinter + ) + libstdcxx_printer.add_version( + "std::chrono::", "zoned_time", StdChronoZonedTimePrinter + ) + libstdcxx_printer.add_version( + "std::chrono::", "leap_second", StdChronoLeapSecondPrinter + ) + libstdcxx_printer.add_version("std::chrono::", "tzdb", StdChronoTzdbPrinter) + # libstdcxx_printer.add_version('std::chrono::(anonymous namespace)', 'Rule', + # StdChronoTimeZoneRulePrinter) + + # C++26 components + libstdcxx_printer.add_version("std::", "text_encoding", StdTextEncodingPrinter) + # Extensions. + libstdcxx_printer.add_version("__gnu_cxx::", "slist", StdSlistPrinter) + + if True: + # These shouldn't be necessary, if GDB "print *i" worked. + # But it often doesn't, so here they are. + libstdcxx_printer.add_container( + "std::", "_List_iterator", StdListIteratorPrinter + ) + libstdcxx_printer.add_container( + "std::", "_List_const_iterator", StdListIteratorPrinter + ) + libstdcxx_printer.add_version( + "std::", "_Rb_tree_iterator", StdRbtreeIteratorPrinter + ) + libstdcxx_printer.add_version( + "std::", "_Rb_tree_const_iterator", StdRbtreeIteratorPrinter + ) + libstdcxx_printer.add_container( + "std::", "_Deque_iterator", StdDequeIteratorPrinter + ) + libstdcxx_printer.add_container( + "std::", "_Deque_const_iterator", StdDequeIteratorPrinter + ) + libstdcxx_printer.add_version( + "__gnu_cxx::", "__normal_iterator", StdVectorIteratorPrinter + ) + libstdcxx_printer.add_container("std::", "_Bit_iterator", StdBitIteratorPrinter) + libstdcxx_printer.add_container( + "std::", "_Bit_const_iterator", StdBitIteratorPrinter + ) + libstdcxx_printer.add_container( + "std::", "_Bit_reference", StdBitReferencePrinter + ) + libstdcxx_printer.add_version( + "__gnu_cxx::", "_Slist_iterator", StdSlistIteratorPrinter + ) + libstdcxx_printer.add_container( + "std::", "_Fwd_list_iterator", StdFwdListIteratorPrinter + ) + libstdcxx_printer.add_container( + "std::", "_Fwd_list_const_iterator", StdFwdListIteratorPrinter + ) + + # Debug (compiled with -D_GLIBCXX_DEBUG) printer + # registrations. + libstdcxx_printer.add("__gnu_debug::_Safe_iterator", StdDebugIteratorPrinter) + + +build_libstdcxx_dictionary() diff --git a/gdb/testsuite/gdb.cp/pretty-print.cc b/gdb/testsuite/gdb.cp/pretty-print.cc new file mode 100644 index 00000000000..eb16757c936 --- /dev/null +++ b/gdb/testsuite/gdb.cp/pretty-print.cc @@ -0,0 +1,80 @@ +/* This testcase is part of GDB, the GNU debugger. + + Copyright 2025 Free Software Foundation, Inc. + + 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 . */ + +namespace std +{ + +inline namespace __cxx11 {} + +template struct allocator {}; + +template struct char_traits; + +namespace __cxx11 { + +template , + typename = allocator<_CharT>> + +class basic_string; + +} // namespace __cx11 + +typedef basic_string string; + +template struct allocator_traits; + +template struct allocator_traits> { + using pointer = _Tp *; +}; + +} // namespace std + +struct __alloc_traits : std::allocator_traits> {}; + +namespace std +{ + +namespace __cxx11 +{ + +template struct basic_string +{ + typedef long size_type; + + size_type npos; + + struct _Alloc_hider + { + _Alloc_hider(__alloc_traits::pointer, const allocator &); + } _M_dataplus; + + basic_string(char *, allocator __a = allocator()) + : _M_dataplus(0, __a) {} +}; + +} // namespace __cxx11 + +} // namespace std + +static char bar[] = "bar"; + +int +main (void) +{ + std::string foo = bar; + return 0; +} diff --git a/gdb/testsuite/gdb.cp/pretty-print.exp b/gdb/testsuite/gdb.cp/pretty-print.exp new file mode 100644 index 00000000000..51f94d07f33 --- /dev/null +++ b/gdb/testsuite/gdb.cp/pretty-print.exp @@ -0,0 +1,41 @@ +# Copyright 2025 Free Software Foundation, Inc. + +# 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 . + +standard_testfile .cc + +set opts {} +lappend opts debug +lappend opts c++ +lappend opts additional_flags=-fno-eliminate-unused-debug-symbols + +if {[prepare_for_testing "failed to prepare" $testfile $srcfile $opts]} { + return -1 +} + +# Load copy of /usr/share/gcc-15/python/libstdcxx/v6/printers.py, slightly +# altered by pre-commit hooks. +gdb_test_no_output "source $srcdir/$subdir/gcc-15-libstdcxx-v6-printers.py" \ + "load libstdc++ pretty printers" + +gdb_test_no_output \ + "python register_libstdcxx_printers(gdb.current_objfile())" \ + "register libstdc++ pretty printers" + +gdb_test "whatis std::string" \ + "std::string" + +# Regression test for PR c++/33480. GDB used to hang. +gdb_test "whatis std::string::npos" \ + "std::string::size_type" base-commit: 5248165867f4265755dbc70f5e63f49deb54e536 -- 2.51.0