| //===-- MsvcStlTree.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/Utility/Status.h" |
| #include "lldb/ValueObject/ValueObject.h" |
| #include "lldb/lldb-enumerations.h" |
| #include "lldb/lldb-forward.h" |
| #include <cstdint> |
| #include <optional> |
| |
| using namespace lldb; |
| using namespace lldb_private; |
| using namespace lldb_private::formatters; |
| |
| // A Node looks as follows: |
| // struct _Tree_node { |
| // _Tree_node *_Left; |
| // _Tree_node *_Parent; |
| // _Tree_node *_Right; |
| // char _Color; |
| // char _Isnil; // true (!= 0) if head or nil node |
| // value_type _Myval; |
| // }; |
| |
| namespace { |
| |
| class MapEntry { |
| public: |
| MapEntry() = default; |
| explicit MapEntry(ValueObjectSP entry_sp) : m_entry_sp(entry_sp) {} |
| explicit MapEntry(ValueObject *entry) |
| : m_entry_sp(entry ? entry->GetSP() : ValueObjectSP()) {} |
| |
| ValueObjectSP left() const { |
| if (!m_entry_sp) |
| return m_entry_sp; |
| return m_entry_sp->GetSyntheticChildAtOffset( |
| 0, m_entry_sp->GetCompilerType(), true); |
| } |
| |
| ValueObjectSP right() const { |
| if (!m_entry_sp) |
| return m_entry_sp; |
| return m_entry_sp->GetSyntheticChildAtOffset( |
| 2 * m_entry_sp->GetProcessSP()->GetAddressByteSize(), |
| m_entry_sp->GetCompilerType(), true); |
| } |
| |
| ValueObjectSP parent() const { |
| if (!m_entry_sp) |
| return m_entry_sp; |
| return m_entry_sp->GetSyntheticChildAtOffset( |
| m_entry_sp->GetProcessSP()->GetAddressByteSize(), |
| m_entry_sp->GetCompilerType(), true); |
| } |
| |
| uint64_t value() const { |
| if (!m_entry_sp) |
| return 0; |
| return m_entry_sp->GetValueAsUnsigned(0); |
| } |
| |
| bool is_nil() const { |
| if (!m_entry_sp) |
| return true; |
| auto isnil_sp = m_entry_sp->GetChildMemberWithName("_Isnil"); |
| if (!isnil_sp) |
| return true; |
| return isnil_sp->GetValueAsUnsigned(1) != 0; |
| } |
| |
| bool error() const { |
| if (!m_entry_sp) |
| return true; |
| return m_entry_sp->GetError().Fail(); |
| } |
| |
| bool is_nullptr() const { return (value() == 0); } |
| |
| ValueObjectSP GetEntry() const { return m_entry_sp; } |
| |
| void SetEntry(ValueObjectSP entry) { m_entry_sp = entry; } |
| |
| bool operator==(const MapEntry &rhs) const { |
| return (rhs.m_entry_sp.get() == m_entry_sp.get()); |
| } |
| |
| private: |
| ValueObjectSP m_entry_sp; |
| }; |
| |
| class MapIterator { |
| public: |
| MapIterator(ValueObject *entry, size_t depth = 0) |
| : m_entry(entry), m_max_depth(depth) {} |
| |
| MapIterator() = default; |
| |
| ValueObjectSP value() { return m_entry.GetEntry(); } |
| |
| ValueObjectSP advance(size_t count) { |
| ValueObjectSP fail; |
| if (m_error) |
| return fail; |
| size_t steps = 0; |
| while (count > 0) { |
| next(); |
| count--, steps++; |
| if (m_error || m_entry.is_nullptr() || (steps > m_max_depth)) |
| return fail; |
| } |
| return m_entry.GetEntry(); |
| } |
| |
| private: |
| /// Mimicks _Tree_unchecked_const_iterator::operator++() |
| void next() { |
| if (m_entry.is_nullptr()) |
| return; |
| MapEntry right(m_entry.right()); |
| if (!right.is_nil()) { |
| m_entry = tree_min(std::move(right)); |
| return; |
| } |
| size_t steps = 0; |
| MapEntry pnode(m_entry.parent()); |
| while (!pnode.is_nil() && |
| m_entry.value() == MapEntry(pnode.right()).value()) { |
| m_entry = pnode; |
| steps++; |
| if (steps > m_max_depth) { |
| m_entry = MapEntry(); |
| return; |
| } |
| pnode.SetEntry(m_entry.parent()); |
| } |
| m_entry = std::move(pnode); |
| } |
| |
| /// Mimicks MSVC STL's _Min() algorithm (finding the leftmost node in the |
| /// subtree). |
| MapEntry tree_min(MapEntry pnode) { |
| if (pnode.is_nullptr()) |
| return MapEntry(); |
| MapEntry left(pnode.left()); |
| size_t steps = 0; |
| while (!left.is_nil()) { |
| if (left.error()) { |
| m_error = true; |
| return MapEntry(); |
| } |
| pnode = left; |
| left.SetEntry(pnode.left()); |
| steps++; |
| if (steps > m_max_depth) |
| return MapEntry(); |
| } |
| return pnode; |
| } |
| |
| MapEntry m_entry; |
| size_t m_max_depth = 0; |
| bool m_error = false; |
| }; |
| |
| } // namespace |
| |
| namespace lldb_private { |
| namespace formatters { |
| class MsvcStlTreeSyntheticFrontEnd : public SyntheticChildrenFrontEnd { |
| public: |
| MsvcStlTreeSyntheticFrontEnd(lldb::ValueObjectSP valobj_sp); |
| |
| ~MsvcStlTreeSyntheticFrontEnd() override = default; |
| |
| 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: |
| /// Returns the ValueObject for the _Tree_node at index \ref idx. |
| /// |
| /// \param[in] idx The child index that we're looking to get the value for. |
| /// |
| /// \param[in] max_depth The maximum search depth after which we stop trying |
| /// to find the node for. |
| /// |
| /// \returns On success, returns the ValueObjectSP corresponding to the |
| /// _Tree_node's _Myval member. |
| /// On failure, nullptr is returned. |
| ValueObjectSP GetValueAt(size_t idx, size_t max_depth); |
| |
| ValueObject *m_tree = nullptr; |
| ValueObject *m_begin_node = nullptr; |
| size_t m_count = UINT32_MAX; |
| std::map<size_t, MapIterator> m_iterators; |
| }; |
| |
| class MsvcStlTreeIterSyntheticFrontEnd : public SyntheticChildrenFrontEnd { |
| public: |
| MsvcStlTreeIterSyntheticFrontEnd(lldb::ValueObjectSP valobj_sp) |
| : SyntheticChildrenFrontEnd(*valobj_sp) {} |
| |
| llvm::Expected<uint32_t> CalculateNumChildren() override { |
| if (!m_inner_sp) |
| return 0; |
| return m_inner_sp->GetNumChildren(); |
| } |
| |
| lldb::ValueObjectSP GetChildAtIndex(uint32_t idx) override { |
| if (!m_inner_sp) |
| return nullptr; |
| return m_inner_sp->GetChildAtIndex(idx); |
| } |
| |
| lldb::ChildCacheState Update() override; |
| |
| llvm::Expected<size_t> GetIndexOfChildWithName(ConstString name) override { |
| if (!m_inner_sp) |
| return llvm::createStringError("There are no children."); |
| return m_inner_sp->GetIndexOfChildWithName(name); |
| } |
| |
| lldb::ValueObjectSP GetSyntheticValue() override { return m_inner_sp; } |
| |
| private: |
| ValueObjectSP m_inner_sp; |
| }; |
| |
| } // namespace formatters |
| } // namespace lldb_private |
| |
| lldb_private::formatters::MsvcStlTreeSyntheticFrontEnd:: |
| MsvcStlTreeSyntheticFrontEnd(lldb::ValueObjectSP valobj_sp) |
| : SyntheticChildrenFrontEnd(*valobj_sp) { |
| if (valobj_sp) |
| Update(); |
| } |
| |
| llvm::Expected<uint32_t> |
| lldb_private::formatters::MsvcStlTreeSyntheticFrontEnd::CalculateNumChildren() { |
| if (m_count != UINT32_MAX) |
| return m_count; |
| |
| if (m_tree == nullptr) |
| return 0; |
| |
| if (auto node_sp = m_tree->GetChildMemberWithName("_Mysize")) { |
| m_count = node_sp->GetValueAsUnsigned(0); |
| return m_count; |
| } |
| |
| return llvm::createStringError("Failed to read size."); |
| } |
| |
| ValueObjectSP |
| lldb_private::formatters::MsvcStlTreeSyntheticFrontEnd::GetValueAt( |
| size_t idx, size_t max_depth) { |
| MapIterator iterator(m_begin_node, max_depth); |
| |
| size_t advance_by = idx; |
| if (idx > 0) { |
| // If we have already created the iterator for the previous |
| // index, we can start from there and advance by 1. |
| auto cached_iterator = m_iterators.find(idx - 1); |
| if (cached_iterator != m_iterators.end()) { |
| iterator = cached_iterator->second; |
| advance_by = 1; |
| } |
| } |
| |
| ValueObjectSP iterated_sp(iterator.advance(advance_by)); |
| if (!iterated_sp) |
| // this tree is garbage - stop |
| return nullptr; |
| |
| ValueObjectSP value_sp = iterated_sp->GetChildMemberWithName("_Myval"); |
| if (!value_sp) |
| return nullptr; |
| |
| m_iterators[idx] = iterator; |
| |
| return value_sp; |
| } |
| |
| lldb::ValueObjectSP |
| lldb_private::formatters::MsvcStlTreeSyntheticFrontEnd::GetChildAtIndex( |
| uint32_t idx) { |
| uint32_t num_children = CalculateNumChildrenIgnoringErrors(); |
| if (idx >= num_children) |
| return nullptr; |
| |
| if (m_tree == nullptr || m_begin_node == nullptr) |
| return nullptr; |
| |
| ValueObjectSP val_sp = GetValueAt(idx, /*max_depth=*/num_children); |
| if (!val_sp) { |
| // this will stop all future searches until an Update() happens |
| m_tree = nullptr; |
| return nullptr; |
| } |
| |
| // at this point we have a valid pair |
| // we need to copy current_sp into a new object otherwise we will end up with |
| // all items named _Myval |
| StreamString name; |
| name.Printf("[%" PRIu64 "]", (uint64_t)idx); |
| return val_sp->Clone(ConstString(name.GetString())); |
| } |
| |
| lldb::ChildCacheState |
| lldb_private::formatters::MsvcStlTreeSyntheticFrontEnd::Update() { |
| m_count = UINT32_MAX; |
| m_tree = m_begin_node = nullptr; |
| m_iterators.clear(); |
| m_tree = |
| m_backend.GetChildAtNamePath({"_Mypair", "_Myval2", "_Myval2"}).get(); |
| if (!m_tree) |
| return lldb::ChildCacheState::eRefetch; |
| |
| m_begin_node = m_tree->GetChildAtNamePath({"_Myhead", "_Left"}).get(); |
| |
| return lldb::ChildCacheState::eRefetch; |
| } |
| |
| llvm::Expected<size_t> |
| lldb_private::formatters::MsvcStlTreeSyntheticFrontEnd::GetIndexOfChildWithName( |
| ConstString name) { |
| auto optional_idx = formatters::ExtractIndexFromString(name.GetCString()); |
| if (!optional_idx) { |
| return llvm::createStringError("Type has no child named '%s'", |
| name.AsCString()); |
| } |
| return *optional_idx; |
| } |
| |
| lldb::ChildCacheState MsvcStlTreeIterSyntheticFrontEnd::Update() { |
| m_inner_sp = nullptr; |
| auto node_sp = m_backend.GetChildMemberWithName("_Ptr"); |
| if (!node_sp) |
| return lldb::eRefetch; |
| |
| MapEntry entry(node_sp.get()); |
| if (entry.is_nil()) |
| return lldb::eRefetch; // end |
| |
| m_inner_sp = node_sp->GetChildMemberWithName("_Myval"); |
| return lldb::eRefetch; |
| } |
| |
| bool formatters::IsMsvcStlTreeIter(ValueObject &valobj) { |
| return valobj.GetChildMemberWithName("_Ptr") != nullptr; |
| } |
| |
| bool formatters::MsvcStlTreeIterSummaryProvider( |
| ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) { |
| auto valobj_sp = valobj.GetNonSyntheticValue(); |
| if (!valobj_sp) |
| return false; |
| auto node_sp = valobj_sp->GetChildMemberWithName("_Ptr"); |
| if (!node_sp) |
| return false; |
| |
| MapEntry entry(node_sp.get()); |
| if (entry.is_nil()) { |
| stream.Printf("end"); |
| return true; |
| } |
| |
| auto value_sp = node_sp->GetChildMemberWithName("_Myval"); |
| if (!value_sp) |
| return false; |
| |
| auto *summary = value_sp->GetSummaryAsCString(); |
| if (summary) |
| stream << summary; |
| return true; |
| } |
| |
| SyntheticChildrenFrontEnd * |
| lldb_private::formatters::MsvcStlTreeIterSyntheticFrontEndCreator( |
| CXXSyntheticChildren *, lldb::ValueObjectSP valobj_sp) { |
| return (valobj_sp ? new MsvcStlTreeIterSyntheticFrontEnd(valobj_sp) |
| : nullptr); |
| } |
| |
| bool formatters::IsMsvcStlMapLike(ValueObject &valobj) { |
| return valobj.GetChildMemberWithName("_Mypair") != nullptr; |
| } |
| |
| SyntheticChildrenFrontEnd * |
| lldb_private::formatters::MsvcStlMapLikeSyntheticFrontEndCreator( |
| lldb::ValueObjectSP valobj_sp) { |
| return (valobj_sp ? new MsvcStlTreeSyntheticFrontEnd(valobj_sp) : nullptr); |
| } |