Some MSVC STL formatters made excessive use of `auto`, especially the `std::deque` one (https://github.com/llvm/llvm-project/pull/172360#pullrequestreview-3639850321). This PR replaces the uses with the concrete type.
408 lines
11 KiB
C++
408 lines
11 KiB
C++
//===-- 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;
|
|
ValueObjectSP 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);
|
|
}
|