Skip to content

Commit f74aaca

Browse files
authored
[lldb][DWARFASTParserClang] Check DW_AT_declaration to determine static data members (#68300)
**Background** Prior to DWARFv4, there was no clear normative text on how to handle static data members. Non-normative text suggested that compilers should use `DW_AT_external` to mark static data members of structrues/unions. Clang does this consistently. However, GCC doesn't, e.g., when the structure/union is in an anonymous namespace (which is C++ standard conformant). Additionally, GCC never emits `DW_AT_data_member_location`s for union members (regardless of storage linkage and storage duration). Since DWARFv5 (issue 161118.1), static data members get emitted as `DW_TAG_variable`. LLDB used to differentiate between static and non-static members by checking the `DW_AT_external` flag and the absence of `DW_AT_data_member_location`. With [D18008](https://reviews.llvm.org/D18008) LLDB started to pretend that union members always have a `0` `DW_AT_data_member_location` by default (because GCC never emits these locations). In [D124409](https://reviews.llvm.org/D124409) LLDB stopped checking the `DW_AT_external` flag to account for the case where GCC doesn't emit the flag for types in anonymous namespaces; instead we only check for presence of `DW_AT_data_member_location`s. The combination of these changes then meant that LLDB would never correctly detect that a union has static data members. **Solution** Instead of unconditionally initializing the `member_byte_offset` to `0` specifically for union members, this patch proposes to check for both the absence of `DW_AT_data_member_location` and `DW_AT_declaration`, which consistently gets emitted for static data members on GCC and Clang. We initialize the `member_byte_offset` to `0` anyway if we determine it wasn't a static. So removing the special case for unions makes this code simpler to reason about. Long-term, we should just use DWARFv5's new representation for static data members. Fixes #68135
1 parent 2e1718a commit f74aaca

File tree

4 files changed

+87
-6
lines changed

4 files changed

+87
-6
lines changed

lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2492,8 +2492,9 @@ struct MemberAttributes {
24922492
DWARFFormValue encoding_form;
24932493
/// Indicates the byte offset of the word from the base address of the
24942494
/// structure.
2495-
uint32_t member_byte_offset;
2495+
uint32_t member_byte_offset = UINT32_MAX;
24962496
bool is_artificial = false;
2497+
bool is_declaration = false;
24972498
};
24982499

24992500
/// Parsed form of all attributes that are relevant for parsing Objective-C
@@ -2627,8 +2628,6 @@ DiscriminantValue &VariantPart::discriminant() { return this->_discriminant; }
26272628
MemberAttributes::MemberAttributes(const DWARFDIE &die,
26282629
const DWARFDIE &parent_die,
26292630
ModuleSP module_sp) {
2630-
member_byte_offset = (parent_die.Tag() == DW_TAG_union_type) ? 0 : UINT32_MAX;
2631-
26322631
DWARFAttributes attributes = die.GetAttributes();
26332632
for (size_t i = 0; i < attributes.Size(); ++i) {
26342633
const dw_attr_t attr = attributes.AttributeAtIndex(i);
@@ -2669,6 +2668,9 @@ MemberAttributes::MemberAttributes(const DWARFDIE &die,
26692668
case DW_AT_artificial:
26702669
is_artificial = form_value.Boolean();
26712670
break;
2671+
case DW_AT_declaration:
2672+
is_declaration = form_value.Boolean();
2673+
break;
26722674
default:
26732675
break;
26742676
}
@@ -2875,10 +2877,18 @@ void DWARFASTParserClang::ParseSingleMember(
28752877
if (class_is_objc_object_or_interface)
28762878
attrs.accessibility = eAccessNone;
28772879

2878-
// Handle static members, which is any member that doesn't have a bit or a
2879-
// byte member offset.
2880+
// Handle static members, which are typically members without
2881+
// locations. However, GCC *never* emits DW_AT_data_member_location
2882+
// for static data members of unions.
2883+
// Non-normative text pre-DWARFv5 recommends marking static
2884+
// data members with an DW_AT_external flag. Clang emits this consistently
2885+
// whereas GCC emits it only for static data members if not part of an
2886+
// anonymous namespace. The flag that is consistently emitted for static
2887+
// data members is DW_AT_declaration, so we check it instead.
2888+
// FIXME: Since DWARFv5, static data members are marked DW_AT_variable so we
2889+
// can consistently detect them on both GCC and Clang without below heuristic.
28802890
if (attrs.member_byte_offset == UINT32_MAX &&
2881-
attrs.data_bit_offset == UINT64_MAX) {
2891+
attrs.data_bit_offset == UINT64_MAX && attrs.is_declaration) {
28822892
Type *var_type = die.ResolveTypeUID(attrs.encoding_form.Reference());
28832893

28842894
if (var_type) {
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
CXX_SOURCES := main.cpp
2+
3+
include Makefile.rules
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
"""
2+
Tests that frame variable and expr work for
3+
C++ unions and their static data members.
4+
"""
5+
import lldb
6+
from lldbsuite.test.lldbtest import *
7+
from lldbsuite.test.decorators import *
8+
import lldbsuite.test.lldbutil as lldbutil
9+
10+
class CppUnionStaticMembersTestCase(TestBase):
11+
def test(self):
12+
"""Tests that frame variable and expr work
13+
for union static data members"""
14+
self.build()
15+
16+
(target, process, main_thread, _) = lldbutil.run_to_source_breakpoint(
17+
self, "return 0", lldb.SBFileSpec("main.cpp")
18+
)
19+
20+
self.expect("frame variable foo", substrs=["val = 42"])
21+
self.expect("frame variable bar", substrs=["val = 137"])
22+
23+
self.expect_expr("foo", result_type="Foo", result_children=[ValueCheck(
24+
name="val", value="42"
25+
)])
26+
self.expect_expr("bar", result_type="Bar", result_children=[ValueCheck(
27+
name="val", value="137"
28+
)])
29+
30+
self.expect_expr("Foo::sVal1", result_type="const int", result_value="-42")
31+
self.expect_expr("Foo::sVal2", result_type="Foo", result_children=[ValueCheck(
32+
name="val", value="42"
33+
)])
34+
35+
@expectedFailureAll
36+
def test_union_in_anon_namespace(self):
37+
"""Tests that frame variable and expr work
38+
for union static data members in anonymous
39+
namespaces"""
40+
self.expect_expr("Bar::sVal1", result_type="const int", result_value="-137")
41+
self.expect_expr("Bar::sVal2", result_type="Bar", result_children=[ValueCheck(
42+
name="val", value="137"
43+
)])
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
union Foo {
2+
int val = 42;
3+
static const int sVal1 = -42;
4+
static Foo sVal2;
5+
};
6+
7+
Foo Foo::sVal2{};
8+
9+
namespace {
10+
union Bar {
11+
int val = 137;
12+
static const int sVal1 = -137;
13+
static Bar sVal2;
14+
};
15+
16+
Bar Bar::sVal2{};
17+
} // namespace
18+
19+
int main() {
20+
Foo foo;
21+
Bar bar;
22+
auto sum = Bar::sVal1 + Foo::sVal1 + Foo::sVal2.val + Bar::sVal2.val;
23+
24+
return 0;
25+
}

0 commit comments

Comments
 (0)