Skip to content

Commit 56df335

Browse files
Rollup merge of rust-lang#140130 - ede1998:add-lldb-btree-providers, r=Mark-Simulacrum
Add LLDB providers for BTreeMap and BTreeSet Fixes rust-lang#111868. I'm unsure what the supported LLDB versions for `rust-lldb` are. I tested with LLDB 18 and 19 and it works with those but I know that it does not work with LLDB 14 which was picked by my `codelldb` extension for some reason (even though it packages LLDB 19). I also had to work around what seems like an LLDB bug to me. Otherwise, I'd have kept the code more similar to the GDB provider. `SBTarget.FindFirstType()` does not find the types that I'm searching for (`LeafNode<i32, i32>*`): ``` target = node.GetTarget() print("leaf type:", node.GetType()) internal_type = target.FindFirstType(node.GetType().GetName()) print("Actual type:", internal_type) ``` which prints ``` leaf type: struct alloc::collections::btree::node::LeafNode<int, int> * Actual type: No value ``` All in all, my experience with LLDB debug provider was very fiddly/they seem to break easily but I think it would be better to have `BTreeMap`/`BTreeSet` providers than not have them. Getting to run the `debuginfo` tests was a pain too because of rust-lang#126092 and errors with `import _lldb` (rust-lang#123621). I ended up re-compling lldb from source against python 3.10 because the tests don't work if lldb is compiled against python 3.12. Also, it seems like the tests are not run in CI? At least I had a test commit in the PR before to trigger a `debuginfo` test failure which I didn't see here in my PR.
2 parents 6ee49ab + 53ef2e0 commit 56df335

File tree

4 files changed

+165
-0
lines changed

4 files changed

+165
-0
lines changed

src/etc/lldb_lookup.py

+11
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ def summary_lookup(valobj: lldb.SBValue, _dict: LLDBOpaque) -> str:
4242
if rust_type == RustType.STD_HASH_SET:
4343
return SizeSummaryProvider(valobj, _dict)
4444

45+
if rust_type == RustType.STD_BTREE_MAP:
46+
return SizeSummaryProvider(valobj, _dict)
47+
if rust_type == RustType.STD_BTREE_SET:
48+
return SizeSummaryProvider(valobj, _dict)
49+
4550
if rust_type == RustType.STD_RC:
4651
return StdRcSummaryProvider(valobj, _dict)
4752
if rust_type == RustType.STD_ARC:
@@ -105,6 +110,12 @@ def synthetic_lookup(valobj: lldb.SBValue, _dict: LLDBOpaque) -> object:
105110
else:
106111
return StdOldHashMapSyntheticProvider(hash_map, _dict, show_values=False)
107112

113+
if rust_type == RustType.STD_BTREE_MAP:
114+
return StdBTreeMapSyntheticProvider(valobj, _dict)
115+
if rust_type == RustType.STD_BTREE_SET:
116+
btree_map = valobj.GetChildAtIndex(0)
117+
return StdBTreeMapSyntheticProvider(btree_map, _dict, show_values=False)
118+
108119
if rust_type == RustType.STD_RC:
109120
return StdRcSyntheticProvider(valobj, _dict)
110121
if rust_type == RustType.STD_ARC:

src/etc/lldb_providers.py

+124
Original file line numberDiff line numberDiff line change
@@ -1151,6 +1151,130 @@ def has_children(self) -> bool:
11511151
return True
11521152

11531153

