Skip to content

Commit 473510a

Browse files
authored
[libc++] Fix mi-mode in GDB pretty printers (#120951)
GDB/MI requires unique names for each child, otherwise fails with "Duplicate variable object name". Also wrapped containers printers were flattened for cleaner visualization in IDEs and CLI. Fixes #62340
1 parent 5e6c74d commit 473510a

File tree

3 files changed

+112
-48
lines changed

3 files changed

+112
-48
lines changed

libcxx/test/libcxx/gdb/gdb_pretty_printer_test.py

Lines changed: 45 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"""
1616

1717
from __future__ import print_function
18+
import json
1819
import re
1920
import gdb
2021
import sys
@@ -29,6 +30,7 @@
2930
# we exit.
3031
has_run_tests = False
3132

33+
has_execute_mi = tuple(map(int, gdb.VERSION.split("."))) >= (14, 2)
3234

3335
class CheckResult(gdb.Command):
3436
def __init__(self):
@@ -42,36 +44,43 @@ def invoke(self, arg, from_tty):
4244

4345
# Stack frame is:
4446
# 0. StopForDebugger
45-
# 1. ComparePrettyPrintToChars or ComparePrettyPrintToRegex
47+
# 1. CompareListChildrenToChars, ComparePrettyPrintToChars or ComparePrettyPrintToRegex
4648
# 2. TestCase
4749
compare_frame = gdb.newest_frame().older()
4850
testcase_frame = compare_frame.older()
4951
test_loc = testcase_frame.find_sal()
52+
test_loc_str = test_loc.symtab.filename + ":" + str(test_loc.line)
5053
# Use interactive commands in the correct context to get the pretty
5154
# printed version
5255

53-
value_str = self._get_value_string(compare_frame, testcase_frame)
56+
frame_name = compare_frame.name()
57+
if frame_name.startswith("CompareListChildren"):
58+
if has_execute_mi:
59+
value = self._get_children(compare_frame)
60+
else:
61+
print("SKIPPED: " + test_loc_str)
62+
return
63+
else:
64+
value = self._get_value(compare_frame, testcase_frame)
5465

55-
# Ignore the convenience variable name and newline
56-
value = value_str[value_str.find("= ") + 2 : -1]
5766
gdb.newest_frame().select()
5867
expectation_val = compare_frame.read_var("expectation")
5968
check_literal = expectation_val.string(encoding="utf-8")
60-
if "PrettyPrintToRegex" in compare_frame.name():
69+
if "PrettyPrintToRegex" in frame_name:
6170
test_fails = not re.search(check_literal, value)
6271
else:
6372
test_fails = value != check_literal
6473

6574
if test_fails:
6675
global test_failures
67-
print("FAIL: " + test_loc.symtab.filename + ":" + str(test_loc.line))
76+
print("FAIL: " + test_loc_str)
6877
print("GDB printed:")
6978
print(" " + repr(value))
7079
print("Value should match:")
7180
print(" " + repr(check_literal))
7281
test_failures += 1
7382
else:
74-
print("PASS: " + test_loc.symtab.filename + ":" + str(test_loc.line))
83+
print("PASS: " + test_loc_str)
7584

7685
except RuntimeError as e:
7786
# At this point, lots of different things could be wrong, so don't try to
@@ -81,18 +90,39 @@ def invoke(self, arg, from_tty):
8190
print(str(e))
8291
test_failures += 1
8392

84-
def _get_value_string(self, compare_frame, testcase_frame):
93+
def _get_children(self, compare_frame):
94+
compare_frame.select()
95+
gdb.execute_mi("-var-create", "value", "*", "value")
96+
r = gdb.execute_mi("-var-list-children", "--simple-values", "value")
97+
gdb.execute_mi("-var-delete", "value")
98+
children = r["children"]
99+
if r["displayhint"] == "map":
100+
r = [
101+
{
102+
"key": json.loads(children[2 * i]["value"]),
103+
"value": json.loads(children[2 * i + 1]["value"]),
104+
}
105+
for i in range(len(children) // 2)
106+
]
107+
else:
108+
r = [json.loads(el["value"]) for el in children]
109+
return json.dumps(r, sort_keys=True)
110+
111+
def _get_value(self, compare_frame, testcase_frame):
85112
compare_frame.select()
86-
if "ComparePrettyPrint" in compare_frame.name():
113+
frame_name = compare_frame.name()
114+
if frame_name.startswith("ComparePrettyPrint"):
87115
s = gdb.execute("p value", to_string=True)
88116
else:
89117
value_str = str(compare_frame.read_var("value"))
90118
clean_expression_str = value_str.strip("'\"")
91119
testcase_frame.select()
92120
s = gdb.execute("p " + clean_expression_str, to_string=True)
93121
if sys.version_info.major == 2:
94-
return s.decode("utf-8")
95-
return s
122+
s = s.decode("utf-8")
123+
124+
# Ignore the convenience variable name and newline
125+
return s[s.find("= ") + 2 : -1]
96126

97127

98128
def exit_handler(event=None):
@@ -112,6 +142,10 @@ def exit_handler(event=None):
112142
# Disable terminal paging
113143
gdb.execute("set height 0")
114144
gdb.execute("set python print-stack full")
145+
146+
if has_execute_mi:
147+
gdb.execute_mi("-enable-pretty-printing")
148+
115149
test_failures = 0
116150
CheckResult()
117151
test_bp = gdb.Breakpoint("StopForDebugger")

libcxx/test/libcxx/gdb/gdb_pretty_printer_test.sh.cpp

Lines changed: 43 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,12 @@ void CompareExpressionPrettyPrintToRegex(
129129
StopForDebugger(&value, &expectation);
130130
}
131131

132+
template <typename TypeToPrint>
133+
void CompareListChildrenToChars(TypeToPrint value, const char* expectation) {
134+
MarkAsLive(value);
135+
StopForDebugger(&value, &expectation);
136+
}
137+
132138
namespace example {
133139
struct example_struct {
134140
int a = 0;
@@ -361,28 +367,28 @@ void multimap_test() {
361367

362368
void queue_test() {
363369
std::queue<int> i_am_empty;
364-
ComparePrettyPrintToChars(i_am_empty,
365-
"std::queue wrapping = {std::deque is empty}");
370+
ComparePrettyPrintToChars(i_am_empty, "std::queue wrapping: std::deque is empty");
366371

367372
std::queue<int> one_two_three(std::deque<int>{1, 2, 3});
368-
ComparePrettyPrintToChars(one_two_three,
369-
"std::queue wrapping = {"
370-
"std::deque with 3 elements = {1, 2, 3}}");
373+
ComparePrettyPrintToChars(
374+
one_two_three,
375+
"std::queue wrapping: "
376+
"std::deque with 3 elements = {1, 2, 3}");
371377
}
372378

373379
void priority_queue_test() {
374380
std::priority_queue<int> i_am_empty;
375-
ComparePrettyPrintToChars(i_am_empty,
376-
"std::priority_queue wrapping = {std::vector of length 0, capacity 0}");
381+
ComparePrettyPrintToChars(i_am_empty, "std::priority_queue wrapping: std::vector of length 0, capacity 0");
377382

378383
std::priority_queue<int> one_two_three;
379384
one_two_three.push(11111);
380385
one_two_three.push(22222);
381386
one_two_three.push(33333);
382387

383-
ComparePrettyPrintToRegex(one_two_three,
384-
R"(std::priority_queue wrapping = )"
385-
R"({std::vector of length 3, capacity 3 = {33333)");
388+
ComparePrettyPrintToRegex(
389+
one_two_three,
390+
R"(std::priority_queue wrapping: )"
391+
R"(std::vector of length 3, capacity 3 = {33333)");
386392

387393
ComparePrettyPrintToRegex(one_two_three, ".*11111.*");
388394
ComparePrettyPrintToRegex(one_two_three, ".*22222.*");
@@ -410,25 +416,22 @@ void set_test() {
410416

411417
void stack_test() {
412418
std::stack<int> test0;
413-
ComparePrettyPrintToChars(test0,
414-
"std::stack wrapping = {std::deque is empty}");
419+
ComparePrettyPrintToChars(test0, "std::stack wrapping: std::deque is empty");
415420
test0.push(5);
416421
test0.push(6);
417-
ComparePrettyPrintToChars(
418-
test0, "std::stack wrapping = {std::deque with 2 elements = {5, 6}}");
422+
ComparePrettyPrintToChars(test0, "std::stack wrapping: std::deque with 2 elements = {5, 6}");
419423
std::stack<bool> test1;
420424
test1.push(true);
421425
test1.push(false);
422-
ComparePrettyPrintToChars(
423-
test1,
424-
"std::stack wrapping = {std::deque with 2 elements = {true, false}}");
426+
ComparePrettyPrintToChars(test1, "std::stack wrapping: std::deque with 2 elements = {true, false}");
425427

426428
std::stack<std::string> test2;
427429
test2.push("Hello");
428430
test2.push("World");
429-
ComparePrettyPrintToChars(test2,
430-
"std::stack wrapping = {std::deque with 2 elements "
431-
"= {\"Hello\", \"World\"}}");
431+
ComparePrettyPrintToChars(
432+
test2,
433+
"std::stack wrapping: std::deque with 2 elements "
434+
"= {\"Hello\", \"World\"}");
432435
}
433436

434437
void multiset_test() {
@@ -662,6 +665,25 @@ void streampos_test() {
662665
ComparePrettyPrintToRegex(test1, "^std::fpos with stream offset:5( with state: {count:0 value:0})?$");
663666
}
664667

668+
void mi_mode_test() {
669+
std::map<int, std::string> one_two_three_map;
670+
one_two_three_map.insert({1, "one"});
671+
one_two_three_map.insert({2, "two"});
672+
one_two_three_map.insert({3, "three"});
673+
CompareListChildrenToChars(
674+
one_two_three_map, R"([{"key": 1, "value": "one"}, {"key": 2, "value": "two"}, {"key": 3, "value": "three"}])");
675+
676+
std::unordered_map<int, std::string> one_two_three_umap;
677+
one_two_three_umap.insert({3, "three"});
678+
one_two_three_umap.insert({2, "two"});
679+
one_two_three_umap.insert({1, "one"});
680+
CompareListChildrenToChars(
681+
one_two_three_umap, R"([{"key": 3, "value": "three"}, {"key": 2, "value": "two"}, {"key": 1, "value": "one"}])");
682+
683+
std::deque<int> one_two_three_deque{1, 2, 3};
684+
CompareListChildrenToChars(one_two_three_deque, "[1, 2, 3]");
685+
}
686+
665687
int main(int, char**) {
666688
framework_self_test();
667689

@@ -694,5 +716,6 @@ int main(int, char**) {
694716
unordered_set_iterator_test();
695717
pointer_negative_test();
696718
streampos_test();
719+
mi_mode_test();
697720
return 0;
698721
}

libcxx/utils/gdb/libcxx/printers.py

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -446,10 +446,13 @@ def _list_it(self):
446446
num_emitted = 0
447447
current_addr = self.start_ptr
448448
start_index = self.first_block_start_index
449+
i = 0
449450
while num_emitted < self.size:
450451
end_index = min(start_index + self.size - num_emitted, self.block_size)
451452
for _, elem in self._bucket_it(current_addr, start_index, end_index):
452-
yield "", elem
453+
key_name = "[%d]" % i
454+
i += 1
455+
yield key_name, elem
453456
num_emitted += end_index - start_index
454457
current_addr = gdb.Value(addr_as_long(current_addr) + _pointer_size).cast(
455458
self.node_type
@@ -494,8 +497,8 @@ def to_string(self):
494497

495498
def _list_iter(self):
496499
current_node = self.first_node
497-
for _ in range(self.size):
498-
yield "", current_node.cast(self.nodetype).dereference()["__value_"]
500+
for i in range(self.size):
501+
yield "[%d]" % i, current_node.cast(self.nodetype).dereference()["__value_"]
499502
current_node = current_node.dereference()["__next_"]
500503

501504
def __iter__(self):
@@ -512,15 +515,14 @@ class StdQueueOrStackPrinter(object):
512515
"""Print a std::queue or std::stack."""
513516

514517
def __init__(self, val):
515-
self.val = val
516-
self.underlying = val["c"]
518+
self.typename = _remove_generics(_prettify_typename(val.type))
519+
self.visualizer = gdb.default_visualizer(val["c"])
517520

518521
def to_string(self):
519-
typename = _remove_generics(_prettify_typename(self.val.type))
520-
return "%s wrapping" % typename
522+
return "%s wrapping: %s" % (self.typename, self.visualizer.to_string())
521523

522524
def children(self):
523-
return iter([("", self.underlying)])
525+
return self.visualizer.children()
524526

525527
def display_hint(self):
526528
return "array"
@@ -530,19 +532,18 @@ class StdPriorityQueuePrinter(object):
530532
"""Print a std::priority_queue."""
531533

532534
def __init__(self, val):
533-
self.val = val
534-
self.underlying = val["c"]
535+
self.typename = _remove_generics(_prettify_typename(val.type))
536+
self.visualizer = gdb.default_visualizer(val["c"])
535537

536538
def to_string(self):
537539
# TODO(tamur): It would be nice to print the top element. The technical
538540
# difficulty is that, the implementation refers to the underlying
539541
# container, which is a generic class. libstdcxx pretty printers do not
540542
# print the top element.
541-
typename = _remove_generics(_prettify_typename(self.val.type))
542-
return "%s wrapping" % typename
543+
return "%s wrapping: %s" % (self.typename, self.visualizer.to_string())
543544

544545
def children(self):
545-
return iter([("", self.underlying)])
546+
return self.visualizer.children()
546547

547548
def display_hint(self):
548549
return "array"
@@ -622,13 +623,16 @@ def _traverse(self):
622623
"""Traverses the binary search tree in order."""
623624
current = self.util.root
624625
skip_left_child = False
626+
i = 0
625627
while True:
626628
if not skip_left_child and self.util.left_child(current):
627629
current = self.util.left_child(current)
628630
continue
629631
skip_left_child = False
630632
for key_value in self._get_key_value(current):
631-
yield "", key_value
633+
key_name = "[%d]" % i
634+
i += 1
635+
yield key_name, key_value
632636
right_child = self.util.right_child(current)
633637
if right_child:
634638
current = right_child
@@ -784,10 +788,13 @@ def __init__(self, val):
784788

785789
def _list_it(self, sentinel_ptr):
786790
next_ptr = sentinel_ptr["__next_"]
791+
i = 0
787792
while str(next_ptr.cast(_void_pointer_type)) != "0x0":
788793
next_val = next_ptr.cast(self.cast_type).dereference()
789794
for key_value in self._get_key_value(next_val):
790-
yield "", key_value
795+
key_name = "[%d]" % i
796+
i += 1
797+
yield key_name, key_value
791798
next_ptr = next_val["__next_"]
792799

793800
def to_string(self):
@@ -851,8 +858,8 @@ def children(self):
851858
return self if self.addr else iter(())
852859

853860
def __iter__(self):
854-
for key_value in self._get_key_value():
855-
yield "", key_value
861+
for i, key_value in enumerate(self._get_key_value()):
862+
yield "[%d]" % i, key_value
856863

857864

858865
class StdUnorderedSetIteratorPrinter(AbstractHashMapIteratorPrinter):

0 commit comments

Comments
 (0)