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 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ add_lldb_library(lldbPluginCPlusPlusLanguage PLUGIN
LibStdcppTuple.cpp
LibStdcppUniquePointer.cpp
MsvcStl.cpp
MsvcStlSmartPointer.cpp
MSVCUndecoratedNameParser.cpp

LINK_COMPONENTS
Expand Down
52 changes: 34 additions & 18 deletions lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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",
Expand All @@ -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(
Expand All @@ -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)?

[](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) {
Expand Down
2 changes: 1 addition & 1 deletion lldb/source/Plugins/Language/CPlusPlus/Generic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.


return ptr.Cast(arg.GetPointerType());
}
8 changes: 8 additions & 0 deletions lldb/source/Plugins/Language/CPlusPlus/MsvcStl.h
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
170 changes: 170 additions & 0 deletions lldb/source/Plugins/Language/CPlusPlus/MsvcStlSmartPointer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
//===-- 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;
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;

?

}

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);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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={})
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()

self.do_test()
Loading