Skip to content

[LLDB] Add formatters for MSVC STL std::deque #150097

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

Nerixyz
Copy link
Contributor

@Nerixyz Nerixyz commented Jul 22, 2025

This PR adds synthetic children for std::deque from MSVC's STL.

Similar to libstdc++ and libc++, the elements are in a T**, so we need to "subscript" twice. The NatVis for deque uses _EEN_DS which contains the block size. We can't access this, but we can access the constexpr _Block_size.

Towards #24834.

@Nerixyz Nerixyz requested a review from JDevlieghere as a code owner July 22, 2025 19:45
@llvmbot llvmbot added the lldb label Jul 22, 2025
@llvmbot
Copy link
Member

llvmbot commented Jul 22, 2025

@llvm/pr-subscribers-lldb

Author: nerix (Nerixyz)

Changes

This PR adds synthetic children for std::deque from MSVC's STL.

Similar to libstdc++ and libc++, the elements are in a T**, so we need to "subscript" twice. The NatVis for deque uses _EEN_DS which contains the block size. We can't access this, but we can access the constexpr _Block_size.

Towards #24834.


Full diff: https://github.com/llvm/llvm-project/pull/150097.diff

5 Files Affected:

  • (modified) lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt (+1)
  • (modified) lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp (+22-5)
  • (modified) lldb/source/Plugins/Language/CPlusPlus/MsvcStl.h (+6)
  • (added) lldb/source/Plugins/Language/CPlusPlus/MsvcStlDeque.cpp (+176)
  • (modified) lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/deque/TestDataFormatterGenericDeque.py (+21-11)
diff --git a/lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt b/lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt
index ab9d991fd48f7..81ad7afe4e305 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
   MsvcStlTuple.cpp
   MsvcStlUnordered.cpp
diff --git a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
index 80dc4609f9b66..c749bf682391a 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++ 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,
@@ -1672,6 +1671,18 @@ GenericUnorderedSyntheticFrontEndCreator(CXXSyntheticChildren *children,
       *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)
@@ -1743,6 +1754,9 @@ static void LoadCommonStlFormatters(lldb::TypeCategoryImplSP cpp_category_sp) {
                   "std::unordered container synthetic children",
                   "^std::unordered_(multi)?(map|set)<.+> ?>$", stl_synth_flags,
                   true);
+  AddCXXSynthetic(cpp_category_sp, GenericDequeSyntheticFrontEndCreator,
+                  "std::deque container synthetic children",
+                  "^std::deque<.+>(( )?&)?$", stl_synth_flags, true);
 
   SyntheticChildren::Flags stl_deref_flags = stl_synth_flags;
   stl_deref_flags.SetFrontEndWantsDereference();
@@ -1786,6 +1800,9 @@ static void LoadCommonStlFormatters(lldb::TypeCategoryImplSP cpp_category_sp) {
                 "MSVC STL/libstdc++ std unordered container summary provider",
                 "^std::unordered_(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 e2a015a537868..f93d03d2a6230 100644
--- a/lldb/source/Plugins/Language/CPlusPlus/MsvcStl.h
+++ b/lldb/source/Plugins/Language/CPlusPlus/MsvcStl.h
@@ -92,6 +92,12 @@ SyntheticChildrenFrontEnd *
 MsvcStlUnorderedSyntheticFrontEndCreator(CXXSyntheticChildren *,
                                          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..08467a8ae4eb6
--- /dev/null
+++ b/lldb/source/Plugins/Language/CPlusPlus/MsvcStlDeque.cpp
@@ -0,0 +1,176 @@
+//===-- 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().GetNonReferenceType();
+  if (deque_type.IsPointerType())
+    deque_type = deque_type.GetPointeeType();
+  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()

Copy link

github-actions bot commented Jul 22, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

@Nerixyz Nerixyz force-pushed the feat/lldb-ms-deque branch from 0c9798c to 4f7b6c7 Compare July 22, 2025 21:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants