Skip to content
Open
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
2 changes: 2 additions & 0 deletions src/etc/lldb_lookup.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ def synthetic_lookup(valobj: lldb.SBValue, _dict: LLDBOpaque) -> object:
)

return ClangEncodedEnumProvider(valobj, _dict)
if rust_type == RustType.REGULAR_UNION:
return UnionSyntheticProvider(valobj, _dict)
if rust_type == RustType.STD_VEC:
return StdVecSyntheticProvider(valobj, _dict)
if rust_type == RustType.STD_VEC_DEQUE:
Expand Down
61 changes: 61 additions & 0 deletions src/etc/lldb_providers.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
eBasicTypeUnsignedChar,
eFormatChar,
eTypeIsInteger,
LLDB_INVALID_ADDRESS,
)

from rust_types import is_tuple_fields
Expand Down Expand Up @@ -407,6 +408,66 @@ def has_children(self) -> bool:
return True


def _child_is_valid(child: SBValue) -> bool:
"""
Return True only if `child` is a non-None, valid SBValue whose load
address satisfies the alignment required by its own type.

This protects against LLDB crashing when it tries to materialise a union
variant that is incompatible with the object's actual memory address —
which happens with #[repr(C, align(N))] types nested inside unions that
also contain zero-sized / lower-aligned variants (e.g. ManuallyDrop).
"""
if child is None or not child.IsValid():
return False

ty = child.GetType()
if not ty.IsValid():
return False

byte_align = ty.GetByteAlign() # SBType.GetByteAlign() → alignment in bytes
if byte_align <= 1:
return True # trivially satisfied

addr = child.GetLoadAddress()
if addr == LLDB_INVALID_ADDRESS:
return True # can't check — let LLDB decide
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you say more about this? If LLDB can decide, why can't it always do the right thing? Does this still leave the bug in place for some set of cases?


return (addr % byte_align) == 0


class UnionSyntheticProvider:
"""Pretty-printer for unions"""

def __init__(self, valobj: SBValue, _dict: LLDBOpaque):
self.valobj = valobj
self._children = []

def num_children(self) -> int:
return len(self._children)

def get_child_index(self, name: str) -> int:
return self.valobj.GetIndexOfChildWithName(name)

def get_child_at_index(self, index: int) -> SBValue:
if index < 0 or index >= len(self._children):
return SBValue() # nothing found
return self._children[index]

def update(self):
# Count only children whose alignment is actually satisfiable so that
# LLDB never asks us for an index that would crash on materialisation.
raw_count = self.valobj.GetNumChildren()
self._children = [
self.valobj.GetChildAtIndex(i)
for i in range(raw_count)
if _child_is_valid(self.valobj.GetChildAtIndex(i))
]

def has_children(self) -> bool:
return self.valobj.MightHaveChildren()


class StdStringSyntheticProvider:
def __init__(self, valobj: SBValue, _dict: LLDBOpaque):
self.valobj = valobj
Expand Down
Loading