[lldb][ADT] Fix LLDB/GDB formatters for PointerUnion after recactoring (#188483)
In #188242, we replaced `PointerUnion`'s `PointerIntPair` storage with `PunnedPointer<void*>`. The old formatters relied on the PIP synthetic provider (LLDB) / `get_pointer_int_pair helper` (GDB) which no longer work. Instead, read raw bytes from `PunnedPointer` and compute the active tag from template argument type alignments -- the same fixed-width encoding the C++ implementation uses. When template arg enumeration is truncated (e.g., function-local types in GDB), the formatters fall back to showing a tag-stripped `void*` instead of silently misdecoding. Alternatives that didn't work out: - Adding a C++ helper (`getActiveMemberIdx`) callable from Python: gets optimized out even with `__attribute__((used, noinline))`, and expression evaluation fails for synthetic children. - Using `isa`/`dyn_cast` checks from Python: requires expression evaluation, which does not work for local types or synthetic children without a frame context. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
ca9ac0e24a
commit
9c6054d29d
@ -33,8 +33,8 @@ llvm::PointerIntPair<int *, 1> PointerIntPair(IntPtr, 1);
|
||||
struct alignas(8) Z {};
|
||||
llvm::PointerUnion<Z *, int *> PointerUnion(IntPtr);
|
||||
|
||||
// No members which instantiate PointerUnionUIntTraits<Z *> (e.g. get<T *>())
|
||||
// are called, and this instance will therefore be raw-printed.
|
||||
// The PunnedPointer-based formatter can decode all PointerUnion instances
|
||||
// from type alignments, regardless of member template instantiation.
|
||||
llvm::PointerUnion<Z *, float *> RawPrintingPointerUnion(nullptr);
|
||||
|
||||
using IlistTag = llvm::ilist_tag<struct A>;
|
||||
|
||||
@ -44,7 +44,7 @@ p PointerIntPair
|
||||
# CHECK: Containing int * = {pointer = 0xabc}
|
||||
p PointerUnion
|
||||
|
||||
# CHECK: PointerUnionMembers<llvm::PointerUnion<Z*, float*>,
|
||||
# CHECK: Containing Z * = {pointer = 0x0}
|
||||
p RawPrintingPointerUnion
|
||||
|
||||
# Switch to print pretty adds newlines to the following statements.
|
||||
|
||||
@ -13,19 +13,20 @@ struct DerivedWithVirtual : public HasVirtual {
|
||||
|
||||
void DerivedWithVirtual::func() {}
|
||||
|
||||
struct alignas(8) Z {};
|
||||
struct Derived : public Z {};
|
||||
|
||||
int main() {
|
||||
int a = 5;
|
||||
float f = 4.0;
|
||||
struct alignas(8) Z {};
|
||||
Z z;
|
||||
|
||||
struct Derived : public Z {};
|
||||
Derived derived;
|
||||
|
||||
DerivedWithVirtual dv;
|
||||
|
||||
llvm::PointerUnion<Z *, float *> z_float(&f);
|
||||
llvm::PointerUnion<Z *, float *> raw_z_float(nullptr);
|
||||
llvm::PointerUnion<Z *, float *> null_float(static_cast<float *>(nullptr));
|
||||
|
||||
llvm::PointerUnion<long long *, int *, float *> long_int_float(&a);
|
||||
llvm::PointerUnion<Z *> z_only(&z);
|
||||
@ -42,4 +43,11 @@ int main() {
|
||||
llvm::PointerUnion<HasVirtual *, float *> virtual_float(&dv);
|
||||
|
||||
puts("Break here");
|
||||
|
||||
// Function-local types stress template_argument lookup in debuggers.
|
||||
struct alignas(8) Local {};
|
||||
Local local;
|
||||
llvm::PointerUnion<Local *, float *> local_float(&local);
|
||||
|
||||
puts("Break here");
|
||||
}
|
||||
|
||||
@ -18,6 +18,7 @@ p &derived
|
||||
p &dv
|
||||
v -T z_float
|
||||
v -T raw_z_float
|
||||
v -T null_float
|
||||
v -T long_int_float
|
||||
v -T z_only
|
||||
v -T union_int_pair
|
||||
@ -34,6 +35,11 @@ continue
|
||||
v -T virtual_float
|
||||
p virtual_float.Pointer
|
||||
|
||||
continue
|
||||
|
||||
p &local
|
||||
v -T local_float
|
||||
|
||||
#--- checks
|
||||
# CHECK: (lldb) p &f
|
||||
# CHECK-NEXT: (float *) [[PTR_F:0x[0-9a-zA-Z]+]]
|
||||
@ -60,6 +66,13 @@ p virtual_float.Pointer
|
||||
# CHECK-NEXT: (Z *) Pointer = nullptr
|
||||
# CHECK-NEXT: }
|
||||
|
||||
# null float* has non-zero raw_value (tag=1), pointer component=0.
|
||||
# LLDB renders this as an address literal rather than nullptr.
|
||||
# CHECK: (lldb) v -T null_float
|
||||
# CHECK-NEXT: (llvm::PointerUnion<Z *, float *>) null_float = {
|
||||
# CHECK-NEXT: (float *) Pointer = 0x{{0+}}
|
||||
# CHECK-NEXT: }
|
||||
|
||||
# CHECK: (lldb) v -T long_int_float
|
||||
# CHECK-NEXT: (llvm::PointerUnion<long long *, int *, float *>) long_int_float = {
|
||||
# CHECK-NEXT: (int *) Pointer = [[PTR_A]]
|
||||
@ -101,3 +114,11 @@ p virtual_float.Pointer
|
||||
|
||||
# CHECK: (lldb) p virtual_float.Pointer
|
||||
# CHECK-NEXT: (DerivedWithVirtual *) [[PTR_DV]]
|
||||
|
||||
# CHECK: (lldb) p &local
|
||||
# CHECK-NEXT: (Local *) [[PTR_LOCAL:0x[0-9a-zA-Z]+]]
|
||||
|
||||
# CHECK: (lldb) v -T local_float
|
||||
# CHECK-NEXT: (llvm::PointerUnion<Local *, float *>) local_float = {
|
||||
# CHECK-NEXT: (Local *) Pointer = [[PTR_LOCAL]]
|
||||
# CHECK-NEXT: }
|
||||
|
||||
@ -443,14 +443,66 @@ class PointerUnionPrinter:
|
||||
return "Containing %s" % self.pointer.type
|
||||
|
||||
|
||||
def _make_pointer_union_raw_fallback(raw_value, min_low_bits):
|
||||
"""Strip tag bits and return as void* when active type is unknown."""
|
||||
pointer = raw_value & ~((1 << min_low_bits) - 1)
|
||||
void_ptr = gdb.lookup_type("void").pointer()
|
||||
return PointerUnionPrinter(gdb.Value(pointer).cast(void_ptr))
|
||||
|
||||
|
||||
def make_pointer_union_printer(val):
|
||||
"""Factory for an llvm::PointerUnion printer."""
|
||||
try:
|
||||
pointer, value = get_pointer_int_pair(val["Val"])
|
||||
except gdb.error:
|
||||
return None # If PointerIntPair cannot be analyzed, print as raw value.
|
||||
pointer_type = val.type.template_argument(int(value))
|
||||
return PointerUnionPrinter(pointer.cast(pointer_type))
|
||||
raw_value = int(
|
||||
val["Val"]["Data"]
|
||||
.address.cast(gdb.lookup_type("uintptr_t").pointer())
|
||||
.dereference()
|
||||
)
|
||||
|
||||
# Collect template argument types.
|
||||
# Distinguish truncation (RuntimeError — type resolution failure) from
|
||||
# normal end-of-args (gdb.error — index out of range).
|
||||
arg_types = []
|
||||
truncated = False
|
||||
while True:
|
||||
try:
|
||||
arg_types.append(val.type.template_argument(len(arg_types)))
|
||||
except RuntimeError:
|
||||
truncated = True
|
||||
break
|
||||
except gdb.error:
|
||||
break
|
||||
if not arg_types:
|
||||
return None
|
||||
|
||||
# Compute tag from type alignments (fixed-width encoding).
|
||||
num_args = len(arg_types)
|
||||
min_low_bits = min(
|
||||
(a.bit_length() - 1 if a > 0 else 0)
|
||||
for a in (t.target().alignof for t in arg_types)
|
||||
)
|
||||
|
||||
# If template args are truncated, we can't reliably decode the tag.
|
||||
# Fall back to showing the raw pointer with tag bits stripped.
|
||||
if truncated:
|
||||
return _make_pointer_union_raw_fallback(raw_value, min_low_bits)
|
||||
|
||||
tag_bits = (num_args - 1).bit_length()
|
||||
if tag_bits > min_low_bits:
|
||||
return _make_pointer_union_raw_fallback(raw_value, min_low_bits)
|
||||
tag_shift = min_low_bits - tag_bits
|
||||
tag_mask = (1 << tag_bits) - 1
|
||||
active_tag = (raw_value >> tag_shift) & tag_mask
|
||||
if active_tag >= num_args:
|
||||
return _make_pointer_union_raw_fallback(raw_value, min_low_bits)
|
||||
|
||||
pointer_type = arg_types[active_tag]
|
||||
align = pointer_type.target().alignof
|
||||
low_bits = align.bit_length() - 1 if align > 0 else 0
|
||||
pointer = raw_value & ~((1 << low_bits) - 1)
|
||||
except (gdb.error, RuntimeError, IndexError):
|
||||
return None
|
||||
return PointerUnionPrinter(gdb.Value(pointer).cast(pointer_type))
|
||||
|
||||
|
||||
class IlistNodePrinter:
|
||||
|
||||
@ -370,35 +370,79 @@ class PointerUnionSynthProvider:
|
||||
|
||||
return self.pointer_valobj
|
||||
|
||||
@staticmethod
|
||||
def _get_low_bits_for_type(ty: lldb.SBType) -> int:
|
||||
"""Return NumLowBitsAvailable for a pointer type (from pointee byte alignment)."""
|
||||
pointee = ty.GetPointeeType()
|
||||
if pointee.IsValid():
|
||||
align = pointee.GetByteAlign()
|
||||
return align.bit_length() - 1 if align > 0 else 0
|
||||
return 0
|
||||
|
||||
def _make_pointer_value(self, name, pointer, ty):
|
||||
"""Create an SBValue for a pointer, using proper byte order and address size."""
|
||||
data = lldb.SBData.CreateDataFromUInt64Array(
|
||||
self.valobj.target.GetByteOrder(),
|
||||
self.valobj.process.GetAddressByteSize(),
|
||||
[pointer],
|
||||
)
|
||||
return self.valobj.CreateValueFromData(name, data, ty)
|
||||
|
||||
def update(self):
|
||||
pointer_int_pair: lldb.SBValue = self.valobj.GetChildMemberWithName(
|
||||
"Val"
|
||||
).GetSyntheticValue()
|
||||
self.pointer_valobj = None
|
||||
|
||||
if not pointer_int_pair:
|
||||
valobj_type = self.valobj.GetType()
|
||||
num_args = valobj_type.GetNumberOfTemplateArguments()
|
||||
if num_args == 0:
|
||||
return
|
||||
|
||||
pointer: lldb.SBValue = pointer_int_pair.GetChildAtIndex(0)
|
||||
if not pointer:
|
||||
# Read the raw uintptr_t from PunnedPointer<void*>.
|
||||
val: lldb.SBValue = self.valobj.GetChildMemberWithName("Val")
|
||||
if not val:
|
||||
return
|
||||
|
||||
active_tag: lldb.SBValue = pointer_int_pair.GetChildAtIndex(1)
|
||||
if not active_tag:
|
||||
return
|
||||
|
||||
# Index into the parameter pack of llvm::PointerUnion to find the active type.
|
||||
active_type: lldb.SBType = self.valobj.GetType().GetTemplateArgumentType(
|
||||
active_tag.GetValueAsUnsigned()
|
||||
byteorder = (
|
||||
"big"
|
||||
if self.valobj.target.GetByteOrder() == lldb.eByteOrderBig
|
||||
else "little"
|
||||
)
|
||||
raw_bytes = val.GetData().uint8s
|
||||
if not raw_bytes:
|
||||
return
|
||||
raw_value = int.from_bytes(raw_bytes, byteorder)
|
||||
|
||||
# Compute tag from type alignments (fixed-width encoding).
|
||||
tag_bits = (num_args - 1).bit_length()
|
||||
min_low_bits = min(
|
||||
self._get_low_bits_for_type(valobj_type.GetTemplateArgumentType(i))
|
||||
for i in range(num_args)
|
||||
)
|
||||
if tag_bits > min_low_bits:
|
||||
return self._set_raw_pointer(raw_value, min_low_bits)
|
||||
tag_shift = min_low_bits - tag_bits
|
||||
tag_mask = (1 << tag_bits) - 1
|
||||
active_tag = (raw_value >> tag_shift) & tag_mask
|
||||
|
||||
if active_tag >= num_args:
|
||||
return self._set_raw_pointer(raw_value, min_low_bits)
|
||||
|
||||
active_type: lldb.SBType = valobj_type.GetTemplateArgumentType(active_tag)
|
||||
if not active_type:
|
||||
return
|
||||
return self._set_raw_pointer(raw_value, min_low_bits)
|
||||
|
||||
data = lldb.SBData()
|
||||
data.SetDataFromUInt64Array([pointer.GetValueAsUnsigned()])
|
||||
# Clear the active type's low bits to recover the pointer.
|
||||
low_bits = self._get_low_bits_for_type(active_type)
|
||||
pointer = raw_value & ~((1 << low_bits) - 1)
|
||||
|
||||
self.pointer_valobj = self.valobj.CreateValueFromData(
|
||||
"Pointer", data, active_type
|
||||
)
|
||||
self.pointer_valobj = self._make_pointer_value("Pointer", pointer, active_type)
|
||||
|
||||
def _set_raw_pointer(self, raw_value, min_low_bits):
|
||||
"""Fallback: strip tag bits and show as void* when active type is unknown."""
|
||||
pointer = raw_value & ~((1 << min_low_bits) - 1)
|
||||
void_ptr_ty = self.valobj.target.FindFirstType("void").GetPointerType()
|
||||
if void_ptr_ty.IsValid():
|
||||
self.pointer_valobj = self._make_pointer_value(
|
||||
"Pointer", pointer, void_ptr_ty
|
||||
)
|
||||
|
||||
|
||||
def DenseMapSummary(valobj: lldb.SBValue, _) -> str:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user