Skip to content

Reland "[lldb][DWARFASTParserClang] Fetch constant value from variable defintion if available" #71800

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 61 additions & 1 deletion lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include "lldb/Symbol/SymbolFile.h"
#include "lldb/Symbol/TypeList.h"
#include "lldb/Symbol/TypeMap.h"
#include "lldb/Symbol/VariableList.h"
#include "lldb/Target/Language.h"
#include "lldb/Utility/LLDBAssert.h"
#include "lldb/Utility/Log.h"
Expand Down Expand Up @@ -139,6 +140,53 @@ static bool ShouldIgnoreArtificialField(llvm::StringRef FieldName) {
return FieldName.starts_with("_vptr$")
// gdb emit vtable pointer as "_vptr.classname"
|| FieldName.starts_with("_vptr.");

std::optional<DWARFFormValue>
DWARFASTParserClang::FindConstantOnVariableDefinition(DWARFDIE die) {
assert(die.Tag() == llvm::dwarf::DW_TAG_member);

auto *dwarf = die.GetDWARF();
if (!dwarf)
return {};

ConstString name{die.GetName()};
if (!name)
return {};

auto *CU = die.GetCU();
if (!CU)
return {};

DWARFASTParser *dwarf_ast = dwarf->GetDWARFParser(*CU);
auto parent_decl_ctx = dwarf_ast->GetDeclContextContainingUIDFromDWARF(die);

// Make sure we populate the GetDieToVariable cache.
VariableList variables;
dwarf->FindGlobalVariables(name, parent_decl_ctx, UINT_MAX, variables);

// The cache contains the variable definition whose DW_AT_specification
// points to our declaration DIE. Look up that definition using our
// declaration.
auto const &die_to_var = dwarf->GetDIEToVariable();
auto it = die_to_var.find(die.GetDIE());
if (it == die_to_var.end())
return {};

auto var_sp = it->getSecond();
assert(var_sp != nullptr);

if (!var_sp->GetLocationIsConstantValueData())
return {};

auto def = dwarf->GetDIE(var_sp->GetID());
auto def_attrs = def.GetAttributes();
DWARFFormValue form_value;
if (!def_attrs.ExtractFormValueAtIndex(
def_attrs.FindAttributeIndex(llvm::dwarf::DW_AT_const_value),
form_value))
return {};

return form_value;
}

TypeSP DWARFASTParserClang::ParseTypeFromClangModule(const SymbolContext &sc,
Expand Down Expand Up @@ -2914,9 +2962,21 @@ void DWARFASTParserClang::ParseSingleMember(

bool unused;
// TODO: Support float/double static members as well.
if (!attrs.const_value_form || !ct.IsIntegerOrEnumerationType(unused))
if (!ct.IsIntegerOrEnumerationType(unused))
return;

// Newer versions of Clang don't emit the DW_AT_const_value
// on the declaration of an inline static data member. Instead
// it's attached to the definition DIE. If that's the case,
// try and fetch it.
if (!attrs.const_value_form) {
auto maybe_form_value = FindConstantOnVariableDefinition(die);
if (!maybe_form_value)
return;

attrs.const_value_form = *maybe_form_value;
}

llvm::Expected<llvm::APInt> const_value_or_err =
ExtractIntFromFormValue(ct, *attrs.const_value_form);
if (!const_value_or_err) {
Expand Down
11 changes: 11 additions & 0 deletions lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.h
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,17 @@ class DWARFASTParserClang : public lldb_private::plugin::dwarf::DWARFASTParser {
lldb_private::CompilerType &class_clang_type,
const lldb::AccessType default_accesibility,
lldb_private::ClangASTImporter::LayoutInfo &layout_info);

/// Tries to find the definition DW_TAG_variable DIE of the the specified
/// DW_TAG_member 'die'. If such definition exists, returns the
/// DW_AT_const_value of that definition if available. Returns std::nullopt
/// otherwise.
///
/// In newer versions of clang, DW_AT_const_value attributes are not attached
/// to the declaration of a inline static data-member anymore, but rather on
/// its definition. This function is used to locate said constant.
std::optional<lldb_private::plugin::dwarf::DWARFFormValue>
FindConstantOnVariableDefinition(lldb_private::plugin::dwarf::DWARFDIE die);
};

/// Parsed form of all attributes that are relevant for type reconstruction.
Expand Down
5 changes: 2 additions & 3 deletions lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3564,9 +3564,8 @@ VariableSP SymbolFileDWARF::ParseVariableDIE(const SymbolContext &sc,
}

// Prefer DW_AT_location over DW_AT_const_value. Both can be emitted e.g.
// for static constexpr member variables -- DW_AT_const_value will be
// present in the class declaration and DW_AT_location in the DIE defining
// the member.
// for static constexpr member variables -- DW_AT_const_value and
// DW_AT_location will both be present in the DIE defining the member.
bool location_is_const_value_data =
const_value_form.IsValid() && !location_form.IsValid();

Expand Down
10 changes: 5 additions & 5 deletions lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.h
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,11 @@ class SymbolFileDWARF : public SymbolFileCommon {
return m_forward_decl_compiler_type_to_die;
}

typedef llvm::DenseMap<const DWARFDebugInfoEntry *, lldb::VariableSP>
DIEToVariableSP;

virtual DIEToVariableSP &GetDIEToVariable() { return m_die_to_variable_sp; }

virtual UniqueDWARFASTTypeMap &GetUniqueDWARFASTTypeMap();

bool ClassOrStructIsVirtual(const DWARFDIE &die);
Expand All @@ -362,9 +367,6 @@ class SymbolFileDWARF : public SymbolFileCommon {
Type *ResolveTypeUID(const DIERef &die_ref);

protected:
typedef llvm::DenseMap<const DWARFDebugInfoEntry *, lldb::VariableSP>
DIEToVariableSP;

SymbolFileDWARF(const SymbolFileDWARF &) = delete;
const SymbolFileDWARF &operator=(const SymbolFileDWARF &) = delete;

Expand Down Expand Up @@ -488,8 +490,6 @@ class SymbolFileDWARF : public SymbolFileCommon {

void UpdateExternalModuleListIfNeeded();

virtual DIEToVariableSP &GetDIEToVariable() { return m_die_to_variable_sp; }

void BuildCuTranslationTable();
std::optional<uint32_t> GetDWARFUnitIndex(uint32_t cu_idx);

Expand Down
7 changes: 7 additions & 0 deletions lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDwo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -142,3 +142,10 @@ SymbolFileDWARFDwo::GetDIE(const DIERef &die_ref) {
return DebugInfo().GetDIE(die_ref);
return GetBaseSymbolFile().GetDIE(die_ref);
}

void SymbolFileDWARFDwo::FindGlobalVariables(
ConstString name, const CompilerDeclContext &parent_decl_ctx,
uint32_t max_matches, VariableList &variables) {
GetBaseSymbolFile().FindGlobalVariables(name, parent_decl_ctx, max_matches,
variables);
}
5 changes: 5 additions & 0 deletions lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDwo.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ class SymbolFileDWARFDwo : public SymbolFileDWARF {
lldb::offset_t &offset,
std::vector<Value> &stack) const override;

void FindGlobalVariables(ConstString name,
const CompilerDeclContext &parent_decl_ctx,
uint32_t max_matches,
VariableList &variables) override;

protected:
DIEToTypePtr &GetDIEToType() override;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,38 @@ def test_class_with_only_const_static(self):

self.expect_expr("ClassWithOnlyConstStatic::member", result_value="3")

def check_global_var(self, name: str, expect_type, expect_val):
var_list = self.target().FindGlobalVariables(name, lldb.UINT32_MAX)
self.assertEqual(len(var_list), 1)
varobj = var_list[0]
self.assertEqual(varobj.type.name, expect_type)
self.assertEqual(varobj.value, expect_val)

# For debug-info produced by older versions of clang, inline static data members
# wouldn't get indexed into the Names accelerator table preventing LLDB from finding
# them.
@expectedFailureAll(compiler=["clang"], compiler_version=["<", "18.0"])
def test_inline_static_members(self):
self.build()
lldbutil.run_to_source_breakpoint(
self, "// break here", lldb.SBFileSpec("main.cpp")
)

self.check_global_var("A::int_val", "const int", "1")
self.check_global_var("A::int_val_with_address", "const int", "2")
self.check_global_var("A::bool_val", "const bool", "true")
self.check_global_var("A::enum_val", "Enum", "enum_case2")
self.check_global_var("A::enum_bool_val", "EnumBool", "enum_bool_case1")
self.check_global_var("A::scoped_enum_val", "ScopedEnum", "scoped_enum_case2")

self.check_global_var("ClassWithOnlyConstStatic::member", "const int", "3")

self.check_global_var("ClassWithConstexprs::member", "const int", "2")
self.check_global_var("ClassWithConstexprs::enum_val", "Enum", "enum_case2")
self.check_global_var(
"ClassWithConstexprs::scoped_enum_val", "ScopedEnum", "scoped_enum_case2"
)

# With older versions of Clang, LLDB fails to evaluate classes with only
# constexpr members when dsymutil is enabled
@expectedFailureAll(
Expand All @@ -139,3 +171,24 @@ def test_class_with_only_constexpr_static(self):
self.expect_expr(
"ClassWithEnumAlias::enum_alias_alias", result_value="scoped_enum_case1"
)

def test_shadowed_static_inline_members(self):
"""Tests that the expression evaluator and SBAPI can both
correctly determine the requested inline static variable
in the presence of multiple variables of the same name."""

self.build()
lldbutil.run_to_name_breakpoint(self, "bar")

self.check_global_var("ns::Foo::mem", "const int", "10")

self.expect_expr("mem", result_value="10")
self.expect_expr("Foo::mem", result_value="10")
self.expect_expr("ns::Foo::mem", result_value="10")
self.expect_expr("::Foo::mem", result_value="-29")

@expectedFailureAll(bugnumber="target var doesn't honour global namespace")
def test_shadowed_static_inline_members_xfail(self):
self.build()
lldbutil.run_to_name_breakpoint(self, "bar")
self.check_global_var("::Foo::mem", "const int", "-29")
20 changes: 20 additions & 0 deletions lldb/test/API/lang/cpp/const_static_integral_member/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,25 @@ struct ClassWithEnumAlias {
ScopedEnum::scoped_enum_case1;
};

namespace ns {
struct Foo {
constexpr static int mem = 10;

void bar() { return; }
};
} // namespace ns

struct Foo {
constexpr static int mem = -29;
};

int func() {
Foo f1;
ns::Foo f2;
f2.bar();
return ns::Foo::mem + Foo::mem;
}

int main() {
A a;

Expand Down Expand Up @@ -124,6 +143,7 @@ int main() {

auto enum_alias_val = ClassWithEnumAlias::enum_alias;
auto enum_alias_alias_val = ClassWithEnumAlias::enum_alias_alias;
auto ret = func();

return 0; // break here
}