[Lldb-commits] [lldb] [lldb] Format `atomic<shared_ptr>` on MSVC STL (PR #237631)
via lldb-commits
lldb-commits at lists.llvm.org
Mon Nov 2 01:37:31 PST 2026
https://github.com/Nerixyz updated https://github.com/llvm/llvm-project/pull/237631
>From 409b7bd8f0421cf34316d45b7fe77d0c388ea4da Mon Sep 17 00:00:00 2001
From: Nerixyz <nerixdev at outlook.de>
Date: Sat, 31 Oct 2026 21:24:13 +0100
Subject: [PATCH] [lldb] Format `atomic<shared_ptr>` on MSVC STL
---
.../Plugins/Language/CPlusPlus/MsvcStl.h | 6 +-
.../Language/CPlusPlus/MsvcStlAtomic.cpp | 178 ++++++++++++++----
.../CPlusPlus/MsvcStlSmartPointer.cpp | 19 +-
.../atomic/TestDataFormatterStdAtomic.py | 20 +-
4 files changed, 171 insertions(+), 52 deletions(-)
diff --git a/lldb/source/Plugins/Language/CPlusPlus/MsvcStl.h b/lldb/source/Plugins/Language/CPlusPlus/MsvcStl.h
index 4ec992b9ed21db..5c1325afdb5178 100644
--- a/lldb/source/Plugins/Language/CPlusPlus/MsvcStl.h
+++ b/lldb/source/Plugins/Language/CPlusPlus/MsvcStl.h
@@ -60,6 +60,10 @@ bool IsMsvcStlSmartPointer(ValueObject &valobj);
bool MsvcStlSmartPointerSummaryProvider(ValueObject &valobj, Stream &stream,
const TypeSummaryOptions &options);
+bool DumpMsvcStlSmartPointerSummary(ValueObject &ptr, ValueObject &rep,
+ uint64_t ctrl_addr, Stream &stream,
+ const TypeSummaryOptions &options);
+
lldb_private::SyntheticChildrenFrontEnd *
MsvcStlSmartPointerSyntheticFrontEndCreator(lldb::ValueObjectSP valobj_sp);
@@ -109,7 +113,7 @@ MsvcStlVariantSyntheticFrontEndCreator(CXXSyntheticChildren *,
lldb::ValueObjectSP valobj_sp);
// MSVC STL std::atomic<>
-bool IsMsvcStlAtomic(ValueObject &valobj);
+bool IsMsvcStlAtomic(ValueObject &valobj, bool *is_smart_ptr = nullptr);
bool MsvcStlAtomicSummaryProvider(ValueObject &valobj, Stream &stream,
const TypeSummaryOptions &options);
SyntheticChildrenFrontEnd *
diff --git a/lldb/source/Plugins/Language/CPlusPlus/MsvcStlAtomic.cpp b/lldb/source/Plugins/Language/CPlusPlus/MsvcStlAtomic.cpp
index 5c3f11a154f285..a39fe74bc09ccc 100644
--- a/lldb/source/Plugins/Language/CPlusPlus/MsvcStlAtomic.cpp
+++ b/lldb/source/Plugins/Language/CPlusPlus/MsvcStlAtomic.cpp
@@ -12,13 +12,15 @@
#include "llvm/Support/ErrorExtras.h"
using namespace lldb;
+using namespace lldb_private;
namespace lldb_private {
namespace formatters {
class MsvcStlAtomicSyntheticFrontEnd : public SyntheticChildrenFrontEnd {
public:
- MsvcStlAtomicSyntheticFrontEnd(lldb::ValueObjectSP valobj_sp);
+ MsvcStlAtomicSyntheticFrontEnd(lldb::ValueObjectSP valobj_sp,
+ bool is_smart_ptr);
llvm::Expected<uint32_t> CalculateNumChildren() override;
@@ -29,20 +31,39 @@ class MsvcStlAtomicSyntheticFrontEnd : public SyntheticChildrenFrontEnd {
llvm::Expected<size_t> GetIndexOfChildWithName(llvm::StringRef name) override;
private:
- ValueObject *m_storage = nullptr;
- CompilerType m_element_type;
+ ValueObjectSP m_storage;
+ bool m_is_smart_ptr = false;
};
} // namespace formatters
} // namespace lldb_private
-lldb_private::formatters::MsvcStlAtomicSyntheticFrontEnd::
- MsvcStlAtomicSyntheticFrontEnd(lldb::ValueObjectSP valobj_sp)
- : SyntheticChildrenFrontEnd(*valobj_sp), m_element_type() {
- if (valobj_sp)
- Update();
+static ValueObjectSP ValueOfAtomic(ValueObject &valobj) {
+ ValueObjectSP storage_sp = valobj.GetChildMemberWithName("_Storage");
+ if (!storage_sp)
+ return nullptr;
+
+ CompilerType backend_type = valobj.GetCompilerType();
+ if (!backend_type)
+ return nullptr;
+
+ CompilerType element_type = backend_type.GetTypeTemplateArgument(0);
+ if (!element_type) {
+ // PDB doesn't have info about templates, so use value_type which equals T.
+ element_type = backend_type.GetDirectNestedTypeWithName("value_type");
+
+ if (!element_type)
+ return nullptr;
+ }
+
+ return valobj.Cast(element_type);
}
+lldb_private::formatters::MsvcStlAtomicSyntheticFrontEnd::
+ MsvcStlAtomicSyntheticFrontEnd(lldb::ValueObjectSP valobj_sp,
+ bool is_smart_ptr)
+ : SyntheticChildrenFrontEnd(*valobj_sp), m_is_smart_ptr(is_smart_ptr) {}
+
llvm::Expected<uint32_t> lldb_private::formatters::
MsvcStlAtomicSyntheticFrontEnd::CalculateNumChildren() {
return m_storage ? 1 : 0;
@@ -51,41 +72,34 @@ llvm::Expected<uint32_t> lldb_private::formatters::
lldb::ValueObjectSP
lldb_private::formatters::MsvcStlAtomicSyntheticFrontEnd::GetChildAtIndex(
uint32_t idx) {
- if (idx == 0 && m_storage && m_element_type.IsValid())
- return m_storage->Cast(m_element_type)->Clone("Value");
+ if (idx == 0 && m_storage)
+ return m_storage;
return nullptr;
}
lldb::ChildCacheState
lldb_private::formatters::MsvcStlAtomicSyntheticFrontEnd::Update() {
- m_storage = nullptr;
- m_element_type.Clear();
-
- ValueObjectSP storage_sp = m_backend.GetChildMemberWithName("_Storage");
- if (!storage_sp)
- return lldb::ChildCacheState::eRefetch;
-
- CompilerType backend_type = m_backend.GetCompilerType();
- if (!backend_type)
- return lldb::ChildCacheState::eRefetch;
-
- m_element_type = backend_type.GetTypeTemplateArgument(0);
- if (!m_element_type) {
- // PDB doesn't have info about templates, so use value_type which equals T.
- m_element_type = backend_type.GetDirectNestedTypeWithName("value_type");
-
- if (!m_element_type)
- return lldb::ChildCacheState::eRefetch;
+ m_storage.reset();
+
+ if (m_is_smart_ptr) {
+ ValueObjectSP ptr = m_backend.GetChildMemberWithName("_Ptr");
+ if (!ptr)
+ return lldb::eRefetch;
+ m_storage = ValueOfAtomic(*ptr);
+ } else {
+ m_storage = ValueOfAtomic(m_backend);
}
- m_storage = storage_sp.get();
- return lldb::ChildCacheState::eRefetch;
+ if (m_storage)
+ m_storage = m_storage->Clone("Value");
+
+ return lldb::eRefetch;
}
llvm::Expected<size_t>
lldb_private::formatters::MsvcStlAtomicSyntheticFrontEnd::
GetIndexOfChildWithName(llvm::StringRef name) {
- if (name == "Value")
+ if (name == "Value" || (m_is_smart_ptr && name == "$$dereference$$"))
return 0;
return llvm::createStringErrorV("type has no child named '{0}'", name);
}
@@ -93,18 +107,96 @@ lldb_private::formatters::MsvcStlAtomicSyntheticFrontEnd::
lldb_private::SyntheticChildrenFrontEnd *
lldb_private::formatters::MsvcStlAtomicSyntheticFrontEndCreator(
CXXSyntheticChildren *, lldb::ValueObjectSP valobj_sp) {
- if (valobj_sp && IsMsvcStlAtomic(*valobj_sp))
- return new MsvcStlAtomicSyntheticFrontEnd(valobj_sp);
+ bool is_smart_ptr = false;
+ if (valobj_sp && IsMsvcStlAtomic(*valobj_sp, &is_smart_ptr))
+ return new MsvcStlAtomicSyntheticFrontEnd(valobj_sp, is_smart_ptr);
return nullptr;
}
+static CompilerType FindLockedPointeeType(Target &target,
+ const CompilerType &locked_ptr_type) {
+ ConstString locked_name = locked_ptr_type.GetTypeName();
+ llvm::StringRef pointee_name = locked_name;
+ if (!pointee_name.consume_front("std::_Locked_pointer<"))
+ return {};
+ if (!pointee_name.consume_back(">"))
+ return {};
+
+ TypeQuery query(pointee_name, TypeQueryOptions::e_find_one);
+ TypeResults results;
+ target.GetImages().FindTypes(/*search_first=*/nullptr, query, results);
+ if (TypeSP type_sp = results.GetFirstType())
+ return type_sp->GetFullCompilerType();
+ return {};
+}
+
+static bool AtomicSharedPtrSummaryProvider(ValueObject &ptr,
+ ValueObject &rep_wrap,
+ Stream &stream,
+ const TypeSummaryOptions &options) {
+ // rep_wrap: _Locked_pointer<_Ref_count_base> {
+ // static constexpr uintptr_t _Lock_mask = 3;
+ // static constexpr uintptr_t _Ptr_value_mask = ~_Lock_mask;
+ //
+ // atomic<uintptr_t> Storage_;
+ // }
+ ValueObjectSP rep_storage_sp = rep_wrap.GetChildAtIndex(0);
+ if (!rep_storage_sp)
+ return false;
+ ValueObjectSP rep_ptr_sp = ValueOfAtomic(*rep_storage_sp);
+ if (!rep_ptr_sp)
+ return false;
+
+ bool ok = false;
+ uint64_t rep_ptr_val = rep_ptr_sp->GetValueAsUnsigned(0, &ok);
+ if (!ok)
+ return false;
+
+ CompilerType rep_type = rep_wrap.GetCompilerType();
+ if (!rep_type)
+ return false;
+ CompilerDecl ptr_value_decl =
+ rep_type.GetStaticFieldWithName("_Ptr_value_mask");
+ if (!ptr_value_decl)
+ return false;
+ uint64_t rep_ptr_mask = ptr_value_decl.GetConstantValue().ULongLong(~(3ULL));
+ rep_ptr_val &= rep_ptr_mask;
+
+ CompilerType ref_count_base = rep_type.GetTypeTemplateArgument(0);
+ if (!ref_count_base) {
+ // PDB: Search for the type by name, as it doesn't know about templates.
+ // Unfortunately, there's no typedef inside the type.
+ TargetSP target = rep_wrap.GetTargetSP();
+ if (!target)
+ return false;
+ ref_count_base = FindLockedPointeeType(*target, rep_type);
+ if (!ref_count_base)
+ return false;
+ }
+
+ ValueObjectSP rep = ValueObject::CreateValueObjectFromAddress(
+ "", rep_ptr_val, rep_wrap.GetExecutionContextRef(), ref_count_base);
+ if (!rep)
+ return false;
+
+ return formatters::DumpMsvcStlSmartPointerSummary(ptr, *rep, rep_ptr_val,
+ stream, options);
+}
+
bool lldb_private::formatters::MsvcStlAtomicSummaryProvider(
ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) {
- ValueObjectSP synth_sp = valobj.GetSyntheticValue();
- if (!synth_sp)
+ ValueObjectSP non_synth = valobj.GetNonSyntheticValue();
+ if (!non_synth)
return false;
+ ValueObjectSP value_sp = valobj.GetChildAtIndex(0);
+ if (!value_sp)
+ return false;
+
+ ValueObjectSP smart_ptr_rep = non_synth->GetChildMemberWithName("_Repptr");
+ if (smart_ptr_rep)
+ return AtomicSharedPtrSummaryProvider(*value_sp, *smart_ptr_rep, stream,
+ options);
- ValueObjectSP value_sp = synth_sp->GetChildAtIndex(0);
std::string summary;
if (value_sp->GetSummaryAsCString(summary, options) && !summary.empty()) {
stream << summary;
@@ -113,8 +205,16 @@ bool lldb_private::formatters::MsvcStlAtomicSummaryProvider(
return false;
}
-bool lldb_private::formatters::IsMsvcStlAtomic(ValueObject &valobj) {
- if (auto valobj_sp = valobj.GetNonSyntheticValue())
- return valobj_sp->GetChildMemberWithName("_Storage") != nullptr;
+bool lldb_private::formatters::IsMsvcStlAtomic(ValueObject &valobj,
+ bool *is_smart_ptr) {
+ if (auto valobj_sp = valobj.GetNonSyntheticValue()) {
+ bool has_storage = valobj_sp->GetChildMemberWithName("_Storage") != nullptr;
+ bool has_smart =
+ !has_storage && valobj_sp->GetChildMemberWithName("_Ptr") != nullptr;
+ if (is_smart_ptr)
+ *is_smart_ptr = has_smart;
+
+ return has_storage || has_smart;
+ }
return false;
}
diff --git a/lldb/source/Plugins/Language/CPlusPlus/MsvcStlSmartPointer.cpp b/lldb/source/Plugins/Language/CPlusPlus/MsvcStlSmartPointer.cpp
index 690addf69f8b95..d8932c9fbcb651 100644
--- a/lldb/source/Plugins/Language/CPlusPlus/MsvcStlSmartPointer.cpp
+++ b/lldb/source/Plugins/Language/CPlusPlus/MsvcStlSmartPointer.cpp
@@ -33,16 +33,23 @@ bool lldb_private::formatters::MsvcStlSmartPointerSummaryProvider(
if (!ctrl_sp || !ptr_sp)
return false;
- DumpCxxSmartPtrPointerSummary(stream, *ptr_sp, options);
+ uint64_t ctrl_addr = ctrl_sp->GetValueAsUnsigned(0);
+
+ return DumpMsvcStlSmartPointerSummary(*ptr_sp, *ctrl_sp, ctrl_addr, stream,
+ options);
+}
+
+bool lldb_private::formatters::DumpMsvcStlSmartPointerSummary(
+ ValueObject &ptr, ValueObject &rep, uint64_t ctrl_addr, Stream &stream,
+ const TypeSummaryOptions &options) {
+ DumpCxxSmartPtrPointerSummary(stream, ptr, options);
- bool success;
- uint64_t ctrl_addr = ctrl_sp->GetValueAsUnsigned(0, &success);
// Empty control field (expired)
- if (!success || ctrl_addr == 0)
+ if (ctrl_addr == 0)
return true;
uint64_t uses = 0;
- if (auto uses_sp = ctrl_sp->GetChildMemberWithName("_Uses")) {
+ if (auto uses_sp = rep.GetChildMemberWithName("_Uses")) {
bool success;
uses = uses_sp->GetValueAsUnsigned(0, &success);
if (!success)
@@ -52,7 +59,7 @@ bool lldb_private::formatters::MsvcStlSmartPointerSummaryProvider(
}
// _Weaks is the number of weak references - (_Uses != 0).
- if (auto weak_count_sp = ctrl_sp->GetChildMemberWithName("_Weaks")) {
+ if (auto weak_count_sp = rep.GetChildMemberWithName("_Weaks")) {
bool success;
uint64_t count = weak_count_sp->GetValueAsUnsigned(0, &success);
if (!success)
diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/atomic/TestDataFormatterStdAtomic.py b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/atomic/TestDataFormatterStdAtomic.py
index 958a0bc312b797..12997f3a5117d2 100644
--- a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/atomic/TestDataFormatterStdAtomic.py
+++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/atomic/TestDataFormatterStdAtomic.py
@@ -18,7 +18,7 @@ def get_variable(self, name):
var.SetPreferSyntheticValue(True)
return var
- def do_test(self, check_shared_ptr: bool = True):
+ def do_test(self):
"""Test that std::atomic is correctly printed by LLDB"""
self.runCmd("file " + self.getBuildArtifact("a.out"), CURRENT_EXECUTABLE_SET)
@@ -110,18 +110,26 @@ def do_test(self, check_shared_ptr: bool = True):
)
# Atomic smart pointers, only available if supported by the library.
- atomic_shared = self.get_variable("atomic_shared")
- if check_shared_ptr and atomic_shared.IsValid():
+ shared_original = self.get_variable("s_value")
+ if shared_original.IsValid():
+ og_summary = shared_original.GetSummary()
+ og_addr = shared_original.GetChildAtIndex(0).GetValueAsAddress()
+
# shared_pointer
- val_shared = atomic_shared.child[0]
+ atomic_shared = self.get_variable("atomic_shared")
+ val_shared = atomic_shared.GetChildAtIndex(0)
self.assertIsNotNone(val_shared, msg="atomic_shared child is None.")
self.assertEqual(300, val_shared.deref.unsigned)
+ self.assertEqual(og_summary, atomic_shared.GetSummary())
+ self.assertEqual(og_addr, val_shared.GetValueAsAddress())
# weak_pointer
atomic_weak = self.get_variable("atomic_weak")
- val_weak = atomic_weak.child[0]
+ val_weak = atomic_weak.GetChildAtIndex(0)
self.assertIsNotNone(val_weak, msg="atomic_weak child is None.")
self.assertEqual(300, val_weak.deref.unsigned)
+ self.assertEqual(og_summary, atomic_weak.GetSummary())
+ self.assertEqual(og_addr, val_weak.GetValueAsAddress())
def verify_floating_point_equal(self, value: str, expected: float):
self.assertAlmostEqual(float(value), float(expected), places=4)
@@ -141,4 +149,4 @@ def test_libstdcxx(self):
def test_msvcstl(self):
# No flags, because the "msvcstl" category checks that the MSVC STL is used by default.
self.build()
- self.do_test(check_shared_ptr=False)
+ self.do_test()
More information about the lldb-commits
mailing list