[LLDB][NativePDB] Allow type lookup in namespaces (#149876)

Previously, `type lookup` for types in namespaces didn't work with the
native PDB plugin, because `FindTypes` would only look for types whose
base name was equal to their full name. PDB/CodeView does not store the
base names in the TPI stream, but the types have their full name (e.g.
`std::thread` instead of `thread`). So `findRecordsByName` would only
return types in the top level namespace.

This PR changes the lookup to go through all types and check their base
name. As that could be a bit expensive, the names are first cached
(similar to the function lookup in the DIA PDB plugin). Potential types
are checked with `TypeQuery::ContextMatches`.

To be able to handle anonymous namespaces, I changed
`TypeQuery::ContextMatches`. The [`TypeQuery`
constructor](9ad7edef42/lldb/source/Symbol/Type.cpp (L76-L79))
inserts all name components as `CompilerContextKind::AnyDeclContext`. To
skip over anonymous namespaces, `ContextMatches` checked if a component
was empty and exactly of kind `Namespace`. For our query, the last check
was always false, so we never skipped anonymous namespaces. DWARF
doesn't have this problem, as it [constructs the context
outside](abe93d9d7e/lldb/source/Plugins/SymbolFile/DWARF/DWARFIndex.cpp (L154-L160))
and has proper information about namespaces. I'm not fully sure if my
change is correct and that it doesn't break other users of `TypeQuery`.

This enables `type lookup <type>` to work on types in namespaces.
However, expressions don't work with this yet, because `FindNamespace`
is unimplemented for native PDB.
This commit is contained in:
nerix 2025-08-04 09:56:04 +02:00 committed by GitHub
parent df71243fa8
commit d95dadff8f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 225 additions and 13 deletions

View File

@ -72,6 +72,16 @@ struct CVTagRecord {
return cvunion.Name;
}
CompilerContextKind contextKind() const {
if (m_kind == Struct || m_kind == Class)
return CompilerContextKind::ClassOrStruct;
if (m_kind == Enum)
return CompilerContextKind::Enum;
assert(m_kind == Union);
return CompilerContextKind::Union;
}
private:
CVTagRecord(llvm::codeview::ClassRecord &&c);
CVTagRecord(llvm::codeview::UnionRecord &&u);

View File

