[lldb] Support simplified template names

See https://discourse.llvm.org/t/dwarf-using-simplified-template-names/58417 for background on simplified template names.

lldb doesn't work with simplified template names because it uses DW_AT_name which doesn't contain template parameters under simplified template names.

Two major changes are required to make lldb work with simplified template names.

1) When building clang ASTs for struct-like dies, we use the name as a cache key. To distinguish between different instantiations of a template class, we need to add in the template parameters.

2) When looking up types, if the requested type name contains '<' and we didn't initially find any types from the index searching the name, strip the template parameters and search the index, then filter out results with non-matching template parameters. This takes advantage of the clang AST's ability to print full names rather than doing it by ourself.

An alternative is to fix up the names in the index to contain the fully qualified name, but that doesn't respect .debug_names.

Reviewed By: labath

Differential Revision: https://reviews.llvm.org/D134378
This commit is contained in:
Arthur Eubanks
2022-08-03 17:09:40 -07:00
parent d3e99d61a4
commit a842f74056
14 changed files with 225 additions and 37 deletions

View File

@@ -163,7 +163,7 @@ public:
/// \{
TypeSystem *GetTypeSystem() const { return m_type_system; }
ConstString GetTypeName() const;
ConstString GetTypeName(bool BaseOnly = false) const;
ConstString GetDisplayTypeName() const;

View File

@@ -135,6 +135,8 @@ public:
ConstString GetName();
ConstString GetBaseName();
llvm::Optional<uint64_t> GetByteSize(ExecutionContextScope *exe_scope);
uint32_t GetNumChildren(bool omit_empty_base_classes);

View File

@@ -208,7 +208,8 @@ public:
// Accessors
virtual ConstString GetTypeName(lldb::opaque_compiler_type_t type) = 0;
virtual ConstString GetTypeName(lldb::opaque_compiler_type_t type,
bool BaseOnly) = 0;
virtual ConstString GetDisplayTypeName(lldb::opaque_compiler_type_t type) = 0;

View File

