Skip to content

Commit 09e0bd3

Browse files
committed
[lldb][DWARFASTParserClang] Check DW_AT_declaration to determine static data members (llvm#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 llvm#68135 (cherry picked from commit f74aaca)
1 parent 1511e98 commit 09e0bd3

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
@@ -2434,8 +2434,9 @@ struct MemberAttributes {
24342434
DWARFFormValue encoding_form;
24352435
/// Indicates the byte offset of the word from the base address of the
24362436
/// structure.
2437-
uint32_t member_byte_offset;
2437+
uint32_t member_byte_offset = UINT32_MAX;
24382438
bool is_artificial = false;
2439+
bool is_declaration = false;
24392440
};
24402441

24412442
/// Parsed form of all attributes that are relevant for parsing Objective-C
@@ -2453,8 +2454,6 @@ struct PropertyAttributes {
24532454
MemberAttributes::MemberAttributes(const DWARFDIE &die,
24542455
const DWARFDIE &parent_die,
24552456
ModuleSP module_sp) {
2456-
member_byte_offset = (parent_die.Tag() == DW_TAG_union_type) ? 0 : UINT32_MAX;
2457-
24582457
DWARFAttributes attributes;
24592458
const size_t num_attributes = die.GetAttributes(attributes);
24602459
for (std::size_t i = 0; i < num_attributes; ++i) {
@@ -2514,6 +2513,9 @@ MemberAttributes::MemberAttributes(const DWARFDIE &die,
25142513
case DW_AT_artificial:
25152514
is_artificial = form_value.Boolean();
25162515
break;
2516+
case DW_AT_declaration:
2517+
is_declaration = form_value.Boolean();
2518+
break;
25172519
default:
25182520
break;
25192521
}
@@ -2715,10 +2717,18 @@ void DWARFASTParserClang::ParseSingleMember(
27152717
if (class_is_objc_object_or_interface)
27162718
attrs.accessibility = eAccessNone;
27172719

2718-
// Handle static members, which is any member that doesn't have a bit or a
2719-
// byte member offset.
2720+
// Handle static members, which are typically members without
2721+
// locations. However, GCC *never* emits DW_AT_data_member_location
2722+
// for static data members of unions.
2723+
// Non-normative text pre-DWARFv5 recommends marking static
2724+
// data members with an DW_AT_external flag. Clang emits this consistently
2725+
// whereas GCC emits it only for static data members if not part of an
2726+
// anonymous namespace. The flag that is consistently emitted for static
2727+
// data members is DW_AT_declaration, so we check it instead.
2728+
// FIXME: Since DWARFv5, static data members are marked DW_AT_variable so we
2729+
// can consistently detect them on both GCC and Clang without below heuristic.
27202730
if (attrs.member_byte_offset == UINT32_MAX &&
2721-
attrs.data_bit_offset == UINT64_MAX) {
2731+
attrs.data_bit_offset == UINT64_MAX && attrs.is_declaration) {
27222732
Type *var_type = die.ResolveTypeUID(attrs.encoding_form.Reference());
27232733

27242734
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)