]> git.ipfire.org Git - thirdparty/binutils-gdb.git/commitdiff
[gdb/c++] Fix hang on whatis std::string::npos
authorTom de Vries <tdevries@suse.de>
Thu, 16 Oct 2025 09:56:35 +0000 (11:56 +0200)
committerTom de Vries <tdevries@suse.de>
Thu, 16 Oct 2025 09:56:35 +0000 (11:56 +0200)
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<char, std::char_traits<char>, \
  std::allocator<char> >
+whatis std::string::npos
type = const std::__cxx11::basic_string<char, std::char_traits<char>, \
  std::allocator<char> >::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<char, std::char_traits<char>, \
  std::allocator<char> >::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
<HANG>
...

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], then
  hand-edited to fix warnings with gcc and clang.
- the pretty-printer .py file is a reduced version of
  /usr/share/gcc-15/python/libstdcxx/v6/printers.py.

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.

The test-case compiles with clang, but the hang didn't reproduce.

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
gdb/testsuite/gdb.cp/pretty-print.cc [new file with mode: 0644]
gdb/testsuite/gdb.cp/pretty-print.exp [new file with mode: 0644]
gdb/testsuite/gdb.cp/pretty-print.py [new file with mode: 0644]

index be66be95d6164b3835add8db41071b46d39c328f..18823dc5ecafc446de69382f6839ff100bc7121f 100644 (file)
@@ -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/pretty-print.cc b/gdb/testsuite/gdb.cp/pretty-print.cc
new file mode 100644 (file)
index 0000000..9065e15
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.  */
+
+namespace std
+{
+
+inline namespace __cxx11 {}
+
+template <typename> struct allocator {};
+
+template <class> struct char_traits;
+
+inline namespace __cxx11 {
+
+template <typename _CharT, typename = char_traits<_CharT>,
+         typename = allocator<_CharT>>
+
+class basic_string;
+
+} // namespace __cx11
+
+typedef basic_string<char> string;
+
+template <typename> struct allocator_traits;
+
+template <typename _Tp> struct allocator_traits<allocator<_Tp>> {
+  using pointer = _Tp *;
+};
+
+} // namespace std
+
+struct __alloc_traits : std::allocator_traits<std::allocator<char>> {};
+
+namespace std
+{
+
+inline namespace __cxx11
+{
+
+template <typename, typename, typename> struct basic_string
+{
+  typedef long size_type;
+
+  size_type npos;
+
+  struct _Alloc_hider
+  {
+    _Alloc_hider(__alloc_traits::pointer, const allocator<char> &);
+  } _M_dataplus;
+
+  basic_string(char *, allocator<char> __a = allocator<char>())
+    : _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 (file)
index 0000000..e1f0475
--- /dev/null
@@ -0,0 +1,53 @@
+# 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 <http://www.gnu.org/licenses/>.
+
+# Print std::string::npos using c++ pretty-printer.  Todo: convert this to a
+# DWARF assembly test-case, such that the regression test also works for clang.
+
+standard_testfile .cc
+
+set opts {}
+lappend opts debug
+lappend opts c++
+if {[have_compile_flag -fno-eliminate-unused-debug-symbols]} {
+    lappend opts additional_flags=-fno-eliminate-unused-debug-symbols
+    # Work around clang warning -Wunused-command-line-argument.
+    lappend opts nowarnings
+}
+
+if {[prepare_for_testing "failed to prepare" $testfile $srcfile $opts]} {
+    return
+}
+
+gdb_test_no_output "source $srcdir/$subdir/pretty-print.py" \
+    "load libstdc++ pretty printers"
+
+gdb_test_no_output \
+    "python register_type_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, at least if the GDB
+# build optimization flags triggered some uninitialized variables in the right
+# way.  I was not able to reproduce the hang with clang, due to different
+# debug info.
+#
+# Prints different strings due to different debug info:
+# - std::string::size_type with gcc, and
+# - size_type with clang
+gdb_test "whatis std::string::npos" \
+    "(std::string::)?size_type"
diff --git a/gdb/testsuite/gdb.cp/pretty-print.py b/gdb/testsuite/gdb.cp/pretty-print.py
new file mode 100644 (file)
index 0000000..6ee8d71
--- /dev/null
@@ -0,0 +1,82 @@
+# 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 <http://www.gnu.org/licenses/>.
+
+# Reduced copy of /usr/share/gcc-15/python/libstdcxx/v6/printers.py.
+
+import gdb
+import gdb.printing
+import gdb.types
+
+
+class FilteringTypePrinter(object):
+
+    def __init__(self, template, name, targ1=None):
+        self._template = template
+        self.name = name
+        self._targ1 = targ1
+        self.enabled = True
+
+    class _recognizer(object):
+        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.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):
+                        return None
+                elif not type_obj.tag.startswith(self._template):
+                    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 self.name
+
+            if self._template.split("::")[-1] == "basic_string":
+                s1 = self._type_obj.tag.replace("__cxx11::", "")
+                s2 = type_obj.tag.replace("__cxx11::", "")
+                if s1 == s2:
+                    return self.name
+
+            return None
+
+    def instantiate(self):
+        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)
+
+
+def register_type_printers(obj):
+    for ch in (("", "char"),):
+        add_one_type_printer(obj, "__cxx11::basic_string", ch[0] + "string", ch[1])