Fix parameter-less template regression in new DWARF reader

PR c++/29896 points out a regression in the new DWARF reader.  It does
not properly handle a case like "break fn", where "fn" is a template
function.

This happens because the new index uses strncasecmp to compare.
However, to make this work correctly, we need a custom function that
ignores template parameters.

This patch adds a custom comparison function and fixes the bug.  A new
test case is included.

Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=29896
This commit is contained in:
Tom Tromey 2022-12-14 14:37:41 -07:00
parent 5a89072f36
commit ac37b79cc4
5 changed files with 226 additions and 22 deletions

View File

@ -25,6 +25,114 @@
#include "ada-lang.h"
#include "split-name.h"
#include <algorithm>
#include "safe-ctype.h"
#include "gdbsupport/selftest.h"
/* See cooked-index.h. */
bool
cooked_index_entry::compare (const char *stra, const char *strb,
bool completing)
{
/* If we've ever matched "<" in both strings, then we disable the
special template parameter handling. */
bool seen_lt = false;
while (*stra != '\0'
&& *strb != '\0'
&& (TOLOWER ((unsigned char) *stra)
== TOLOWER ((unsigned char ) *strb)))
{
if (*stra == '<')
seen_lt = true;
++stra;
++strb;
}
unsigned c1 = TOLOWER ((unsigned char) *stra);
unsigned c2 = TOLOWER ((unsigned char) *strb);
if (completing)
{
/* When completing, if one string ends earlier than the other,
consider them as equal. Also, completion mode ignores the
special '<' handling. */
if (c1 == '\0' || c2 == '\0')
return false;
/* Fall through to the generic case. */
}
else if (seen_lt)
{
/* Fall through to the generic case. */
}
else if (c1 == '\0' || c1 == '<')
{
/* Maybe they both end at the same spot. */
if (c2 == '\0' || c2 == '<')
return false;
/* First string ended earlier. */
return true;
}
else if (c2 == '\0' || c2 == '<')
{
/* Second string ended earlier. */
return false;
}
return c1 < c2;
}
#if GDB_SELF_TEST
namespace {
void
test_compare ()
{
SELF_CHECK (!cooked_index_entry::compare ("abcd", "abcd", false));
SELF_CHECK (!cooked_index_entry::compare ("abcd", "abcd", false));
SELF_CHECK (!cooked_index_entry::compare ("abcd", "abcd", true));
SELF_CHECK (!cooked_index_entry::compare ("abcd", "abcd", true));
SELF_CHECK (cooked_index_entry::compare ("abcd", "ABCDE", false));
SELF_CHECK (!cooked_index_entry::compare ("ABCDE", "abcd", false));
SELF_CHECK (!cooked_index_entry::compare ("abcd", "ABCDE", true));
SELF_CHECK (!cooked_index_entry::compare ("ABCDE", "abcd", true));
SELF_CHECK (!cooked_index_entry::compare ("name", "name<>", false));
SELF_CHECK (!cooked_index_entry::compare ("name<>", "name", false));
SELF_CHECK (!cooked_index_entry::compare ("name", "name<>", true));
SELF_CHECK (!cooked_index_entry::compare ("name<>", "name", true));
SELF_CHECK (!cooked_index_entry::compare ("name<arg>", "name<arg>", false));
SELF_CHECK (!cooked_index_entry::compare ("name<arg>", "name<arg>", false));
SELF_CHECK (!cooked_index_entry::compare ("name<arg>", "name<arg>", true));
SELF_CHECK (!cooked_index_entry::compare ("name<arg>", "name<ag>", true));
SELF_CHECK (!cooked_index_entry::compare ("name<arg<more>>",
"name<arg<more>>", false));
SELF_CHECK (!cooked_index_entry::compare ("name", "name<arg<more>>", false));
SELF_CHECK (!cooked_index_entry::compare ("name<arg<more>>", "name", false));
SELF_CHECK (cooked_index_entry::compare ("name<arg<", "name<arg<more>>",
false));
SELF_CHECK (!cooked_index_entry::compare ("name<arg<",
"name<arg<more>>",
true));
SELF_CHECK (!cooked_index_entry::compare ("name<arg<more>>", "name<arg<",
false));
SELF_CHECK (!cooked_index_entry::compare ("name<arg<more>>", "name<arg<",
true));
SELF_CHECK (cooked_index_entry::compare ("", "abcd", false));
SELF_CHECK (!cooked_index_entry::compare ("", "abcd", true));
SELF_CHECK (!cooked_index_entry::compare ("abcd", "", false));
SELF_CHECK (!cooked_index_entry::compare ("abcd", "", true));
}
} /* anonymous namespace */
#endif /* GDB_SELF_TEST */
/* See cooked-index.h. */
@ -247,30 +355,24 @@ cooked_index::do_finalize ()
/* See cooked-index.h. */
cooked_index::range
cooked_index::find (gdb::string_view name, bool completing)
cooked_index::find (const std::string &name, bool completing)
{
wait ();
auto lower = std::lower_bound (m_entries.begin (), m_entries.end (),
name,
auto lower = std::lower_bound (m_entries.begin (), m_entries.end (), name,
[=] (const cooked_index_entry *entry,
const gdb::string_view &n)
const std::string &n)
{
int cmp = strncasecmp (entry->canonical, n.data (), n.length ());
if (cmp != 0 || completing)
return cmp < 0;
return strlen (entry->canonical) < n.length ();
return cooked_index_entry::compare (entry->canonical, n.c_str (),
completing);
});
auto upper = std::upper_bound (m_entries.begin (), m_entries.end (),
name,
[=] (const gdb::string_view &n,
auto upper = std::upper_bound (m_entries.begin (), m_entries.end (), name,
[=] (const std::string &n,
const cooked_index_entry *entry)
{
int cmp = strncasecmp (n.data (), entry->canonical, n.length ());
if (cmp != 0 || completing)
return cmp < 0;
return n.length () < strlen (entry->canonical);
return cooked_index_entry::compare (n.c_str (), entry->canonical,
completing);
});
return range (lower, upper);
@ -311,7 +413,7 @@ cooked_index_vector::get_addrmaps ()
/* See cooked-index.h. */
cooked_index_vector::range
cooked_index_vector::find (gdb::string_view name, bool completing)
cooked_index_vector::find (const std::string &name, bool completing)
{
std::vector<cooked_index::range> result_range;
result_range.reserve (m_vector.size ());
@ -339,3 +441,12 @@ cooked_index_vector::get_main () const
return result;
}
void _initialize_cooked_index ();
void
_initialize_cooked_index ()
{
#if GDB_SELF_TEST
selftests::register_test ("cooked_index_entry::compare", test_compare);
#endif
}

View File

@ -143,11 +143,16 @@ struct cooked_index_entry : public allocate_on_obstack
STORAGE. */
const char *full_name (struct obstack *storage) const;
/* Entries must be sorted case-insensitively; this compares two
entries. */
/* Compare two strings, case-insensitively. Return true if STRA is
less than STRB. If one string has template parameters, but the
other does not, then they are considered to be equal; so for
example "t<x>" == "t<x>", "t<x>" < "t<y>", but "t" == "t<x>". */
static bool compare (const char *stra, const char *strb, bool completing);
/* Compare two entries by canonical name. */
bool operator< (const cooked_index_entry &other) const
{
return strcasecmp (canonical, other.canonical) < 0;
return compare (canonical, other.canonical, false);
}
/* The name as it appears in DWARF. This always points into one of
@ -232,7 +237,7 @@ public:
/* Look up an entry by name. Returns a range of all matching
results. If COMPLETING is true, then a larger range, suitable
for completion, will be returned. */
range find (gdb::string_view name, bool completing);
range find (const std::string &name, bool completing);
private:
@ -335,7 +340,7 @@ public:
/* Look up an entry by name. Returns a range of all matching
results. If COMPLETING is true, then a larger range, suitable
for completion, will be returned. */
range find (gdb::string_view name, bool completing);
range find (const std::string &name, bool completing);
/* Return a range of all the entries. */
range all_entries ()

View File

@ -18741,8 +18741,9 @@ cooked_index_functions::expand_symtabs_matching
{
std::vector<gdb::string_view> name_vec
= lookup_name_without_params.split_name (lang);
std::string last_name = gdb::to_string (name_vec.back ());
for (const cooked_index_entry *entry : table->find (name_vec.back (),
for (const cooked_index_entry *entry : table->find (last_name,
completing))
{
QUIT;

View File

@ -0,0 +1,46 @@
/* Test case for template breakpoint test.
Copyright 2022-2023 Free Software Foundation, Inc.
This file is part of GDB.
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/>. */
template<typename T>
struct outer
{
template<typename Q = int>
int fn (int x)
{
return x + 23;
}
};
template<typename T = int>
int toplev (int y)
{
return y;
}
outer<int> outer1;
outer<char> outer2;
int main ()
{
int x1 = outer1.fn (0);
int x2 = outer2.fn<short> (-46);
int x3 = toplev<char> (0);
int x4 = toplev (0);
return x1 + x2;
}

View File

@ -0,0 +1,41 @@
# Copyright 2022-2023 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/>.
# This file is part of the gdb testsuite.
# Test template breakpoints without parameters.
if { [skip_cplus_tests] } { continue }
standard_testfile .cc
if {[prepare_for_testing "failed to prepare" $testfile $srcfile {debug c++}]} {
return -1
}
gdb_breakpoint "outer<int>::fn" message
delete_breakpoints
gdb_breakpoint "outer<char>::fn<short>" message
delete_breakpoints
gdb_test "break outer::fn" "Breakpoint $decimal at .*2 locations."
delete_breakpoints
gdb_test "break toplev" "Breakpoint $decimal at .*2 locations."
delete_breakpoints
gdb_breakpoint "toplev<char>" message
delete_breakpoints