| //===-- LibStdcpp.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 "LibStdcpp.h" | |
| #include "LibCxx.h" | |
| #include "Plugins/Language/CPlusPlus/CxxStringTypes.h" | |
| #include "Plugins/Language/CPlusPlus/Generic.h" | |
| #include "Plugins/Language/CPlusPlus/VectorIterator.h" | |
| #include "lldb/DataFormatters/FormattersHelpers.h" | |
| #include "lldb/DataFormatters/StringPrinter.h" | |
| #include "lldb/Target/Target.h" | |
| #include "lldb/Utility/DataBufferHeap.h" | |
| #include "lldb/Utility/Endian.h" | |
| #include "lldb/Utility/LLDBLog.h" | |
| #include "lldb/Utility/Status.h" | |
| #include "lldb/Utility/Stream.h" | |
| #include "lldb/ValueObject/ValueObject.h" | |
| #include "lldb/ValueObject/ValueObjectConstResult.h" | |
| #include "llvm/Support/ErrorExtras.h" | |
| #include <optional> | |
| using namespace lldb; | |
| using namespace lldb_private; | |
| using namespace lldb_private::formatters; | |
| namespace { | |
| using StringElementType = StringPrinter::StringElementType; | |
| class LibstdcppMapIteratorSyntheticFrontEnd : public SyntheticChildrenFrontEnd { | |
| /* | |
| (std::_Rb_tree_iterator<std::pair<const int, std::basic_string<char, | |
| std::char_traits<char>, std::allocator<char> > > >) ibeg = { | |
| (_Base_ptr) _M_node = 0x0000000100103910 { | |
| (std::_Rb_tree_color) _M_color = _S_black | |
| (std::_Rb_tree_node_base::_Base_ptr) _M_parent = 0x00000001001038c0 | |
| (std::_Rb_tree_node_base::_Base_ptr) _M_left = 0x0000000000000000 | |
| (std::_Rb_tree_node_base::_Base_ptr) _M_right = 0x0000000000000000 | |
| } | |
| } | |
| */ | |
| public: | |
| explicit LibstdcppMapIteratorSyntheticFrontEnd(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: | |
| ExecutionContextRef m_exe_ctx_ref; | |
| lldb::addr_t m_pair_address = 0; | |
| CompilerType m_pair_type; | |
| lldb::ValueObjectSP m_pair_sp; | |
| }; | |
| class LibStdcppSharedPtrSyntheticFrontEnd : public SyntheticChildrenFrontEnd { | |
| public: | |
| explicit LibStdcppSharedPtrSyntheticFrontEnd(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: | |
| // The lifetime of a ValueObject and all its derivative ValueObjects | |
| // (children, clones, etc.) is managed by a ClusterManager. These | |
| // objects are only destroyed when every shared pointer to any of them | |
| // is destroyed, so we must not store a shared pointer to any ValueObject | |
| // derived from our backend ValueObject (since we're in the same cluster). | |
| ValueObject *m_ptr_obj = nullptr; // Underlying pointer (held, not owned) | |
| }; | |
| } // end of anonymous namespace | |
| LibstdcppMapIteratorSyntheticFrontEnd::LibstdcppMapIteratorSyntheticFrontEnd( | |
| lldb::ValueObjectSP valobj_sp) | |
| : SyntheticChildrenFrontEnd(*valobj_sp), m_exe_ctx_ref(), m_pair_type(), | |
| m_pair_sp() { | |
| if (valobj_sp) | |
| Update(); | |
| } | |
| lldb::ChildCacheState LibstdcppMapIteratorSyntheticFrontEnd::Update() { | |
| ValueObjectSP valobj_sp = m_backend.GetSP(); | |
| if (!valobj_sp) | |
| return lldb::ChildCacheState::eRefetch; | |
| TargetSP target_sp(valobj_sp->GetTargetSP()); | |
| if (!target_sp) | |
| return lldb::ChildCacheState::eRefetch; | |
| bool is_64bit = (target_sp->GetArchitecture().GetAddressByteSize() == 8); | |
| if (!valobj_sp) | |
| return lldb::ChildCacheState::eRefetch; | |
| m_exe_ctx_ref = valobj_sp->GetExecutionContextRef(); | |
| ValueObjectSP _M_node_sp(valobj_sp->GetChildMemberWithName("_M_node")); | |
| if (!_M_node_sp) | |
| return lldb::ChildCacheState::eRefetch; | |
| m_pair_address = _M_node_sp->GetValueAsUnsigned(0); | |
| if (m_pair_address == 0) | |
| return lldb::ChildCacheState::eRefetch; | |
| m_pair_address += (is_64bit ? 32 : 16); | |
| CompilerType my_type(valobj_sp->GetCompilerType()); | |
| if (my_type.GetNumTemplateArguments() >= 1) { | |
| CompilerType pair_type = my_type.GetTypeTemplateArgument(0); | |
| if (!pair_type) | |
| return lldb::ChildCacheState::eRefetch; | |
| m_pair_type = pair_type; | |
| } else | |
| return lldb::ChildCacheState::eRefetch; | |
| return lldb::ChildCacheState::eReuse; | |
| } | |
| llvm::Expected<uint32_t> | |
| LibstdcppMapIteratorSyntheticFrontEnd::CalculateNumChildren() { | |
| return 2; | |
| } | |
| lldb::ValueObjectSP | |
| LibstdcppMapIteratorSyntheticFrontEnd::GetChildAtIndex(uint32_t idx) { | |
| if (m_pair_address != 0 && m_pair_type) { | |
| if (!m_pair_sp) | |
| m_pair_sp = CreateChildValueObjectFromAddress("pair", m_pair_address, | |
| m_exe_ctx_ref, m_pair_type); | |
| if (m_pair_sp) | |
| return m_pair_sp->GetChildAtIndex(idx); | |
| } | |
| return lldb::ValueObjectSP(); | |
| } | |
| llvm::Expected<size_t> | |
| LibstdcppMapIteratorSyntheticFrontEnd::GetIndexOfChildWithName( | |
| ConstString name) { | |
| if (name == "first") | |
| return 0; | |
| if (name == "second") | |
| return 1; | |
| return llvm::createStringErrorV("type has no child named '{0}'", name); | |
| } | |
| SyntheticChildrenFrontEnd * | |
| lldb_private::formatters::LibstdcppMapIteratorSyntheticFrontEndCreator( | |
| CXXSyntheticChildren *, lldb::ValueObjectSP valobj_sp) { | |
| return (valobj_sp ? new LibstdcppMapIteratorSyntheticFrontEnd(valobj_sp) | |
| : nullptr); | |
| } | |
| /* | |
| (lldb) fr var ibeg --ptr-depth 1 | |
| (__gnu_cxx::__normal_iterator<int *, std::vector<int, std::allocator<int> > >) | |
| ibeg = { | |
| _M_current = 0x00000001001037a0 { | |
| *_M_current = 1 | |
| } | |
| } | |
| */ | |
| SyntheticChildrenFrontEnd * | |
| lldb_private::formatters::LibStdcppVectorIteratorSyntheticFrontEndCreator( | |
| CXXSyntheticChildren *, lldb::ValueObjectSP valobj_sp) { | |
| return (valobj_sp ? new VectorIteratorSyntheticFrontEnd( | |
| valobj_sp, {ConstString("_M_current")}) | |
| : nullptr); | |
| } | |
| bool lldb_private::formatters::LibStdcppStringSummaryProvider( | |
| ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) { | |
| ValueObjectSP ptr = valobj.GetChildAtNamePath({"_M_dataplus", "_M_p"}); | |
| if (!ptr || !ptr->GetError().Success()) | |
| stream << "Summary Unavailable"; | |
| else | |
| stream << ptr->GetSummaryAsCString(); | |
| return true; | |
| } | |
| template <StringPrinter::StringElementType element_type> | |
| static bool formatStringViewImpl(ValueObject &valobj, Stream &stream, | |
| const TypeSummaryOptions &summary_options, | |
| std::string prefix_token) { | |
| auto data_sp = valobj.GetChildMemberWithName("_M_str"); | |
| auto size_sp = valobj.GetChildMemberWithName("_M_len"); | |
| if (!data_sp || !size_sp) | |
| return false; | |
| bool success = false; | |
| uint64_t size = size_sp->GetValueAsUnsigned(0, &success); | |
| if (!success) { | |
| stream << "Summary Unavailable"; | |
| return true; | |
| } | |
| StreamString scratch_stream; | |
| success = StringBufferSummaryProvider<element_type>( | |
| scratch_stream, summary_options, data_sp, size, prefix_token); | |
| if (success) | |
| stream << scratch_stream.GetData(); | |
| else | |
| stream << "Summary Unavailable"; | |
| return true; | |
| } | |
| bool lldb_private::formatters::LibStdcppWStringViewSummaryProvider( | |
| ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) { | |
| auto wchar_t_size = GetWCharByteSize(valobj); | |
| if (!wchar_t_size) | |
| return false; | |
| switch (*wchar_t_size) { | |
| case 1: | |
| return formatStringViewImpl<StringElementType::UTF8>(valobj, stream, | |
| options, "L"); | |
| case 2: | |
| return formatStringViewImpl<StringElementType::UTF16>(valobj, stream, | |
| options, "L"); | |
| case 4: | |
| return formatStringViewImpl<StringElementType::UTF32>(valobj, stream, | |
| options, "L"); | |
| } | |
| return false; | |
| } | |
| template <StringElementType element_type> | |
| static constexpr const char *getPrefixToken() { | |
| switch (element_type) { | |
| case StringElementType::ASCII: | |
| return ""; | |
| case StringElementType::UTF8: | |
| return "u8"; | |
| case StringElementType::UTF16: | |
| return "u"; | |
| case StringElementType::UTF32: | |
| return "U"; | |
| } | |
| llvm_unreachable("invalid element type"); | |
| } | |
| template <StringPrinter::StringElementType element_type> | |
| bool lldb_private::formatters::LibStdcppStringViewSummaryProvider( | |
| ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) { | |
| return formatStringViewImpl<element_type>(valobj, stream, options, | |
| getPrefixToken<element_type>()); | |
| } | |
| template bool lldb_private::formatters::LibStdcppStringViewSummaryProvider< | |
| StringElementType::ASCII>(ValueObject &, Stream &, | |
| const TypeSummaryOptions &); | |
| template bool lldb_private::formatters::LibStdcppStringViewSummaryProvider< | |
| StringElementType::UTF8>(ValueObject &, Stream &, | |
| const TypeSummaryOptions &); | |
| template bool lldb_private::formatters::LibStdcppStringViewSummaryProvider< | |
| StringElementType::UTF16>(ValueObject &, Stream &, | |
| const TypeSummaryOptions &); | |
| template bool lldb_private::formatters::LibStdcppStringViewSummaryProvider< | |
| StringElementType::UTF32>(ValueObject &, Stream &, | |
| const TypeSummaryOptions &); | |
| LibStdcppSharedPtrSyntheticFrontEnd::LibStdcppSharedPtrSyntheticFrontEnd( | |
| lldb::ValueObjectSP valobj_sp) | |
| : SyntheticChildrenFrontEnd(*valobj_sp) { | |
| if (valobj_sp) | |
| Update(); | |
| } | |
| llvm::Expected<uint32_t> | |
| LibStdcppSharedPtrSyntheticFrontEnd::CalculateNumChildren() { | |
| return 1; | |
| } | |
| lldb::ValueObjectSP | |
| LibStdcppSharedPtrSyntheticFrontEnd::GetChildAtIndex(uint32_t idx) { | |
| if (!m_ptr_obj) | |
| return nullptr; | |
| if (idx == 0) | |
| return m_ptr_obj->GetSP(); | |
| if (idx == 1) { | |
| ValueObjectSP valobj_sp = m_backend.GetSP(); | |
| if (!valobj_sp) | |
| return nullptr; | |
| Status status; | |
| ValueObjectSP value_sp = m_ptr_obj->Dereference(status); | |
| if (status.Success()) | |
| return value_sp; | |
| } | |
| return lldb::ValueObjectSP(); | |
| } | |
| lldb::ChildCacheState LibStdcppSharedPtrSyntheticFrontEnd::Update() { | |
| auto backend = m_backend.GetSP(); | |
| if (!backend) | |
| return lldb::ChildCacheState::eRefetch; | |
| auto valobj_sp = backend->GetNonSyntheticValue(); | |
| if (!valobj_sp) | |
| return lldb::ChildCacheState::eRefetch; | |
| auto ptr_obj_sp = valobj_sp->GetChildMemberWithName("_M_ptr"); | |
| if (!ptr_obj_sp) | |
| return lldb::ChildCacheState::eRefetch; | |
| auto cast_ptr_sp = GetDesugaredSmartPointerValue(*ptr_obj_sp, *valobj_sp); | |
| if (!cast_ptr_sp) | |
| return lldb::ChildCacheState::eRefetch; | |
| m_ptr_obj = cast_ptr_sp->Clone("pointer").get(); | |
| return lldb::ChildCacheState::eRefetch; | |
| } | |
| llvm::Expected<size_t> | |
| LibStdcppSharedPtrSyntheticFrontEnd::GetIndexOfChildWithName(ConstString name) { | |
| if (name == "pointer") | |
| return 0; | |
| if (name == "object" || name == "$$dereference$$") | |
| return 1; | |
| return llvm::createStringErrorV("type has no child named '{0}'", name); | |
| } | |
| SyntheticChildrenFrontEnd * | |
| lldb_private::formatters::LibStdcppSharedPtrSyntheticFrontEndCreator( | |
| CXXSyntheticChildren *, lldb::ValueObjectSP valobj_sp) { | |
| return (valobj_sp ? new LibStdcppSharedPtrSyntheticFrontEnd(valobj_sp) | |
| : nullptr); | |
| } | |
| bool lldb_private::formatters::LibStdcppSmartPointerSummaryProvider( | |
| ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) { | |
| ValueObjectSP valobj_sp(valobj.GetNonSyntheticValue()); | |
| if (!valobj_sp) | |
| return false; | |
| ValueObjectSP ptr_sp(valobj_sp->GetChildMemberWithName("_M_ptr")); | |
| if (!ptr_sp) | |
| return false; | |
| DumpCxxSmartPtrPointerSummary(stream, *ptr_sp, options); | |
| ValueObjectSP pi_sp = valobj_sp->GetChildAtNamePath({"_M_refcount", "_M_pi"}); | |
| if (!pi_sp) | |
| return false; | |
| bool success; | |
| uint64_t pi_addr = pi_sp->GetValueAsUnsigned(0, &success); | |
| // Empty control field. We're done. | |
| if (!success || pi_addr == 0) | |
| return true; | |
| int64_t shared_count = 0; | |
| if (auto count_sp = pi_sp->GetChildMemberWithName("_M_use_count")) { | |
| bool success; | |
| shared_count = count_sp->GetValueAsSigned(0, &success); | |
| if (!success) | |
| return false; | |
| stream.Printf(" strong=%" PRId64, shared_count); | |
| } | |
| // _M_weak_count is the number of weak references + (_M_use_count != 0). | |
| if (auto weak_count_sp = pi_sp->GetChildMemberWithName("_M_weak_count")) { | |
| bool success; | |
| int64_t count = weak_count_sp->GetValueAsUnsigned(0, &success); | |
| if (!success) | |
| return false; | |
| stream.Printf(" weak=%" PRId64, count - (shared_count != 0)); | |
| } | |
| return true; | |
| } | |
| static uint64_t LibStdcppVariantNposValue(size_t index_byte_size) { | |
| switch (index_byte_size) { | |
| case 1: | |
| return 0xff; | |
| case 2: | |
| return 0xffff; | |
| default: | |
| return 0xffff'ffff; | |
| } | |
| } | |
| bool formatters::LibStdcppVariantSummaryProvider( | |
| ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) { | |
| ValueObjectSP valobj_sp = valobj.GetNonSyntheticValue(); | |
| if (!valobj_sp) | |
| return false; | |
| ValueObjectSP index_obj = valobj_sp->GetChildMemberWithName("_M_index"); | |
| ValueObjectSP data_obj = valobj_sp->GetChildMemberWithName("_M_u"); | |
| if (!index_obj || !data_obj) | |
| return false; | |
| auto index_bytes_or_err = index_obj->GetByteSize(); | |
| if (!index_bytes_or_err) { | |
| LLDB_LOG_ERROR(GetLog(LLDBLog::DataFormatters), | |
| index_bytes_or_err.takeError(), | |
| "failed to get variant index byte size: {0}"); | |
| return false; | |
| } | |
| auto npos_value = LibStdcppVariantNposValue(*index_bytes_or_err); | |
| auto index = index_obj->GetValueAsUnsigned(0); | |
| if (index == npos_value) { | |
| stream.Printf(" No Value"); | |
| return true; | |
| } | |
| auto variant_type = | |
| valobj_sp->GetCompilerType().GetCanonicalType().GetNonReferenceType(); | |
| if (!variant_type) | |
| return false; | |
| if (index >= variant_type.GetNumTemplateArguments(true)) { | |
| stream.Printf(" <Invalid>"); | |
| return true; | |
| } | |
| auto active_type = variant_type.GetTypeTemplateArgument(index, true); | |
| stream << " Active Type = " << active_type.GetDisplayTypeName() << " "; | |
| return true; | |
| } | |
| static std::optional<int64_t> | |
| LibStdcppExtractOrderingValue(ValueObject &valobj) { | |
| lldb::ValueObjectSP value_sp = valobj.GetChildMemberWithName("_M_value"); | |
| if (!value_sp) | |
| return std::nullopt; | |
| bool success; | |
| int64_t value = value_sp->GetValueAsSigned(0, &success); | |
| if (!success) | |
| return std::nullopt; | |
| return value; | |
| } | |
| bool lldb_private::formatters::LibStdcppPartialOrderingSummaryProvider( | |
| ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) { | |
| std::optional<int64_t> value = LibStdcppExtractOrderingValue(valobj); | |
| if (!value) | |
| return false; | |
| switch (*value) { | |
| case -1: | |
| stream << "less"; | |
| break; | |
| case 0: | |
| stream << "equivalent"; | |
| break; | |
| case 1: | |
| stream << "greater"; | |
| break; | |
| case -128: | |
| case 2: | |
| stream << "unordered"; | |
| break; | |
| default: | |
| return false; | |
| } | |
| return true; | |
| } | |
| bool lldb_private::formatters::LibStdcppWeakOrderingSummaryProvider( | |
| ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) { | |
| std::optional<int64_t> value = LibStdcppExtractOrderingValue(valobj); | |
| if (!value) | |
| return false; | |
| switch (*value) { | |
| case -1: | |
| stream << "less"; | |
| break; | |
| case 0: | |
| stream << "equivalent"; | |
| break; | |
| case 1: | |
| stream << "greater"; | |
| break; | |
| default: | |
| return false; | |
| } | |
| return true; | |
| } | |
| bool lldb_private::formatters::LibStdcppStrongOrderingSummaryProvider( | |
| ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) { | |
| std::optional<int64_t> value = LibStdcppExtractOrderingValue(valobj); | |
| if (!value) | |
| return false; | |
| switch (*value) { | |
| case -1: | |
| stream << "less"; | |
| break; | |
| case 0: | |
| stream << "equal"; | |
| break; | |
| case 1: | |
| stream << "greater"; | |
| break; | |
| default: | |
| return false; | |
| } | |
| return true; | |
| } | |
| bool lldb_private::formatters::LibStdcppSourceLocationSummaryProvider( | |
| ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) { | |
| ValueObjectSP impl_sp = valobj.GetChildMemberWithName("_M_impl"); | |
| if (!impl_sp) | |
| return false; | |
| ValueObjectSP file_sp = impl_sp->GetChildMemberWithName("_M_file_name"); | |
| ValueObjectSP function_sp = | |
| impl_sp->GetChildMemberWithName("_M_function_name"); | |
| ValueObjectSP line_sp = impl_sp->GetChildMemberWithName("_M_line"); | |
| ValueObjectSP column_sp = impl_sp->GetChildMemberWithName("_M_column"); | |
| if (!file_sp || !function_sp || !line_sp || !column_sp) | |
| return false; | |
| bool success = false; | |
| uint64_t line = line_sp->GetValueAsUnsigned(0, &success); | |
| if (!success) | |
| return false; | |
| uint64_t column = column_sp->GetValueAsUnsigned(0, &success); | |
| if (!success) | |
| return false; | |
| const char *file = file_sp->GetSummaryAsCString(); | |
| stream.Format("{0}:{1}:{2}", file ? file : "<unknown>", line, column); | |
| if (const char *function = function_sp->GetSummaryAsCString()) | |
| stream.Printf(" (%s)", function); | |
| return true; | |
| } |