/* Shards for the cooked index
Copyright (C) 2022-2025 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 "dwarf2/cooked-index-shard.h"
#include "dwarf2/tag.h"
#include "dwarf2/index-common.h"
#include "cp-support.h"
#include "c-lang.h"
#include "ada-lang.h"
/* Return true if a plain "main" could be the main program for this
language. Languages that are known to use some other mechanism are
excluded here. */
static bool
language_may_use_plain_main (enum language lang)
{
/* No need to handle "unknown" here. */
return (lang == language_c
|| lang == language_objc
|| lang == language_cplus
|| lang == language_m2
|| lang == language_asm
|| lang == language_opencl
|| lang == language_minimal);
}
/* See cooked-index-shard.h. */
cooked_index_entry *
cooked_index_shard::create (sect_offset die_offset,
enum dwarf_tag tag,
cooked_index_flag flags,
enum language lang,
const char *name,
cooked_index_entry_ref parent_entry,
dwarf2_per_cu *per_cu)
{
if (tag == DW_TAG_module || tag == DW_TAG_namespace)
flags &= ~IS_STATIC;
else if (lang == language_cplus
&& (tag == DW_TAG_class_type
|| tag == DW_TAG_interface_type
|| tag == DW_TAG_structure_type
|| tag == DW_TAG_union_type
|| tag == DW_TAG_enumeration_type
|| tag == DW_TAG_enumerator))
flags &= ~IS_STATIC;
else if (tag_is_type (tag))
flags |= IS_STATIC;
return new (&m_storage) cooked_index_entry (die_offset, tag, flags,
lang, name, parent_entry,
per_cu);
}
/* See cooked-index-shard.h. */
cooked_index_entry *
cooked_index_shard::add (sect_offset die_offset, enum dwarf_tag tag,
cooked_index_flag flags, enum language lang,
const char *name, cooked_index_entry_ref parent_entry,
dwarf2_per_cu *per_cu)
{
cooked_index_entry *result = create (die_offset, tag, flags, lang, 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;
else if ((flags & IS_PARENT_DEFERRED) == 0
&& parent_entry.resolved == nullptr
&& m_main == nullptr
&& language_may_use_plain_main (lang)
&& strcmp (name, "main") == 0)
m_main = result;
return result;
}
/* See cooked-index-shard.h. */
void
cooked_index_shard::handle_gnat_encoded_entry
(cooked_index_entry *entry,
htab_t gnat_entries,
std::vector &new_entries)
{
/* We decode Ada names in a particular way: operators and wide
characters are left as-is. This is done to make name matching a
bit simpler; and for wide characters, it means the choice of Ada
source charset does not affect the indexer directly. */
std::string canonical = ada_decode (entry->name, false, false, false);
if (canonical.empty ())
{
entry->canonical = entry->name;
return;
}
std::vector names = split_name (canonical.c_str (),
split_style::DOT_STYLE);
std::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)
{
const char *new_name = m_names.insert (name);
last = create (entry->die_offset, DW_TAG_module,
IS_SYNTHESIZED, language_ada, new_name, parent,
entry->per_cu);
last->canonical = last->name;
new_entries.push_back (last);
*slot = last;
}
parent = last;
}
entry->set_parent (parent);
entry->canonical = m_names.insert (tail);
}
/* Hash a cooked index entry by name pointer value.
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. */
struct cooked_index_entry_name_ptr_hash
{
using is_avalanching = void;
std::uint64_t operator () (const cooked_index_entry *entry) const noexcept
{
return ankerl::unordered_dense::hash () (entry->name);
}
};
/* Compare cooked index entries by name pointer value. */
struct cooked_index_entry_name_ptr_eq
{
bool operator () (const cooked_index_entry *a,
const cooked_index_entry *b) const noexcept
{
return a->name == b->name;
}
};
/* See cooked-index-shard.h. */
void
cooked_index_shard::finalize (const parent_map_map *parent_maps)
{
gdb::unordered_set seen_names;
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 std::string_view *sv = (const std::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));
std::vector new_gnat_entries;
for (cooked_index_entry *entry : m_entries)
{
if ((entry->flags & IS_PARENT_DEFERRED) != 0)
{
const cooked_index_entry *new_parent
= parent_maps->find (entry->get_deferred_parent ());
entry->resolve_parent (new_parent);
}
/* 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->lang == language_ada)
{
/* Newer versions of GNAT emit DW_TAG_module and use a
hierarchical structure. In this case, we don't need to
do any extra work. This can be detected by looking for a
GNAT-encoded name. */
if (strstr (entry->name, "__") == nullptr)
{
entry->canonical = entry->name;
/* If the entry does not have a parent, then there's
nothing extra to do here -- the entry itself is
sufficient.
However, if it does have a parent, we have to
synthesize an entry with the full name. This is
unfortunate, but it's necessary due to how some of
the Ada name-lookup code currently works. For
example, without this, ada_get_tsd_type will
fail.
Eventually it would be good to change the Ada lookup
code, and then remove these entries (and supporting
code in cooked_index_entry::full_name). */
if (entry->get_parent () != nullptr)
{
const char *fullname
= entry->full_name (&m_storage, FOR_ADA_LINKAGE_NAME);
cooked_index_entry *linkage = create (entry->die_offset,
entry->tag,
(entry->flags
| IS_LINKAGE
| IS_SYNTHESIZED),
language_ada,
fullname,
nullptr,
entry->per_cu);
linkage->canonical = fullname;
new_gnat_entries.push_back (linkage);
}
}
else
handle_gnat_encoded_entry (entry, gnat_entries.get (),
new_gnat_entries);
}
else if (entry->lang == language_cplus || entry->lang == language_c)
{
auto [it, inserted] = seen_names.insert (entry);
if (inserted)
{
/* No entry with that name was present, compute the canonical
name. */
gdb::unique_xmalloc_ptr canon_name
= (entry->lang == language_cplus
? cp_canonicalize_string (entry->name)
: c_canonicalize_name (entry->name));
if (canon_name == nullptr)
entry->canonical = entry->name;
else
entry->canonical = m_names.insert (std::move (canon_name));
}
else
{
/* An entry with that name was present, re-use its canonical
name. */
entry->canonical = (*it)->canonical;
}
}
else
entry->canonical = entry->name;
}
/* Make sure any new Ada entries end up in the results. This isn't
done when creating these new entries to avoid invalidating the
m_entries iterator used in the foreach above. */
m_entries.insert (m_entries.end (), new_gnat_entries.begin (),
new_gnat_entries.end ());
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-shard.h. */
cooked_index_shard::range
cooked_index_shard::find (const std::string &name, bool completing) const
{
struct comparator
{
cooked_index_entry::comparison_mode mode;
bool operator() (const cooked_index_entry *entry,
const char *name) const noexcept
{
return cooked_index_entry::compare (entry->canonical, name, mode) < 0;
}
bool operator() (const char *name,
const cooked_index_entry *entry) const noexcept
{
return cooked_index_entry::compare (entry->canonical, name, mode) > 0;
}
};
return std::make_from_tuple
(std::equal_range (m_entries.cbegin (), m_entries.cend (), name.c_str (),
comparator { (completing
? cooked_index_entry::COMPLETE
: cooked_index_entry::MATCH) }));
}