Skip to content

Commit

Permalink
Reland "[lldb] Add support for DW_AT_default_value in template params"
Browse files Browse the repository at this point in the history
**Summary**

This patch makes LLDB understand the `DW_AT_default_value` on
template argument DIEs. As a result, type summaries will no
longer contain the defaulted template arguments, reducing
noise substantially. E.g.,

Before:
```
(lldb) v nested
(std::vector<std::vector<std::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::basic_string<char, std::char_traits<char>, std::allocator<char> > > >, std::allocator<std::vector<std::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::basic_string<char, std::char_traits<char>, std::allocator <char> > > > > >) nested = size=0 {}
```

After:
```
(lldb) v nested
(std::vector<std::vector<std::basic_string<char> > >) nested = size=0 {}
```

See discussion in https://reviews.llvm.org/D140423

**Testing**

* Adjust API tests
* Added unit-test

Differential Revision: https://reviews.llvm.org/D141828
  • Loading branch information
Michael137 committed Jan 27, 2023
1 parent f7be1aa commit ce6a56e
Show file tree
Hide file tree
Showing 10 changed files with 412 additions and 41 deletions.
16 changes: 12 additions & 4 deletions lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp
Expand Up @@ -2003,6 +2003,7 @@ bool DWARFASTParserClang::ParseTemplateDIE(
CompilerType clang_type;
uint64_t uval64 = 0;
bool uval64_valid = false;
bool is_default_template_arg = false;
if (num_attributes > 0) {
DWARFFormValue form_value;
for (size_t i = 0; i < num_attributes; ++i) {
Expand Down Expand Up @@ -2033,6 +2034,10 @@ bool DWARFASTParserClang::ParseTemplateDIE(
uval64 = form_value.Unsigned();
}
break;
case DW_AT_default_value:
if (attributes.ExtractFormValueAtIndex(i, form_value))
is_default_template_arg = form_value.Boolean();
break;
default:
break;
}
Expand All @@ -2058,16 +2063,19 @@ bool DWARFASTParserClang::ParseTemplateDIE(
template_param_infos.InsertArg(
name,
clang::TemplateArgument(ast, llvm::APSInt(apint, !is_signed),
ClangUtil::GetQualType(clang_type)));
ClangUtil::GetQualType(clang_type),
is_default_template_arg));
} else {
template_param_infos.InsertArg(
name,
clang::TemplateArgument(ClangUtil::GetQualType(clang_type)));
name, clang::TemplateArgument(ClangUtil::GetQualType(clang_type),
/*isNullPtr*/ false,
is_default_template_arg));
}
} else {
auto *tplt_type = m_ast.CreateTemplateTemplateParmDecl(template_name);
template_param_infos.InsertArg(
name, clang::TemplateArgument(clang::TemplateName(tplt_type)));
name, clang::TemplateArgument(clang::TemplateName(tplt_type),
is_default_template_arg));
}
}
}
Expand Down
Expand Up @@ -31,38 +31,32 @@ def test(self):
ValueCheck(value="2"),
]

# The debug info vector type doesn't know about the default template
# arguments, so they will be printed.
debug_info_vector_type = "std::vector<int, std::allocator<int> >"

# The C++ module vector knows its default template arguments and will
# suppress them.
module_vector_type = "std::vector<int>"
vector_type = "std::vector<int>"

