https://github.com/Nerixyz updated https://github.com/llvm/llvm-project/pull/148285
>From 104fbb9daa6ea0d4483939662ae08a8d42d77f44 Mon Sep 17 00:00:00 2001 From: Nerixyz <nerix...@outlook.de> Date: Fri, 11 Jul 2025 22:06:47 +0200 Subject: [PATCH 1/3] [LLDB] Add formatters for MSVC STL std::(forward_)list --- lldb/examples/synthetic/gnu_libstdcpp.py | 9 - .../Plugins/Language/CPlusPlus/CMakeLists.txt | 2 +- .../Language/CPlusPlus/CPlusPlusLanguage.cpp | 62 +++- .../{LibCxxList.cpp => GenericList.cpp} | 294 +++++++++++++++--- .../Plugins/Language/CPlusPlus/MsvcStl.h | 9 + .../TestDataFormatterGenericForwardList.py | 33 +- .../list/TestDataFormatterGenericList.py | 35 ++- .../loop/TestDataFormatterGenericListLoop.py | 18 +- .../generic/list/loop/main.cpp | 5 - 9 files changed, 356 insertions(+), 111 deletions(-) rename lldb/source/Plugins/Language/CPlusPlus/{LibCxxList.cpp => GenericList.cpp} (58%) diff --git a/lldb/examples/synthetic/gnu_libstdcpp.py b/lldb/examples/synthetic/gnu_libstdcpp.py index 20b9488af5597..e59eb5fa18c02 100644 --- a/lldb/examples/synthetic/gnu_libstdcpp.py +++ b/lldb/examples/synthetic/gnu_libstdcpp.py @@ -6,15 +6,6 @@ # thing for your setup -def ForwardListSummaryProvider(valobj, dict): - list_capping_size = valobj.GetTarget().GetMaximumNumberOfChildrenToDisplay() - text = "size=" + str(valobj.GetNumChildren()) - if valobj.GetNumChildren() > list_capping_size: - return "(capped) " + text - else: - return text - - def StdOptionalSummaryProvider(valobj, dict): has_value = valobj.GetNumChildren() > 0 # We add wrapping spaces for consistency with the libcxx formatter diff --git a/lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt b/lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt index 296159ea28407..c7f12c1255ece 100644 --- a/lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt +++ b/lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt @@ -14,11 +14,11 @@ add_lldb_library(lldbPluginCPlusPlusLanguage PLUGIN CxxStringTypes.cpp Generic.cpp GenericBitset.cpp + GenericList.cpp GenericOptional.cpp LibCxx.cpp LibCxxAtomic.cpp LibCxxInitializerList.cpp - LibCxxList.cpp LibCxxMap.cpp LibCxxQueue.cpp LibCxxRangesRefView.cpp diff --git a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp index 9a869f3ea0289..8e5bd014fcd85 100644 --- a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp +++ b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp @@ -1440,14 +1440,12 @@ static void LoadLibStdcppFormatters(lldb::TypeCategoryImplSP cpp_category_sp) { stl_deref_flags, "lldb.formatters.cpp.gnu_libstdcpp.StdUnorderedMapSynthProvider"))); cpp_category_sp->AddTypeSynthetic( - "^std::((__debug::)?|(__cxx11::)?)list<.+>(( )?&)?$", - eFormatterMatchRegex, + "^std::__(debug|cxx11)::list<.+>(( )?&)?$", eFormatterMatchRegex, SyntheticChildrenSP(new ScriptedSyntheticChildren( stl_deref_flags, "lldb.formatters.cpp.gnu_libstdcpp.StdListSynthProvider"))); cpp_category_sp->AddTypeSynthetic( - "^std::((__debug::)?|(__cxx11::)?)forward_list<.+>(( )?&)?$", - eFormatterMatchRegex, + "^std::__(debug|cxx11)::forward_list<.+>(( )?&)?$", eFormatterMatchRegex, SyntheticChildrenSP(new ScriptedSyntheticChildren( stl_synth_flags, "lldb.formatters.cpp.gnu_libstdcpp.StdForwardListSynthProvider"))); @@ -1501,22 +1499,19 @@ static void LoadLibStdcppFormatters(lldb::TypeCategoryImplSP cpp_category_sp) { "^std::(__debug::)?unordered_(multi)?(map|set)<.+> >$", stl_summary_flags, true); - AddCXXSummary(cpp_category_sp, - lldb_private::formatters::ContainerSizeSummaryProvider, - "libstdc++ std::list summary provider", - "^std::((__debug::)?|(__cxx11::)?)list<.+>(( )?&)?$", - stl_summary_flags, true); + AddCXXSummary( + cpp_category_sp, lldb_private::formatters::ContainerSizeSummaryProvider, + "libstdc++ std::list summary provider", + "^std::__(debug|cxx11)::list<.+>(( )?&)?$", stl_summary_flags, true); AddCXXSummary(cpp_category_sp, ContainerSizeSummaryProvider, "libstdc++ std::tuple summary provider", "^std::tuple<.*>(( )?&)?$", stl_summary_flags, true); - cpp_category_sp->AddTypeSummary( - "^std::((__debug::)?|(__cxx11::)?)forward_list<.+>(( )?&)?$", - eFormatterMatchRegex, - TypeSummaryImplSP(new ScriptSummaryFormat( - stl_summary_flags, - "lldb.formatters.cpp.gnu_libstdcpp.ForwardListSummaryProvider"))); + AddCXXSummary(cpp_category_sp, ContainerSizeSummaryProvider, + "libstdc++ std::forward_list summary provider", + "^std::__(debug|cxx11)::forward_list<.+>(( )?&)?$", + stl_summary_flags, true); cpp_category_sp->AddTypeSummary( "^std::variant<.+>$", eFormatterMatchRegex, TypeSummaryImplSP(new ScriptSummaryFormat( @@ -1613,6 +1608,31 @@ static bool GenericUniquePtrSummaryProvider(ValueObject &valobj, Stream &stream, return LibStdcppUniquePointerSummaryProvider(valobj, stream, options); } +static SyntheticChildrenFrontEnd * +GenericListSyntheticFrontEndCreator(CXXSyntheticChildren *children, + lldb::ValueObjectSP valobj_sp) { + if (!valobj_sp) + return nullptr; + + if (IsMsvcStlList(*valobj_sp)) + return MsvcStlListSyntheticFrontEndCreator(children, valobj_sp); + return new ScriptedSyntheticChildren::FrontEnd( + "lldb.formatters.cpp.gnu_libstdcpp.StdListSynthProvider", *valobj_sp); +} + +static SyntheticChildrenFrontEnd * +GenericForwardListSyntheticFrontEndCreator(CXXSyntheticChildren *children, + lldb::ValueObjectSP valobj_sp) { + if (!valobj_sp) + return nullptr; + + if (IsMsvcStlList(*valobj_sp)) + return MsvcStlForwardListSyntheticFrontEndCreator(children, valobj_sp); + return new ScriptedSyntheticChildren::FrontEnd( + "lldb.formatters.cpp.gnu_libstdcpp.StdForwardListSynthProvider", + *valobj_sp); +} + /// Load formatters that are formatting types from more than one STL static void LoadCommonStlFormatters(lldb::TypeCategoryImplSP cpp_category_sp) { if (!cpp_category_sp) @@ -1668,6 +1688,12 @@ static void LoadCommonStlFormatters(lldb::TypeCategoryImplSP cpp_category_sp) { AddCXXSynthetic(cpp_category_sp, GenericUniquePtrSyntheticFrontEndCreator, "std::unique_ptr synthetic children", "^std::unique_ptr<.+>(( )?&)?$", stl_synth_flags, true); + AddCXXSynthetic(cpp_category_sp, GenericListSyntheticFrontEndCreator, + "std::list synthetic children", "^std::list<.+>(( )?&)?$", + stl_synth_flags, true); + AddCXXSynthetic(cpp_category_sp, GenericForwardListSyntheticFrontEndCreator, + "std::forward_list synthetic children", + "^std::forward_list<.+>(( )?&)?$", stl_synth_flags, true); AddCXXSummary(cpp_category_sp, GenericSmartPointerSummaryProvider, "MSVC STL/libstdc++ std::shared_ptr summary provider", @@ -1678,6 +1704,12 @@ static void LoadCommonStlFormatters(lldb::TypeCategoryImplSP cpp_category_sp) { AddCXXSummary(cpp_category_sp, GenericUniquePtrSummaryProvider, "MSVC STL/libstdc++ std::unique_ptr summary provider", "^std::unique_ptr<.+>(( )?&)?$", stl_summary_flags, true); + AddCXXSummary(cpp_category_sp, ContainerSizeSummaryProvider, + "MSVC STL/libstdc++ std::list summary provider", + "^std::list<.+>(( )?&)?$", stl_summary_flags, true); + AddCXXSummary(cpp_category_sp, ContainerSizeSummaryProvider, + "MSVC STL/libstdc++ std::forward_list summary provider", + "^std::forward_list<.+>(( )?&)?$", stl_summary_flags, true); } static void LoadMsvcStlFormatters(lldb::TypeCategoryImplSP cpp_category_sp) { diff --git a/lldb/source/Plugins/Language/CPlusPlus/LibCxxList.cpp b/lldb/source/Plugins/Language/CPlusPlus/GenericList.cpp similarity index 58% rename from lldb/source/Plugins/Language/CPlusPlus/LibCxxList.cpp rename to lldb/source/Plugins/Language/CPlusPlus/GenericList.cpp index 826e6ab090e10..ea1edbfd3ac9b 100644 --- a/lldb/source/Plugins/Language/CPlusPlus/LibCxxList.cpp +++ b/lldb/source/Plugins/Language/CPlusPlus/GenericList.cpp @@ -1,4 +1,4 @@ -//===-- LibCxxList.cpp ----------------------------------------------------===// +//===-- GenericList.cpp ---------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -7,14 +7,11 @@ //===----------------------------------------------------------------------===// #include "LibCxx.h" +#include "MsvcStl.h" -#include "Plugins/TypeSystem/Clang/TypeSystemClang.h" #include "lldb/DataFormatters/FormattersHelpers.h" #include "lldb/Target/Target.h" -#include "lldb/Utility/DataBufferHeap.h" -#include "lldb/Utility/Endian.h" #include "lldb/Utility/Status.h" -#include "lldb/Utility/Stream.h" #include "lldb/ValueObject/ValueObject.h" #include "lldb/ValueObject/ValueObjectConstResult.h" #include "lldb/lldb-enumerations.h" @@ -25,31 +22,27 @@ using namespace lldb_private::formatters; namespace { -class ListEntry { +enum class StlType { + LibCxx, + MsvcStl, +}; + +template <StlType Stl> class ListEntry { public: ListEntry() = default; ListEntry(ValueObjectSP entry_sp) : m_entry_sp(std::move(entry_sp)) {} ListEntry(ValueObject *entry) : m_entry_sp(entry ? entry->GetSP() : ValueObjectSP()) {} - ListEntry next() { - if (!m_entry_sp) - return ListEntry(); - return ListEntry(m_entry_sp->GetChildMemberWithName("__next_")); - } - - ListEntry prev() { - if (!m_entry_sp) - return ListEntry(); - return ListEntry(m_entry_sp->GetChildMemberWithName("__prev_")); - } - uint64_t value() const { if (!m_entry_sp) return 0; return m_entry_sp->GetValueAsUnsigned(0); } + ListEntry next(); + ListEntry prev(); + bool null() { return (value() == 0); } explicit operator bool() { return GetEntry() && !null(); } @@ -66,10 +59,34 @@ class ListEntry { ValueObjectSP m_entry_sp; }; -class ListIterator { +template <> ListEntry<StlType::LibCxx> ListEntry<StlType::LibCxx>::next() { + if (!m_entry_sp) + return ListEntry(); + return ListEntry(m_entry_sp->GetChildMemberWithName("__next_")); +} + +template <> ListEntry<StlType::LibCxx> ListEntry<StlType::LibCxx>::prev() { + if (!m_entry_sp) + return ListEntry(); + return ListEntry(m_entry_sp->GetChildMemberWithName("__prev_")); +} + +template <> ListEntry<StlType::MsvcStl> ListEntry<StlType::MsvcStl>::next() { + if (!m_entry_sp) + return ListEntry(); + return ListEntry(m_entry_sp->GetChildMemberWithName("_Next")); +} + +template <> ListEntry<StlType::MsvcStl> ListEntry<StlType::MsvcStl>::prev() { + if (!m_entry_sp) + return ListEntry(); + return ListEntry(m_entry_sp->GetChildMemberWithName("_Prev")); +} + +template <StlType Stl> class ListIterator { public: ListIterator() = default; - ListIterator(ListEntry entry) : m_entry(std::move(entry)) {} + ListIterator(ListEntry<Stl> entry) : m_entry(std::move(entry)) {} ListIterator(ValueObjectSP entry) : m_entry(std::move(entry)) {} ListIterator(ValueObject *entry) : m_entry(entry) {} @@ -101,9 +118,10 @@ class ListIterator { void prev() { m_entry = m_entry.prev(); } private: - ListEntry m_entry; + ListEntry<Stl> m_entry; }; +template <StlType Stl> class AbstractListFrontEnd : public SyntheticChildrenFrontEnd { public: llvm::Expected<size_t> GetIndexOfChildWithName(ConstString name) override { @@ -124,33 +142,31 @@ class AbstractListFrontEnd : public SyntheticChildrenFrontEnd { ValueObject *m_head = nullptr; static constexpr bool g_use_loop_detect = true; - size_t m_loop_detected = 0; // The number of elements that have had loop - // detection run over them. - ListEntry m_slow_runner; // Used for loop detection - ListEntry m_fast_runner; // Used for loop detection + size_t m_loop_detected = 0; // The number of elements that have had loop + // detection run over them. + ListEntry<Stl> m_slow_runner; // Used for loop detection + ListEntry<Stl> m_fast_runner; // Used for loop detection size_t m_list_capping_size = 0; CompilerType m_element_type; - std::map<size_t, ListIterator> m_iterators; + std::map<size_t, ListIterator<Stl>> m_iterators; bool HasLoop(size_t count); ValueObjectSP GetItem(size_t idx); }; -class ForwardListFrontEnd : public AbstractListFrontEnd { +class LibCxxForwardListFrontEnd : public AbstractListFrontEnd<StlType::LibCxx> { public: - ForwardListFrontEnd(ValueObject &valobj); + LibCxxForwardListFrontEnd(ValueObject &valobj); llvm::Expected<uint32_t> CalculateNumChildren() override; ValueObjectSP GetChildAtIndex(uint32_t idx) override; lldb::ChildCacheState Update() override; }; -class ListFrontEnd : public AbstractListFrontEnd { +class LibCxxListFrontEnd : public AbstractListFrontEnd<StlType::LibCxx> { public: - ListFrontEnd(lldb::ValueObjectSP valobj_sp); - - ~ListFrontEnd() override = default; + LibCxxListFrontEnd(lldb::ValueObjectSP valobj_sp); llvm::Expected<uint32_t> CalculateNumChildren() override; @@ -163,9 +179,34 @@ class ListFrontEnd : public AbstractListFrontEnd { ValueObject *m_tail = nullptr; }; +class MsvcStlForwardListFrontEnd + : public AbstractListFrontEnd<StlType::MsvcStl> { +public: + MsvcStlForwardListFrontEnd(ValueObject &valobj); + + llvm::Expected<uint32_t> CalculateNumChildren() override; + ValueObjectSP GetChildAtIndex(uint32_t idx) override; + lldb::ChildCacheState Update() override; +}; + +class MsvcStlListFrontEnd : public AbstractListFrontEnd<StlType::MsvcStl> { +public: + MsvcStlListFrontEnd(lldb::ValueObjectSP valobj_sp); + + llvm::Expected<uint32_t> CalculateNumChildren() override; + + lldb::ValueObjectSP GetChildAtIndex(uint32_t idx) override; + + lldb::ChildCacheState Update() override; + +private: + ValueObject *m_tail = nullptr; +}; + } // end anonymous namespace -lldb::ChildCacheState AbstractListFrontEnd::Update() { +template <StlType Stl> +lldb::ChildCacheState AbstractListFrontEnd<Stl>::Update() { m_loop_detected = 0; m_count = UINT32_MAX; m_head = nullptr; @@ -191,7 +232,7 @@ lldb::ChildCacheState AbstractListFrontEnd::Update() { return lldb::ChildCacheState::eRefetch; } -bool AbstractListFrontEnd::HasLoop(size_t count) { +template <StlType Stl> bool AbstractListFrontEnd<Stl>::HasLoop(size_t count) { if (!g_use_loop_detect) return false; // don't bother checking for a loop if we won't actually need to jump nodes @@ -201,7 +242,7 @@ bool AbstractListFrontEnd::HasLoop(size_t count) { if (m_loop_detected == 0) { // This is the first time we are being run (after the last update). Set up // the loop invariant for the first element. - m_slow_runner = ListEntry(m_head).next(); + m_slow_runner = ListEntry<Stl>(m_head).next(); m_fast_runner = m_slow_runner.next(); m_loop_detected = 1; } @@ -225,9 +266,10 @@ bool AbstractListFrontEnd::HasLoop(size_t count) { return m_slow_runner == m_fast_runner; } -ValueObjectSP AbstractListFrontEnd::GetItem(size_t idx) { +template <StlType Stl> +ValueObjectSP AbstractListFrontEnd<Stl>::GetItem(size_t idx) { size_t advance = idx; - ListIterator current(m_head); + ListIterator<Stl> current(m_head); if (idx > 0) { auto cached_iterator = m_iterators.find(idx - 1); if (cached_iterator != m_iterators.end()) { @@ -240,16 +282,16 @@ ValueObjectSP AbstractListFrontEnd::GetItem(size_t idx) { return value_sp; } -ForwardListFrontEnd::ForwardListFrontEnd(ValueObject &valobj) +LibCxxForwardListFrontEnd::LibCxxForwardListFrontEnd(ValueObject &valobj) : AbstractListFrontEnd(valobj) { Update(); } -llvm::Expected<uint32_t> ForwardListFrontEnd::CalculateNumChildren() { +llvm::Expected<uint32_t> LibCxxForwardListFrontEnd::CalculateNumChildren() { if (m_count != UINT32_MAX) return m_count; - ListEntry current(m_head); + ListEntry<StlType::LibCxx> current(m_head); m_count = 0; while (current && m_count < m_list_capping_size) { ++m_count; @@ -258,7 +300,7 @@ llvm::Expected<uint32_t> ForwardListFrontEnd::CalculateNumChildren() { return m_count; } -ValueObjectSP ForwardListFrontEnd::GetChildAtIndex(uint32_t idx) { +ValueObjectSP LibCxxForwardListFrontEnd::GetChildAtIndex(uint32_t idx) { if (idx >= CalculateNumChildrenIgnoringErrors()) return nullptr; @@ -289,7 +331,7 @@ ValueObjectSP ForwardListFrontEnd::GetChildAtIndex(uint32_t idx) { m_element_type); } -lldb::ChildCacheState ForwardListFrontEnd::Update() { +lldb::ChildCacheState LibCxxForwardListFrontEnd::Update() { AbstractListFrontEnd::Update(); Status err; @@ -312,13 +354,13 @@ lldb::ChildCacheState ForwardListFrontEnd::Update() { return ChildCacheState::eRefetch; } -ListFrontEnd::ListFrontEnd(lldb::ValueObjectSP valobj_sp) +LibCxxListFrontEnd::LibCxxListFrontEnd(lldb::ValueObjectSP valobj_sp) : AbstractListFrontEnd(*valobj_sp) { if (valobj_sp) Update(); } -llvm::Expected<uint32_t> ListFrontEnd::CalculateNumChildren() { +llvm::Expected<uint32_t> LibCxxListFrontEnd::CalculateNumChildren() { if (m_count != UINT32_MAX) return m_count; if (!m_head || !m_tail || m_node_address == 0) @@ -351,7 +393,7 @@ llvm::Expected<uint32_t> ListFrontEnd::CalculateNumChildren() { if (next_val == prev_val) return 1; uint64_t size = 2; - ListEntry current(m_head); + ListEntry<StlType::LibCxx> current(m_head); while (current.next() && current.next().value() != m_node_address) { size++; current = current.next(); @@ -361,7 +403,7 @@ llvm::Expected<uint32_t> ListFrontEnd::CalculateNumChildren() { return m_count = (size - 1); } -lldb::ValueObjectSP ListFrontEnd::GetChildAtIndex(uint32_t idx) { +lldb::ValueObjectSP LibCxxListFrontEnd::GetChildAtIndex(uint32_t idx) { static ConstString g_value("__value_"); static ConstString g_next("__next_"); @@ -412,7 +454,7 @@ lldb::ValueObjectSP ListFrontEnd::GetChildAtIndex(uint32_t idx) { m_element_type); } -lldb::ChildCacheState ListFrontEnd::Update() { +lldb::ChildCacheState LibCxxListFrontEnd::Update() { AbstractListFrontEnd::Update(); m_tail = nullptr; m_node_address = 0; @@ -432,13 +474,167 @@ lldb::ChildCacheState ListFrontEnd::Update() { return lldb::ChildCacheState::eRefetch; } +MsvcStlForwardListFrontEnd::MsvcStlForwardListFrontEnd(ValueObject &valobj) + : AbstractListFrontEnd(valobj) { + Update(); +} + +llvm::Expected<uint32_t> MsvcStlForwardListFrontEnd::CalculateNumChildren() { + if (m_count != UINT32_MAX) + return m_count; + + ListEntry<StlType::MsvcStl> current(m_head); + m_count = 0; + while (current && m_count < m_list_capping_size) { + ++m_count; + current = current.next(); + } + return m_count; +} + +ValueObjectSP MsvcStlForwardListFrontEnd::GetChildAtIndex(uint32_t idx) { + if (idx >= CalculateNumChildrenIgnoringErrors()) + return nullptr; + + if (!m_head) + return nullptr; + + if (HasLoop(idx + 1)) + return nullptr; + + ValueObjectSP current_sp = GetItem(idx); + if (!current_sp) + return nullptr; + + current_sp = current_sp->GetChildAtIndex(1); // get the _Myval child + if (!current_sp) + return nullptr; + + // we need to copy current_sp into a new object otherwise we will end up with + // all items named _Myval + DataExtractor data; + Status error; + current_sp->GetData(data, error); + if (error.Fail()) + return nullptr; + + return CreateValueObjectFromData(llvm::formatv("[{0}]", idx).str(), data, + m_backend.GetExecutionContextRef(), + m_element_type); +} + +lldb::ChildCacheState MsvcStlForwardListFrontEnd::Update() { + AbstractListFrontEnd::Update(); + + if (auto head_sp = + m_backend.GetChildAtNamePath({"_Mypair", "_Myval2", "_Myhead"})) + m_head = head_sp.get(); + + return ChildCacheState::eRefetch; +} + +MsvcStlListFrontEnd::MsvcStlListFrontEnd(lldb::ValueObjectSP valobj_sp) + : AbstractListFrontEnd(*valobj_sp) { + if (valobj_sp) + Update(); +} + +llvm::Expected<uint32_t> MsvcStlListFrontEnd::CalculateNumChildren() { + if (m_count != UINT32_MAX) + return m_count; + if (!m_head || !m_tail) + return 0; + + auto size_sp = + m_backend.GetChildAtNamePath({"_Mypair", "_Myval2", "_Mysize"}); + if (!size_sp) + return llvm::createStringError("Failed to resolve size."); + + m_count = size_sp->GetValueAsUnsigned(UINT32_MAX); + if (m_count == UINT32_MAX) + return llvm::createStringError("Failed to read size value."); + + return m_count; +} + +lldb::ValueObjectSP MsvcStlListFrontEnd::GetChildAtIndex(uint32_t idx) { + if (idx >= CalculateNumChildrenIgnoringErrors()) + return lldb::ValueObjectSP(); + + if (!m_head || !m_tail) + return lldb::ValueObjectSP(); + + if (HasLoop(idx + 1)) + return lldb::ValueObjectSP(); + + ValueObjectSP current_sp = GetItem(idx); + if (!current_sp) + return lldb::ValueObjectSP(); + + current_sp = current_sp->GetChildAtIndex(2); // get the _Myval child + if (!current_sp) + return lldb::ValueObjectSP(); + + // we need to copy current_sp into a new object otherwise we will end up with + // all items named _Myval + DataExtractor data; + Status error; + current_sp->GetData(data, error); + if (error.Fail()) + return lldb::ValueObjectSP(); + + StreamString name; + name.Printf("[%" PRIu64 "]", (uint64_t)idx); + return CreateValueObjectFromData(name.GetString(), data, + m_backend.GetExecutionContextRef(), + m_element_type); +} + +lldb::ChildCacheState MsvcStlListFrontEnd::Update() { + AbstractListFrontEnd::Update(); + m_tail = nullptr; + m_head = nullptr; + + ValueObjectSP last = + m_backend.GetChildAtNamePath({"_Mypair", "_Myval2", "_Myhead"}); + if (!last) + return lldb::ChildCacheState::eRefetch; + ValueObjectSP first = last->GetChildMemberWithName("_Next"); + if (!first) + return lldb::ChildCacheState::eRefetch; + + m_head = first.get(); + m_tail = last.get(); + + return lldb::ChildCacheState::eRefetch; +} + SyntheticChildrenFrontEnd *formatters::LibcxxStdListSyntheticFrontEndCreator( CXXSyntheticChildren *, lldb::ValueObjectSP valobj_sp) { - return (valobj_sp ? new ListFrontEnd(valobj_sp) : nullptr); + return (valobj_sp ? new LibCxxListFrontEnd(valobj_sp) : nullptr); } SyntheticChildrenFrontEnd * formatters::LibcxxStdForwardListSyntheticFrontEndCreator( CXXSyntheticChildren *, lldb::ValueObjectSP valobj_sp) { - return valobj_sp ? new ForwardListFrontEnd(*valobj_sp) : nullptr; + return valobj_sp ? new LibCxxForwardListFrontEnd(*valobj_sp) : nullptr; +} + +bool formatters::IsMsvcStlList(ValueObject &valobj) { + if (auto valobj_sp = valobj.GetNonSyntheticValue()) + return valobj_sp->GetChildMemberWithName("_Mypair") != nullptr; + + return false; +} + +SyntheticChildrenFrontEnd * +formatters::MsvcStlListSyntheticFrontEndCreator(CXXSyntheticChildren *, + lldb::ValueObjectSP valobj_sp) { + return (valobj_sp ? new MsvcStlListFrontEnd(valobj_sp) : nullptr); +} + +SyntheticChildrenFrontEnd * +formatters::MsvcStlForwardListSyntheticFrontEndCreator( + CXXSyntheticChildren *, lldb::ValueObjectSP valobj_sp) { + return valobj_sp ? new MsvcStlForwardListFrontEnd(*valobj_sp) : nullptr; } diff --git a/lldb/source/Plugins/Language/CPlusPlus/MsvcStl.h b/lldb/source/Plugins/Language/CPlusPlus/MsvcStl.h index fe75bf275f8e2..656965e5fe765 100644 --- a/lldb/source/Plugins/Language/CPlusPlus/MsvcStl.h +++ b/lldb/source/Plugins/Language/CPlusPlus/MsvcStl.h @@ -45,6 +45,15 @@ bool MsvcStlUniquePtrSummaryProvider(ValueObject &valobj, Stream &stream, lldb_private::SyntheticChildrenFrontEnd * MsvcStlUniquePtrSyntheticFrontEndCreator(lldb::ValueObjectSP valobj_sp); +// For both std::list and std::forward_list +bool IsMsvcStlList(ValueObject &valobj); +SyntheticChildrenFrontEnd * +MsvcStlForwardListSyntheticFrontEndCreator(CXXSyntheticChildren *, + lldb::ValueObjectSP valobj_sp); +SyntheticChildrenFrontEnd * +MsvcStlListSyntheticFrontEndCreator(CXXSyntheticChildren *, + lldb::ValueObjectSP valobj_sp); + } // namespace formatters } // namespace lldb_private diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/forward_list/TestDataFormatterGenericForwardList.py b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/forward_list/TestDataFormatterGenericForwardList.py index f63f8fe1d6a62..45695c43b42a9 100644 --- a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/forward_list/TestDataFormatterGenericForwardList.py +++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/forward_list/TestDataFormatterGenericForwardList.py @@ -7,9 +7,6 @@ from lldbsuite.test.lldbtest import * from lldbsuite.test import lldbutil -USE_LIBSTDCPP = "USE_LIBSTDCPP" -USE_LIBCPP = "USE_LIBCPP" - class TestDataFormatterGenericForwardList(TestBase): def setUp(self): @@ -17,9 +14,8 @@ def setUp(self): self.line = line_number("main.cpp", "// break here") self.namespace = "std" - def do_test(self, stdlib_type): + def do_test(self): """Test that std::forward_list is displayed correctly""" - self.build(dictionary={stdlib_type: "1"}) lldbutil.run_to_source_breakpoint( self, "// break here", lldb.SBFileSpec("main.cpp", False) ) @@ -76,10 +72,8 @@ def do_test(self, stdlib_type): substrs=["size=24", "[0]", "[1]", "[2]", "..."], ) - def do_test_ptr_and_ref(self, stdlib_type): + def do_test_ptr_and_ref(self): """Test that ref and ptr to std::forward_list is displayed correctly""" - self.build(dictionary={stdlib_type: "1"}) - (_, process, _, bkpt) = lldbutil.run_to_source_breakpoint( self, "Check ref and ptr", lldb.SBFileSpec("main.cpp", False) ) @@ -158,16 +152,31 @@ def do_test_ptr_and_ref(self, stdlib_type): @add_test_categories(["libstdcxx"]) def test_libstdcpp(self): - self.do_test(USE_LIBSTDCPP) + self.build(dictionary={"USE_LIBSTDCPP": 1}) + self.do_test() @add_test_categories(["libstdcxx"]) def test_ptr_and_ref_libstdcpp(self): - self.do_test_ptr_and_ref(USE_LIBSTDCPP) + self.build(dictionary={"USE_LIBSTDCPP": 1}) + self.do_test_ptr_and_ref() @add_test_categories(["libc++"]) def test_libcpp(self): - self.do_test(USE_LIBCPP) + self.build(dictionary={"USE_LIBCPP": 1}) + self.do_test() @add_test_categories(["libc++"]) def test_ptr_and_ref_libcpp(self): - self.do_test_ptr_and_ref(USE_LIBCPP) + self.build(dictionary={"USE_LIBCPP": 1}) + self.do_test_ptr_and_ref() + + @add_test_categories(["msvcstl"]) + def test_msvcstl(self): + # No flags, because the "msvcstl" category checks that the MSVC STL is used by default. + self.build() + self.do_test() + + @add_test_categories(["msvcstl"]) + def test_ptr_and_ref_msvcstl(self): + self.build() + self.do_test_ptr_and_ref() diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/list/TestDataFormatterGenericList.py b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/list/TestDataFormatterGenericList.py index 78c93b1e3caea..c0207e6ab5911 100644 --- a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/list/TestDataFormatterGenericList.py +++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/list/TestDataFormatterGenericList.py @@ -8,9 +8,6 @@ from lldbsuite.test.lldbtest import * from lldbsuite.test import lldbutil -USE_LIBSTDCPP = "USE_LIBSTDCPP" -USE_LIBCPP = "USE_LIBCPP" - class GenericListDataFormatterTestCase(TestBase): def setUp(self): @@ -25,9 +22,8 @@ def setUp(self): "main.cpp", "// Set final break point at this line." ) - def do_test_with_run_command(self, stdlib_type): + def do_test_with_run_command(self, *, is_libstdcpp=False): """Test that that file and class static variables display correctly.""" - self.build(dictionary={stdlib_type: "1"}) self.runCmd("file " + self.getBuildArtifact("a.out"), CURRENT_EXECUTABLE_SET) lldbutil.run_break_set_by_file_and_line( @@ -62,7 +58,7 @@ def cleanup(): "frame variable numbers_list --raw", matching=False, substrs=["size=0"] ) - if stdlib_type == USE_LIBSTDCPP: + if is_libstdcpp: self.expect( "frame variable &numbers_list._M_impl._M_node --raw", matching=False, @@ -230,10 +226,8 @@ def cleanup(): "text_list.MightHaveChildren() says False for non empty!", ) - def do_test_ptr_and_ref(self, stdlib_type): + def do_test_ptr_and_ref(self): """Test that ref and ptr to std::list is displayed correctly""" - self.build(dictionary={stdlib_type: "1"}) - (_, process, _, bkpt) = lldbutil.run_to_source_breakpoint( self, "Check ref and ptr", lldb.SBFileSpec("main.cpp", False) ) @@ -302,16 +296,31 @@ def do_test_ptr_and_ref(self, stdlib_type): @add_test_categories(["libstdcxx"]) def test_with_run_command_libstdcpp(self): - self.do_test_with_run_command(USE_LIBSTDCPP) + self.build(dictionary={"USE_LIBSTDCPP": 1}) + self.do_test_with_run_command(is_libstdcpp=True) @add_test_categories(["libstdcxx"]) def test_ptr_and_ref_libstdcpp(self): - self.do_test_ptr_and_ref(USE_LIBSTDCPP) + self.build(dictionary={"USE_LIBSTDCPP": 1}) + self.do_test_ptr_and_ref() @add_test_categories(["libc++"]) def test_with_run_command_libcpp(self): - self.do_test_with_run_command(USE_LIBCPP) + self.build(dictionary={"USE_LIBCPP": 1}) + self.do_test_with_run_command() @add_test_categories(["libc++"]) def test_ptr_and_ref_libcpp(self): - self.do_test_ptr_and_ref(USE_LIBCPP) + self.build(dictionary={"USE_LIBCPP": 1}) + self.do_test_ptr_and_ref() + + @add_test_categories(["msvcstl"]) + def test_with_run_command_msvcstl(self): + # No flags, because the "msvcstl" category checks that the MSVC STL is used by default. + self.build() + self.do_test_with_run_command() + + @add_test_categories(["msvcstl"]) + def test_ptr_and_ref_msvcstl(self): + self.build() + self.do_test_ptr_and_ref() diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/list/loop/TestDataFormatterGenericListLoop.py b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/list/loop/TestDataFormatterGenericListLoop.py index 039c703491759..f6174dd786380 100644 --- a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/list/loop/TestDataFormatterGenericListLoop.py +++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/list/loop/TestDataFormatterGenericListLoop.py @@ -9,15 +9,11 @@ from lldbsuite.test.lldbtest import * from lldbsuite.test import lldbutil -USE_LIBSTDCPP = "USE_LIBSTDCPP" -USE_LIBCPP = "USE_LIBCPP" - class GenericListDataFormatterTestCase(TestBase): NO_DEBUG_INFO_TESTCASE = True - def do_test_with_run_command(self, stdlib_type): - self.build(dictionary={stdlib_type: "1"}) + def do_test_with_run_command(self): exe = self.getBuildArtifact("a.out") target = self.dbg.CreateTarget(exe) self.assertTrue(target and target.IsValid(), "Target is valid") @@ -64,8 +60,16 @@ def do_test_with_run_command(self, stdlib_type): @add_test_categories(["libstdcxx"]) def test_with_run_command_libstdcpp(self): - self.do_test_with_run_command(USE_LIBSTDCPP) + self.build(dictionary={"USE_LIBSTDCPP": 1}) + self.do_test_with_run_command() @add_test_categories(["libc++"]) def test_with_run_command_libcpp(self): - self.do_test_with_run_command(USE_LIBCPP) + self.build(dictionary={"USE_LIBCPP": 1}) + self.do_test_with_run_command() + + @add_test_categories(["msvcstl"]) + def test_with_run_command_msvcstl(self): + # No flags, because the "msvcstl" category checks that the MSVC STL is used by default. + self.build() + self.do_test_with_run_command() diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/list/loop/main.cpp b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/list/loop/main.cpp index e797b3d04dd6b..b31d4ca909ecb 100644 --- a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/list/loop/main.cpp +++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/list/loop/main.cpp @@ -1,8 +1,3 @@ -// Evil hack: To simulate memory corruption, we want to fiddle with some internals of std::list. -// Make those accessible to us. -#define private public -#define protected public - #include <list> #include <stdio.h> #include <assert.h> >From 86aa535884c8896e9ee0466e0b2c7b7d76595226 Mon Sep 17 00:00:00 2001 From: Nerixyz <nerix...@outlook.de> Date: Tue, 15 Jul 2025 16:36:22 +0200 Subject: [PATCH 2/3] fix: mention that it's a debug summary --- .../Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp index 8e5bd014fcd85..6244901ff9322 100644 --- a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp +++ b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp @@ -1499,10 +1499,10 @@ static void LoadLibStdcppFormatters(lldb::TypeCategoryImplSP cpp_category_sp) { "^std::(__debug::)?unordered_(multi)?(map|set)<.+> >$", stl_summary_flags, true); - AddCXXSummary( - cpp_category_sp, lldb_private::formatters::ContainerSizeSummaryProvider, - "libstdc++ std::list summary provider", - "^std::__(debug|cxx11)::list<.+>(( )?&)?$", stl_summary_flags, true); + AddCXXSummary(cpp_category_sp, GenericCappedContainerSummaryProvider, + "libstdc++ debug std::list summary provider", + "^std::__(debug|cxx11)::list<.+>(( )?&)?$", stl_summary_flags, + true); AddCXXSummary(cpp_category_sp, ContainerSizeSummaryProvider, "libstdc++ std::tuple summary provider", >From b219eaefafd8ae4d00fc5ccdd3a1cab3e8942681 Mon Sep 17 00:00:00 2001 From: Nerixyz <nerix...@outlook.de> Date: Tue, 15 Jul 2025 16:37:43 +0200 Subject: [PATCH 3/3] feat: show that a container is capped --- .../Language/CPlusPlus/CPlusPlusLanguage.cpp | 16 +++++------ .../Plugins/Language/CPlusPlus/Generic.cpp | 17 ++++++++++++ .../Plugins/Language/CPlusPlus/Generic.h | 3 +++ .../TestDataFormatterGenericForwardList.py | 4 ++- .../list/TestDataFormatterGenericList.py | 27 +++++++++++++++++++ .../data-formatter-stl/generic/list/main.cpp | 7 ++++- 6 files changed, 64 insertions(+), 10 deletions(-) diff --git a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp index 6244901ff9322..346e1edbc14c1 100644 --- a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp +++ b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp @@ -1038,12 +1038,12 @@ static void LoadLibCxxFormatters(lldb::TypeCategoryImplSP cpp_category_sp) { "libc++ summary provider for the valarray proxy arrays", "^std::__[[:alnum:]]+::(gslice|mask|indirect)_array<.+>$", stl_summary_flags, true); + AddCXXSummary(cpp_category_sp, GenericCappedContainerSummaryProvider, + "libc++ std::list summary provider", + "^std::__[[:alnum:]]+::forward_list<.+>$", stl_summary_flags, + true); AddCXXSummary( - cpp_category_sp, lldb_private::formatters::ContainerSizeSummaryProvider, - "libc++ std::list summary provider", - "^std::__[[:alnum:]]+::forward_list<.+>$", stl_summary_flags, true); - AddCXXSummary( - cpp_category_sp, lldb_private::formatters::ContainerSizeSummaryProvider, + cpp_category_sp, GenericCappedContainerSummaryProvider, "libc++ std::list summary provider", // A POSIX variant of: "^std::__(?!cxx11:)[[:alnum:]]+::list<.+>$" // so that it does not clash with: "^std::(__cxx11::)?list<.+>$" @@ -1508,7 +1508,7 @@ static void LoadLibStdcppFormatters(lldb::TypeCategoryImplSP cpp_category_sp) { "libstdc++ std::tuple summary provider", "^std::tuple<.*>(( )?&)?$", stl_summary_flags, true); - AddCXXSummary(cpp_category_sp, ContainerSizeSummaryProvider, + AddCXXSummary(cpp_category_sp, GenericCappedContainerSummaryProvider, "libstdc++ std::forward_list summary provider", "^std::__(debug|cxx11)::forward_list<.+>(( )?&)?$", stl_summary_flags, true); @@ -1704,10 +1704,10 @@ static void LoadCommonStlFormatters(lldb::TypeCategoryImplSP cpp_category_sp) { AddCXXSummary(cpp_category_sp, GenericUniquePtrSummaryProvider, "MSVC STL/libstdc++ std::unique_ptr summary provider", "^std::unique_ptr<.+>(( )?&)?$", stl_summary_flags, true); - AddCXXSummary(cpp_category_sp, ContainerSizeSummaryProvider, + AddCXXSummary(cpp_category_sp, GenericCappedContainerSummaryProvider, "MSVC STL/libstdc++ std::list summary provider", "^std::list<.+>(( )?&)?$", stl_summary_flags, true); - AddCXXSummary(cpp_category_sp, ContainerSizeSummaryProvider, + AddCXXSummary(cpp_category_sp, GenericCappedContainerSummaryProvider, "MSVC STL/libstdc++ std::forward_list summary provider", "^std::forward_list<.+>(( )?&)?$", stl_summary_flags, true); } diff --git a/lldb/source/Plugins/Language/CPlusPlus/Generic.cpp b/lldb/source/Plugins/Language/CPlusPlus/Generic.cpp index bfe86e4665f65..0d06640223033 100644 --- a/lldb/source/Plugins/Language/CPlusPlus/Generic.cpp +++ b/lldb/source/Plugins/Language/CPlusPlus/Generic.cpp @@ -23,3 +23,20 @@ lldb::ValueObjectSP lldb_private::formatters::GetDesugaredSmartPointerValue( return ptr.Cast(arg.GetPointerType()); } + +bool lldb_private::formatters::GenericCappedContainerSummaryProvider( + ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) { + lldb::TargetSP target_sp = valobj.GetTargetSP(); + if (!target_sp) + return false; + + auto num_children = valobj.GetNumChildren(); + if (!num_children) + return false; + + if (*num_children > target_sp->GetMaximumNumberOfChildrenToDisplay()) + stream << "(capped) "; + + stream.Printf("size=%d", *num_children); + return true; +} diff --git a/lldb/source/Plugins/Language/CPlusPlus/Generic.h b/lldb/source/Plugins/Language/CPlusPlus/Generic.h index f3946225ed48d..8f1b492ba2d1e 100644 --- a/lldb/source/Plugins/Language/CPlusPlus/Generic.h +++ b/lldb/source/Plugins/Language/CPlusPlus/Generic.h @@ -24,6 +24,9 @@ bool GenericOptionalSummaryProvider(ValueObject &valobj, Stream &stream, lldb::ValueObjectSP GetDesugaredSmartPointerValue(ValueObject &ptr, ValueObject &container); +bool GenericCappedContainerSummaryProvider(ValueObject &valobj, Stream &stream, + const TypeSummaryOptions &options); + } // namespace formatters } // namespace lldb_private diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/forward_list/TestDataFormatterGenericForwardList.py b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/forward_list/TestDataFormatterGenericForwardList.py index 45695c43b42a9..891a003b3687a 100644 --- a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/forward_list/TestDataFormatterGenericForwardList.py +++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/forward_list/TestDataFormatterGenericForwardList.py @@ -66,10 +66,12 @@ def do_test(self): ], ) + # We first queried the list above with a limit of 24. + # Now, we know that it has at least 24 elements. self.expect( "frame variable thousand_elts", matching=True, - substrs=["size=24", "[0]", "[1]", "[2]", "..."], + substrs=["(capped) size=24", "[0]", "[1]", "[2]", "..."], ) def do_test_ptr_and_ref(self): diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/list/TestDataFormatterGenericList.py b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/list/TestDataFormatterGenericList.py index c0207e6ab5911..fa4737b3b4d5b 100644 --- a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/list/TestDataFormatterGenericList.py +++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/list/TestDataFormatterGenericList.py @@ -46,6 +46,7 @@ def cleanup(): self.runCmd("type summary clear", check=False) self.runCmd("type filter clear", check=False) self.runCmd("type synth clear", check=False) + self.runCmd("settings clear target.max-children-count", check=False) # Execute the cleanup function during test case tear down. self.addTearDownHook(cleanup) @@ -226,6 +227,32 @@ def cleanup(): "text_list.MightHaveChildren() says False for non empty!", ) + self.runCmd("settings set target.max-children-count 4") + self.expect( + "frame variable big_list", + substrs=[ + "size=4" if is_libstdcpp else "(capped) size=27", + "[0]", + "0", + "[1]", + "1", + "[2]", + "2", + "[3]", + "3", + "...", + ], + ) + # Check that 4 is not contained in the output + self.expect("frame variable big_list", matching=False, substrs=["[4]"]) + + # Check that there is no capping if the max children are set high enough + self.runCmd("settings set target.max-children-count 27") + self.expect("frame variable big_list", substrs=["size=27", "[26]"]) + self.expect( + "frame variable big_list", matching=False, substrs=["(capped)", "..."] + ) + def do_test_ptr_and_ref(self): """Test that ref and ptr to std::list is displayed correctly""" (_, process, _, bkpt) = lldbutil.run_to_source_breakpoint( diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/list/main.cpp b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/list/main.cpp index 5537b20f4763e..6a8c7ca1e1548 100644 --- a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/list/main.cpp +++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/list/main.cpp @@ -37,7 +37,12 @@ int main() text_list.push_back(std::string("!!!")); by_ref_and_ptr(text_list, &text_list); - + + int_list big_list{ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, + 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, + }; + return 0; // Set final break point at this line. } _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits