Skip to content

Commit bdae26f

Browse files
authored
[LLDB][DWARF] Use the same qualified name computation for Rust (#165840)
Currently LLDB's `ParseRustVariantPart` generates the following `CXXRecordDecl` for a Rust enum ```rust enum AA { A(u8) } ``` ``` CXXRecordDecl 0x5555568d5970 <<invalid sloc>> <invalid sloc> struct AA |-CXXRecordDecl 0x5555568d5ab0 <<invalid sloc>> <invalid sloc> union test_issue::AA$Inner definition | |-CXXRecordDecl 0x5555568d5d18 <<invalid sloc>> <invalid sloc> struct A$Variant definition | | |-DefinitionData pass_in_registers aggregate standard_layout trivially_copyable trivial | | | `-Destructor simple irrelevant trivial needs_implicit | | `-FieldDecl 0x555555a77880 <<invalid sloc>> <invalid sloc> value 'test_issue::AA::A' | `-FieldDecl 0x555555a778f0 <<invalid sloc>> <invalid sloc> $variant$ 'test_issue::AA::test_issue::AA$Inner::A$Variant' |-CXXRecordDecl 0x5555568d5c48 <<invalid sloc>> <invalid sloc> struct A definition | `-FieldDecl 0x555555a777e0 <<invalid sloc>> <invalid sloc> __0 'unsigned char' `-FieldDecl 0x555555a77960 <<invalid sloc>> <invalid sloc> $variants$ 'test_issue::AA::test_issue::AA$Inner' ``` While when the Rust enum type name is the same as its variant name, the generated `CXXRecordDecl` becomes the following – there's a circular reference between `struct A$Variant` and `struct A`, causing #163048. ```rust enum A { A(u8) } ``` ``` CXXRecordDecl 0x5555568d5760 <<invalid sloc>> <invalid sloc> struct A |-CXXRecordDecl 0x5555568d58a0 <<invalid sloc>> <invalid sloc> union test_issue::A$Inner definition | |-CXXRecordDecl 0x5555568d5a38 <<invalid sloc>> <invalid sloc> struct A$Variant definition | | `-FieldDecl 0x5555568d5b70 <<invalid sloc>> <invalid sloc> value 'test_issue::A' <---- bug here | `-FieldDecl 0x5555568d5be0 <<invalid sloc>> <invalid sloc> $variant$ 'test_issue::A::test_issue::A$Inner::A$Variant' `-FieldDecl 0x5555568d5c50 <<invalid sloc>> <invalid sloc> $variants$ 'test_issue::A::test_issue::A$Inner' ``` The problem was caused by `GetUniqueTypeNameAndDeclaration` not returning the correct qualified name for DWARF DIE `test_issue::A::A`, instead, it returned `A`. This caused `ParseStructureLikeDIE` to find the wrong type `test_issue::A` and returned early. The failure in `GetUniqueTypeNameAndDeclaration` appears to stem from a language check that returns early unless the language is C++. I changed it so Rust follows the C++ path rather than returning. I’m not entirely sure this is the right approach — Rust’s qualified name rules look similar, but not identical? Alternatively, we could add a Rust-specific implementation that forms qualified names according to Rust's rules.
1 parent e992280 commit bdae26f

File tree

5 files changed

+1262
-2
lines changed

5 files changed

+1262
-2
lines changed

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1705,8 +1705,11 @@ void DWARFASTParserClang::GetUniqueTypeNameAndDeclaration(
17051705
// For C++, we rely solely upon the one definition rule that says
17061706
// only one thing can exist at a given decl context. We ignore the
17071707
// file and line that things are declared on.
1708-
if (!die.IsValid() || !Language::LanguageIsCPlusPlus(language) ||
1709-
unique_typename.IsEmpty())
1708+
// FIXME: Rust pretends to be C++ for now, so use C++ name qualification rules
1709+
if (!Language::LanguageIsCPlusPlus(language) &&
1710+
language != lldb::eLanguageTypeRust)
1711+
return;
1712+
if (!die.IsValid() || unique_typename.IsEmpty())
17101713
return;
17111714
decl_declaration.Clear();
17121715
std::string qualified_name;
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
"""Helper library to traverse data emitted for Rust enums """
2+
from lldbsuite.test.lldbtest import *
3+
4+
DISCRIMINANT_MEMBER_NAME = "$discr$"
5+
VALUE_MEMBER_NAME = "value"
6+
7+
8+
class RustEnumValue:
9+
def __init__(self, value: lldb.SBValue):
10+
self.value = value
11+
12+
def getAllVariantTypes(self):
13+
result = []
14+
for i in range(self._inner().GetNumChildren()):
15+
result.append(self.getVariantByIndex(i).GetDisplayTypeName())
16+
return result
17+
18+
def _inner(self) -> lldb.SBValue:
19+
return self.value.GetChildAtIndex(0)
20+
21+
def getVariantByIndex(self, index):
22+
return (
23+
self._inner()
24+
.GetChildAtIndex(index)
25+
.GetChildMemberWithName(VALUE_MEMBER_NAME)
26+
)
27+
28+
@staticmethod
29+
def _getDiscriminantValueAsUnsigned(discr_sbvalue: lldb.SBValue):
30+
byte_size = discr_sbvalue.GetType().GetByteSize()
31+
error = lldb.SBError()
32+
33+
# when discriminant is u16 Clang emits 'unsigned char'
34+
# and LLDB seems to treat it as character type disalowing to call GetValueAsUnsigned
35+
if byte_size == 1:
36+
return discr_sbvalue.GetData().GetUnsignedInt8(error, 0)
37+
elif byte_size == 2:
38+
return discr_sbvalue.GetData().GetUnsignedInt16(error, 0)
39+
elif byte_size == 4:
40+
return discr_sbvalue.GetData().GetUnsignedInt32(error, 0)
41+
elif byte_size == 8:
42+
return discr_sbvalue.GetData().GetUnsignedInt64(error, 0)
43+
else:
44+
return discr_sbvalue.GetValueAsUnsigned()
45+
46+
def getCurrentVariantIndex(self):
47+
default_index = 0
48+
for i in range(self._inner().GetNumChildren()):
49+
variant: lldb.SBValue = self._inner().GetChildAtIndex(i)
50+
discr = variant.GetChildMemberWithName(DISCRIMINANT_MEMBER_NAME)
51+
if discr.IsValid():
52+
discr_unsigned_value = RustEnumValue._getDiscriminantValueAsUnsigned(
53+
discr
54+
)
55+
if variant.GetName() == f"$variant${discr_unsigned_value}":
56+
return discr_unsigned_value
57+
else:
58+
default_index = i
59+
return default_index
60+
61+
def getFields(self):
62+
result = []
63+
for i in range(self._inner().GetNumChildren()):
64+
type: lldb.SBType = self._inner().GetType()
65+
result.append(type.GetFieldAtIndex(i).GetName())
66+
return result
67+
68+
def getCurrentValue(self) -> lldb.SBValue:
69+
return self.getVariantByIndex(self.getCurrentVariantIndex())
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
"""Test that lldb recognizes enum variant emitted by Rust compiler """
2+
import logging
3+
4+
import lldb
5+
from lldbsuite.test.decorators import *
6+
from lldbsuite.test.lldbtest import *
7+
from RustEnumValue import RustEnumValue
8+
9+
10+
class TestRustEnumStructs(TestBase):
11+
def setUp(self):
12+
TestBase.setUp(self)
13+
src_dir = self.getSourceDir()
14+
yaml_path = os.path.join(src_dir, "main.yaml")
15+
obj_path = self.getBuildArtifact("main.o")
16+
self.yaml2obj(yaml_path, obj_path)
17+
self.dbg.CreateTarget(obj_path)
18+
19+
def getFromGlobal(self, name):
20+
values = self.target().FindGlobalVariables(name, 1)
21+
self.assertEqual(values.GetSize(), 1)
22+
return RustEnumValue(values[0])
23+
24+
def test_enum_instance(self):
25+
# static ENUM_INSTANCE: A = A::A(B::B(10));
26+
value = self.getFromGlobal("ENUM_INSTANCE").getCurrentValue()
27+
self.assertEqual(value.GetType().GetDisplayTypeName(), "main::A::A")
28+
29+
value_b = RustEnumValue(value.GetChildAtIndex(0))
30+
self.assertEqual(
31+
value_b.getCurrentValue()
32+
.GetChildAtIndex(0)
33+
.GetData()
34+
.GetUnsignedInt8(lldb.SBError(), 0),
35+
10,
36+
)
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/// Command:
2+
/// rustc -g --emit=obj --crate-type=bin -C panic=abort -C link-arg=-nostdlib main.rs && obj2yaml main.o -o main.yaml
3+
4+
pub enum A {
5+
A(B),
6+
}
7+
8+
pub enum B {
9+
B(u8),
10+
}
11+
12+
static ENUM_INSTANCE: A = A::A(B::B(10));
13+
14+
pub fn main() {
15+
}

0 commit comments

Comments
 (0)