# First muddy the scratch AST with non-C++ module types.
self.expect_expr("a", result_type=debug_info_vector_type,
self.expect_expr("a", result_type=vector_type,
result_children=children)
self.expect_expr("dbg_info_vec",
result_type="std::vector<DbgInfoClass, std::allocator<DbgInfoClass> >",
result_type="std::vector<DbgInfoClass>",
result_children=[
ValueCheck(type="DbgInfoClass", children=[
ValueCheck(name="ints", type=debug_info_vector_type, children=[
ValueCheck(name="ints", type=vector_type, children=[
ValueCheck(value="1")
])
])
])

# Enable the C++ module import and get the module vector type.
self.runCmd("settings set target.import-std-module true")
self.expect_expr("a", result_type=module_vector_type,
self.expect_expr("a", result_type=vector_type,
result_children=children)

# Test mixed debug info/module types
self.expect_expr("dbg_info_vec",
result_type="std::vector<DbgInfoClass>",
result_children=[
ValueCheck(type="DbgInfoClass", children=[
ValueCheck(name="ints", type=module_vector_type, children=[
ValueCheck(name="ints", type=vector_type, children=[
ValueCheck(value="1")
])
])
Expand All @@ -71,15 +65,15 @@ def test(self):

# Turn off the C++ module import and use debug info types again.
self.runCmd("settings set target.import-std-module false")
self.expect_expr("a", result_type=debug_info_vector_type,
self.expect_expr("a", result_type=vector_type,
result_children=children)

# Test the types that were previoiusly mixed debug info/module types.
self.expect_expr("dbg_info_vec",
result_type="std::vector<DbgInfoClass, std::allocator<DbgInfoClass> >",
result_type="std::vector<DbgInfoClass>",
result_children=[
ValueCheck(type="DbgInfoClass", children=[
ValueCheck(name="ints", type=debug_info_vector_type, children=[
ValueCheck(name="ints", type=vector_type, children=[
ValueCheck(value="1")
])
])
Expand Down
Expand Up @@ -15,17 +15,15 @@ def test(self):
lldb.SBFileSpec("main.cpp"))

# Test printing the vector before enabling any C++ module setting.
self.expect_expr("a", result_type="std::vector<int, std::allocator<int> >")
self.expect_expr("a", result_type="std::vector<int>")

# Set loading the import-std-module to 'fallback' which loads the module
# and retries when an expression fails to parse.
self.runCmd("settings set target.import-std-module fallback")

# Printing the vector still works. This should return the same type
# as before as this shouldn't use a C++ module type (the C++ module type
# is hiding the second template parameter as it's equal to the default
# argument which the C++ module has type info for).
self.expect_expr("a", result_type="std::vector<int, std::allocator<int> >")
# as before as this shouldn't use a C++ module type.
self.expect_expr("a", result_type="std::vector<int>")

# This expression can only parse with a C++ module. LLDB should
# automatically fall back to import the C++ module to get this working.
Expand Down
Expand Up @@ -60,7 +60,7 @@ def test_shared_ptr_variables(self):

valobj = self.expect_var_path(
"sp_str",
type="std::shared_ptr<std::basic_string<char, std::char_traits<char>, std::allocator<char> > >",
type="std::shared_ptr<std::basic_string<char> >",
children=[ValueCheck(name="__ptr_", summary='"hello"')],
)
self.assertRegex(valobj.summary, r'^"hello"( strong=1)? weak=1$')
Expand Down
Expand Up @@ -70,8 +70,7 @@ def cleanup():
'(%s::u32string) u32_string = U"🍄🍅🍆🍌"'%ns,
# FIXME: This should have a 'U' prefix.
'(%s::u32string) u32_empty = ""'%ns,
'(%s::basic_string<unsigned char, %s::char_traits<unsigned char>, '
'%s::allocator<unsigned char> >) uchar = "aaaaa"'%(ns,ns,ns),
'(%s::basic_string<unsigned char>) uchar = "aaaaa"'%(ns),
'(%s::string *) null_str = nullptr'%ns,
])

Expand Down Expand Up @@ -108,8 +107,7 @@ def cleanup():
'(%s::u16string) u16_string = u"ß水氶"'%ns,
'(%s::u32string) u32_string = U"🍄🍅🍆🍌"'%ns,
'(%s::u32string) u32_empty = ""'%ns,
'(%s::basic_string<unsigned char, %s::char_traits<unsigned char>, '
'%s::allocator<unsigned char> >) uchar = "aaaaa"'%(ns,ns,ns),
'(%s::basic_string<unsigned char>) uchar = "aaaaa"'%(ns),
'(%s::string *) null_str = nullptr'%ns,
])

Expand Down
Expand Up @@ -96,10 +96,10 @@ def cleanup():
type='std::u32string_view',
summary='""')
self.expect_var_path('uchar_source',
type='std::basic_string<unsigned char, std::char_traits<unsigned char>, std::allocator<unsigned char> >',
type='std::basic_string<unsigned char>',
summary='"aaaaaaaaaa"')
self.expect_var_path('uchar',
type='std::basic_string_view<unsigned char, std::char_traits<unsigned char> >',
type='std::basic_string_view<unsigned char>',
summary='"aaaaa"')
self.expect_var_path('oops',
type='std::string_view',
Expand Down Expand Up @@ -172,10 +172,10 @@ def cleanup():
type='std::u32string_view',
summary='""')
self.expect_var_path('uchar_source',
type='std::basic_string<unsigned char, std::char_traits<unsigned char>, std::allocator<unsigned char> >',
type='std::basic_string<unsigned char>',
summary='"aaaaaaaaaa"')
self.expect_var_path('uchar',
type='std::basic_string_view<unsigned char, std::char_traits<unsigned char> >',
type='std::basic_string_view<unsigned char>',
summary='"aaaaa"')

self.runCmd('cont')
Expand Down
Expand Up @@ -22,7 +22,7 @@ def test_unique_ptr_variables(self):

valobj = self.expect_var_path(
"up_empty",
type="std::unique_ptr<int, std::default_delete<int> >",
type="std::unique_ptr<int>",
summary="nullptr",
children=[ValueCheck(name="__value_")],
)
Expand All @@ -36,37 +36,37 @@ def test_unique_ptr_variables(self):

valobj = self.expect_var_path(
"up_int",
type="std::unique_ptr<int, std::default_delete<int> >",
type="std::unique_ptr<int>",
summary="10",
children=[ValueCheck(name="__value_")],
)
self.assertNotEqual(valobj.child[0].unsigned, 0)

valobj = self.expect_var_path(
"up_int_ref",
type="std::unique_ptr<int, std::default_delete<int> > &",
type="std::unique_ptr<int> &",
summary="10",
children=[ValueCheck(name="__value_")],
)
self.assertNotEqual(valobj.child[0].unsigned, 0)

valobj = self.expect_var_path(
"up_int_ref_ref",
type="std::unique_ptr<int, std::default_delete<int> > &&",
type="std::unique_ptr<int> &&",
summary="10",
children=[ValueCheck(name="__value_")],
)
self.assertNotEqual(valobj.child[0].unsigned, 0)

valobj = self.expect_var_path(
"up_str",
type="std::unique_ptr<std::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::default_delete<std::basic_string<char, std::char_traits<char>, std::allocator<char> > > >",
type="std::unique_ptr<std::basic_string<char> >",
summary='"hello"',
children=[ValueCheck(name="__value_", summary='"hello"')],
)

valobj = self.expect_var_path(
"up_user", type="std::unique_ptr<User, std::default_delete<User> >"
"up_user", type="std::unique_ptr<User>"
)
self.assertRegex(valobj.summary, "^User @ 0x0*[1-9a-f][0-9a-f]+$")
self.assertNotEqual(valobj.child[0].unsigned, 0)
Expand Down
3 changes: 2 additions & 1 deletion lldb/unittests/SymbolFile/DWARF/CMakeLists.txt
Expand Up @@ -24,6 +24,7 @@ add_lldb_unittest(SymbolFileDWARFTests
)

set(test_inputs
test-dwarf.exe)
test-dwarf.exe
DW_AT_default_value-test.yaml)

add_unittest_inputs(SymbolFileDWARFTests "${test_inputs}")
60 changes: 59 additions & 1 deletion lldb/unittests/SymbolFile/DWARF/DWARFASTParserClangTests.cpp
Expand Up @@ -11,6 +11,7 @@
#include "Plugins/SymbolFile/DWARF/DWARFDIE.h"
#include "TestingSupport/Symbol/ClangTestUtils.h"
#include "TestingSupport/Symbol/YAMLModuleTester.h"
#include "lldb/Core/Debugger.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"

Expand All @@ -19,7 +20,14 @@ using namespace lldb_private;
using namespace lldb_private::dwarf;

namespace {
class DWARFASTParserClangTests : public testing::Test {};
static std::once_flag debugger_initialize_flag;

class DWARFASTParserClangTests : public testing::Test {
void SetUp() override {
std::call_once(debugger_initialize_flag,
[]() { Debugger::Initialize(nullptr); });
}
};

class DWARFASTParserClangStub : public DWARFASTParserClang {
public:
Expand Down Expand Up @@ -404,3 +412,53 @@ TEST_F(ExtractIntFromFormValueTest, TestUnsignedInt) {
EXPECT_THAT_EXPECTED(Extract(ast.UnsignedIntTy, uint_max + 2),
llvm::Failed());
}

TEST_F(DWARFASTParserClangTests, TestDefaultTemplateParamParsing) {
// Tests parsing DW_AT_default_value for template parameters.
auto BufferOrError = llvm::MemoryBuffer::getFile(
GetInputFilePath("DW_AT_default_value-test.yaml"), /*IsText=*/true);
ASSERT_TRUE(BufferOrError);
YAMLModuleTester t(BufferOrError.get()->getBuffer());

DWARFUnit *unit = t.GetDwarfUnit();
ASSERT_NE(unit, nullptr);
const DWARFDebugInfoEntry *cu_entry = unit->DIE().GetDIE();
ASSERT_EQ(cu_entry->Tag(), DW_TAG_compile_unit);
DWARFDIE cu_die(unit, cu_entry);

auto holder = std::make_unique<clang_utils::TypeSystemClangHolder>("ast");
auto &ast_ctx = *holder->GetAST();
DWARFASTParserClangStub ast_parser(ast_ctx);

llvm::SmallVector<lldb::TypeSP, 2> types;
for (DWARFDIE die : cu_die.children()) {
if (die.Tag() == DW_TAG_class_type) {
SymbolContext sc;
bool new_type = false;
types.push_back(ast_parser.ParseTypeFromDWARF(sc, die, &new_type));
}
}

ASSERT_EQ(types.size(), 3U);

auto check_decl = [](auto const *decl) {
clang::ClassTemplateSpecializationDecl const *ctsd =
llvm::dyn_cast_or_null<clang::ClassTemplateSpecializationDecl>(decl);
ASSERT_NE(ctsd, nullptr);

auto const &args = ctsd->getTemplateArgs();
ASSERT_GT(args.size(), 0U);

for (auto const &arg : args.asArray()) {
EXPECT_TRUE(arg.getIsDefaulted());
}
};

for (auto const &type_sp : types) {
ASSERT_NE(type_sp, nullptr);
auto const *decl = ClangUtil::GetAsTagDecl(type_sp->GetFullCompilerType());
if (decl->getName() == "bar" || decl->getName() == "baz") {
check_decl(decl);
}
}
}

0 comments on commit ce6a56e

Please sign in to comment.