[Lldb-commits] [lldb] [LLDB] Add formatters for MSVC STL std::deque (PR #150097)
via lldb-commits
lldb-commits at lists.llvm.org
Wed Jul 23 08:08:13 PDT 2025
https://github.com/Nerixyz updated https://github.com/llvm/llvm-project/pull/150097
>From ee9043309a33595e312bd7ee9ad0b05e164e4fe7 Mon Sep 17 00:00:00 2001
From: Nerixyz <nerixdev at outlook.de>
Date: Tue, 22 Jul 2025 17:52:21 +0200
Subject: [PATCH] [LLDB] Add formatters for MSVC STL std::deque
---
.../Plugins/Language/CPlusPlus/CMakeLists.txt | 1 +
.../Language/CPlusPlus/CPlusPlusLanguage.cpp | 28 ++-
.../Plugins/Language/CPlusPlus/MsvcStl.h | 6 +
.../Language/CPlusPlus/MsvcStlDeque.cpp | 174 ++++++++++++++++++
.../deque/TestDataFormatterGenericDeque.py | 32 ++--
5 files changed, 225 insertions(+), 16 deletions(-)
create mode 100644 lldb/source/Plugins/Language/CPlusPlus/MsvcStlDeque.cpp
diff --git a/lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt b/lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt
index c10aa7482594c..cbc6f149ac303 100644
--- a/lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt
+++ b/lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt
@@ -35,6 +35,7 @@ add_lldb_library(lldbPluginCPlusPlusLanguage PLUGIN
LibStdcppUniquePointer.cpp
MsvcStl.cpp
MsvcStlAtomic.cpp
+ MsvcStlDeque.cpp
MsvcStlSmartPointer.cpp
MsvcStlTree.cpp
MsvcStlTuple.cpp
diff --git a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
index 4f62999adc1a7..e69f2f677e9ab 100644
--- a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
+++ b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
@@ -1414,7 +1414,7 @@ static void LoadLibStdcppFormatters(lldb::TypeCategoryImplSP cpp_category_sp) {
stl_synth_flags,
"lldb.formatters.cpp.gnu_libstdcpp.StdMapLikeSynthProvider")));
cpp_category_sp->AddTypeSynthetic(
- "^std::(__debug)?deque<.+>(( )?&)?$", eFormatterMatchRegex,
+ "^std::__debug::deque<.+>(( )?&)?$", eFormatterMatchRegex,
SyntheticChildrenSP(new ScriptedSyntheticChildren(
stl_deref_flags,
"lldb.formatters.cpp.gnu_libstdcpp.StdDequeSynthProvider")));
@@ -1472,10 +1472,9 @@ static void LoadLibStdcppFormatters(lldb::TypeCategoryImplSP cpp_category_sp) {
"libstdc++ debug std::set summary provider",
"^std::__debug::set<.+> >(( )?&)?$", stl_summary_flags, true);
- AddCXXSummary(
- cpp_category_sp, lldb_private::formatters::ContainerSizeSummaryProvider,
- "libstdc++ std::deque summary provider",
- "^std::(__debug::)?deque<.+>(( )?&)?$", stl_summary_flags, true);
+ AddCXXSummary(cpp_category_sp, ContainerSizeSummaryProvider,
+ "libstdc++ debug std::deque summary provider",
+ "^std::__debug::deque<.+>(( )?&)?$", stl_summary_flags, true);
AddCXXSummary(
cpp_category_sp, lldb_private::formatters::ContainerSizeSummaryProvider,
@@ -1684,6 +1683,18 @@ GenericMapLikeSyntheticFrontEndCreator(CXXSyntheticChildren *children,
"lldb.formatters.cpp.gnu_libstdcpp.StdMapLikeSynthProvider", *valobj_sp);
}
+static SyntheticChildrenFrontEnd *
+GenericDequeSyntheticFrontEndCreator(CXXSyntheticChildren *children,
+ ValueObjectSP valobj_sp) {
+ if (!valobj_sp)
+ return nullptr;
+
+ if (IsMsvcStlDeque(*valobj_sp))
+ return MsvcStlDequeSyntheticFrontEndCreator(children, valobj_sp);
+ return new ScriptedSyntheticChildren::FrontEnd(
+ "lldb.formatters.cpp.gnu_libstdcpp.StdDequeSynthProvider", *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)
@@ -1761,6 +1772,10 @@ static void LoadCommonStlFormatters(lldb::TypeCategoryImplSP cpp_category_sp) {
AddCXXSynthetic(cpp_category_sp, GenericOptionalSyntheticFrontEndCreator,
"std::optional synthetic children",
"^std::optional<.+>(( )?&)?$", stl_deref_flags, true);
+ AddCXXSynthetic(cpp_category_sp, GenericDequeSyntheticFrontEndCreator,
+ "std::deque container synthetic children",
+ "^std::deque<.+>(( )?&)?$", stl_deref_flags, true);
+
AddCXXSynthetic(cpp_category_sp, GenericMapLikeSyntheticFrontEndCreator,
"std::(multi)?map/set synthetic children",
"^std::(multi)?(map|set)<.+>(( )?&)?$", stl_synth_flags,
@@ -1806,6 +1821,9 @@ static void LoadCommonStlFormatters(lldb::TypeCategoryImplSP cpp_category_sp) {
"MSVC STL/libstdc++ std::(multi)?map/set summary provider",
"^std::(multi)?(map|set)<.+>(( )?&)?$", stl_summary_flags,
true);
+ AddCXXSummary(cpp_category_sp, ContainerSizeSummaryProvider,
+ "MSVC STL/libstd++ std::deque summary provider",
+ "^std::deque<.+>(( )?&)?$", stl_summary_flags, true);
}
static void LoadMsvcStlFormatters(lldb::TypeCategoryImplSP cpp_category_sp) {
diff --git a/lldb/source/Plugins/Language/CPlusPlus/MsvcStl.h b/lldb/source/Plugins/Language/CPlusPlus/MsvcStl.h
index cce22a336c37b..490794ccbfb53 100644
--- a/lldb/source/Plugins/Language/CPlusPlus/MsvcStl.h
+++ b/lldb/source/Plugins/Language/CPlusPlus/MsvcStl.h
@@ -103,6 +103,12 @@ bool IsMsvcStlMapLike(ValueObject &valobj);
lldb_private::SyntheticChildrenFrontEnd *
MsvcStlMapLikeSyntheticFrontEndCreator(lldb::ValueObjectSP valobj_sp);
+// MSVC STL std::deque<>
+bool IsMsvcStlDeque(ValueObject &valobj);
+SyntheticChildrenFrontEnd *
+MsvcStlDequeSyntheticFrontEndCreator(CXXSyntheticChildren *,
+ lldb::ValueObjectSP valobj_sp);
+
} // namespace formatters
} // namespace lldb_private
diff --git a/lldb/source/Plugins/Language/CPlusPlus/MsvcStlDeque.cpp b/lldb/source/Plugins/Language/CPlusPlus/MsvcStlDeque.cpp
new file mode 100644
index 0000000000000..873354381a6da
--- /dev/null
+++ b/lldb/source/Plugins/Language/CPlusPlus/MsvcStlDeque.cpp
@@ -0,0 +1,174 @@
+//===-- MsvcStlDeque.cpp --------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "MsvcStl.h"
+
+#include "lldb/DataFormatters/FormattersHelpers.h"
+#include "lldb/DataFormatters/TypeSynthetic.h"
+
+using namespace lldb;
+
+namespace lldb_private {
+namespace formatters {
+
+class MsvcStlDequeSyntheticFrontEnd : public SyntheticChildrenFrontEnd {
+public:
+ MsvcStlDequeSyntheticFrontEnd(lldb::ValueObjectSP valobj_sp);
+
+ llvm::Expected<uint32_t> CalculateNumChildren() override;
+
+ lldb::ValueObjectSP GetChildAtIndex(uint32_t idx) override;
+
+ lldb::ChildCacheState Update() override;
+
+ llvm::Expected<size_t> GetIndexOfChildWithName(ConstString name) override;
+
+private:
+ ValueObject *m_map = nullptr;
+ ExecutionContextRef m_exe_ctx_ref;
+
+ size_t m_block_size = 0;
+ size_t m_offset = 0;
+ size_t m_map_size = 0;
+
+ size_t m_element_size = 0;
+ CompilerType m_element_type;
+
+ uint32_t m_size = 0;
+};
+
+} // namespace formatters
+} // namespace lldb_private
+
+lldb_private::formatters::MsvcStlDequeSyntheticFrontEnd::
+ MsvcStlDequeSyntheticFrontEnd(lldb::ValueObjectSP valobj_sp)
+ : SyntheticChildrenFrontEnd(*valobj_sp) {
+ if (valobj_sp)
+ Update();
+}
+
+llvm::Expected<uint32_t> lldb_private::formatters::
+ MsvcStlDequeSyntheticFrontEnd::CalculateNumChildren() {
+ if (!m_map)
+ return llvm::createStringError("Failed to read size");
+ return m_size;
+}
+
+lldb::ValueObjectSP
+lldb_private::formatters::MsvcStlDequeSyntheticFrontEnd::GetChildAtIndex(
+ uint32_t idx) {
+ if (idx >= m_size || !m_map)
+ return nullptr;
+ ProcessSP process_sp(m_exe_ctx_ref.GetProcessSP());
+ if (!process_sp)
+ return nullptr;
+
+ // _EEN_DS = _Block_size
+ // _Map[(($i + _Myoff) / _EEN_DS) % _Mapsize][($i + _Myoff) % _EEN_DS]
+ size_t first_idx = ((idx + m_offset) / m_block_size) % m_map_size;
+ lldb::addr_t first_address = m_map->GetValueAsUnsigned(0) +
+ first_idx * process_sp->GetAddressByteSize();
+
+ Status err;
+ lldb::addr_t second_base =
+ process_sp->ReadPointerFromMemory(first_address, err);
+ if (err.Fail())
+ return nullptr;
+
+ size_t second_idx = (idx + m_offset) % m_block_size;
+ size_t second_address = second_base + second_idx * m_element_size;
+
+ StreamString name;
+ name.Printf("[%" PRIu64 "]", (uint64_t)idx);
+ return CreateValueObjectFromAddress(name.GetString(), second_address,
+ m_backend.GetExecutionContextRef(),
+ m_element_type);
+}
+
+lldb::ChildCacheState
+lldb_private::formatters::MsvcStlDequeSyntheticFrontEnd::Update() {
+ m_size = 0;
+ m_map = nullptr;
+ m_element_type.Clear();
+
+ auto storage_sp = m_backend.GetChildAtNamePath({"_Mypair", "_Myval2"});
+ if (!storage_sp)
+ return lldb::eRefetch;
+
+ auto deque_type = m_backend.GetCompilerType();
+ if (!deque_type)
+ return lldb::eRefetch;
+
+ auto block_size_decl = deque_type.GetStaticFieldWithName("_Block_size");
+ if (!block_size_decl)
+ return lldb::eRefetch;
+ auto block_size = block_size_decl.GetConstantValue();
+ if (!block_size.IsValid())
+ return lldb::eRefetch;
+
+ auto element_type = deque_type.GetTypeTemplateArgument(0);
+ if (!element_type)
+ return lldb::eRefetch;
+ auto element_size = element_type.GetByteSize(nullptr);
+ if (!element_size)
+ return lldb::eRefetch;
+
+ auto offset_sp = storage_sp->GetChildMemberWithName("_Myoff");
+ auto map_size_sp = storage_sp->GetChildMemberWithName("_Mapsize");
+ auto map_sp = storage_sp->GetChildMemberWithName("_Map");
+ auto size_sp = storage_sp->GetChildMemberWithName("_Mysize");
+ if (!offset_sp || !map_size_sp || !map_sp || !size_sp)
+ return lldb::eRefetch;
+
+ bool ok = false;
+ uint64_t offset = offset_sp->GetValueAsUnsigned(0, &ok);
+ if (!ok)
+ return lldb::eRefetch;
+
+ uint64_t map_size = map_size_sp->GetValueAsUnsigned(0, &ok);
+ if (!ok)
+ return lldb::eRefetch;
+
+ uint64_t size = size_sp->GetValueAsUnsigned(0, &ok);
+ if (!ok)
+ return lldb::eRefetch;
+
+ m_map = map_sp.get();
+ m_exe_ctx_ref = m_backend.GetExecutionContextRef();
+ m_block_size = block_size.ULongLong();
+ m_offset = offset;
+ m_map_size = map_size;
+ m_element_size = *element_size;
+ m_element_type = element_type;
+ m_size = size;
+ return lldb::eRefetch;
+}
+
+llvm::Expected<size_t> lldb_private::formatters::MsvcStlDequeSyntheticFrontEnd::
+ GetIndexOfChildWithName(ConstString name) {
+ if (!m_map)
+ return llvm::createStringError("Type has no child named '%s'",
+ name.AsCString());
+ if (auto optional_idx = ExtractIndexFromString(name.GetCString()))
+ return *optional_idx;
+
+ return llvm::createStringError("Type has no child named '%s'",
+ name.AsCString());
+}
+
+bool lldb_private::formatters::IsMsvcStlDeque(ValueObject &valobj) {
+ if (auto valobj_sp = valobj.GetNonSyntheticValue())
+ return valobj_sp->GetChildMemberWithName("_Mypair") != nullptr;
+ return false;
+}
+
+lldb_private::SyntheticChildrenFrontEnd *
+lldb_private::formatters::MsvcStlDequeSyntheticFrontEndCreator(
+ CXXSyntheticChildren *, lldb::ValueObjectSP valobj_sp) {
+ return new MsvcStlDequeSyntheticFrontEnd(valobj_sp);
+}
diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/deque/TestDataFormatterGenericDeque.py b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/deque/TestDataFormatterGenericDeque.py
index f52f6f7ec3ffb..2332eff7b10dd 100644
--- a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/deque/TestDataFormatterGenericDeque.py
+++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/deque/TestDataFormatterGenericDeque.py
@@ -3,9 +3,6 @@
from lldbsuite.test.lldbtest import *
from lldbsuite.test import lldbutil
-USE_LIBSTDCPP = "USE_LIBSTDCPP"
-USE_LIBCPP = "USE_LIBCPP"
-
class GenericDequeDataFormatterTestCase(TestBase):
def findVariable(self, name):
@@ -56,8 +53,7 @@ def check_numbers(self, var_name, show_ptr=False):
],
)
- def do_test(self, stdlib_type):
- self.build(dictionary={stdlib_type: "1"})
+ def do_test(self):
(_, process, _, bkpt) = lldbutil.run_to_source_breakpoint(
self, "break here", lldb.SBFileSpec("main.cpp")
)
@@ -135,15 +131,22 @@ def do_test(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(["libc++"])
def test_libcpp(self):
- self.do_test(USE_LIBCPP)
+ self.build(dictionary={"USE_LIBCPP": 1})
+ self.do_test()
+
+ @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()
- def do_test_ref_and_ptr(self, stdlib_type: str):
+ def do_test_ref_and_ptr(self):
"""Test formatting of std::deque& and std::deque*"""
- self.build(dictionary={stdlib_type: "1"})
(self.target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(
self, "stop here", lldb.SBFileSpec("main.cpp", False)
)
@@ -157,8 +160,15 @@ def do_test_ref_and_ptr(self, stdlib_type: str):
@add_test_categories(["libstdcxx"])
def test_libstdcpp_ref_and_ptr(self):
- self.do_test_ref_and_ptr(USE_LIBSTDCPP)
+ self.build(dictionary={"USE_LIBSTDCPP": 1})
+ self.do_test_ref_and_ptr()
@add_test_categories(["libc++"])
def test_libcpp_ref_and_ptr(self):
- self.do_test_ref_and_ptr(USE_LIBCPP)
+ self.build(dictionary={"USE_LIBCPP": 1})
+ self.do_test_ref_and_ptr()
+
+ @add_test_categories(["msvcstl"])
+ def test_msvcstl_ref_and_ptr(self):
+ self.build()
+ self.do_test_ref_and_ptr()
More information about the lldb-commits
mailing list