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

Merged
merged 1 commit into from
Jul 23, 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 @@ -35,6 +35,7 @@ add_lldb_library(lldbPluginCPlusPlusLanguage PLUGIN
LibStdcppUniquePointer.cpp
MsvcStl.cpp
MsvcStlAtomic.cpp
MsvcStlDeque.cpp
MsvcStlSmartPointer.cpp
MsvcStlTree.cpp
MsvcStlTuple.cpp
Expand Down
28 changes: 23 additions & 5 deletions lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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")));
Expand Down Expand Up @@ -1472,10 +1472,9 @@ static void LoadLibStdcppFormatters(lldb::TypeCategoryImplSP cpp_category_sp) {
"libstdc++ debug 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,
Expand Down Expand Up @@ -1684,6 +1683,18 @@ GenericMapLikeSyntheticFrontEndCreator(CXXSyntheticChildren *children,
"lldb.formatters.cpp.gnu_libstdcpp.StdMapLikeSynthProvider", *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)
Expand Down Expand Up @@ -1761,6 +1772,10 @@ static void LoadCommonStlFormatters(lldb::TypeCategoryImplSP cpp_category_sp) {
AddCXXSynthetic(cpp_category_sp, GenericOptionalSyntheticFrontEndCreator,
"std::optional synthetic children",
"^std::optional<.+>(( )?&)?$", stl_deref_flags, true);
AddCXXSynthetic(cpp_category_sp, GenericDequeSyntheticFrontEndCreator,
"std::deque container synthetic children",
"^std::deque<.+>(( )?&)?$", stl_deref_flags, true);

AddCXXSynthetic(cpp_category_sp, GenericMapLikeSyntheticFrontEndCreator,
"std::(multi)?map/set synthetic children",
"^std::(multi)?(map|set)<.+>(( )?&)?$", stl_synth_flags,
Expand Down Expand Up @@ -1806,6 +1821,9 @@ static void LoadCommonStlFormatters(lldb::TypeCategoryImplSP cpp_category_sp) {
"MSVC STL/libstdc++ std::(multi)?map/set summary provider",
"^std::(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) {
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 @@ -103,6 +103,12 @@ bool IsMsvcStlMapLike(ValueObject &valobj);
lldb_private::SyntheticChildrenFrontEnd *
MsvcStlMapLikeSyntheticFrontEndCreator(lldb::ValueObjectSP valobj_sp);

// MSVC STL std::deque<>
bool IsMsvcStlDeque(ValueObject &valobj);
SyntheticChildrenFrontEnd *
MsvcStlDequeSyntheticFrontEndCreator(CXXSyntheticChildren *,
lldb::ValueObjectSP valobj_sp);

} // namespace formatters
} // namespace lldb_private

Expand Down
174 changes: 174 additions & 0 deletions lldb/source/Plugins/Language/CPlusPlus/MsvcStlDeque.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
//===-- 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();
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);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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")
)
Expand Down Expand Up @@ -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)
)
Expand All @@ -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()
Loading