1154+
def children_of_node(node_ptr: SBValue, height: int):
1155+
def get_edges(node: SBValue) -> SBValue:
1156+
# BTreeMap implementation does ad-hoc polymorphism between LeafNode and InternalNode
1157+
# with raw pointers.
1158+
# https://github.com/rust-lang/rust/issues/90520#issuecomment-2211103129
1159+
# Implementing this the same way as the GDB provider with type casting fails
1160+
# because LLDB does not find the target type for some reason.
1161+
# Therefore, we manually do the pointer arithmetic to get the edges array
1162+
# and handle it as raw pointers later instead of MaybeUninit<NonNull<LeafNode<K,V>>>.
1163+
# We can do that because InternalNode is repr(C).
1164+
leaf_ptr_type = node.GetType()
1165+
# Array has a constant length of 2 * B
1166+
edges_arr_type = leaf_ptr_type.GetArrayType(12)
1167+
node_addr = node.unsigned
1168+
leaf_size = leaf_ptr_type.GetPointeeType().size
1169+
edges_addr = node_addr + leaf_size
1170+
return node.CreateValueFromAddress("edges", edges_addr, edges_arr_type)
1171+
1172+
def unwrap_item_from_array_of_maybe_uninit(arr: SBValue, index: int) -> SBValue:
1173+
element = arr.GetChildAtIndex(index)
1174+
return element.GetChildMemberWithName("value").GetChildMemberWithName("value")
1175+
1176+
if node_ptr.type.name.startswith("alloc::collections::btree::node::BoxedNode<"):
1177+
# BACKCOMPAT: rust 1.49
1178+
node_ptr = node_ptr.GetChildMemberWithName("ptr")
1179+
1180+
if not node_ptr.type.IsPointerType():
1181+
# After the first recursion, this method is called with a raw pointer type directly
1182+
# instead of NonNull<T>
1183+
node_ptr = unwrap_unique_or_non_null(node_ptr)
1184+
1185+
leaf = node_ptr.Dereference()
1186+
keys = leaf.GetChildMemberWithName("keys")
1187+
vals = leaf.GetChildMemberWithName("vals")
1188+
length = leaf.GetChildMemberWithName("len").unsigned
1189+
edges = get_edges(node_ptr) if height > 0 else None
1190+
1191+
for i in range(length + 1):
1192+
if height > 0:
1193+
child_ptr = edges.GetChildAtIndex(i)
1194+
yield from children_of_node(child_ptr, height - 1)
1195+
if i < length:
1196+
# Avoid "Cannot perform pointer math on incomplete type" on zero-sized arrays.
1197+
key_type_size = keys.type.size
1198+
val_type_size = vals.type.size
1199+
key = (
1200+
unwrap_item_from_array_of_maybe_uninit(keys, i)
1201+
if key_type_size > 0
1202+
else node_ptr.EvaluateExpression("()")
1203+
)
1204+
val = (
1205+
unwrap_item_from_array_of_maybe_uninit(vals, i)
1206+
if val_type_size > 0
1207+
else node_ptr.EvaluateExpression("()")
1208+
)
1209+
yield key, val
1210+
1211+
1212+
class StdBTreeMapSyntheticProvider:
1213+
def __init__(self, valobj: SBValue, _dict: LLDBOpaque, show_values: bool = True):
1214+
self.valobj = valobj
1215+
self._dict = _dict
1216+
self.show_values = show_values
1217+
1218+
def num_children(self) -> int:
1219+
return self.size
1220+
1221+
def get_child_index(self, name: str) -> int:
1222+
index = name.lstrip("[").rstrip("]")
1223+
if index.isdigit():
1224+
return int(index)
1225+
else:
1226+
return -1
1227+
1228+
def get_child_at_index(self, index: int) -> SBValue:
1229+
key, value = self.items[index]
1230+
if self.show_values:
1231+
data = key.GetData()
1232+
assert data.Append(value.GetData()), "Failed to create key value pair"
1233+
return self.valobj.CreateValueFromData("[%s]" % index, data, self.pair_type)
1234+
return self.valobj.CreateValueFromData("[%s]" % index, key.GetData(), key.type)
1235+
1236+
def update(self) -> bool:
1237+
self.size = self.valobj.GetChildMemberWithName("length").unsigned
1238+
self.items = []
1239+
1240+
# Determine the type for the tuple (Key, Value)
1241+
# - get_template_args helper breaks on console because type is shown as
1242+
# `core::marker::PhantomData<(&str, &str) *>`
1243+
# - Type lookup after get_template_args helper fails with codelldb for unclear reasons
1244+
# - Native `template_args[0]` from LLDB fails with codelldb and just says `T` if printed
1245+
# on console
1246+
marker = self.valobj.GetChildMemberWithName("_marker")
1247+
marker_type = marker.GetType()
1248+
box = marker_type.GetTemplateArgumentType(0)
1249+
self.pair_type = box.GetPointeeType()
1250+
1251+
if self.size == 0:
1252+
return
1253+
1254+
root = self.valobj.GetChildMemberWithName("root")
1255+
1256+
if root.type.name.startswith("core::option::Option<"):
1257+
synthetic_children = root.children[0]
1258+
current_variant = synthetic_children.GetChildMemberWithName("$variant$")
1259+
root = current_variant.GetChildMemberWithName(
1260+
"value"
1261+
).GetChildMemberWithName("__0")
1262+
1263+
height = root.GetChildMemberWithName("height")
1264+
node_ptr = root.GetChildMemberWithName("node")
1265+
1266+
self.items = [
1267+
(key, value) for key, value in children_of_node(node_ptr, height.unsigned)
1268+
]
1269+
1270+
assert len(self.items) == self.size
1271+
1272+
return False
1273+
1274+
def has_children(self) -> bool:
1275+
return True
1276+
1277+
11541278
def StdRcSummaryProvider(valobj: SBValue, _dict: LLDBOpaque) -> str:
11551279
strong = valobj.GetChildMemberWithName("strong").GetValueAsUnsigned()
11561280
weak = valobj.GetChildMemberWithName("weak").GetValueAsUnsigned()

