Skip to content

[LLDB] Add formatters for MSVC STL std::shared_ptr #147575

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 8, 2025

This PR adds formatters for std::shared_ptr and std::weak_ptr. They are similar to the ones from libc++ and libstdc++.
Section from MSVC STL NatVis.

To support debugging with PDB debug info, I had to add an early exit in GetDesugaredSmartPointerValue, because with PDB, LLDB doesn't know about template types. This isn't an issue here, since the typedef type is already resolved there, so no casting is needed.

The tests don't check for PDB - maybe this should be changed? I don't know a good way to do this. PDB has the downside that it resolves typedefs. Here in particular, the test for element_type would need to be replaced with User and std::string with std::basic_string<char,std::char_traits<char>,std::allocator<char> >.

Towards #24834.

@llvmbot
Copy link
Member

llvmbot commented Jul 8, 2025

@llvm/pr-subscribers-lldb

Author: nerix (Nerixyz)

Changes

This PR adds formatters for std::shared_ptr and std::weak_ptr. They are similar to the ones from libc++ and libstdc++.
Section from MSVC STL NatVis.

To support debugging with PDB debug info, I had to add an early exit in GetDesugaredSmartPointerValue, because with PDB, LLDB doesn't know about template types. This isn't an issue here, since the typedef type is already resolved there, so no casting is needed.

The tests don't check for PDB - maybe this should be changed? I don't know a good way to do this. PDB has the downside that it resolves typedefs. Here in particular, the test for element_type would need to be replaced with User and std::string with std::basic_string&lt;char,std::char_traits&lt;char&gt;,std::allocator&lt;char&gt; &gt;.

Towards #24834.


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

6 Files Affected:

  • (modified) lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt (+1)
  • (modified) lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp (+34-18)
  • (modified) lldb/source/Plugins/Language/CPlusPlus/Generic.cpp (+1-1)
  • (modified) lldb/source/Plugins/Language/CPlusPlus/MsvcStl.h (+8)
  • (added) lldb/source/Plugins/Language/CPlusPlus/MsvcStlSmartPointer.cpp (+169)
  • (modified) lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/shared_ptr/TestDataFormatterStdSharedPtr.py (+6)
