Skip to content

[LLDB] Add formatters for MSVC STL unordered containers #149519

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 3 commits into from
Jul 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
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 @@ -37,6 +37,7 @@ add_lldb_library(lldbPluginCPlusPlusLanguage PLUGIN
MsvcStlAtomic.cpp
MsvcStlSmartPointer.cpp
MsvcStlTuple.cpp
MsvcStlUnordered.cpp
MsvcStlVariant.cpp
MsvcStlVector.cpp
MSVCUndecoratedNameParser.cpp
Expand Down
28 changes: 24 additions & 4 deletions lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1434,8 +1434,7 @@ static void LoadLibStdcppFormatters(lldb::TypeCategoryImplSP cpp_category_sp) {
stl_deref_flags,
"lldb.formatters.cpp.gnu_libstdcpp.StdMapLikeSynthProvider")));
cpp_category_sp->AddTypeSynthetic(
"^std::(__debug::)?unordered_(multi)?(map|set)<.+> >$",
eFormatterMatchRegex,
"^std::__debug::unordered_(multi)?(map|set)<.+> >$", eFormatterMatchRegex,
SyntheticChildrenSP(new ScriptedSyntheticChildren(
stl_deref_flags,
"lldb.formatters.cpp.gnu_libstdcpp.StdUnorderedMapSynthProvider")));
Expand Down Expand Up @@ -1490,8 +1489,8 @@ static void LoadLibStdcppFormatters(lldb::TypeCategoryImplSP cpp_category_sp) {

AddCXXSummary(cpp_category_sp,
lldb_private::formatters::ContainerSizeSummaryProvider,
"libstdc++ std unordered container summary provider",
"^std::(__debug::)?unordered_(multi)?(map|set)<.+> >$",
"libstdc++ debug std unordered container summary provider",
"^std::__debug::unordered_(multi)?(map|set)<.+> >$",
stl_summary_flags, true);

AddCXXSummary(
Expand Down Expand Up @@ -1660,6 +1659,19 @@ static bool GenericVariantSummaryProvider(ValueObject &valobj, Stream &stream,
return LibStdcppVariantSummaryProvider(valobj, stream, options);
}

static SyntheticChildrenFrontEnd *
GenericUnorderedSyntheticFrontEndCreator(CXXSyntheticChildren *children,
ValueObjectSP valobj_sp) {
if (!valobj_sp)
return nullptr;

if (IsMsvcStlUnordered(*valobj_sp))
return MsvcStlUnorderedSyntheticFrontEndCreator(children, valobj_sp);
return new ScriptedSyntheticChildren::FrontEnd(
"lldb.formatters.cpp.gnu_libstdcpp.StdUnorderedMapSynthProvider",
*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)
Expand Down Expand Up @@ -1727,6 +1739,10 @@ static void LoadCommonStlFormatters(lldb::TypeCategoryImplSP cpp_category_sp) {
AddCXXSynthetic(cpp_category_sp, GenericVariantSyntheticFrontEndCreator,
"std::variant synthetic children", "^std::variant<.*>$",
stl_synth_flags, true);
AddCXXSynthetic(cpp_category_sp, GenericUnorderedSyntheticFrontEndCreator,
"std::unordered container synthetic children",
"^std::unordered_(multi)?(map|set)<.+> ?>$", stl_synth_flags,
true);

SyntheticChildren::Flags stl_deref_flags = stl_synth_flags;
stl_deref_flags.SetFrontEndWantsDereference();
Expand Down Expand Up @@ -1766,6 +1782,10 @@ static void LoadCommonStlFormatters(lldb::TypeCategoryImplSP cpp_category_sp) {
AddCXXSummary(cpp_category_sp, GenericVariantSummaryProvider,
"MSVC STL/libstdc++ std::variant summary provider",
"^std::variant<.*>$", stl_summary_flags, true);
AddCXXSummary(cpp_category_sp, ContainerSizeSummaryProvider,
"MSVC STL/libstdc++ std unordered container summary provider",
"^std::unordered_(multi)?(map|set)<.+> ?>$", stl_summary_flags,
true);
}

static void LoadMsvcStlFormatters(lldb::TypeCategoryImplSP cpp_category_sp) {
Expand Down
6 changes: 6 additions & 0 deletions lldb/source/Plugins/Language/CPlusPlus/MsvcStl.h
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,12 @@ SyntheticChildrenFrontEnd *
MsvcStlAtomicSyntheticFrontEndCreator(CXXSyntheticChildren *,
lldb::ValueObjectSP valobj_sp);

// MSVC STL std::unordered_(multi){map|set}<>
bool IsMsvcStlUnordered(ValueObject &valobj);
SyntheticChildrenFrontEnd *
MsvcStlUnorderedSyntheticFrontEndCreator(CXXSyntheticChildren *,
lldb::ValueObjectSP valobj_sp);

} // namespace formatters
} // namespace lldb_private

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

using namespace lldb;
using namespace lldb_private;

namespace {

class UnorderedFrontEnd : public SyntheticChildrenFrontEnd {
public:
UnorderedFrontEnd(ValueObject &valobj) : SyntheticChildrenFrontEnd(valobj) {
Update();
}

llvm::Expected<size_t> GetIndexOfChildWithName(ConstString name) override {
if (!m_list_sp)
return llvm::createStringError("Missing _List");
return m_list_sp->GetIndexOfChildWithName(name);
}

lldb::ChildCacheState Update() override;

llvm::Expected<uint32_t> CalculateNumChildren() override {
if (!m_list_sp)
return llvm::createStringError("Missing _List");
return m_list_sp->GetNumChildren();
}

ValueObjectSP GetChildAtIndex(uint32_t idx) override {
if (!m_list_sp)
return nullptr;
return m_list_sp->GetChildAtIndex(idx);
}

private:
ValueObjectSP m_list_sp;
};

} // namespace

lldb::ChildCacheState UnorderedFrontEnd::Update() {
m_list_sp = nullptr;
ValueObjectSP list_sp = m_backend.GetChildMemberWithName("_List");
if (!list_sp)
return lldb::ChildCacheState::eRefetch;
m_list_sp = list_sp->GetSyntheticValue();
return lldb::ChildCacheState::eRefetch;
}

bool formatters::IsMsvcStlUnordered(ValueObject &valobj) {
if (auto valobj_sp = valobj.GetNonSyntheticValue())
return valobj_sp->GetChildMemberWithName("_List") != nullptr;
return false;
}

SyntheticChildrenFrontEnd *formatters::MsvcStlUnorderedSyntheticFrontEndCreator(
CXXSyntheticChildren *, lldb::ValueObjectSP valobj_sp) {
if (valobj_sp)
return new UnorderedFrontEnd(*valobj_sp);
return nullptr;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,13 @@
from lldbsuite.test.lldbtest import *
from lldbsuite.test import lldbutil

USE_LIBSTDCPP = "USE_LIBSTDCPP"
USE_LIBCPP = "USE_LIBCPP"


class GenericUnorderedDataFormatterTestCase(TestBase):
def setUp(self):
TestBase.setUp(self)
self.namespace = "std"

def do_test_with_run_command(self, stdlib_type):
self.build(dictionary={stdlib_type: "1"})
def do_test_with_run_command(self):
self.runCmd("file " + self.getBuildArtifact("a.out"), CURRENT_EXECUTABLE_SET)

lldbutil.run_break_set_by_source_regexp(self, "Set break point at this line.")
Expand Down Expand Up @@ -127,8 +123,23 @@ def look_for_content_and_continue(self, var_name, patterns):

@add_test_categories(["libstdcxx"])
def test_with_run_command_libstdcpp(self):
self.do_test_with_run_command(USE_LIBSTDCPP)
self.build(dictionary={"USE_LIBSTDCPP": 1})
self.do_test_with_run_command()

@add_test_categories(["libstdcxx"])
def test_with_run_command_libstdcxx_debug(self):
self.build(
dictionary={"USE_LIBSTDCPP": 1, "CXXFLAGS_EXTRAS": "-D_GLIBCXX_DEBUG"}
)
self.do_test_with_run_command()

@add_test_categories(["libc++"])
def test_with_run_command_libcpp(self):
self.do_test_with_run_command(USE_LIBCPP)
self.build(dictionary={"USE_LIBCPP": 1})
self.do_test_with_run_command()
Copy link
Member

Choose a reason for hiding this comment

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

Huh looks like we're not actually testing the __debug libstdc++ version. Probably because we don't have support for it anyway. Might be nice to add an XFAILed test for it here.

Ofc not necessary as part of this PR

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Looks like this just works.


@add_test_categories(["msvcstl"])
def test_with_run_command_msvcstl(self):
# No flags, because the "msvcstl" category checks that the MSVC STL is used by default.
self.build()
self.do_test_with_run_command()
Loading