Skip to content

Commit d1e9d0b

Browse files
committed
[LLDB][DataFormatter] Add data formatter for libcxx std::unordered_map iterator
This patch adds a formatter for libcxx's `std::unordered_map` iterators. The implementation follows a similar appraoch to the `std::map` iterator formatter. I was hesistant about coupling the two into a common implementation since the libcxx layouts might change for one of the the containers but not the other. All `std::unordered_map` iterators are covered with this patch: 1. const/non-const key/value iterators 2. const/non-const bucket iterators Note that, we currently don't have a formatter for `std::unordered_map`. This patch doesn't change that, we merely add support for its iterators, because that's what Xcode users requested. One can still see contents of `std::unordered_map`, whereas with iterators it's less ergonomic. **Testing** * Added API test Differential Revision: https://reviews.llvm.org/D129364
1 parent 51d3f42 commit d1e9d0b

File tree

6 files changed

+312
-0
lines changed

6 files changed

+312
-0
lines changed

lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -911,6 +911,14 @@ static void LoadLibCxxFormatters(lldb::TypeCategoryImplSP cpp_category_sp) {
911911
"std::map iterator synthetic children",
912912
ConstString("^std::__[[:alnum:]]+::__map_iterator<.+>$"), stl_synth_flags,
913913
true);
914+
915+
AddCXXSynthetic(
916+
cpp_category_sp,
917+
lldb_private::formatters::
918+
LibCxxUnorderedMapIteratorSyntheticFrontEndCreator,
919+
"std::unordered_map iterator synthetic children",
920+
ConstString("^std::__[[:alnum:]]+::__hash_map_(const_)?iterator<.+>$"),
921+
stl_synth_flags, true);
914922
}
915923

