[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<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
This commit is contained in:
Tom de Vries
2025-10-16 11:38:53 +02:00
parent 5a7173cd46
commit fb81c8c1a6
4 changed files with 220 additions and 4 deletions

View 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;

View File

@@ -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;
}

View File

@@ -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"

View File

@@ -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])