diff --git a/lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt b/lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt
index 3ec3cad4b8178..296159ea28407 100644
--- a/lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt
+++ b/lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt
@@ -34,6 +34,7 @@ add_lldb_library(lldbPluginCPlusPlusLanguage PLUGIN
   LibStdcppTuple.cpp
   LibStdcppUniquePointer.cpp
   MsvcStl.cpp
+  MsvcStlSmartPointer.cpp
   MSVCUndecoratedNameParser.cpp
 
   LINK_COMPONENTS
diff --git a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
index 17963c0273ba8..345c03bd019e8 100644
--- a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
+++ b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
@@ -1540,16 +1540,6 @@ static void LoadLibStdcppFormatters(lldb::TypeCategoryImplSP cpp_category_sp) {
       lldb_private::formatters::LibStdcppUniquePtrSyntheticFrontEndCreator,
       "std::unique_ptr synthetic children", "^std::unique_ptr<.+>(( )?&)?$",
       stl_synth_flags, true);
-  AddCXXSynthetic(
-      cpp_category_sp,
-      lldb_private::formatters::LibStdcppSharedPtrSyntheticFrontEndCreator,
-      "std::shared_ptr synthetic children", "^std::shared_ptr<.+>(( )?&)?$",
-      stl_synth_flags, true);
-  AddCXXSynthetic(
-      cpp_category_sp,
-      lldb_private::formatters::LibStdcppSharedPtrSyntheticFrontEndCreator,
-      "std::weak_ptr synthetic children", "^std::weak_ptr<.+>(( )?&)?$",
-      stl_synth_flags, true);
   AddCXXSynthetic(
       cpp_category_sp,
       lldb_private::formatters::LibStdcppTupleSyntheticFrontEndCreator,
@@ -1580,14 +1570,6 @@ static void LoadLibStdcppFormatters(lldb::TypeCategoryImplSP 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::LibStdcppSmartPointerSummaryProvider,
-                "libstdc++ std::shared_ptr summary provider",
-                "^std::shared_ptr<.+>(( )?&)?$", stl_summary_flags, true);
-  AddCXXSummary(cpp_category_sp,
-                lldb_private::formatters::LibStdcppSmartPointerSummaryProvider,
-                "libstdc++ std::weak_ptr summary provider",
-                "^std::weak_ptr<.+>(( )?&)?$", stl_summary_flags, true);
   AddCXXSummary(cpp_category_sp,
                 lldb_private::formatters::StdlibCoroutineHandleSummaryProvider,
                 "libstdc++ std::coroutine_handle summary provider",
@@ -1611,6 +1593,10 @@ static void LoadCommonStlFormatters(lldb::TypeCategoryImplSP cpp_category_sp) {
       .SetDontShowValue(false)
       .SetShowMembersOneLiner(false)
       .SetHideItemNames(false);
+  SyntheticChildren::Flags stl_synth_flags;
+  stl_synth_flags.SetCascades(true).SetSkipPointers(false).SetSkipReferences(
+      false);
+
   using StringElementType = StringPrinter::StringElementType;
 
   RegisterStdStringSummaryProvider(
@@ -1636,6 +1622,36 @@ static void LoadCommonStlFormatters(lldb::TypeCategoryImplSP cpp_category_sp) {
             return LibStdcppStringSummaryProvider(valobj, stream, options);
           },
           "MSVC STL/libstdc++ std::wstring summary provider"));
+
+  auto smart_ptr_creator =
+      [](CXXSyntheticChildren *children,
+         ValueObjectSP valobj_sp) -> SyntheticChildrenFrontEnd * {
+    if (!valobj_sp)
+      return nullptr;
+
+    if (auto *msvc = MsvcStlSmartPointerSyntheticFrontEndCreator(valobj_sp))
+      return msvc;
+
+    return LibStdcppSharedPtrSyntheticFrontEndCreator(children, valobj_sp);
+  };
+  AddCXXSynthetic(cpp_category_sp, smart_ptr_creator,
+                  "std::shared_ptr synthetic children",
+                  "^std::shared_ptr<.+>(( )?&)?$", stl_synth_flags, true);
+  AddCXXSynthetic(cpp_category_sp, smart_ptr_creator,
+                  "std::weak_ptr synthetic children",
+                  "^std::weak_ptr<.+>(( )?&)?$", stl_synth_flags, true);
+
+  auto smart_ptr_summary = [](ValueObject &valobj, Stream &stream,
+                              const TypeSummaryOptions &options) {
+    return MsvcStlSmartPointerSummaryProvider(valobj, stream, options) ||
+           LibStdcppSmartPointerSummaryProvider(valobj, stream, options);
+  };
+  AddCXXSummary(cpp_category_sp, smart_ptr_summary,
+                "MSVC STL/libstdc++ std::shared_ptr summary provider",
+                "^std::shared_ptr<.+>(( )?&)?$", stl_summary_flags, true);
+  AddCXXSummary(cpp_category_sp, smart_ptr_summary,
+                "MSVC STL/libstdc++ std::weak_ptr summary provider",
+                "^std::weak_ptr<.+>(( )?&)?$", stl_summary_flags, true);
 }
 
 static void LoadMsvcStlFormatters(lldb::TypeCategoryImplSP cpp_category_sp) {
diff --git a/lldb/source/Plugins/Language/CPlusPlus/Generic.cpp b/lldb/source/Plugins/Language/CPlusPlus/Generic.cpp
index b237a8a27090c..a6f8645fa41a1 100644
--- a/lldb/source/Plugins/Language/CPlusPlus/Generic.cpp
+++ b/lldb/source/Plugins/Language/CPlusPlus/Generic.cpp
@@ -16,7 +16,7 @@ lldb::ValueObjectSP lldb_private::formatters::GetDesugaredSmartPointerValue(
 
   auto arg = container_type.GetTypeTemplateArgument(0);
   if (!arg)
-    return nullptr;
+    return ptr.GetSP(); // FIXME: PDB doesn't have info about template arguments
 
   return ptr.Cast(arg.GetPointerType());
 }
diff --git a/lldb/source/Plugins/Language/CPlusPlus/MsvcStl.h b/lldb/source/Plugins/Language/CPlusPlus/MsvcStl.h
index e4ed923033aa7..edf3f4e8a5387 100644
--- a/lldb/source/Plugins/Language/CPlusPlus/MsvcStl.h
+++ b/lldb/source/Plugins/Language/CPlusPlus/MsvcStl.h
@@ -29,6 +29,14 @@ bool MsvcStlWStringSummaryProvider(
     ValueObject &valobj, Stream &stream,
     const TypeSummaryOptions &options); // VC 2015+ std::wstring
 
+// MSVC STL std::shared_ptr<> and std::weak_ptr<>
+bool IsMsvcStlSmartPointer(ValueObject &valobj);
+bool MsvcStlSmartPointerSummaryProvider(ValueObject &valobj, Stream &stream,
+                                        const TypeSummaryOptions &options);
+
+lldb_private::SyntheticChildrenFrontEnd *
+MsvcStlSmartPointerSyntheticFrontEndCreator(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
new file mode 100644
index 0000000000000..0dc35be768ad4
--- /dev/null
+++ b/lldb/source/Plugins/Language/CPlusPlus/MsvcStlSmartPointer.cpp
@@ -0,0 +1,169 @@
+//===-- MsvcStlSmartPointer.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 "Generic.h"
+#include "MsvcStl.h"
+
+#include "lldb/DataFormatters/FormattersHelpers.h"
+#include "lldb/DataFormatters/TypeSynthetic.h"
+
+using namespace lldb;
+
+bool lldb_private::formatters::IsMsvcStlSmartPointer(ValueObject &valobj) {
+  std::vector<uint32_t> indexes;
+  return valobj.GetCompilerType().GetIndexOfChildMemberWithName("_Ptr", true,
+                                                                indexes) > 0;
+}
+
+bool lldb_private::formatters::MsvcStlSmartPointerSummaryProvider(
+    ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) {
+  ValueObjectSP valobj_sp(valobj.GetNonSyntheticValue());
+  if (!valobj_sp)
+    return false;
+
+  ValueObjectSP ptr_sp(valobj_sp->GetChildMemberWithName("_Ptr"));
+  ValueObjectSP ctrl_sp(valobj_sp->GetChildMemberWithName("_Rep"));
+  if (!ctrl_sp || !ptr_sp)
+    return false;
+
+  DumpCxxSmartPtrPointerSummary(stream, *ptr_sp, options);
+
+  bool success;
+  uint64_t ctrl_addr = ctrl_sp->GetValueAsUnsigned(0, &success);
+  // Empty control field (expired)
+  if (!success || ctrl_addr == 0)
+    return true;
+
+  uint64_t uses = 0;
+  if (auto uses_sp = ctrl_sp->GetChildMemberWithName("_Uses")) {
+    bool success;
+    uses = uses_sp->GetValueAsUnsigned(0, &success);
+    if (!success)
+      return false;
+
+    stream.Printf(" strong=%" PRIu64, uses);
+  }
+
+  // _Weaks is the number of weak references - (_Uses != 0).
+  if (auto weak_count_sp = ctrl_sp->GetChildMemberWithName("_Weaks")) {
+    bool success;
+    uint64_t count = weak_count_sp->GetValueAsUnsigned(0, &success);
+    if (!success)
+      return false;
+
+    stream.Printf(" weak=%" PRIu64, count - (uses != 0));
+  }
+
+  return true;
+}
+
+namespace lldb_private {
+namespace formatters {
+
+class MsvcStlSmartPointerSyntheticFrontEnd : public SyntheticChildrenFrontEnd {
+public:
+  MsvcStlSmartPointerSyntheticFrontEnd(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;
+
+  ~MsvcStlSmartPointerSyntheticFrontEnd() override;
+
+private:
+  ValueObject *m_ptr_obj = nullptr;
+};
+
+} // namespace formatters
+} // namespace lldb_private
+
+lldb_private::formatters::MsvcStlSmartPointerSyntheticFrontEnd::
+    MsvcStlSmartPointerSyntheticFrontEnd(lldb::ValueObjectSP valobj_sp)
+    : SyntheticChildrenFrontEnd(*valobj_sp) {
+  if (valobj_sp)
+    Update();
+}
+
+llvm::Expected<uint32_t> lldb_private::formatters::
+    MsvcStlSmartPointerSyntheticFrontEnd::CalculateNumChildren() {
+  return (m_ptr_obj ? 1 : 0);
+}
+
+lldb::ValueObjectSP
+lldb_private::formatters::MsvcStlSmartPointerSyntheticFrontEnd::GetChildAtIndex(
+    uint32_t idx) {
+  if (!m_ptr_obj)
+    return lldb::ValueObjectSP();
+
+  ValueObjectSP valobj_sp = m_backend.GetSP();
+  if (!valobj_sp)
+    return lldb::ValueObjectSP();
+
+  if (idx == 0)
+    return m_ptr_obj->GetSP();
+
+  if (idx == 1) {
+    Status status;
+    ValueObjectSP value_sp = m_ptr_obj->Dereference(status);
+    if (status.Success())
+      return value_sp;
+  }
+
+  return lldb::ValueObjectSP();
+}
+
+lldb::ChildCacheState
+lldb_private::formatters::MsvcStlSmartPointerSyntheticFrontEnd::Update() {
+  m_ptr_obj = nullptr;
+
+  ValueObjectSP valobj_sp = m_backend.GetSP();
+  if (!valobj_sp)
+    return lldb::ChildCacheState::eRefetch;
+
+  auto ptr_obj_sp = valobj_sp->GetChildMemberWithName("_Ptr");
+  if (!ptr_obj_sp)
+    return lldb::ChildCacheState::eRefetch;
+
+  auto cast_ptr_sp = GetDesugaredSmartPointerValue(*ptr_obj_sp, *valobj_sp);
+  if (!cast_ptr_sp)
+    return lldb::ChildCacheState::eRefetch;
+
+  m_ptr_obj = cast_ptr_sp->Clone(ConstString("pointer")).get();
+  return lldb::ChildCacheState::eRefetch;
+}
+
+llvm::Expected<size_t>
+lldb_private::formatters::MsvcStlSmartPointerSyntheticFrontEnd::
+    GetIndexOfChildWithName(ConstString name) {
+  if (name == "_Ptr" || name == "pointer")
+    return 0;
+
+  if (name == "object" || name == "$$dereference$$")
+    return 1;
+
+  return llvm::createStringError("Type has no child named '%s'",
+                                 name.AsCString());
+}
+
+lldb_private::formatters::MsvcStlSmartPointerSyntheticFrontEnd::
+    ~MsvcStlSmartPointerSyntheticFrontEnd() = default;
+
+lldb_private::SyntheticChildrenFrontEnd *
+lldb_private::formatters::MsvcStlSmartPointerSyntheticFrontEndCreator(lldb::ValueObjectSP valobj_sp) {
+  if (!valobj_sp)
+    return nullptr;
+
+  if (!IsMsvcStlSmartPointer(*valobj_sp))
+    return nullptr;
+
+  return new MsvcStlSmartPointerSyntheticFrontEnd(valobj_sp);
+}
diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/shared_ptr/TestDataFormatterStdSharedPtr.py b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/shared_ptr/TestDataFormatterStdSharedPtr.py
index 3d8569da0332e..b52227b3611ab 100644
--- a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/shared_ptr/TestDataFormatterStdSharedPtr.py
+++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/shared_ptr/TestDataFormatterStdSharedPtr.py
@@ -118,3 +118,9 @@ def test_libcxx(self):
     def test_libstdcxx(self):
         self.build(dictionary={"USE_LIBSTDCPP": 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(dictionary={})
+        self.do_test()

Copy link

github-actions bot commented Jul 8, 2025

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

@Nerixyz Nerixyz force-pushed the feat/ms-smart-ptr branch from ad7fbd6 to 8894b65 Compare July 8, 2025 17:34
Comment on lines +124 to +125
# No flags, because the "msvcstl" category checks that the MSVC STL is used by default.
self.build(dictionary={})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# No flags, because the "msvcstl" category checks that the MSVC STL is used by default.
self.build(dictionary={})
# No flags, because the "msvcstl" category checks that the MSVC STL is used by default.
self.build()

@@ -16,7 +16,7 @@ lldb::ValueObjectSP lldb_private::formatters::GetDesugaredSmartPointerValue(

auto arg = container_type.GetTypeTemplateArgument(0);
if (!arg)
return nullptr;
return ptr.GetSP(); // FIXME: PDB doesn't have info about template arguments
Copy link
Member

@Michael137 Michael137 Jul 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of adding a workaround here can we just not call this at all in MsvcStlSmartPointer.cpp? And add a FIXME there

Copy link
Member

@Michael137 Michael137 Jul 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah but this only is a problem with PDB. So for MSVC STL+DWARF this would pass (which I guess is what the bots are running?).

I don't think we need the FIXME in that case tbh. I think it's reasonable to say that if we don't have enough debug-info to look at the template argument, then let the pointer type be whatever we got from debug-info. Worth adding a comment about that though.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you confirm what configuration the bots are running though? Are we testing DWARF and PDB somewhere? Or just one of them?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you confirm what configuration the bots are running though?

The API tests always run with DWARF: Makefile.rules. That's why it's a bit more complicated to add a PDB setting there - some tests would break with PDB.

Are we testing DWARF and PDB somewhere?

Not in API tests. The shell tests do use PDB (when passing --compiler=clang-cl), because /Z7 is passed in build.py. msstl_smoke.cpp does this, for example.

@@ -1636,6 +1622,36 @@ static void LoadCommonStlFormatters(lldb::TypeCategoryImplSP cpp_category_sp) {
return LibStdcppStringSummaryProvider(valobj, stream, options);
},
"MSVC STL/libstdc++ std::wstring summary provider"));

auto smart_ptr_creator =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These registrations are getting a bit noisy in my opinion. Could we move all this to a separate helper called RegisterCommonStlSmartPtrFormatters (or something like that?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will be a problem for the other types as well. What about having a Common{Type}SyntheticFrontEndCreator and Common{Type}SummaryProvider in Generic.h (or maybe a new header)?

Comment on lines +18 to +20
std::vector<uint32_t> indexes;
return valobj.GetCompilerType().GetIndexOfChildMemberWithName("_Ptr", true,
indexes) > 0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
std::vector<uint32_t> indexes;
return valobj.GetCompilerType().GetIndexOfChildMemberWithName("_Ptr", true,
indexes) > 0;
return valobj.GetChildMemberWithName("_Ptr") != nullptr;

?

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