Skip to content

[LLDB] Add formatters for MSVC STL std::unique_ptr #148248

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

Merged
merged 1 commit into from
Jul 14, 2025

Conversation

Nerixyz
Copy link
Contributor

@Nerixyz Nerixyz commented Jul 11, 2025

This PR adds a summary and synthetic children for std::unique_ptr from MSVC's STL (NatVis).

As with libc++, the deleter is only shown if it's non-empty.

Tested both the shared_ptr and unique_ptr tests on Windows.
Towards #24834.

@llvmbot
Copy link
Member

llvmbot commented Jul 11, 2025

@llvm/pr-subscribers-lldb

Author: nerix (Nerixyz)

Changes

This PR adds a summary and synthetic children for std::unique_ptr from MSVC's STL (NatVis).

As with libc++, the deleter is only shown if it's non-empty.

Tested both the shared_ptr and unique_ptr tests on Windows.
Towards #24834.


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

4 Files Affected:

  • (modified) lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp (+27-4)
  • (modified) lldb/source/Plugins/Language/CPlusPlus/MsvcStl.h (+8)
  • (modified) lldb/source/Plugins/Language/CPlusPlus/MsvcStlSmartPointer.cpp (+115)
  • (modified) lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/unique_ptr/TestDataFormatterStdUniquePtr.py (+11)
diff --git a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
index 2db3e6f0ca315..9a869f3ea0289 100644
--- a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
+++ b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
@@ -1566,10 +1566,6 @@ static void LoadLibStdcppFormatters(lldb::TypeCategoryImplSP cpp_category_sp) {
       "std::optional synthetic child", "^std::optional<.+>(( )?&)?$",
       stl_deref_flags, true);
 
-  AddCXXSummary(cpp_category_sp,
-                lldb_private::formatters::LibStdcppUniquePointerSummaryProvider,
-                "libstdc++ std::unique_ptr summary provider",
-                "^std::unique_ptr<.+>(( )?&)?$", stl_summary_flags, true);
   AddCXXSummary(cpp_category_sp,
                 lldb_private::formatters::StdlibCoroutineHandleSummaryProvider,
                 "libstdc++ std::coroutine_handle summary provider",
@@ -1599,6 +1595,24 @@ GenericSmartPointerSummaryProvider(ValueObject &valobj, Stream &stream,
   return LibStdcppSmartPointerSummaryProvider(valobj, stream, options);
 }
 
+static lldb_private::SyntheticChildrenFrontEnd *
+GenericUniquePtrSyntheticFrontEndCreator(CXXSyntheticChildren *children,
+                                         lldb::ValueObjectSP valobj_sp) {
+  if (!valobj_sp)
+    return nullptr;
+
+  if (IsMsvcStlUniquePtr(*valobj_sp))
+    return MsvcStlUniquePtrSyntheticFrontEndCreator(valobj_sp);
+  return LibStdcppUniquePtrSyntheticFrontEndCreator(children, valobj_sp);
+}
+
+static bool GenericUniquePtrSummaryProvider(ValueObject &valobj, Stream &stream,
+                                            const TypeSummaryOptions &options) {
+  if (IsMsvcStlUniquePtr(valobj))
+    return MsvcStlUniquePtrSummaryProvider(valobj, stream, options);
+  return LibStdcppUniquePointerSummaryProvider(valobj, stream, options);
+}
+
 /// Load formatters that are formatting types from more than one STL
 static void LoadCommonStlFormatters(lldb::TypeCategoryImplSP cpp_category_sp) {
   if (!cpp_category_sp)
@@ -1642,12 +1656,18 @@ static void LoadCommonStlFormatters(lldb::TypeCategoryImplSP cpp_category_sp) {
           },
           "MSVC STL/libstdc++ std::wstring summary provider"));
 
+  stl_summary_flags.SetDontShowChildren(false);
+  stl_summary_flags.SetSkipPointers(false);
+
   AddCXXSynthetic(cpp_category_sp, GenericSmartPointerSyntheticFrontEndCreator,
                   "std::shared_ptr synthetic children",
                   "^std::shared_ptr<.+>(( )?&)?$", stl_synth_flags, true);
   AddCXXSynthetic(cpp_category_sp, GenericSmartPointerSyntheticFrontEndCreator,
                   "std::weak_ptr synthetic children",
                   "^std::weak_ptr<.+>(( )?&)?$", stl_synth_flags, true);