@@ -1523,6 +1523,41 @@ TypeSP DWARFASTParserClang::UpdateSymbolContextScopeForType(
return type_sp;
}
std::string
DWARFASTParserClang::GetTemplateParametersString(const DWARFDIE &die) {
if (llvm::StringRef(die.GetName()).contains("<"))
return std::string();
TypeSystemClang::TemplateParameterInfos template_param_infos;
if (!ParseTemplateParameterInfos(die, template_param_infos))
return std::string();
std::string all_template_names;
llvm::SmallVector<clang::TemplateArgument, 2> args =
template_param_infos.args;
if (template_param_infos.hasParameterPack())
args.append(template_param_infos.packed_args->args);
if (args.empty())
return std::string();
for (auto &arg : args) {
std::string template_name;
llvm::raw_string_ostream os(template_name);
arg.print(m_ast.getASTContext().getPrintingPolicy(), os, true);
if (!template_name.empty()) {
if (all_template_names.empty()) {
all_template_names.append("<");
} else {
all_template_names.append(", ");
}
all_template_names.append(template_name);
}
}
assert(!all_template_names.empty() && "no template parameters?");
// Spacing doesn't matter as long as we're consistent because we're only using
// this to deduplicate C++ symbols.
all_template_names.append(">");
return all_template_names;
}
std::string
DWARFASTParserClang::GetCPlusPlusQualifiedName(const DWARFDIE &die) {
if (!die.IsValid())
@@ -1534,6 +1569,9 @@ DWARFASTParserClang::GetCPlusPlusQualifiedName(const DWARFDIE &die) {
DWARFDIE parent_decl_ctx_die = die.GetParentDeclContextDIE();
// TODO: change this to get the correct decl context parent....
while (parent_decl_ctx_die) {
// The name may not contain template parameters due to simplified template
// names; we must reconstruct the full name from child template parameter
// dies via GetTemplateParametersString().
const dw_tag_t parent_tag = parent_decl_ctx_die.Tag();
switch (parent_tag) {
case DW_TAG_namespace: {
@@ -1551,6 +1589,8 @@ DWARFASTParserClang::GetCPlusPlusQualifiedName(const DWARFDIE &die) {
case DW_TAG_structure_type:
case DW_TAG_union_type: {
if (const char *class_union_struct_name = parent_decl_ctx_die.GetName()) {
qualified_name.insert(0,
GetTemplateParametersString(parent_decl_ctx_die));
qualified_name.insert(0, "::");
qualified_name.insert(0, class_union_struct_name);
}
@@ -1568,6 +1608,7 @@ DWARFASTParserClang::GetCPlusPlusQualifiedName(const DWARFDIE &die) {
qualified_name.append("::");
qualified_name.append(name);
qualified_name.append(GetTemplateParametersString(die));
return qualified_name;
}
@@ -1772,36 +1813,36 @@ DWARFASTParserClang::ParseStructureLikeDIE(const SymbolContext &sc,
metadata.SetUserID(die.GetID());
metadata.SetIsDynamicCXXType(dwarf->ClassOrStructIsVirtual(die));
if (attrs.name.GetStringRef().contains('<')) {
TypeSystemClang::TemplateParameterInfos template_param_infos;
if (ParseTemplateParameterInfos(die, template_param_infos)) {
clang::ClassTemplateDecl *class_template_decl =
m_ast.ParseClassTemplateDecl(
decl_ctx, GetOwningClangModule(die), attrs.accessibility,
attrs.name.GetCString(), tag_decl_kind, template_param_infos);
if (!class_template_decl) {
if (log) {
dwarf->GetObjectFile()->GetModule()->LogMessage(
log,
"SymbolFileDWARF(%p) - 0x%8.8x: %s type \"%s\" "
"clang::ClassTemplateDecl failed to return a decl.",
static_cast<void *>(this), die.GetOffset(),
DW_TAG_value_to_name(tag), attrs.name.GetCString());
}
return TypeSP();
TypeSystemClang::TemplateParameterInfos template_param_infos;
if (ParseTemplateParameterInfos(die, template_param_infos) &&
(!template_param_infos.args.empty() ||
template_param_infos.packed_args)) {
clang::ClassTemplateDecl *class_template_decl =
m_ast.ParseClassTemplateDecl(
decl_ctx, GetOwningClangModule(die), attrs.accessibility,
attrs.name.GetCString(), tag_decl_kind, template_param_infos);
if (!class_template_decl) {
if (log) {
dwarf->GetObjectFile()->GetModule()->LogMessage(
log,
"SymbolFileDWARF(%p) - 0x%8.8x: %s type \"%s\" "
"clang::ClassTemplateDecl failed to return a decl.",
static_cast<void *>(this), die.GetOffset(),
DW_TAG_value_to_name(tag), attrs.name.GetCString());
}
clang::ClassTemplateSpecializationDecl *class_specialization_decl =
m_ast.CreateClassTemplateSpecializationDecl(
decl_ctx, GetOwningClangModule(die), class_template_decl,
tag_decl_kind, template_param_infos);
clang_type = m_ast.CreateClassTemplateSpecializationType(
class_specialization_decl);
clang_type_was_created = true;
m_ast.SetMetadata(class_template_decl, metadata);
m_ast.SetMetadata(class_specialization_decl, metadata);
return TypeSP();
}
clang::ClassTemplateSpecializationDecl *class_specialization_decl =
m_ast.CreateClassTemplateSpecializationDecl(
decl_ctx, GetOwningClangModule(die), class_template_decl,
tag_decl_kind, template_param_infos);
clang_type = m_ast.CreateClassTemplateSpecializationType(
class_specialization_decl);
clang_type_was_created = true;
m_ast.SetMetadata(class_template_decl, metadata);
m_ast.SetMetadata(class_specialization_decl, metadata);
}
if (!clang_type_was_created) {

View File

@@ -127,6 +127,10 @@ protected:
lldb_private::TypeSystemClang::TemplateParameterInfos
&template_param_infos);
/// Get the template parameters of a die as a string if the die name does not
/// already contain them. This happens with -gsimple-template-names.
std::string GetTemplateParametersString(const DWARFDIE &die);
std::string GetCPlusPlusQualifiedName(const DWARFDIE &die);
bool ParseChildMembers(

View File

@@ -2492,6 +2492,45 @@ void SymbolFileDWARF::FindTypes(
return types.GetSize() < max_matches;
});
// With -gsimple-template-names, a templated type's DW_AT_name will not
// contain the template parameters. Try again stripping '<' and anything
// after, filtering out entries with template parameters that don't match.
if (types.GetSize() < max_matches) {
const llvm::StringRef name_ref = name.GetStringRef();
auto it = name_ref.find('<');
if (it != llvm::StringRef::npos) {
const llvm::StringRef name_no_template_params = name_ref.slice(0, it);
const llvm::StringRef template_params = name_ref.slice(it, name_ref.size());
m_index->GetTypes(ConstString(name_no_template_params), [&](DWARFDIE die) {
if (!DIEInDeclContext(parent_decl_ctx, die))
return true; // The containing decl contexts don't match
const llvm::StringRef base_name = GetTypeForDIE(die)->GetBaseName().AsCString();
auto it = base_name.find('<');
// If the candidate qualified name doesn't have '<', it doesn't have
// template params to compare.
if (it == llvm::StringRef::npos)
return true;
// Filter out non-matching instantiations by comparing template params.
const llvm::StringRef base_name_template_params =
base_name.slice(it, base_name.size());
if (template_params != base_name_template_params)
return true;
Type *matching_type = ResolveType(die, true, true);
if (!matching_type)
return true;
// We found a type pointer, now find the shared pointer form our type
// list.
types.InsertUnique(matching_type->shared_from_this());
return types.GetSize() < max_matches;
});
}
}
// Next search through the reachable Clang modules. This only applies for
// DWARF objects compiled with -gmodules that haven't been processed by
// dsymutil.

View File

@@ -3781,7 +3781,8 @@ bool TypeSystemClang::GetCompleteType(lldb::opaque_compiler_type_t type) {
allow_completion);
}
ConstString TypeSystemClang::GetTypeName(lldb::opaque_compiler_type_t type) {
ConstString TypeSystemClang::GetTypeName(lldb::opaque_compiler_type_t type,
bool BaseOnly) {
if (!type)
return ConstString();
@@ -3803,7 +3804,9 @@ ConstString TypeSystemClang::GetTypeName(lldb::opaque_compiler_type_t type) {
return ConstString(GetTypeNameForDecl(typedef_decl));
}
return ConstString(qual_type.getAsString(GetTypePrintingPolicy()));
clang::PrintingPolicy printing_policy(GetTypePrintingPolicy());
printing_policy.SuppressScope = BaseOnly;
return ConstString(qual_type.getAsString(printing_policy));
}
ConstString
@@ -9353,7 +9356,9 @@ clang::ClassTemplateDecl *TypeSystemClang::ParseClassTemplateDecl(
const TypeSystemClang::TemplateParameterInfos &template_param_infos) {
if (template_param_infos.IsValid()) {
std::string template_basename(parent_name);
template_basename.erase(template_basename.find('<'));
// With -gsimple-template-names we may omit template parameters in the name.
if (auto i = template_basename.find('<'); i != std::string::npos)
template_basename.erase(i);
return CreateClassTemplateDecl(decl_ctx, owning_module, access_type,
template_basename.c_str(), tag_decl_kind,

View File

@@ -645,7 +645,8 @@ public:
// Accessors
ConstString GetTypeName(lldb::opaque_compiler_type_t type) override;
ConstString GetTypeName(lldb::opaque_compiler_type_t type,
bool BaseOnly) override;
ConstString GetDisplayTypeName(lldb::opaque_compiler_type_t type) override;

View File

@@ -280,9 +280,9 @@ size_t CompilerType::GetPointerByteSize() const {
return 0;
}
ConstString CompilerType::GetTypeName() const {
ConstString CompilerType::GetTypeName(bool BaseOnly) const {
if (IsValid()) {
return m_type_system->GetTypeName(m_type);
return m_type_system->GetTypeName(m_type, BaseOnly);
}
return ConstString("<invalid>");
}

View File

@@ -305,6 +305,10 @@ ConstString Type::GetName() {
return m_name;
}
ConstString Type::GetBaseName() {
return GetForwardCompilerType().GetTypeName(/*BaseOnly*/ true);
}
void Type::DumpTypeName(Stream *s) { GetName().Dump(s, "<invalid-type-name>"); }
void Type::DumpValue(ExecutionContext *exe_ctx, Stream *s,

View File

@@ -51,7 +51,7 @@ class TestCase(TestBase):
# Record types without a defining declaration are not complete.
self.assertPointeeIncomplete("FwdClass *", "fwd_class")
self.assertPointeeIncomplete("FwdClassTypedef *", "fwd_class_typedef")
self.assertPointeeIncomplete("FwdTemplateClass<> *", "fwd_template_class")
self.assertPointeeIncomplete("FwdTemplateClass<int> *", "fwd_template_class")
# A pointer type is complete even when it points to an incomplete type.
fwd_class_ptr = self.expect_expr("fwd_class", result_type="FwdClass *")

View File

@@ -0,0 +1,3 @@
CXX_SOURCES := main.cpp
include Makefile.rules

View File

@@ -0,0 +1,61 @@
"""
Test that we return only the requested template instantiation.
"""
import lldb
import lldbsuite.test.lldbutil as lldbutil
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
class UniqueTypesTestCase(TestBase):
def do_test(self, debug_flags):
"""Test that we only display the requested Foo instantiation, not all Foo instantiations."""
self.build(dictionary=debug_flags)
lldbutil.run_to_source_breakpoint(self, "// Set breakpoint here", lldb.SBFileSpec("main.cpp"))
self.expect("image lookup -A -t '::Foo<char>'", DATA_TYPES_DISPLAYED_CORRECTLY, substrs=["1 match found"])
self.expect("image lookup -A -t 'Foo<char>'", DATA_TYPES_DISPLAYED_CORRECTLY, substrs=["1 match found"])
self.expect("image lookup -A -t 'Foo<int>'", DATA_TYPES_DISPLAYED_CORRECTLY, substrs=["1 match found"])
self.expect("image lookup -A -t 'Foo<Foo<int> >'", DATA_TYPES_DISPLAYED_CORRECTLY, substrs=["1 match found"])
self.expect("image lookup -A -t 'Foo<float>'", DATA_TYPES_DISPLAYED_CORRECTLY, error=True)
self.expect("image lookup -A -t '::FooPack<char>'", DATA_TYPES_DISPLAYED_CORRECTLY, substrs=["1 match found"])
self.expect("image lookup -A -t 'FooPack<char>'", DATA_TYPES_DISPLAYED_CORRECTLY, substrs=["1 match found"])
self.expect("image lookup -A -t 'FooPack<int>'", DATA_TYPES_DISPLAYED_CORRECTLY, substrs=["1 match found"])
self.expect("image lookup -A -t 'FooPack<Foo<int> >'", DATA_TYPES_DISPLAYED_CORRECTLY, substrs=["1 match found"])
self.expect("image lookup -A -t 'FooPack<char, int>'", DATA_TYPES_DISPLAYED_CORRECTLY, substrs=["1 match found"])
self.expect("image lookup -A -t 'FooPack<char, float>'", DATA_TYPES_DISPLAYED_CORRECTLY, substrs=["1 match found"])
self.expect("image lookup -A -t 'FooPack<int, int>'", DATA_TYPES_DISPLAYED_CORRECTLY, substrs=["1 match found"])
self.expect("image lookup -A -t 'FooPack<int, int, int>'", DATA_TYPES_DISPLAYED_CORRECTLY, substrs=["1 match found"])
self.expect("image lookup -A -t 'FooPack<float>'", DATA_TYPES_DISPLAYED_CORRECTLY, error=True)
self.expect("image lookup -A -t 'FooPack<float, int>'", DATA_TYPES_DISPLAYED_CORRECTLY, error=True)
self.expect("image lookup -A -t '::Foo<int>::Nested<char>'", DATA_TYPES_DISPLAYED_CORRECTLY, substrs=["1 match found"])
self.expect("image lookup -A -t 'Foo<int>::Nested<char>'", DATA_TYPES_DISPLAYED_CORRECTLY, substrs=["1 match found"])
self.expect("image lookup -A -t 'Foo<char>::Nested<char>'", DATA_TYPES_DISPLAYED_CORRECTLY, error=True)
self.expect("image lookup -A -t 'Foo<int>::Nested<int>'", DATA_TYPES_DISPLAYED_CORRECTLY, error=True)
self.expect("image lookup -A -t 'Nested<char>'", DATA_TYPES_DISPLAYED_CORRECTLY, substrs=["1 match found"])
self.expect("image lookup -A -t '::Nested<char>'", DATA_TYPES_DISPLAYED_CORRECTLY, error=True)
self.expect_expr("t1", result_type="Foo<char>")
self.expect_expr("t1", result_type="Foo<char>")
self.expect_expr("t2", result_type="Foo<int>")
self.expect_expr("t3", result_type="Foo<Foo<int> >")
self.expect_expr("p1", result_type="FooPack<char>")
self.expect_expr("p2", result_type="FooPack<int>")
self.expect_expr("p3", result_type="FooPack<Foo<int> >")
self.expect_expr("p4", result_type="FooPack<char, int>")
self.expect_expr("p5", result_type="FooPack<char, float>")
self.expect_expr("p6", result_type="FooPack<int, int>")
self.expect_expr("p7", result_type="FooPack<int, int, int>")
self.expect_expr("n1", result_type="Foo<int>::Nested<char>")
@skipIf(compiler=no_match("clang"))
@skipIf(compiler_version=["<", "15.0"])
def test_simple_template_names(self):
self.do_test(dict(CFLAGS_EXTRAS="-gsimple-template-names"))
@skipIf(compiler=no_match("clang"))
@skipIf(compiler_version=["<", "15.0"])
def test_no_simple_template_names(self):
self.do_test(dict(CFLAGS_EXTRAS="-gno-simple-template-names"))

View File

@@ -0,0 +1,27 @@
template <class T> struct Foo {
T t;
template <class U> class Nested {
U u;
};
};
template <class T, class... Ss> class FooPack {
T t;
};
int main() {
Foo<char> t1;
Foo<int> t2;
Foo<Foo<int>> t3;
FooPack<char> p1;
FooPack<int> p2;
FooPack<Foo<int>> p3;
FooPack<char, int> p4;
FooPack<char, float> p5;
FooPack<int, int> p6;
FooPack<int, int, int> p7;
Foo<int>::Nested<char> n1;
// Set breakpoint here
}