@ -1720,18 +1720,22 @@ void SymbolFileNativePDB::FindTypes(const lldb_private::TypeQuery &query,
std::lock_guard<std::recursive_mutex> guard(GetModuleMutex());
std::vector<TypeIndex> matches =
m_index->tpi().findRecordsByName(query.GetTypeBasename().GetStringRef());
// We can't query for the full name because the type might reside
// in an anonymous namespace. Search for the basename in our map and check the
// matching types afterwards.
std::vector<uint32_t> matches;
m_type_base_names.GetValues(query.GetTypeBasename(), matches);
for (TypeIndex type_idx : matches) {
TypeSP type_sp = GetOrCreateType(type_idx);
if (!type_sp)
for (uint32_t match_idx : matches) {
std::vector context = GetContextForType(TypeIndex(match_idx));
if (context.empty())
continue;
// We resolved a type. Get the fully qualified name to ensure it matches.
ConstString name = type_sp->GetQualifiedName();
TypeQuery type_match(name.GetStringRef(), TypeQueryOptions::e_exact_match);
if (query.ContextMatches(type_match.GetContextRef())) {
if (query.ContextMatches(context)) {
TypeSP type_sp = GetOrCreateType(TypeIndex(match_idx));
if (!type_sp)
continue;
results.InsertUnique(type_sp);
if (results.Done(query))
return;
@ -2201,11 +2205,15 @@ void SymbolFileNativePDB::BuildParentMap() {
CVTagRecord tag = CVTagRecord::create(type);
RecordIndices &indices = record_indices[tag.asTag().getUniqueName()];
if (tag.asTag().isForwardRef())
if (tag.asTag().isForwardRef()) {
indices.forward = *ti;
else
} else {
indices.full = *ti;
auto base_name = MSVCUndecoratedNameParser::DropScope(tag.name());
m_type_base_names.Append(ConstString(base_name), ti->getIndex());
}
if (indices.full != TypeIndex::None() &&
indices.forward != TypeIndex::None()) {
forward_to_full[indices.forward] = indices.full;
@ -2261,6 +2269,10 @@ void SymbolFileNativePDB::BuildParentMap() {
llvm::consumeError(std::move(error));
}
// After calling Append(), the type-name map needs to be sorted again to be
// able to look up a type by its name.
m_type_base_names.Sort();
// Now that we know the forward -> full mapping of all type indices, we can
// re-write all the indices. At the end of this process, we want a mapping
// consisting of fwd -> full and full -> full for all child -> parent indices.
@ -2353,3 +2365,52 @@ SymbolFileNativePDB::GetParentType(llvm::codeview::TypeIndex ti) {
return std::nullopt;
return parent_iter->second;
}
std::vector<CompilerContext>
SymbolFileNativePDB::GetContextForType(TypeIndex ti) {
CVType type = m_index->tpi().getType(ti);
if (!IsTagRecord(type))
return {};
CVTagRecord tag = CVTagRecord::create(type);
std::optional<Type::ParsedName> parsed_name =
Type::GetTypeScopeAndBasename(tag.name());
if (!parsed_name)
return {{tag.contextKind(), ConstString(tag.name())}};
std::vector<CompilerContext> ctx;
// assume everything is a namespace at first
for (llvm::StringRef scope : parsed_name->scope) {
ctx.emplace_back(CompilerContextKind::Namespace, ConstString(scope));
}
// we know the kind of our own type
ctx.emplace_back(tag.contextKind(), ConstString(parsed_name->basename));
// try to find the kind of parents
for (auto &el : llvm::reverse(llvm::drop_end(ctx))) {
std::optional<TypeIndex> parent = GetParentType(ti);
if (!parent)
break;
ti = *parent;
type = m_index->tpi().getType(ti);
switch (type.kind()) {
case LF_CLASS:
case LF_STRUCTURE:
case LF_INTERFACE:
el.kind = CompilerContextKind::ClassOrStruct;
continue;
case LF_UNION:
el.kind = CompilerContextKind::Union;
continue;
case LF_ENUM:
el.kind = CompilerContextKind::Enum;
continue;
default:
break;
}
break;
}
return ctx;
}

View File

@ -258,6 +258,8 @@ private:
void ParseInlineSite(PdbCompilandSymId inline_site_id, Address func_addr);
std::vector<CompilerContext> GetContextForType(llvm::codeview::TypeIndex ti);
llvm::BumpPtrAllocator m_allocator;
lldb::addr_t m_obj_load_address = 0;
@ -278,6 +280,8 @@ private:
llvm::DenseMap<lldb::user_id_t, std::shared_ptr<InlineSite>> m_inline_sites;
llvm::DenseMap<llvm::codeview::TypeIndex, llvm::codeview::TypeIndex>
m_parent_types;
lldb_private::UniqueCStringMap<uint32_t> m_type_base_names;
};
} // namespace npdb

View File

@ -817,10 +817,12 @@ Type::GetTypeScopeAndBasename(llvm::StringRef name) {
case ':':
if (prev_is_colon && template_depth == 0) {
llvm::StringRef scope_name = name.slice(name_begin, pos.index() - 1);
// The itanium demangler uses this string to represent anonymous
// The demanglers use these strings to represent anonymous
// namespaces. Convert it to a more language-agnostic form (which is
// also used in DWARF).
if (scope_name == "(anonymous namespace)")
if (scope_name == "(anonymous namespace)" ||
scope_name == "`anonymous namespace'" ||
scope_name == "`anonymous-namespace'")
scope_name = "";
result.scope.push_back(scope_name);
name_begin = pos.index() + 1;

View File

@ -0,0 +1,135 @@
# REQUIRES: target-windows
# Test namespace lookup.
# RUN: split-file %s %t
# RUN: %build --nodefaultlib -o %t.exe -- %t/main.cpp
# RUN: %lldb -f %t.exe -s \
# RUN: %t/commands.input 2>&1 | FileCheck %s
#--- main.cpp
struct S {
char a[1];
};
namespace Outer {
struct S {
char a[2];
};
namespace Inner1 {
struct S {
char a[3];
};
namespace Inner2 {
struct S {
char a[4];
};
} // namespace Inner2
} // namespace Inner1
namespace Inner2 {
struct S {
char a[5];
};
} // namespace Inner2
namespace {
struct A {
char a[6];
};
} // namespace
} // namespace Outer
namespace {
struct A {
char a[7];
};
} // namespace
int main(int argc, char **argv) {
S s;
Outer::S os;
Outer::Inner1::S oi1s;
Outer::Inner1::Inner2::S oi1i2s;
Outer::Inner2::S oi2s;
A a1;
Outer::A a2;
return sizeof(s) + sizeof(os) + sizeof(oi1s) + sizeof(oi1i2s) + sizeof(oi2s) + sizeof(a1) + sizeof(a2);
}
#--- commands.input
b main
r
type lookup S
type lookup ::S
type lookup Outer::S
type lookup Outer::Inner1::S
type lookup Inner1::S
type lookup Outer::Inner1::Inner2::S
type lookup Inner2::S
type lookup Outer::Inner2::S
type lookup Outer::A
type lookup A
type lookup ::A
expr sizeof(S)
expr sizeof(A)
quit
# CHECK: (lldb) type lookup S
# CHECK: struct S {
# CHECK: struct S {
# CHECK: struct S {
# CHECK: struct S {
# CHECK: struct S {
# CHECK: }
# CHECK-NEXT: (lldb) type lookup ::S
# CHECK-NEXT: struct S {
# CHECK-NEXT: char a[1];
# CHECK-NEXT: }
# CHECK-NEXT: (lldb) type lookup Outer::S
# CHECK-NEXT: struct S {
# CHECK-NEXT: char a[2];
# CHECK-NEXT: }
# CHECK-NEXT: (lldb) type lookup Outer::Inner1::S
# CHECK-NEXT: struct S {
# CHECK-NEXT: char a[3];
# CHECK-NEXT: }
# CHECK-NEXT: (lldb) type lookup Inner1::S
# CHECK-NEXT: struct S {
# CHECK-NEXT: char a[3];
# CHECK-NEXT: }
# CHECK-NEXT: (lldb) type lookup Outer::Inner1::Inner2::S
# CHECK-NEXT: struct S {
# CHECK-NEXT: char a[4];
# CHECK-NEXT: }
# CHECK-NEXT: (lldb) type lookup Inner2::S
# CHECK-NEXT: struct S {
# CHECK: struct S {
# CHECK: }
# CHECK-NEXT: (lldb) type lookup Outer::Inner2::S
# CHECK-NEXT: struct S {
# CHECK-NEXT: char a[5];
# CHECK-NEXT: }
# CHECK-NEXT: (lldb) type lookup Outer::A
# CHECK-NEXT: struct A {
# CHECK-NEXT: char a[6];
# CHECK-NEXT: }
# CHECK-NEXT: (lldb) type lookup A
# CHECK-NEXT: struct A {
# CHECK: struct A {
# CHECK: }
# CHECK-NEXT: (lldb) type lookup ::A
# CHECK-NEXT: struct A {
# CHECK-NEXT: char a[7];
# CHECK-NEXT: }
# CHECK-NEXT: (lldb) expr sizeof(S)
# CHECK-NEXT: (__size_t) $0 = 1
# CHECK-NEXT: (lldb) expr sizeof(A)
# CHECK-NEXT: (__size_t) $1 = 7