Skip to content
32 changes: 13 additions & 19 deletions lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1335,31 +1335,25 @@ static void LoadLibCxxFormatters(lldb::TypeCategoryImplSP cpp_category_sp) {
static void RegisterStdStringSummaryProvider(
const lldb::TypeCategoryImplSP &category_sp, llvm::StringRef string_ty,
llvm::StringRef char_ty, lldb::TypeSummaryImplSP summary_sp) {
auto makeSpecifier = [](llvm::StringRef name) {
return std::make_shared<lldb_private::TypeNameSpecifierImpl>(
name, eFormatterMatchExact);
};

category_sp->AddTypeSummary(makeSpecifier(string_ty), summary_sp);

// std::basic_string<char>
category_sp->AddTypeSummary(
makeSpecifier(llvm::formatv("std::basic_string<{}>", char_ty).str()),
std::make_shared<lldb_private::TypeNameSpecifierImpl>(
string_ty, eFormatterMatchExact),
summary_sp);
// std::basic_string<char,std::char_traits<char>,std::allocator<char> >

// std::basic_string<char>
category_sp->AddTypeSummary(
makeSpecifier(llvm::formatv("std::basic_string<{0},std::char_traits<{0}>,"
"std::allocator<{0}> >",
char_ty)
.str()),
std::make_shared<lldb_private::TypeNameSpecifierImpl>(
llvm::formatv("std::basic_string<{}>", char_ty).str(),
eFormatterMatchExact),
summary_sp);
// std::basic_string<char, std::char_traits<char>, std::allocator<char> >
// std::basic_string<char, std::char_traits<char>, std::allocator<char>>
category_sp->AddTypeSummary(
makeSpecifier(
llvm::formatv("std::basic_string<{0}, std::char_traits<{0}>, "
"std::allocator<{0}> >",
std::make_shared<lldb_private::TypeNameSpecifierImpl>(
llvm::formatv("std::basic_string<{0}, ?std::char_traits<{0}>, "
"?std::allocator<{0}> ?>",
char_ty)
.str()),
.str(),
eFormatterMatchRegex),
summary_sp);
}

Expand Down
20 changes: 17 additions & 3 deletions lldb/source/Plugins/Language/CPlusPlus/GenericList.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -526,9 +526,17 @@ ValueObjectSP MsvcStlForwardListFrontEnd::GetChildAtIndex(uint32_t idx) {
lldb::ChildCacheState MsvcStlForwardListFrontEnd::Update() {
AbstractListFrontEnd::Update();

if (auto head_sp =
m_backend.GetChildAtNamePath({"_Mypair", "_Myval2", "_Myhead"}))
m_head = head_sp.get();
auto head_sp =
m_backend.GetChildAtNamePath({"_Mypair", "_Myval2", "_Myhead"});
if (!head_sp)
return ChildCacheState::eRefetch;

m_head = head_sp.get();
if (!m_element_type) {
auto val_sp = head_sp->GetChildMemberWithName("_Myval");
if (val_sp)
m_element_type = val_sp->GetCompilerType();
}

return ChildCacheState::eRefetch;
}
Expand Down Expand Up @@ -606,6 +614,12 @@ lldb::ChildCacheState MsvcStlListFrontEnd::Update() {
m_head = first.get();
m_tail = last.get();

if (!m_element_type) {
auto val_sp = m_head->GetChildMemberWithName("_Myval");
if (val_sp)
m_element_type = val_sp->GetCompilerType();
}

return lldb::ChildCacheState::eRefetch;
}

Expand Down
16 changes: 10 additions & 6 deletions lldb/source/Plugins/Language/CPlusPlus/GenericOptional.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -117,12 +117,16 @@ ValueObjectSP GenericOptionalFrontend::GetChildAtIndex(uint32_t _idx) {
ValueObjectSP candidate = val_sp->GetChildMemberWithName("_M_value");
if (candidate)
val_sp = candidate;
} else if (m_stdlib == StdLib::MsvcStl)
// Same issue as with LibCxx
val_sp = m_backend.GetChildMemberWithName("_Has_value")
->GetParent()
->GetChildAtIndex(0)
->GetChildMemberWithName("_Value");
} else if (m_stdlib == StdLib::MsvcStl) {
// PDB flattens anonymous unions to the parent
val_sp = m_backend.GetChildMemberWithName("_Value");
// With DWARF and NativePDB, same issue as with LibCxx
if (!val_sp)
val_sp = m_backend.GetChildMemberWithName("_Has_value")
->GetParent()
->GetChildAtIndex(0)
->GetChildMemberWithName("_Value");
}

if (!val_sp)
return ValueObjectSP();
Expand Down
17 changes: 10 additions & 7 deletions lldb/source/Plugins/Language/CPlusPlus/MsvcStlDeque.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -111,13 +111,6 @@ lldb_private::formatters::MsvcStlDequeSyntheticFrontEnd::Update() {
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");
Expand All @@ -138,6 +131,16 @@ lldb_private::formatters::MsvcStlDequeSyntheticFrontEnd::Update() {
if (!ok)
return lldb::eRefetch;

auto element_type = deque_type.GetTypeTemplateArgument(0);
if (!element_type) {
element_type = map_sp->GetCompilerType().GetPointeeType().GetPointeeType();
if (!element_type)
return lldb::eRefetch;
}
auto element_size = element_type.GetByteSize(nullptr);
if (!element_size)
return lldb::eRefetch;

m_map = map_sp.get();
m_exe_ctx_ref = m_backend.GetExecutionContextRef();
m_block_size = block_size.ULongLong();
Expand Down
32 changes: 22 additions & 10 deletions lldb/source/Plugins/Language/CPlusPlus/MsvcStlVariant.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,11 @@ namespace {
// }

ValueObjectSP GetStorageMember(ValueObject &valobj, llvm::StringRef name) {
// Find the union
// DIA PDB flattens the union into the storage
if (valobj.GetNumChildrenIgnoringErrors(3) >= 2)
return valobj.GetChildMemberWithName(name);

// DWARF and NativePDB: Find the union
ValueObjectSP union_sp = valobj.GetChildAtIndex(0);
if (!union_sp)
return nullptr;
Expand All @@ -65,14 +69,18 @@ std::optional<int64_t> GetIndexValue(ValueObject &valobj) {
}

ValueObjectSP GetNthStorage(ValueObject &outer, int64_t index) {
// We need to find the std::_Variant_storage base class.

// -> std::_SMF_control (typedef to std::_Variant_base)
ValueObjectSP container_sp = outer.GetSP()->GetChildAtIndex(0);
if (!container_sp)
// navigate "down" to std::_SMF_control/std::_Variant_base
// by finding the holder of "_Which". This might be down a few levels if a
// variant member isn't trivally destructible/copyable/etc.
ValueObjectSP which_sp = outer.GetChildMemberWithName("_Which");
if (!which_sp)
return nullptr;
ValueObject *parent = which_sp->GetParent();
if (!parent)
return nullptr;
// -> std::_Variant_storage
container_sp = container_sp->GetChildAtIndex(0);

// Now go to std::_Variant_storage
ValueObjectSP container_sp = parent->GetChildAtIndex(0);
if (!container_sp)
return nullptr;

Expand Down Expand Up @@ -119,8 +127,12 @@ bool formatters::MsvcStlVariantSummaryProvider(
storage_type = storage_type.GetTypedefedType();

CompilerType active_type = storage_type.GetTypeTemplateArgument(1, true);
if (!active_type)
return false;
if (!active_type) {
ValueObjectSP head = GetHead(*storage);
active_type = head->GetCompilerType();
if (!active_type)
return false;
}

stream << " Active Type = " << active_type.GetDisplayTypeName() << " ";
return true;
Expand Down
188 changes: 188 additions & 0 deletions lldb/test/Shell/SymbolFile/NativePDB/stl_types.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
# REQUIRES: target-windows
Copy link
Member

Choose a reason for hiding this comment

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

Instead of having these shell tests can we just mark the relevant formatters with the pdb categories introduced in #149305? Maybe that was already your plan :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yea, that was my plan. Once that's merged, I'll update the tests.


# Test that LLDB can format types from MSVC's STL
# RUN: split-file %s %t
# RUN: %build --compiler=clang-cl --std c++20 -o %t.exe -- %t/main.cpp
# RUN: %lldb -f %t.exe -s \
# RUN: %t/commands.input 2>&1 | FileCheck %s

#--- main.cpp

#include <bitset>
#include <coroutine>
#include <deque>
#include <forward_list>
#include <list>
#include <map>
#include <memory>
#include <optional>
#include <set>
#include <string>
#include <tuple>
#include <unordered_map>
#include <unordered_set>
#include <variant>
#include <vector>

int main() {
std::shared_ptr<int> sp = std::make_shared<int>(41);
std::weak_ptr<int> wp = sp;
std::unique_ptr<int> unique(new int(42));
std::optional<std::u16string> opt = u"abc";
std::string str = "str";
std::u8string u8str = u8"str";
std::wstring wStr = L"wstr";
std::tuple<int, bool, float> tuple{1, false, 4.2};
std::coroutine_handle<> coroHandle;
std::bitset<16> bitset(123);

std::map<int, int> map{{1, 2}, {2, 4}, {3, 6}};
auto mapIt = map.find(3);
std::set<int> set{1, 2, 3};
std::multimap<int, int> mMap{{1, 2}, {1, 1}, {2, 4}};
std::multiset<int> mSet{1, 2, 3};

std::variant<int, float, std::string, std::monostate> variant = "wow";
std::list<int> list{1, 2, 3};
std::forward_list<int> fwList{1, 2, 3};

std::unordered_map<int, int> uMap{{1, 2}, {2, 4}, {3, 6}};
std::unordered_set<int> uSet{1, 2, 4};
std::unordered_multimap<int, int> uMMap{{1, 2}, {1, 1}, {2, 4}};
std::unordered_multiset<int> uMSet{1, 1, 2};
std::deque<int> deque{1, 2, 3};
std::vector<int> vec{1, 2, 3};
return 0; // break here
}

#--- commands.input

br s -p "break here"
r

fr v sp
fr v wp
fr v unique
# FIXME: _Has_value is put into the anonymous union along with _Value
# fr v opt
fr v str
fr v u8str
fr v wStr
fr v tuple
fr v map
fr v mapIt
fr v set
fr v mMap
fr v mSet
fr v variant
fr v list
fr v fwList
fr v uMap
fr v uSet
fr v uMMap
fr v uMSet
# FIXME: Static _Block_size is found but doesn't have a value
# fr v deque
fr v vec

quit

# CHECK: (lldb) fr v sp
# CHECK-NEXT: (std::shared_ptr<int>) sp = 41 strong=1 weak=1 {
# CHECK-NEXT: pointer = 0x{{.*}}
# CHECK-NEXT: }
# CHECK-NEXT: (lldb) fr v wp
# CHECK-NEXT: (std::weak_ptr<int>) wp = 41 strong=1 weak=1 {
# CHECK-NEXT: pointer = 0x{{.*}}
# CHECK-NEXT: }
# CHECK-NEXT: (lldb) fr v unique
# CHECK-NEXT: (std::unique_ptr<int, std::default_delete<int>>) unique = 42 {
# CHECK-NEXT: pointer = 0x{{.*}}
# CHECK-NEXT: }
# CHECK-NEXT: (lldb) fr v str
# CHECK-NEXT: (std::basic_string<char, std::char_traits<char>, std::allocator<char>>) str = "str"
# CHECK-NEXT: (lldb) fr v u8str
# CHECK-NEXT: (std::basic_string<char8_t, std::char_traits<char8_t>, std::allocator<char8_t>>) u8str = u8"str"
# CHECK-NEXT: (lldb) fr v wStr
# CHECK-NEXT: (std::basic_string<wchar_t, std::char_traits<wchar_t>, std::allocator<wchar_t>>) wStr = L"wstr"
# CHECK-NEXT: (lldb) fr v tuple
# CHECK-NEXT: (std::tuple<int, bool, float>) tuple = size=3 {
# CHECK-NEXT: [0] = 1
# CHECK-NEXT: [1] = false
# CHECK-NEXT: [2] = 4.{{.*}}
# CHECK-NEXT: }
# CHECK-NEXT: (lldb) fr v map
# CHECK-NEXT: (std::map<int, int, std::less<int>, std::allocator<std::pair<int const, int>>>) map = size=3 {
# CHECK-NEXT: [0] = (first = 1, second = 2)
# CHECK-NEXT: [1] = (first = 2, second = 4)
# CHECK-NEXT: [2] = (first = 3, second = 6)
# CHECK-NEXT: }
# CHECK-NEXT: (lldb) fr v mapIt
# CHECK-NEXT: (std::_Tree_iterator<std::_Tree_val<std::_Tree_simple_types<std::pair<int const, int>>>>) mapIt = {
# CHECK-NEXT: first = 3
# CHECK-NEXT: second = 6
# CHECK-NEXT: }
# CHECK-NEXT: (lldb) fr v set
# CHECK-NEXT: (std::set<int, std::less<int>, std::allocator<int>>) set = size=3 {
# CHECK-NEXT: [0] = 1
# CHECK-NEXT: [1] = 2
# CHECK-NEXT: [2] = 3
# CHECK-NEXT: }
# CHECK-NEXT: (lldb) fr v mMap
# CHECK-NEXT: (std::multimap<int, int, std::less<int>, std::allocator<std::pair<int const, int>>>) mMap = size=3 {
# CHECK-NEXT: [0] = (first = 1, second = 2)
# CHECK-NEXT: [1] = (first = 1, second = 1)
# CHECK-NEXT: [2] = (first = 2, second = 4)
# CHECK-NEXT: }
# CHECK-NEXT: (lldb) fr v mSet
# CHECK-NEXT: (std::multiset<int, std::less<int>, std::allocator<int>>) mSet = size=3 {
# CHECK-NEXT: [0] = 1
# CHECK-NEXT: [1] = 2
# CHECK-NEXT: [2] = 3
# CHECK-NEXT: }
# CHECK-NEXT: (lldb) fr v variant
# CHECK-NEXT: (std::variant<int, float, std::basic_string<char, std::char_traits<char>, std::allocator<char>>, std::monostate>) variant = Active Type = std::basic_string<char, std::char_traits<char>, std::allocator<char>> {
# CHECK-NEXT: Value = "wow"
# CHECK-NEXT: }
# CHECK-NEXT: (lldb) fr v list
# CHECK-NEXT: (std::list<int, std::allocator<int>>) list = size=3 {
# CHECK-NEXT: [0] = 1
# CHECK-NEXT: [1] = 2
# CHECK-NEXT: [2] = 3
# CHECK-NEXT: }
# CHECK-NEXT: (lldb) fr v fwList
# CHECK-NEXT: (std::forward_list<int, std::allocator<int>>) fwList = size=3 {
# CHECK-NEXT: [0] = 1
# CHECK-NEXT: [1] = 2
# CHECK-NEXT: [2] = 3
# CHECK-NEXT: }
# CHECK-NEXT: (lldb) fr v uMap
# CHECK-NEXT: (std::unordered_map<int, int, std::hash<int>, std::equal_to<int>, std::allocator<std::pair<int const, int>>>) uMap = size=3 {
# CHECK-NEXT: [0] = (first = 1, second = 2)
# CHECK-NEXT: [1] = (first = 2, second = 4)
# CHECK-NEXT: [2] = (first = 3, second = 6)
# CHECK-NEXT: }
# CHECK-NEXT: (lldb) fr v uSet
# CHECK-NEXT: (std::unordered_set<int, std::hash<int>, std::equal_to<int>, std::allocator<int>>) uSet = size=3 {
# CHECK-NEXT: [0] = 1
# CHECK-NEXT: [1] = 2
# CHECK-NEXT: [2] = 4
# CHECK-NEXT: }
# CHECK-NEXT: (lldb) fr v uMMap
# CHECK-NEXT: (std::unordered_multimap<int, int, std::hash<int>, std::equal_to<int>, std::allocator<std::pair<int const, int>>>) uMMap = size=3 {
# CHECK-NEXT: [0] = (first = 1, second = 2)
# CHECK-NEXT: [1] = (first = 1, second = 1)
# CHECK-NEXT: [2] = (first = 2, second = 4)
# CHECK-NEXT: }
# CHECK-NEXT: (lldb) fr v uMSet
# CHECK-NEXT: (std::unordered_multiset<int, std::hash<int>, std::equal_to<int>, std::allocator<int>>) uMSet = size=3 {
# CHECK-NEXT: [0] = 1
# CHECK-NEXT: [1] = 1
# CHECK-NEXT: [2] = 2
# CHECK-NEXT: }
# CHECK-NEXT: (lldb) fr v vec
# CHECK-NEXT: (std::vector<int, std::allocator<int>>) vec = size=3 {
# CHECK-NEXT: [0] = 1
# CHECK-NEXT: [1] = 2
# CHECK-NEXT: [2] = 3
# CHECK-NEXT: }
Loading
Loading