+  AddCXXSynthetic(cpp_category_sp, GenericUniquePtrSyntheticFrontEndCreator,
+                  "std::unique_ptr synthetic children",
+                  "^std::unique_ptr<.+>(( )?&)?$", stl_synth_flags, true);
 
   AddCXXSummary(cpp_category_sp, GenericSmartPointerSummaryProvider,
                 "MSVC STL/libstdc++ std::shared_ptr summary provider",
@@ -1655,6 +1675,9 @@ static void LoadCommonStlFormatters(lldb::TypeCategoryImplSP cpp_category_sp) {
   AddCXXSummary(cpp_category_sp, GenericSmartPointerSummaryProvider,
                 "MSVC STL/libstdc++ std::weak_ptr summary provider",
                 "^std::weak_ptr<.+>(( )?&)?$", stl_summary_flags, true);
+  AddCXXSummary(cpp_category_sp, GenericUniquePtrSummaryProvider,
+                "MSVC STL/libstdc++ std::unique_ptr summary provider",
+                "^std::unique_ptr<.+>(( )?&)?$", 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 edf3f4e8a5387..fe75bf275f8e2 100644
--- a/lldb/source/Plugins/Language/CPlusPlus/MsvcStl.h
+++ b/lldb/source/Plugins/Language/CPlusPlus/MsvcStl.h
@@ -37,6 +37,14 @@ bool MsvcStlSmartPointerSummaryProvider(ValueObject &valobj, Stream &stream,
 lldb_private::SyntheticChildrenFrontEnd *
 MsvcStlSmartPointerSyntheticFrontEndCreator(lldb::ValueObjectSP valobj_sp);
 
+// MSVC STL std::unique_ptr<>
+bool IsMsvcStlUniquePtr(ValueObject &valobj);
+bool MsvcStlUniquePtrSummaryProvider(ValueObject &valobj, Stream &stream,
+                                     const TypeSummaryOptions &options);
+
+lldb_private::SyntheticChildrenFrontEnd *
+MsvcStlUniquePtrSyntheticFrontEndCreator(lldb::ValueObjectSP valobj_sp);
+
 } // namespace formatters
 } // namespace lldb_private
 
diff --git a/lldb/source/Plugins/Language/CPlusPlus/MsvcStlSmartPointer.cpp b/lldb/source/Plugins/Language/CPlusPlus/MsvcStlSmartPointer.cpp
index b1aecc4b6611a..6f66540f3cba9 100644
--- a/lldb/source/Plugins/Language/CPlusPlus/MsvcStlSmartPointer.cpp
+++ b/lldb/source/Plugins/Language/CPlusPlus/MsvcStlSmartPointer.cpp
@@ -84,6 +84,23 @@ class MsvcStlSmartPointerSyntheticFrontEnd : public SyntheticChildrenFrontEnd {
   ValueObject *m_ptr_obj = nullptr;
 };
 
+class MsvcStlUniquePtrSyntheticFrontEnd : public SyntheticChildrenFrontEnd {
+public:
+  MsvcStlUniquePtrSyntheticFrontEnd(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:
+  lldb::ValueObjectSP m_value_ptr_sp;
+  lldb::ValueObjectSP m_deleter_sp;
+};
+
 } // namespace formatters
 } // namespace lldb_private
 
@@ -163,3 +180,101 @@ lldb_private::formatters::MsvcStlSmartPointerSyntheticFrontEndCreator(
     lldb::ValueObjectSP valobj_sp) {
   return new MsvcStlSmartPointerSyntheticFrontEnd(valobj_sp);
 }
+
+bool lldb_private::formatters::IsMsvcStlUniquePtr(ValueObject &valobj) {
+  if (auto valobj_sp = valobj.GetNonSyntheticValue())
+    return valobj_sp->GetChildMemberWithName("_Mypair") != nullptr;
+
+  return false;
+}
+
+bool lldb_private::formatters::MsvcStlUniquePtrSummaryProvider(
+    ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) {
+  ValueObjectSP valobj_sp(valobj.GetNonSyntheticValue());
+  if (!valobj_sp)
+    return false;
+
+  ValueObjectSP ptr_sp(valobj_sp->GetChildAtNamePath({"_Mypair", "_Myval2"}));
+  if (!ptr_sp)
+    return false;
+
+  DumpCxxSmartPtrPointerSummary(stream, *ptr_sp, options);
+
+  return true;
+}
+
+lldb_private::formatters::MsvcStlUniquePtrSyntheticFrontEnd::
+    MsvcStlUniquePtrSyntheticFrontEnd(lldb::ValueObjectSP valobj_sp)
+    : SyntheticChildrenFrontEnd(*valobj_sp) {
+  if (valobj_sp)
+    Update();
+}
+
+llvm::Expected<uint32_t> lldb_private::formatters::
+    MsvcStlUniquePtrSyntheticFrontEnd::CalculateNumChildren() {
+  if (m_value_ptr_sp)
+    return m_deleter_sp ? 2 : 1;
+  return 0;
+}
+
+lldb::ValueObjectSP
+lldb_private::formatters::MsvcStlUniquePtrSyntheticFrontEnd::GetChildAtIndex(
+    uint32_t idx) {
+  if (!m_value_ptr_sp)
+    return lldb::ValueObjectSP();
+
+  if (idx == 0)
+    return m_value_ptr_sp;
+
+  if (idx == 1)
+    return m_deleter_sp;
+
+  if (idx == 2) {
+    Status status;
+    auto value_sp = m_value_ptr_sp->Dereference(status);
+    if (status.Success()) {
+      return value_sp;
+    }
+  }
+
+  return lldb::ValueObjectSP();
+}
+
+lldb::ChildCacheState
+lldb_private::formatters::MsvcStlUniquePtrSyntheticFrontEnd::Update() {
+  ValueObjectSP valobj_sp = m_backend.GetSP();
+  if (!valobj_sp)
+    return lldb::ChildCacheState::eRefetch;
+
+  ValueObjectSP pair_sp = valobj_sp->GetChildMemberWithName("_Mypair");
+  if (!pair_sp)
+    return lldb::ChildCacheState::eRefetch;
+
+  if (auto value_ptr_sp = pair_sp->GetChildMemberWithName("_Myval2"))
+    m_value_ptr_sp = value_ptr_sp->Clone(ConstString("pointer"));
+
+  // Only present if the deleter is non-empty
+  if (auto deleter_sp = pair_sp->GetChildMemberWithName("_Myval1"))
+    m_deleter_sp = deleter_sp->Clone(ConstString("deleter"));
+
+  return lldb::ChildCacheState::eRefetch;
+}
+
+llvm::Expected<size_t>
+lldb_private::formatters::MsvcStlUniquePtrSyntheticFrontEnd::
+    GetIndexOfChildWithName(ConstString name) {
+  if (name == "pointer")
+    return 0;
+  if (name == "deleter")
+    return 1;
+  if (name == "obj" || name == "object" || name == "$$dereference$$")
+    return 2;
+  return llvm::createStringError("Type has no child named '%s'",
+                                 name.AsCString());
+}
+
+lldb_private::SyntheticChildrenFrontEnd *
+lldb_private::formatters::MsvcStlUniquePtrSyntheticFrontEndCreator(
+    lldb::ValueObjectSP valobj_sp) {
+  return new MsvcStlUniquePtrSyntheticFrontEnd(valobj_sp);
+}
diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/unique_ptr/TestDataFormatterStdUniquePtr.py b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/unique_ptr/TestDataFormatterStdUniquePtr.py
index 1b204c7d6ef02..0b68b1b532bb0 100644
--- a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/unique_ptr/TestDataFormatterStdUniquePtr.py
+++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/unique_ptr/TestDataFormatterStdUniquePtr.py
@@ -102,6 +102,12 @@ def test_libcxx(self):
         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_recursive_unique_ptr(self):
         # Tests that LLDB can handle when we have a loop in the unique_ptr
         # reference chain and that it correctly handles the different options
@@ -155,3 +161,8 @@ def test_recursive_unique_ptr_libstdcxx(self):
     def test_recursive_unique_ptr_libcxx(self):
         self.build(dictionary={"USE_LIBCPP": 1})
         self.do_test_recursive_unique_ptr()
+
+    @add_test_categories(["msvcstl"])
+    def test_recursive_unique_ptr_msvcstl(self):
+        self.build()
+        self.do_test_recursive_unique_ptr()

@Michael137 Michael137 merged commit da68e72 into llvm:main Jul 14, 2025
11 checks passed
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.

3 participants