916924
static void LoadLibStdcppFormatters(lldb::TypeCategoryImplSP cpp_category_sp) {

lldb/source/Plugins/Language/CPlusPlus/LibCxx.cpp

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727

2828
#include "Plugins/LanguageRuntime/CPlusPlus/CPPLanguageRuntime.h"
2929
#include "Plugins/TypeSystem/Clang/TypeSystemClang.h"
30+
#include "lldb/lldb-enumerations.h"
3031
#include <tuple>
3132

3233
using namespace lldb;
@@ -283,6 +284,22 @@ bool lldb_private::formatters::LibCxxMapIteratorSyntheticFrontEnd::Update() {
283284
llvm::dyn_cast_or_null<TypeSystemClang>(pair_type.GetTypeSystem());
284285
if (!ast_ctx)
285286
return false;
287+
288+
// Mimick layout of std::__tree_iterator::__ptr_ and read it in
289+
// from process memory.
290+
//
291+
// The following shows the contiguous block of memory:
292+
//
293+
// +-----------------------------+ class __tree_end_node
294+
// __ptr_ | pointer __left_; |
295+
// +-----------------------------+ class __tree_node_base
296+
// | pointer __right_; |
297+
// | __parent_pointer __parent_; |
298+
// | bool __is_black_; |
299+
// +-----------------------------+ class __tree_node
300+
// | __node_value_type __value_; | <<< our key/value pair
301+
// +-----------------------------+
302+
//
286303
CompilerType tree_node_type = ast_ctx->CreateStructForIdentifier(
287304
ConstString(),
288305
{{"ptr0",
@@ -359,6 +376,156 @@ lldb_private::formatters::LibCxxMapIteratorSyntheticFrontEndCreator(
359376
: nullptr);
360377
}
361378

379+
lldb_private::formatters::LibCxxUnorderedMapIteratorSyntheticFrontEnd::
380+
LibCxxUnorderedMapIteratorSyntheticFrontEnd(lldb::ValueObjectSP valobj_sp)
381+
: SyntheticChildrenFrontEnd(*valobj_sp) {
382+
if (valobj_sp)
383+
Update();
384+
}
385+
386+
bool lldb_private::formatters::LibCxxUnorderedMapIteratorSyntheticFrontEnd::
387+
Update() {
388+
m_pair_sp.reset();
389+
m_iter_ptr = nullptr;
390+
391+
ValueObjectSP valobj_sp = m_backend.GetSP();
392+
if (!valobj_sp)
393+
return false;
394+
395+
TargetSP target_sp(valobj_sp->GetTargetSP());
396+
397+
if (!target_sp)
398+
return false;
399+
400+
if (!valobj_sp)
401+
return false;
402+
403+
auto exprPathOptions = ValueObject::GetValueForExpressionPathOptions()
404+
.DontCheckDotVsArrowSyntax()
405+
.SetSyntheticChildrenTraversal(
406+
ValueObject::GetValueForExpressionPathOptions::
407+
SyntheticChildrenTraversal::None);
408+
409+
// This must be a ValueObject* because it is a child of the ValueObject we
410+
// are producing children for it if were a ValueObjectSP, we would end up
411+
// with a loop (iterator -> synthetic -> child -> parent == iterator) and
412+
// that would in turn leak memory by never allowing the ValueObjects to die
413+
// and free their memory.
414+
m_iter_ptr =
415+
valobj_sp
416+
->GetValueForExpressionPath(".__i_.__node_", nullptr, nullptr,
417+
exprPathOptions, nullptr)
418+
.get();
419+
420+
if (m_iter_ptr) {
421+
auto iter_child(
422+
valobj_sp->GetChildMemberWithName(ConstString("__i_"), true));
423+
if (!iter_child) {
424+
m_iter_ptr = nullptr;
425+
return false;
426+
}
427+
428+
CompilerType node_type(iter_child->GetCompilerType()
429+
.GetTypeTemplateArgument(0)
430+
.GetPointeeType());
431+
432+
CompilerType pair_type(node_type.GetTypeTemplateArgument(0));
433+
434+
std::string name;
435+
uint64_t bit_offset_ptr;
436+
uint32_t bitfield_bit_size_ptr;
437+
bool is_bitfield_ptr;
438+
439+
pair_type = pair_type.GetFieldAtIndex(
440+
0, name, &bit_offset_ptr, &bitfield_bit_size_ptr, &is_bitfield_ptr);
441+
if (!pair_type) {
442+
m_iter_ptr = nullptr;
443+
return false;
444+
}
445+
446+
uint64_t addr = m_iter_ptr->GetValueAsUnsigned(LLDB_INVALID_ADDRESS);
447+
m_iter_ptr = nullptr;
448+
449+
if (addr == 0 || addr == LLDB_INVALID_ADDRESS)
450+
return false;
451+
452+
TypeSystemClang *ast_ctx =
453+
llvm::dyn_cast_or_null<TypeSystemClang>(pair_type.GetTypeSystem());
454+
if (!ast_ctx)
455+
return false;
456+
457+
// Mimick layout of std::__hash_iterator::__node_ and read it in
458+
// from process memory.
459+
//
460+
// The following shows the contiguous block of memory:
461+
//
462+
// +-----------------------------+ class __hash_node_base
463+
// __node_ | __next_pointer __next_; |
464+
// +-----------------------------+ class __hash_node
465+
// | size_t __hash_; |
466+
// | __node_value_type __value_; | <<< our key/value pair
467+
// +-----------------------------+
468+
//
469+
CompilerType tree_node_type = ast_ctx->CreateStructForIdentifier(
470+
ConstString(),
471+
{{"__next_",
472+
ast_ctx->GetBasicType(lldb::eBasicTypeVoid).GetPointerType()},
473+
{"__hash_", ast_ctx->GetBasicType(lldb::eBasicTypeUnsignedLongLong)},
474+
{"__value_", pair_type}});
475+
llvm::Optional<uint64_t> size = tree_node_type.GetByteSize(nullptr);
476+
if (!size)
477+
return false;
478+
WritableDataBufferSP buffer_sp(new DataBufferHeap(*size, 0));
479+
ProcessSP process_sp(target_sp->GetProcessSP());
480+
Status error;
481+
process_sp->ReadMemory(addr, buffer_sp->GetBytes(),
482+
buffer_sp->GetByteSize(), error);
483+
if (error.Fail())
484+
return false;
485+
DataExtractor extractor(buffer_sp, process_sp->GetByteOrder(),
486+
process_sp->GetAddressByteSize());
487+
auto pair_sp = CreateValueObjectFromData(
488+
"pair", extractor, valobj_sp->GetExecutionContextRef(), tree_node_type);
489+
if (pair_sp)
490+
m_pair_sp = pair_sp->GetChildAtIndex(2, true);
491+
}
492+
493+
return false;
494+
}
495+
496+
size_t lldb_private::formatters::LibCxxUnorderedMapIteratorSyntheticFrontEnd::
497+
CalculateNumChildren() {
498+
return 2;
499+
}
500+
501+
lldb::ValueObjectSP lldb_private::formatters::
502+
LibCxxUnorderedMapIteratorSyntheticFrontEnd::GetChildAtIndex(size_t idx) {
503+
if (m_pair_sp)
504+
return m_pair_sp->GetChildAtIndex(idx, true);
505+
return lldb::ValueObjectSP();
506+
}
507+
508+
bool lldb_private::formatters::LibCxxUnorderedMapIteratorSyntheticFrontEnd::
509+
MightHaveChildren() {
510+
return true;
511+
}
512+
513+
size_t lldb_private::formatters::LibCxxUnorderedMapIteratorSyntheticFrontEnd::
514+
GetIndexOfChildWithName(ConstString name) {
515+
if (name == "first")
516+
return 0;
517+
if (name == "second")
518+
return 1;
519+
return UINT32_MAX;
520+
}
521+
522+
SyntheticChildrenFrontEnd *
523+
lldb_private::formatters::LibCxxUnorderedMapIteratorSyntheticFrontEndCreator(
524+
CXXSyntheticChildren *, lldb::ValueObjectSP valobj_sp) {
525+
return (valobj_sp ? new LibCxxUnorderedMapIteratorSyntheticFrontEnd(valobj_sp)
526+
: nullptr);
527+
}
528+
362529
/*
363530
(lldb) fr var ibeg --raw --ptr-depth 1 -T
364531
(std::__1::__wrap_iter<int *>) ibeg = {

lldb/source/Plugins/Language/CPlusPlus/LibCxx.h

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,56 @@ SyntheticChildrenFrontEnd *
103103
LibCxxMapIteratorSyntheticFrontEndCreator(CXXSyntheticChildren *,
104104
lldb::ValueObjectSP);
105105

106+
/// Formats libcxx's std::unordered_map iterators
107+
///
108+
/// In raw form a std::unordered_map::iterator is represented as follows:
109+
///
110+
/// (lldb) var it --raw --ptr-depth 1
111+
/// (std::__1::__hash_map_iterator<
112+
/// std::__1::__hash_iterator<
113+
/// std::__1::__hash_node<
114+
/// std::__1::__hash_value_type<
115+
/// std::__1::basic_string<char, std::__1::char_traits<char>,
116+
/// std::__1::allocator<char> >, std::__1::basic_string<char,
117+
/// std::__1::char_traits<char>, std::__1::allocator<char> > >,
118+
/// void *> *> >)
119+
/// it = {
120+
/// __i_ = {
121+
/// __node_ = 0x0000600001700040 {
122+
/// __next_ = 0x0000600001704000
123+
/// }
124+
/// }
125+
/// }
126+
class LibCxxUnorderedMapIteratorSyntheticFrontEnd
127+
: public SyntheticChildrenFrontEnd {
128+
public:
129+
LibCxxUnorderedMapIteratorSyntheticFrontEnd(lldb::ValueObjectSP valobj_sp);
130+
131+
~LibCxxUnorderedMapIteratorSyntheticFrontEnd() override = default;
132+
133+
size_t CalculateNumChildren() override;
134+
135+
lldb::ValueObjectSP GetChildAtIndex(size_t idx) override;
136+
137+
bool Update() override;
138+
139+
bool MightHaveChildren() override;
140+
141+
size_t GetIndexOfChildWithName(ConstString name) override;
142+
143+
private:
144+
ValueObject *m_iter_ptr = nullptr; ///< Held, not owned. Child of iterator
145+
///< ValueObject supplied at construction.
146+
147+
lldb::ValueObjectSP m_pair_sp; ///< ValueObject for the key/value pair
148+
///< that the iterator currently points
149+
///< to.
150+
};
151+
152+
SyntheticChildrenFrontEnd *
153+
LibCxxUnorderedMapIteratorSyntheticFrontEndCreator(CXXSyntheticChildren *,
154+
lldb::ValueObjectSP);
155+
106156
SyntheticChildrenFrontEnd *
107157
LibCxxVectorIteratorSyntheticFrontEndCreator(CXXSyntheticChildren *,
108158
lldb::ValueObjectSP);
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
CXX_SOURCES := main.cpp
2+
3+
USE_LIBCPP := 1
4+
5+
CXXFLAGS_EXTRAS := -O0
6+
include Makefile.rules
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
"""
2+
Test formatting of std::unordered_map related structures.
3+
"""
4+
5+
import lldb
6+
from lldbsuite.test.decorators import *
7+
from lldbsuite.test.lldbtest import *
8+
from lldbsuite.test import lldbutil
9+
10+
class LibcxxUnorderedMapDataFormatterTestCase(TestBase):
11+
12+
@add_test_categories(['libc++'])
13+
def test_iterator_formatters(self):
14+
"""Test that std::unordered_map related structures are formatted correctly when printed.
15+
Currently only tests format of std::unordered_map iterators.
16+
"""
17+
self.build()
18+
(self.target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(
19+
self, 'Break here', lldb.SBFileSpec('main.cpp', False))
20+
21+
# Test empty iterators
22+
self.expect_expr('empty_iter', '')
23+
self.expect_expr('const_empty_iter', '')
24+
25+
lldbutil.continue_to_breakpoint(process, bkpt)
26+
27+
# Check that key/value is correctly formatted
28+
self.expect_expr('foo', result_children=[
29+
ValueCheck(name='first', summary='"Foo"'),
30+
ValueCheck(name='second', summary='"Bar"')
31+
])
32+
33+
# Check invalid iterator is empty
34+
self.expect_expr('invalid', '')
35+
36+
# Const key/val iterator
37+
self.expect_expr('const_baz', result_children=[
38+
ValueCheck(name='first', summary='"Baz"'),
39+
ValueCheck(name='second', summary='"Qux"')
40+
])
41+
42+
# Bucket iterators
43+
# I.e., std::__hash_map_const_iterator<const_local_iterator<...>>
44+
# and std::__hash_map_iterator<local_iterator<...>>
45+
self.expect_expr('bucket_it', result_children=[
46+
ValueCheck(name='first', summary='"Baz"'),
47+
ValueCheck(name='second', summary='"Qux"')
48+
])
49+
50+
self.expect_expr('const_bucket_it', result_children=[
51+
ValueCheck(name='first', summary='"Baz"'),
52+
ValueCheck(name='second', summary='"Qux"')
53+
])
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#include <cstdio>
2+
#include <string>
3+
#include <unordered_map>
4+
5+
using StringMapT = std::unordered_map<std::string, std::string>;
6+
7+
int main() {
8+
StringMapT string_map;
9+
{
10+
auto empty_iter = string_map.begin();
11+
auto const_empty_iter = string_map.cbegin();
12+
std::printf("Break here");
13+
}
14+
string_map["Foo"] = "Bar";
15+
string_map["Baz"] = "Qux";
16+
17+
{
18+
auto foo = string_map.find("Foo");
19+
auto invalid = string_map.find("Invalid");
20+
21+
StringMapT::const_iterator const_baz = string_map.find("Baz");
22+
auto bucket_it = string_map.begin(string_map.bucket("Baz"));
23+
auto const_bucket_it = string_map.cbegin(string_map.bucket("Baz"));
24+
std::printf("Break here");
25+
}
26+
27+
return 0;
28+
}

0 commit comments

Comments
 (0)