/* DIE indexing
Copyright (C) 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 . */
#include "defs.h"
#include "dwarf2/cooked-index.h"
#include "dwarf2/read.h"
#include "cp-support.h"
#include "c-lang.h"
#include "ada-lang.h"
#include "split-name.h"
#include
#include "safe-ctype.h"
#include "gdbsupport/selftest.h"
/* See cooked-index.h. */
bool
language_requires_canonicalization (enum language lang)
{
return (lang == language_ada
|| lang == language_c
|| lang == language_cplus);
}
/* See cooked-index.h. */
int
cooked_index_entry::compare (const char *stra, const char *strb,
comparison_mode mode)
{
auto munge = [] (char c) -> unsigned char
{
/* We want to sort '<' before any other printable character.
So, rewrite '<' to something just before ' '. */
if (c == '<')
return '\x1f';
return TOLOWER ((unsigned char) c);
};
while (*stra != '\0'
&& *strb != '\0'
&& (munge (*stra) == munge (*strb)))
{
++stra;
++strb;
}
unsigned char c1 = munge (*stra);
unsigned char c2 = munge (*strb);
if (c1 == c2)
return 0;
/* When completing, if STRB ends earlier than STRA, consider them as
equal. When comparing, if STRB ends earlier and STRA ends with
'<', consider them as equal. */
if (mode == COMPLETE || (mode == MATCH && c1 == munge ('<')))
{
if (c2 == '\0')
return 0;
}
return c1 < c2 ? -1 : 1;
}
#if GDB_SELF_TEST
namespace {
void
test_compare ()
{
/* Convenience aliases. */
const auto mode_compare = cooked_index_entry::MATCH;
const auto mode_sort = cooked_index_entry::SORT;
const auto mode_complete = cooked_index_entry::COMPLETE;
SELF_CHECK (cooked_index_entry::compare ("abcd", "abcd",
mode_compare) == 0);
SELF_CHECK (cooked_index_entry::compare ("abcd", "abcd",
mode_complete) == 0);
SELF_CHECK (cooked_index_entry::compare ("abcd", "ABCDE",
mode_compare) < 0);
SELF_CHECK (cooked_index_entry::compare ("ABCDE", "abcd",
mode_compare) > 0);
SELF_CHECK (cooked_index_entry::compare ("abcd", "ABCDE",
mode_complete) < 0);
SELF_CHECK (cooked_index_entry::compare ("ABCDE", "abcd",
mode_complete) == 0);
SELF_CHECK (cooked_index_entry::compare ("name", "name<>",
mode_compare) < 0);
SELF_CHECK (cooked_index_entry::compare ("name<>", "name",
mode_compare) == 0);
SELF_CHECK (cooked_index_entry::compare ("name", "name<>",
mode_complete) < 0);
SELF_CHECK (cooked_index_entry::compare ("name<>", "name",
mode_complete) == 0);
SELF_CHECK (cooked_index_entry::compare ("name", "name",
mode_compare) == 0);
SELF_CHECK (cooked_index_entry::compare ("name", "name",
mode_compare) > 0);
SELF_CHECK (cooked_index_entry::compare ("name", "name",
mode_complete) == 0);
SELF_CHECK (cooked_index_entry::compare ("name", "name",
mode_complete) > 0);
SELF_CHECK (cooked_index_entry::compare ("name>",
"name>",
mode_compare) == 0);
SELF_CHECK (cooked_index_entry::compare ("name", "name>",
mode_compare) < 0);
SELF_CHECK (cooked_index_entry::compare ("name>", "name",
mode_compare) == 0);
SELF_CHECK (cooked_index_entry::compare ("name>", "name 0);
SELF_CHECK (cooked_index_entry::compare ("name>", "name 0);
SELF_CHECK (cooked_index_entry::compare ("abcd", "", mode_complete) == 0);
SELF_CHECK (cooked_index_entry::compare ("func", "func",
mode_sort) < 0);
SELF_CHECK (cooked_index_entry::compare ("func", "func1",
mode_sort) < 0);
}
} /* anonymous namespace */
#endif /* GDB_SELF_TEST */
/* See cooked-index.h. */
const char *
cooked_index_entry::full_name (struct obstack *storage, bool for_main) const
{
const char *local_name = for_main ? name : canonical;
if ((flags & IS_LINKAGE) != 0 || parent_entry == nullptr)
return local_name;
const char *sep = nullptr;
switch (per_cu->lang ())
{
case language_cplus:
case language_rust:
sep = "::";
break;
case language_go:
case language_d:
case language_ada:
sep = ".";
break;
default:
return local_name;
}
parent_entry->write_scope (storage, sep, for_main);
obstack_grow0 (storage, local_name, strlen (local_name));
return (const char *) obstack_finish (storage);
}
/* See cooked-index.h. */
void
cooked_index_entry::write_scope (struct obstack *storage,
const char *sep,
bool for_main) const
{
if (parent_entry != nullptr)
parent_entry->write_scope (storage, sep, for_main);
const char *local_name = for_main ? name : canonical;
obstack_grow (storage, local_name, strlen (local_name));
obstack_grow (storage, sep, strlen (sep));
}
/* See cooked-index.h. */
const cooked_index_entry *
cooked_index::add (sect_offset die_offset, enum dwarf_tag tag,
cooked_index_flag flags, const char *name,
const cooked_index_entry *parent_entry,
dwarf2_per_cu_data *per_cu)
{
cooked_index_entry *result = create (die_offset, tag, flags, name,
parent_entry, per_cu);
m_entries.push_back (result);
/* An explicitly-tagged main program should always override the
implicit "main" discovery. */
if ((flags & IS_MAIN) != 0)
m_main = result;
return result;
}
/* See cooked-index.h. */
void
cooked_index::finalize ()
{
m_future = gdb::thread_pool::g_thread_pool->post_task ([this] ()
{
do_finalize ();
});
}
/* See cooked-index.h. */
gdb::unique_xmalloc_ptr
cooked_index::handle_gnat_encoded_entry (cooked_index_entry *entry,
htab_t gnat_entries)
{
std::string canonical = ada_decode (entry->name, false, false);
if (canonical.empty ())
return {};
std::vector names = split_name (canonical.c_str (),
split_style::DOT);
gdb::string_view tail = names.back ();
names.pop_back ();
const cooked_index_entry *parent = nullptr;
for (const auto &name : names)
{
uint32_t hashval = dwarf5_djb_hash (name);
void **slot = htab_find_slot_with_hash (gnat_entries, &name,
hashval, INSERT);
/* CUs are processed in order, so we only need to check the most
recent entry. */
cooked_index_entry *last = (cooked_index_entry *) *slot;
if (last == nullptr || last->per_cu != entry->per_cu)
{
gdb::unique_xmalloc_ptr new_name
= make_unique_xstrndup (name.data (), name.length ());
last = create (entry->die_offset, DW_TAG_namespace,
0, new_name.get (), parent,
entry->per_cu);
last->canonical = last->name;
m_names.push_back (std::move (new_name));
*slot = last;
}
parent = last;
}
entry->parent_entry = parent;
return make_unique_xstrndup (tail.data (), tail.length ());
}
/* See cooked-index.h. */
void
cooked_index::do_finalize ()
{
auto hash_name_ptr = [] (const void *p)
{
const cooked_index_entry *entry = (const cooked_index_entry *) p;
return htab_hash_pointer (entry->name);
};
auto eq_name_ptr = [] (const void *a, const void *b) -> int
{
const cooked_index_entry *ea = (const cooked_index_entry *) a;
const cooked_index_entry *eb = (const cooked_index_entry *) b;
return ea->name == eb->name;
};
/* We can use pointer equality here because names come from
.debug_str, which will normally be unique-ified by the linker.
Also, duplicates are relatively harmless -- they just mean a bit
of extra memory is used. */
htab_up seen_names (htab_create_alloc (10, hash_name_ptr, eq_name_ptr,
nullptr, xcalloc, xfree));
auto hash_entry = [] (const void *e)
{
const cooked_index_entry *entry = (const cooked_index_entry *) e;
return dwarf5_djb_hash (entry->canonical);
};
auto eq_entry = [] (const void *a, const void *b) -> int
{
const cooked_index_entry *ae = (const cooked_index_entry *) a;
const gdb::string_view *sv = (const gdb::string_view *) b;
return (strlen (ae->canonical) == sv->length ()
&& strncasecmp (ae->canonical, sv->data (), sv->length ()) == 0);
};
htab_up gnat_entries (htab_create_alloc (10, hash_entry, eq_entry,
nullptr, xcalloc, xfree));
for (cooked_index_entry *entry : m_entries)
{
/* Note that this code must be kept in sync with
language_requires_canonicalization. */
gdb_assert (entry->canonical == nullptr);
if ((entry->flags & IS_LINKAGE) != 0)
entry->canonical = entry->name;
else if (entry->per_cu->lang () == language_ada)
{
gdb::unique_xmalloc_ptr canon_name
= handle_gnat_encoded_entry (entry, gnat_entries.get ());
if (canon_name == nullptr)
entry->canonical = entry->name;
else
{
entry->canonical = canon_name.get ();
m_names.push_back (std::move (canon_name));
}
}
else if (entry->per_cu->lang () == language_cplus
|| entry->per_cu->lang () == language_c)
{
void **slot = htab_find_slot (seen_names.get (), entry,
INSERT);
if (*slot == nullptr)
{
gdb::unique_xmalloc_ptr canon_name
= (entry->per_cu->lang () == language_cplus
? cp_canonicalize_string (entry->name)
: c_canonicalize_name (entry->name));
if (canon_name == nullptr)
entry->canonical = entry->name;
else
{
entry->canonical = canon_name.get ();
m_names.push_back (std::move (canon_name));
}
}
else
{
const cooked_index_entry *other
= (const cooked_index_entry *) *slot;
entry->canonical = other->canonical;
}
}
else
entry->canonical = entry->name;
}
m_names.shrink_to_fit ();
m_entries.shrink_to_fit ();
std::sort (m_entries.begin (), m_entries.end (),
[] (const cooked_index_entry *a, const cooked_index_entry *b)
{
return *a < *b;
});
}
/* See cooked-index.h. */
cooked_index::range
cooked_index::find (const std::string &name, bool completing)
{
wait ();
cooked_index_entry::comparison_mode mode = (completing
? cooked_index_entry::COMPLETE
: cooked_index_entry::MATCH);
auto lower = std::lower_bound (m_entries.begin (), m_entries.end (), name,
[=] (const cooked_index_entry *entry,
const std::string &n)
{
return cooked_index_entry::compare (entry->canonical, n.c_str (), mode) < 0;
});
auto upper = std::upper_bound (m_entries.begin (), m_entries.end (), name,
[=] (const std::string &n,
const cooked_index_entry *entry)
{
return cooked_index_entry::compare (entry->canonical, n.c_str (), mode) > 0;
});
return range (lower, upper);
}
cooked_index_vector::cooked_index_vector (vec_type &&vec)
: m_vector (std::move (vec))
{
for (auto &idx : m_vector)
idx->finalize ();
}
/* See cooked-index.h. */
dwarf2_per_cu_data *
cooked_index_vector::lookup (CORE_ADDR addr)
{
for (const auto &index : m_vector)
{
dwarf2_per_cu_data *result = index->lookup (addr);
if (result != nullptr)
return result;
}
return nullptr;
}
/* See cooked-index.h. */
std::vector
cooked_index_vector::get_addrmaps ()
{
std::vector result;
for (const auto &index : m_vector)
result.push_back (index->m_addrmap);
return result;
}
/* See cooked-index.h. */
cooked_index_vector::range
cooked_index_vector::find (const std::string &name, bool completing)
{
std::vector result_range;
result_range.reserve (m_vector.size ());
for (auto &entry : m_vector)
result_range.push_back (entry->find (name, completing));
return range (std::move (result_range));
}
/* See cooked-index.h. */
const cooked_index_entry *
cooked_index_vector::get_main () const
{
const cooked_index_entry *result = nullptr;
for (const auto &index : m_vector)
{
const cooked_index_entry *entry = index->get_main ();
/* Choose the first "main" we see. The choice among several is
arbitrary. See the comment by the sole caller to understand
the rationale for filtering by language. */
if (entry != nullptr
&& !language_requires_canonicalization (entry->per_cu->lang ()))
{
result = entry;
break;
}
}
return result;
}
void _initialize_cooked_index ();
void
_initialize_cooked_index ()
{
#if GDB_SELF_TEST
selftests::register_test ("cooked_index_entry::compare", test_compare);
#endif
}