From d1e9d0b27f3a59f80787bfe5cf10d4373275c477 Mon Sep 17 00:00:00 2001 From: Michael Buch Date: Fri, 8 Jul 2022 10:47:04 +0100 Subject: [PATCH] [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 --- .../Language/CPlusPlus/CPlusPlusLanguage.cpp | 8 + lldb/source/Plugins/Language/CPlusPlus/LibCxx.cpp | 167 +++++++++++++++++++++ lldb/source/Plugins/Language/CPlusPlus/LibCxx.h | 50 ++++++ .../libcxx/unordered_map/Makefile | 6 + .../TestDataFormatterLibccUnorderedMap.py | 53 +++++++ .../libcxx/unordered_map/main.cpp | 28 ++++ 6 files changed, 312 insertions(+) create mode 100644 lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx/unordered_map/Makefile create mode 100644 lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx/unordered_map/TestDataFormatterLibccUnorderedMap.py create mode 100644 lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx/unordered_map/main.cpp diff --git a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp index 82f8258..89bee3e 100644 --- a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp +++ b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp @@ -911,6 +911,14 @@ static void LoadLibCxxFormatters(lldb::TypeCategoryImplSP cpp_category_sp) { "std::map iterator synthetic children", ConstString("^std::__[[:alnum:]]+::__map_iterator<.+>$"), stl_synth_flags, true); + + AddCXXSynthetic( + cpp_category_sp, + lldb_private::formatters:: + LibCxxUnorderedMapIteratorSyntheticFrontEndCreator, + "std::unordered_map iterator synthetic children", + ConstString("^std::__[[:alnum:]]+::__hash_map_(const_)?iterator<.+>$"), + stl_synth_flags, true); } static void LoadLibStdcppFormatters(lldb::TypeCategoryImplSP cpp_category_sp) { diff --git a/lldb/source/Plugins/Language/CPlusPlus/LibCxx.cpp b/lldb/source/Plugins/Language/CPlusPlus/LibCxx.cpp index 6c651c6..aaf3464 100644 --- a/lldb/source/Plugins/Language/CPlusPlus/LibCxx.cpp +++ b/lldb/source/Plugins/Language/CPlusPlus/LibCxx.cpp @@ -27,6 +27,7 @@ #include "Plugins/LanguageRuntime/CPlusPlus/CPPLanguageRuntime.h" #include "Plugins/TypeSystem/Clang/TypeSystemClang.h" +#include "lldb/lldb-enumerations.h" #include using namespace lldb; @@ -283,6 +284,22 @@ bool lldb_private::formatters::LibCxxMapIteratorSyntheticFrontEnd::Update() { llvm::dyn_cast_or_null(pair_type.GetTypeSystem()); if (!ast_ctx) return false; + + // Mimick layout of std::__tree_iterator::__ptr_ and read it in + // from process memory. + // + // The following shows the contiguous block of memory: + // + // +-----------------------------+ class __tree_end_node + // __ptr_ | pointer __left_; | + // +-----------------------------+ class __tree_node_base + // | pointer __right_; | + // | __parent_pointer __parent_; | + // | bool __is_black_; | + // +-----------------------------+ class __tree_node + // | __node_value_type __value_; | <<< our key/value pair + // +-----------------------------+ + // CompilerType tree_node_type = ast_ctx->CreateStructForIdentifier( ConstString(), {{"ptr0", @@ -359,6 +376,156 @@ lldb_private::formatters::LibCxxMapIteratorSyntheticFrontEndCreator( : nullptr); } +lldb_private::formatters::LibCxxUnorderedMapIteratorSyntheticFrontEnd:: + LibCxxUnorderedMapIteratorSyntheticFrontEnd(lldb::ValueObjectSP valobj_sp) + : SyntheticChildrenFrontEnd(*valobj_sp) { + if (valobj_sp) + Update(); +} + +bool lldb_private::formatters::LibCxxUnorderedMapIteratorSyntheticFrontEnd:: + Update() { + m_pair_sp.reset(); + m_iter_ptr = nullptr; + + ValueObjectSP valobj_sp = m_backend.GetSP(); + if (!valobj_sp) + return false; + + TargetSP target_sp(valobj_sp->GetTargetSP()); + + if (!target_sp) + return false; + + if (!valobj_sp) + return false; + + auto exprPathOptions = ValueObject::GetValueForExpressionPathOptions() + .DontCheckDotVsArrowSyntax() + .SetSyntheticChildrenTraversal( + ValueObject::GetValueForExpressionPathOptions:: + SyntheticChildrenTraversal::None); + + // This must be a ValueObject* because it is a child of the ValueObject we + // are producing children for it if were a ValueObjectSP, we would end up + // with a loop (iterator -> synthetic -> child -> parent == iterator) and + // that would in turn leak memory by never allowing the ValueObjects to die + // and free their memory. + m_iter_ptr = + valobj_sp + ->GetValueForExpressionPath(".__i_.__node_", nullptr, nullptr, + exprPathOptions, nullptr) + .get(); + + if (m_iter_ptr) { + auto iter_child( + valobj_sp->GetChildMemberWithName(ConstString("__i_"), true)); + if (!iter_child) { + m_iter_ptr = nullptr; + return false; + } + + CompilerType node_type(iter_child->GetCompilerType() + .GetTypeTemplateArgument(0) + .GetPointeeType()); + + CompilerType pair_type(node_type.GetTypeTemplateArgument(0)); + + std::string name; + uint64_t bit_offset_ptr; + uint32_t bitfield_bit_size_ptr; + bool is_bitfield_ptr; + + pair_type = pair_type.GetFieldAtIndex( + 0, name, &bit_offset_ptr, &bitfield_bit_size_ptr, &is_bitfield_ptr); + if (!pair_type) { + m_iter_ptr = nullptr; + return false; + } + + uint64_t addr = m_iter_ptr->GetValueAsUnsigned(LLDB_INVALID_ADDRESS); + m_iter_ptr = nullptr; + + if (addr == 0 || addr == LLDB_INVALID_ADDRESS) + return false; + + TypeSystemClang *ast_ctx = + llvm::dyn_cast_or_null(pair_type.GetTypeSystem()); + if (!ast_ctx) + return false; + + // Mimick layout of std::__hash_iterator::__node_ and read it in + // from process memory. + // + // The following shows the contiguous block of memory: + // + // +-----------------------------+ class __hash_node_base + // __node_ | __next_pointer __next_; | + // +-----------------------------+ class __hash_node + // | size_t __hash_; | + // | __node_value_type __value_; | <<< our key/value pair + // +-----------------------------+ + // + CompilerType tree_node_type = ast_ctx->CreateStructForIdentifier( + ConstString(), + {{"__next_", + ast_ctx->GetBasicType(lldb::eBasicTypeVoid).GetPointerType()}, + {"__hash_", ast_ctx->GetBasicType(lldb::eBasicTypeUnsignedLongLong)}, + {"__value_", pair_type}}); + llvm::Optional size = tree_node_type.GetByteSize(nullptr); + if (!size) + return false; + WritableDataBufferSP buffer_sp(new DataBufferHeap(*size, 0)); + ProcessSP process_sp(target_sp->GetProcessSP()); + Status error; + process_sp->ReadMemory(addr, buffer_sp->GetBytes(), + buffer_sp->GetByteSize(), error); + if (error.Fail()) + return false; + DataExtractor extractor(buffer_sp, process_sp->GetByteOrder(), + process_sp->GetAddressByteSize()); + auto pair_sp = CreateValueObjectFromData( + "pair", extractor, valobj_sp->GetExecutionContextRef(), tree_node_type); + if (pair_sp) + m_pair_sp = pair_sp->GetChildAtIndex(2, true); + } + + return false; +} + +size_t lldb_private::formatters::LibCxxUnorderedMapIteratorSyntheticFrontEnd:: + CalculateNumChildren() { + return 2; +} + +lldb::ValueObjectSP lldb_private::formatters:: + LibCxxUnorderedMapIteratorSyntheticFrontEnd::GetChildAtIndex(size_t idx) { + if (m_pair_sp) + return m_pair_sp->GetChildAtIndex(idx, true); + return lldb::ValueObjectSP(); +} + +bool lldb_private::formatters::LibCxxUnorderedMapIteratorSyntheticFrontEnd:: + MightHaveChildren() { + return true; +} + +size_t lldb_private::formatters::LibCxxUnorderedMapIteratorSyntheticFrontEnd:: + GetIndexOfChildWithName(ConstString name) { + if (name == "first") + return 0; + if (name == "second") + return 1; + return UINT32_MAX; +} + +SyntheticChildrenFrontEnd * +lldb_private::formatters::LibCxxUnorderedMapIteratorSyntheticFrontEndCreator( + CXXSyntheticChildren *, lldb::ValueObjectSP valobj_sp) { + return (valobj_sp ? new LibCxxUnorderedMapIteratorSyntheticFrontEnd(valobj_sp) + : nullptr); +} + /* (lldb) fr var ibeg --raw --ptr-depth 1 -T (std::__1::__wrap_iter) ibeg = { diff --git a/lldb/source/Plugins/Language/CPlusPlus/LibCxx.h b/lldb/source/Plugins/Language/CPlusPlus/LibCxx.h index b4e789e6..b5ade4a 100644 --- a/lldb/source/Plugins/Language/CPlusPlus/LibCxx.h +++ b/lldb/source/Plugins/Language/CPlusPlus/LibCxx.h @@ -103,6 +103,56 @@ SyntheticChildrenFrontEnd * LibCxxMapIteratorSyntheticFrontEndCreator(CXXSyntheticChildren *, lldb::ValueObjectSP); +/// Formats libcxx's std::unordered_map iterators +/// +/// In raw form a std::unordered_map::iterator is represented as follows: +/// +/// (lldb) var it --raw --ptr-depth 1 +/// (std::__1::__hash_map_iterator< +/// std::__1::__hash_iterator< +/// std::__1::__hash_node< +/// std::__1::__hash_value_type< +/// std::__1::basic_string, +/// std::__1::allocator >, std::__1::basic_string, std::__1::allocator > >, +/// void *> *> >) +/// it = { +/// __i_ = { +/// __node_ = 0x0000600001700040 { +/// __next_ = 0x0000600001704000 +/// } +/// } +/// } +class LibCxxUnorderedMapIteratorSyntheticFrontEnd + : public SyntheticChildrenFrontEnd { +public: + LibCxxUnorderedMapIteratorSyntheticFrontEnd(lldb::ValueObjectSP valobj_sp); + + ~LibCxxUnorderedMapIteratorSyntheticFrontEnd() override = default; + + size_t CalculateNumChildren() override; + + lldb::ValueObjectSP GetChildAtIndex(size_t idx) override; + + bool Update() override; + + bool MightHaveChildren() override; + + size_t GetIndexOfChildWithName(ConstString name) override; + +private: + ValueObject *m_iter_ptr = nullptr; ///< Held, not owned. Child of iterator + ///< ValueObject supplied at construction. + + lldb::ValueObjectSP m_pair_sp; ///< ValueObject for the key/value pair + ///< that the iterator currently points + ///< to. +}; + +SyntheticChildrenFrontEnd * +LibCxxUnorderedMapIteratorSyntheticFrontEndCreator(CXXSyntheticChildren *, + lldb::ValueObjectSP); + SyntheticChildrenFrontEnd * LibCxxVectorIteratorSyntheticFrontEndCreator(CXXSyntheticChildren *, lldb::ValueObjectSP); diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx/unordered_map/Makefile b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx/unordered_map/Makefile new file mode 100644 index 0000000..564cbad --- /dev/null +++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx/unordered_map/Makefile @@ -0,0 +1,6 @@ +CXX_SOURCES := main.cpp + +USE_LIBCPP := 1 + +CXXFLAGS_EXTRAS := -O0 +include Makefile.rules diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx/unordered_map/TestDataFormatterLibccUnorderedMap.py b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx/unordered_map/TestDataFormatterLibccUnorderedMap.py new file mode 100644 index 0000000..4188703 --- /dev/null +++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx/unordered_map/TestDataFormatterLibccUnorderedMap.py @@ -0,0 +1,53 @@ +""" +Test formatting of std::unordered_map related structures. +""" + +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil + +class LibcxxUnorderedMapDataFormatterTestCase(TestBase): + + @add_test_categories(['libc++']) + def test_iterator_formatters(self): + """Test that std::unordered_map related structures are formatted correctly when printed. + Currently only tests format of std::unordered_map iterators. + """ + self.build() + (self.target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint( + self, 'Break here', lldb.SBFileSpec('main.cpp', False)) + + # Test empty iterators + self.expect_expr('empty_iter', '') + self.expect_expr('const_empty_iter', '') + + lldbutil.continue_to_breakpoint(process, bkpt) + + # Check that key/value is correctly formatted + self.expect_expr('foo', result_children=[ + ValueCheck(name='first', summary='"Foo"'), + ValueCheck(name='second', summary='"Bar"') + ]) + + # Check invalid iterator is empty + self.expect_expr('invalid', '') + + # Const key/val iterator + self.expect_expr('const_baz', result_children=[ + ValueCheck(name='first', summary='"Baz"'), + ValueCheck(name='second', summary='"Qux"') + ]) + + # Bucket iterators + # I.e., std::__hash_map_const_iterator> + # and std::__hash_map_iterator> + self.expect_expr('bucket_it', result_children=[ + ValueCheck(name='first', summary='"Baz"'), + ValueCheck(name='second', summary='"Qux"') + ]) + + self.expect_expr('const_bucket_it', result_children=[ + ValueCheck(name='first', summary='"Baz"'), + ValueCheck(name='second', summary='"Qux"') + ]) diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx/unordered_map/main.cpp b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx/unordered_map/main.cpp new file mode 100644 index 0000000..adcea69 --- /dev/null +++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx/unordered_map/main.cpp @@ -0,0 +1,28 @@ +#include +#include +#include + +using StringMapT = std::unordered_map; + +int main() { + StringMapT string_map; + { + auto empty_iter = string_map.begin(); + auto const_empty_iter = string_map.cbegin(); + std::printf("Break here"); + } + string_map["Foo"] = "Bar"; + string_map["Baz"] = "Qux"; + + { + auto foo = string_map.find("Foo"); + auto invalid = string_map.find("Invalid"); + + StringMapT::const_iterator const_baz = string_map.find("Baz"); + auto bucket_it = string_map.begin(string_map.bucket("Baz")); + auto const_bucket_it = string_map.cbegin(string_map.bucket("Baz")); + std::printf("Break here"); + } + + return 0; +} -- 2.7.4