src/tools/compiletest/src/runtest/debugger.rs

+3
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,9 @@ impl DebuggerCommands {
111111
}
112112

113113
/// Check that the pattern in `check_line` applies to `line`. Returns `true` if they do match.
114+
/// Note that this matching is lazy, i.e. matches as little as possible and thus might leave
115+
/// stuff unparsed, failing the check.
116+
/// This is different to usual regex or global matching that is typically eager.
114117
fn check_single_line(line: &str, check_line: &str) -> bool {
115118
// Allow check lines to leave parts unspecified (e.g., uninitialized
116119
// bits in the wrong case of an enum) with the notation "[...]".

tests/debuginfo/pretty-std-collections.rs

+27
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,33 @@
5151

5252
// lldb-command:run
5353

54+
// lldb-command: v btree_set
55+
// lldb-check:[...] size=15 { [0] = 0 [1] = 1 [2] = 2 [3] = 3 [4] = 4 [5] = 5 [6] = 6 [7] = 7 [8] = 8 [9] = 9 [10] = 10 [11] = 11 [12] = 12 [13] = 13 [14] = 14 }
56+
57+
// lldb-command: v empty_btree_set
58+
// lldb-check:[...] size=0
59+
60+
// lldb-command: v btree_map
61+
// lldb-check:[...] size=15 { [0] = { 0 = 0 1 = 0 } [1] = { 0 = 1 1 = 1 } [2] = { 0 = 2 1 = 2 } [3] = { 0 = 3 1 = 3 } [4] = { 0 = 4 1 = 4 } [5] = { 0 = 5 1 = 5 } [6] = { 0 = 6 1 = 6 } [7] = { 0 = 7 1 = 7 } [8] = { 0 = 8 1 = 8 } [9] = { 0 = 9 1 = 9 } [10] = { 0 = 10 1 = 10 } [11] = { 0 = 11 1 = 11 } [12] = { 0 = 12 1 = 12 } [13] = { 0 = 13 1 = 13 } [14] = { 0 = 14 1 = 14 } }
62+
63+
// lldb-command: v option_btree_map
64+
// lldb-check:[...] size=2 { [0] = { 0 = false 1 = [...] } [1] = { 0 = true 1 = [...]
65+
// (The matching here is lazy, so we cannot add braces at the end because they would match
66+
// intermediate braces and fail because not the entire input was consumed.)
67+
68+
// lldb-command: v nasty_btree_map
69+
// lldb-check:[...] size=15 { [0] = { 0 = 0 1 = { 0 = 0 } } [1] = { 0 = 1 1 = { 0 = 1 } } [2] = { 0 = 2 1 = { 0 = 2 } } [3] = { 0 = 3 1 = { 0 = 3 } } [4] = { 0 = 4 1 = { 0 = 4 } } [5] = { 0 = 5 1 = { 0 = 5 } } [6] = { 0 = 6 1 = { 0 = 6 } } [7] = { 0 = 7 1 = { 0 = 7 } } [8] = { 0 = 8 1 = { 0 = 8 } } [9] = { 0 = 9 1 = { 0 = 9 } } [10] = { 0 = 10 1 = { 0 = 10 } } [11] = { 0 = 11 1 = { 0 = 11 } } [12] = { 0 = 12 1 = { 0 = 12 } } [13] = { 0 = 13 1 = { 0 = 13 } } [14] = { 0 = 14 1 = { 0 = 14 } } }
70+
// (Does not print out the type name in lldb)
71+
72+
// lldb-command: v zst_key_btree_map
73+
// lldb-check:[...] size=1 { [0] = { 1 = 1 } }
74+
75+
// lldb-command: v zst_val_btree_map
76+
// lldb-check:[...] size=1 { [0] = { 0 = 1 } }
77+
78+
// lldb-command: v zst_key_val_btree_map
79+
// lldb-check:[...] size=1 { [0] = {} }
80+
5481
// lldb-command:v vec_deque
5582
// lldb-check:[...] size=3 { [0] = 5 [1] = 3 [2] = 7 }
5683

0 commit comments

Comments
 (0)