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.
70 lines
2.5 KiB
Python
70 lines
2.5 KiB
Python
"""Helper library to traverse data emitted for Rust enums """
|
|
from lldbsuite.test.lldbtest import *
|
|
|
|
DISCRIMINANT_MEMBER_NAME = "$discr$"
|
|
VALUE_MEMBER_NAME = "value"
|
|
|
|
|
|
class RustEnumValue:
|
|
def __init__(self, value: lldb.SBValue):
|
|
self.value = value
|
|
|
|
def getAllVariantTypes(self):
|
|
result = []
|
|
for i in range(self._inner().GetNumChildren()):
|
|
result.append(self.getVariantByIndex(i).GetDisplayTypeName())
|
|
return result
|
|
|
|
def _inner(self) -> lldb.SBValue:
|
|
return self.value.GetChildAtIndex(0)
|
|
|
|
def getVariantByIndex(self, index):
|
|
return (
|
|
self._inner()
|
|
.GetChildAtIndex(index)
|
|
.GetChildMemberWithName(VALUE_MEMBER_NAME)
|
|
)
|
|
|
|
@staticmethod
|
|
def _getDiscriminantValueAsUnsigned(discr_sbvalue: lldb.SBValue):
|
|
byte_size = discr_sbvalue.GetType().GetByteSize()
|
|
error = lldb.SBError()
|
|
|
|
# when discriminant is u16 Clang emits 'unsigned char'
|
|
# and LLDB seems to treat it as character type disalowing to call GetValueAsUnsigned
|
|
if byte_size == 1:
|
|
return discr_sbvalue.GetData().GetUnsignedInt8(error, 0)
|
|
elif byte_size == 2:
|
|
return discr_sbvalue.GetData().GetUnsignedInt16(error, 0)
|
|
elif byte_size == 4:
|
|
return discr_sbvalue.GetData().GetUnsignedInt32(error, 0)
|
|
elif byte_size == 8:
|
|
return discr_sbvalue.GetData().GetUnsignedInt64(error, 0)
|
|
else:
|
|
return discr_sbvalue.GetValueAsUnsigned()
|
|
|
|
def getCurrentVariantIndex(self):
|
|
default_index = 0
|
|
for i in range(self._inner().GetNumChildren()):
|
|
variant: lldb.SBValue = self._inner().GetChildAtIndex(i)
|
|
discr = variant.GetChildMemberWithName(DISCRIMINANT_MEMBER_NAME)
|
|
if discr.IsValid():
|
|
discr_unsigned_value = RustEnumValue._getDiscriminantValueAsUnsigned(
|
|
discr
|
|
)
|
|
if variant.GetName() == f"$variant${discr_unsigned_value}":
|
|
return discr_unsigned_value
|
|
else:
|
|
default_index = i
|
|
return default_index
|
|
|
|
def getFields(self):
|
|
result = []
|
|
for i in range(self._inner().GetNumChildren()):
|
|
type: lldb.SBType = self._inner().GetType()
|
|
result.append(type.GetFieldAtIndex(i).GetName())
|
|
return result
|
|
|
|
def getCurrentValue(self) -> lldb.SBValue:
|
|
return self.getVariantByIndex(self.getCurrentVariantIndex())
|