| //===-- LibCxx.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 "LibCxx.h" | |
| #include "Plugins/Language/CPlusPlus/CxxStringTypes.h" | |
| #include "Plugins/Language/CPlusPlus/Generic.h" | |
| #include "Plugins/Language/CPlusPlus/VectorIterator.h" | |
| #include "Plugins/LanguageRuntime/CPlusPlus/CPPLanguageRuntime.h" | |
| #include "lldb/Core/Debugger.h" | |
| #include "lldb/Core/FormatEntity.h" | |
| #include "lldb/DataFormatters/FormattersHelpers.h" | |
| #include "lldb/DataFormatters/StringPrinter.h" | |
| #include "lldb/DataFormatters/TypeSummary.h" | |
| #include "lldb/Target/SectionLoadList.h" | |
| #include "lldb/Target/Target.h" | |
| #include "lldb/Utility/ConstString.h" | |
| #include "lldb/Utility/DataBufferHeap.h" | |
| #include "lldb/Utility/Endian.h" | |
| #include "lldb/Utility/Status.h" | |
| #include "lldb/Utility/Stream.h" | |
| #include "lldb/ValueObject/ValueObject.h" | |
| #include "lldb/ValueObject/ValueObjectConstResult.h" | |
| #include "lldb/lldb-enumerations.h" | |
| #include "lldb/lldb-forward.h" | |
| #include "llvm/Support/ErrorExtras.h" | |
| #include <optional> | |
| #include <tuple> | |
| using namespace lldb; | |
| using namespace lldb_private; | |
| using namespace lldb_private::formatters; | |
| static void consumeInlineNamespace(llvm::StringRef &name) { | |
| // Delete past an inline namespace, if any: __[a-zA-Z0-9_]+:: | |
| auto scratch = name; | |
| if (scratch.consume_front("__") && | |
| std::isalnum(static_cast<unsigned char>(scratch[0]))) { | |
| scratch = scratch.drop_while( | |
| [](char c) { return std::isalnum(static_cast<unsigned char>(c)); }); | |
| if (scratch.consume_front("::")) { | |
| // Successfully consumed a namespace. | |
| name = scratch; | |
| } | |
| } | |
| } | |
| bool lldb_private::formatters::isStdTemplate(ConstString type_name, | |
| llvm::StringRef type) { | |
| llvm::StringRef name = type_name.GetStringRef(); | |
| // The type name may be prefixed with `std::__<inline-namespace>::`. | |
| if (name.consume_front("std::")) | |
| consumeInlineNamespace(name); | |
| return name.consume_front(type) && name.starts_with("<"); | |
| } | |
| lldb::ValueObjectSP lldb_private::formatters::GetChildMemberWithName( | |
| ValueObject &obj, llvm::ArrayRef<ConstString> alternative_names) { | |
| for (ConstString name : alternative_names) { | |
| lldb::ValueObjectSP child_sp = obj.GetChildMemberWithName(name); | |
| if (child_sp) | |
| return child_sp; | |
| } | |
| return {}; | |
| } | |
| lldb::ValueObjectSP | |
| lldb_private::formatters::GetFirstValueOfLibCXXCompressedPair( | |
| ValueObject &pair) { | |
| ValueObjectSP value; | |
| ValueObjectSP first_child = pair.GetChildAtIndex(0); | |
| if (first_child) | |
| value = first_child->GetChildMemberWithName("__value_"); | |
| if (!value) { | |
| // pre-c88580c member name | |
| value = pair.GetChildMemberWithName("__first_"); | |
| } | |
| return value; | |
| } | |
| lldb::ValueObjectSP | |
| lldb_private::formatters::GetSecondValueOfLibCXXCompressedPair( | |
| ValueObject &pair) { | |
| ValueObjectSP value; | |
| if (pair.GetNumChildrenIgnoringErrors() > 1) { | |
| ValueObjectSP second_child = pair.GetChildAtIndex(1); | |
| if (second_child) { | |
| value = second_child->GetChildMemberWithName("__value_"); | |
| } | |
| } | |
| if (!value) { | |
| // pre-c88580c member name | |
| value = pair.GetChildMemberWithName("__second_"); | |
| } | |
| return value; | |
| } | |
| std::pair<lldb::ValueObjectSP, bool> | |
| lldb_private::formatters::GetValueOrOldCompressedPair( | |
| ValueObject &obj, llvm::StringRef child_name, | |
| llvm::StringRef compressed_pair_name) { | |
| auto is_old_compressed_pair = [](ValueObject &pair_obj) -> bool { | |
| return isStdTemplate(pair_obj.GetTypeName(), "__compressed_pair"); | |
| }; | |
| ValueObjectSP node_sp(obj.GetChildMemberWithName(child_name)); | |
| if (node_sp) | |
| return {node_sp, is_old_compressed_pair(*node_sp)}; | |
| // Try the even older __compressed_pair layout. | |
| assert(!compressed_pair_name.empty()); | |
| node_sp = obj.GetChildMemberWithName(compressed_pair_name); | |
| // Unrecognized layout (possibly older than LLDB supports). | |
| if (!node_sp) | |
| return {nullptr, false}; | |
| // Expected old compressed_pair layout, but got something else. | |
| if (!is_old_compressed_pair(*node_sp)) | |
| return {nullptr, false}; | |
| return {node_sp, true}; | |
| } | |
| bool lldb_private::formatters::LibcxxFunctionSummaryProvider( | |
| ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) { | |
| ValueObjectSP valobj_sp(valobj.GetNonSyntheticValue()); | |
| if (!valobj_sp) | |
| return false; | |
| ExecutionContext exe_ctx(valobj_sp->GetExecutionContextRef()); | |
| Process *process = exe_ctx.GetProcessPtr(); | |
| if (process == nullptr) | |
| return false; | |
| CPPLanguageRuntime *cpp_runtime = CPPLanguageRuntime::Get(*process); | |
| if (!cpp_runtime) | |
| return false; | |
| CPPLanguageRuntime::LibCppStdFunctionCallableInfo callable_info = | |
| cpp_runtime->FindLibCppStdFunctionCallableInfo(valobj_sp); | |
| switch (callable_info.callable_case) { | |
| case CPPLanguageRuntime::LibCppStdFunctionCallableCase::Invalid: | |
| stream.Printf(" __f_ = %" PRIu64, callable_info.member_f_pointer_value); | |
| return false; | |
| break; | |
| case CPPLanguageRuntime::LibCppStdFunctionCallableCase::Lambda: | |
| stream.Format(" Lambda in File {0} at Line {1}", | |
| callable_info.callable_line_entry.GetFile().GetFilename(), | |
| callable_info.callable_line_entry.line); | |
| break; | |
| case CPPLanguageRuntime::LibCppStdFunctionCallableCase::CallableObject: | |
| stream.Format(" Function in File {0} at Line {1}", | |
| callable_info.callable_line_entry.GetFile().GetFilename(), | |
| callable_info.callable_line_entry.line); | |
| break; | |
| case CPPLanguageRuntime::LibCppStdFunctionCallableCase::FreeOrMemberFunction: | |
| stream.Printf(" Function = %s ", | |
| callable_info.callable_symbol.GetName().GetCString()); | |
| break; | |
| } | |
| return true; | |
| } | |
| bool lldb_private::formatters::LibcxxSmartPointerSummaryProvider( | |
| ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) { | |
| ValueObjectSP valobj_sp(valobj.GetNonSyntheticValue()); | |
| if (!valobj_sp) | |
| return false; | |
| ValueObjectSP ptr_sp(valobj_sp->GetChildMemberWithName("__ptr_")); | |
| ValueObjectSP ctrl_sp(valobj_sp->GetChildMemberWithName("__cntrl_")); | |
| if (!ctrl_sp || !ptr_sp) | |
| return false; | |
| DumpCxxSmartPtrPointerSummary(stream, *ptr_sp, options); | |
| bool success; | |
| uint64_t ctrl_addr = ctrl_sp->GetValueAsUnsigned(0, &success); | |
| // Empty control field. We're done. | |
| if (!success || ctrl_addr == 0) | |
| return true; | |
| if (auto count_sp = ctrl_sp->GetChildMemberWithName("__shared_owners_")) { | |
| bool success; | |
| uint64_t count = count_sp->GetValueAsUnsigned(0, &success); | |
| if (!success) | |
| return false; | |
| // std::shared_ptr releases the underlying resource when the | |
| // __shared_owners_ count hits -1. So `__shared_owners_ == 0` indicates 1 | |
| // owner. Hence add +1 here. | |
| stream.Printf(" strong=%" PRIu64, count + 1); | |
| } | |
| if (auto weak_count_sp = | |
| ctrl_sp->GetChildMemberWithName("__shared_weak_owners_")) { | |
| bool success; | |
| uint64_t count = weak_count_sp->GetValueAsUnsigned(0, &success); | |
| if (!success) | |
| return false; | |
| // Unlike __shared_owners_, __shared_weak_owners_ indicates the exact | |
| // std::weak_ptr reference count. | |
| stream.Printf(" weak=%" PRIu64, count); | |
| } | |
| return true; | |
| } | |
| bool lldb_private::formatters::LibcxxUniquePointerSummaryProvider( | |
| ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) { | |
| ValueObjectSP valobj_sp(valobj.GetNonSyntheticValue()); | |
| if (!valobj_sp) | |
| return false; | |
| auto [ptr_sp, is_compressed_pair] = | |
| GetValueOrOldCompressedPair(*valobj_sp, "__ptr_", "__ptr_"); | |
| if (!ptr_sp) | |
| return false; | |
| if (is_compressed_pair) | |
| ptr_sp = GetFirstValueOfLibCXXCompressedPair(*ptr_sp); | |
| if (!ptr_sp) | |
| return false; | |
| DumpCxxSmartPtrPointerSummary(stream, *ptr_sp, options); | |
| return true; | |
| } | |
| static std::optional<int64_t> LibcxxExtractOrderingValue(ValueObject &valobj) { | |
| lldb::ValueObjectSP value_sp = valobj.GetChildMemberWithName("__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::LibcxxPartialOrderingSummaryProvider( | |
| ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) { | |
| std::optional<int64_t> value = LibcxxExtractOrderingValue(valobj); | |
| if (!value) | |
| return false; | |
| switch (*value) { | |
| case -1: | |
| stream << "less"; | |
| break; | |
| case 0: | |
| stream << "equivalent"; | |
| break; | |
| case 1: | |
| stream << "greater"; | |
| break; | |
| case -127: | |
| stream << "unordered"; | |
| break; | |
| default: | |
| return false; | |
| } | |
| return true; | |
| } | |
| bool lldb_private::formatters::LibcxxWeakOrderingSummaryProvider( | |
| ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) { | |
| std::optional<int64_t> value = LibcxxExtractOrderingValue(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::LibcxxStrongOrderingSummaryProvider( | |
| ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) { | |
| std::optional<int64_t> value = LibcxxExtractOrderingValue(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; | |
| } | |
| /* | |
| (lldb) fr var ibeg --raw --ptr-depth 1 -T | |
| (std::__1::__wrap_iter<int *>) ibeg = { | |
| (std::__1::__wrap_iter<int *>::iterator_type) __i = 0x00000001001037a0 { | |
| (int) *__i = 1 | |
| } | |
| } | |
| */ | |
| SyntheticChildrenFrontEnd * | |
| lldb_private::formatters::LibCxxVectorIteratorSyntheticFrontEndCreator( | |
| CXXSyntheticChildren *, lldb::ValueObjectSP valobj_sp) { | |
| return (valobj_sp ? new VectorIteratorSyntheticFrontEnd( | |
| valobj_sp, {ConstString("__i_"), ConstString("__i")}) | |
| : nullptr); | |
| } | |
| lldb_private::formatters::LibcxxSharedPtrSyntheticFrontEnd:: | |
| LibcxxSharedPtrSyntheticFrontEnd(lldb::ValueObjectSP valobj_sp) | |
| : SyntheticChildrenFrontEnd(*valobj_sp), m_cntrl(nullptr), | |
| m_ptr_obj(nullptr) { | |
| if (valobj_sp) | |
| Update(); | |
| } | |
| llvm::Expected<uint32_t> lldb_private::formatters:: | |
| LibcxxSharedPtrSyntheticFrontEnd::CalculateNumChildren() { | |
| return (m_cntrl ? 1 : 0); | |
| } | |
| lldb::ValueObjectSP | |
| lldb_private::formatters::LibcxxSharedPtrSyntheticFrontEnd::GetChildAtIndex( | |
| uint32_t idx) { | |
| if (!m_cntrl || !m_ptr_obj) | |
| return lldb::ValueObjectSP(); | |
| ValueObjectSP valobj_sp = m_backend.GetSP(); | |
| if (!valobj_sp) | |
| return lldb::ValueObjectSP(); | |
| if (idx == 0) | |
| return m_ptr_obj->GetSP(); | |
| if (idx == 1) { | |
| Status status; | |
| ValueObjectSP value_sp = m_ptr_obj->Dereference(status); | |
| if (status.Success()) | |
| return value_sp; | |
| } | |
| return lldb::ValueObjectSP(); | |
| } | |
| lldb::ChildCacheState | |
| lldb_private::formatters::LibcxxSharedPtrSyntheticFrontEnd::Update() { | |
| m_cntrl = nullptr; | |
| m_ptr_obj = nullptr; | |
| 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; | |
| auto ptr_obj_sp = valobj_sp->GetChildMemberWithName("__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(); | |
| lldb::ValueObjectSP cntrl_sp(valobj_sp->GetChildMemberWithName("__cntrl_")); | |
| m_cntrl = cntrl_sp.get(); // need to store the raw pointer to avoid a circular | |
| // dependency | |
| return lldb::ChildCacheState::eRefetch; | |
| } | |
| llvm::Expected<size_t> | |
| lldb_private::formatters::LibcxxSharedPtrSyntheticFrontEnd:: | |
| 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); | |
| } | |
| lldb_private::formatters::LibcxxSharedPtrSyntheticFrontEnd:: | |
| ~LibcxxSharedPtrSyntheticFrontEnd() = default; | |
| SyntheticChildrenFrontEnd * | |
| lldb_private::formatters::LibcxxSharedPtrSyntheticFrontEndCreator( | |
| CXXSyntheticChildren *, lldb::ValueObjectSP valobj_sp) { | |
| return (valobj_sp ? new LibcxxSharedPtrSyntheticFrontEnd(valobj_sp) | |
| : nullptr); | |
| } | |
| lldb_private::formatters::LibcxxUniquePtrSyntheticFrontEnd:: | |
| LibcxxUniquePtrSyntheticFrontEnd(lldb::ValueObjectSP valobj_sp) | |
| : SyntheticChildrenFrontEnd(*valobj_sp) { | |
| if (valobj_sp) | |
| Update(); | |
| } | |
| lldb_private::formatters::LibcxxUniquePtrSyntheticFrontEnd:: | |
| ~LibcxxUniquePtrSyntheticFrontEnd() = default; | |
| SyntheticChildrenFrontEnd * | |
| lldb_private::formatters::LibcxxUniquePtrSyntheticFrontEndCreator( | |
| CXXSyntheticChildren *, lldb::ValueObjectSP valobj_sp) { | |
| return (valobj_sp ? new LibcxxUniquePtrSyntheticFrontEnd(valobj_sp) | |
| : nullptr); | |
| } | |
| llvm::Expected<uint32_t> lldb_private::formatters:: | |
| LibcxxUniquePtrSyntheticFrontEnd::CalculateNumChildren() { | |
| if (m_value_ptr_sp) | |
| return m_deleter_sp ? 2 : 1; | |
| return 0; | |
| } | |
| lldb::ValueObjectSP | |
| lldb_private::formatters::LibcxxUniquePtrSyntheticFrontEnd::GetChildAtIndex( | |
| uint32_t idx) { | |
| if (!m_value_ptr_sp) | |
| return lldb::ValueObjectSP(); | |
| if (idx == 0) | |
| return m_value_ptr_sp; | |
| if (idx == 1) | |
| return m_deleter_sp; | |
| if (idx == 2) { | |
| Status status; | |
| auto value_sp = m_value_ptr_sp->Dereference(status); | |
| if (status.Success()) { | |
| return value_sp; | |
| } | |
| } | |
| return lldb::ValueObjectSP(); | |
| } | |
| lldb::ChildCacheState | |
| lldb_private::formatters::LibcxxUniquePtrSyntheticFrontEnd::Update() { | |
| ValueObjectSP valobj_sp = m_backend.GetSP(); | |
| if (!valobj_sp) | |
| return lldb::ChildCacheState::eRefetch; | |
| auto [ptr_sp, is_compressed_pair] = | |
| GetValueOrOldCompressedPair(*valobj_sp, "__ptr_", "__ptr_"); | |
| if (!ptr_sp) | |
| return lldb::ChildCacheState::eRefetch; | |
| // Retrieve the actual pointer and the deleter, and clone them to give them | |
| // user-friendly names. | |
| if (is_compressed_pair) { | |
| if (ValueObjectSP value_pointer_sp = | |
| GetFirstValueOfLibCXXCompressedPair(*ptr_sp)) | |
| m_value_ptr_sp = value_pointer_sp->Clone("pointer"); | |
| if (ValueObjectSP deleter_sp = | |
| GetSecondValueOfLibCXXCompressedPair(*ptr_sp)) | |
| m_deleter_sp = deleter_sp->Clone("deleter"); | |
| } else { | |
| m_value_ptr_sp = ptr_sp->Clone("pointer"); | |
| if (ValueObjectSP deleter_sp = | |
| valobj_sp->GetChildMemberWithName("__deleter_")) | |
| if (deleter_sp->GetNumChildrenIgnoringErrors() > 0) | |
| m_deleter_sp = deleter_sp->Clone("deleter"); | |
| } | |
| return lldb::ChildCacheState::eRefetch; | |
| } | |
| llvm::Expected<size_t> | |
| lldb_private::formatters::LibcxxUniquePtrSyntheticFrontEnd:: | |
| GetIndexOfChildWithName(ConstString name) { | |
| if (name == "pointer") | |
| return 0; | |
| if (name == "deleter") | |
| return 1; | |
| if (name == "obj" || name == "object" || name == "$$dereference$$") | |
| return 2; | |
| return llvm::createStringErrorV("type has no child named '{0}'", name); | |
| } | |
| /// The field layout in a libc++ string (cap, side, data or data, size, cap). | |
| namespace { | |
| enum class StringLayout { CSD, DSC }; | |
| } | |
| static ValueObjectSP ExtractLibCxxStringData(ValueObject &valobj) { | |
| auto [valobj_r_sp, is_compressed_pair] = | |
| GetValueOrOldCompressedPair(valobj, "__rep_", "__r_"); | |
| if (!valobj_r_sp) | |
| return nullptr; | |
| if (is_compressed_pair) | |
| return GetFirstValueOfLibCXXCompressedPair(*valobj_r_sp); | |
| return valobj_r_sp; | |
| } | |
| /// Determine the size in bytes of \p valobj (a libc++ std::string object) and | |
| /// extract its data payload. Return the size + payload pair. | |
| // TODO: Support big-endian architectures. | |
| static std::optional<std::pair<uint64_t, ValueObjectSP>> | |
| ExtractLibcxxStringInfo(ValueObject &valobj) { | |
| ValueObjectSP valobj_rep_sp = ExtractLibCxxStringData(valobj); | |
| if (!valobj_rep_sp || !valobj_rep_sp->GetError().Success()) | |
| return {}; | |
| ValueObjectSP l = valobj_rep_sp->GetChildMemberWithName("__l"); | |
| if (!l) | |
| return {}; | |
| auto index_or_err = l->GetIndexOfChildWithName("__data_"); | |
| if (!index_or_err) { | |
| LLDB_LOG_ERROR(GetLog(LLDBLog::DataFormatters), index_or_err.takeError(), | |
| "{0}"); | |
| return {}; | |
| } | |
| StringLayout layout = | |
| *index_or_err == 0 ? StringLayout::DSC : StringLayout::CSD; | |
| bool short_mode = false; // this means the string is in short-mode and the | |
| // data is stored inline | |
| bool using_bitmasks = true; // Whether the class uses bitmasks for the mode | |
| // flag (pre-D123580). | |
| uint64_t size; | |
| uint64_t size_mode_value = 0; | |
| ValueObjectSP short_sp = valobj_rep_sp->GetChildMemberWithName("__s"); | |
| if (!short_sp) | |
| return {}; | |
| ValueObjectSP is_long = short_sp->GetChildMemberWithName("__is_long_"); | |
| ValueObjectSP size_sp = short_sp->GetChildMemberWithName("__size_"); | |
| if (!size_sp) | |
| return {}; | |
| if (is_long) { | |
| using_bitmasks = false; | |
| short_mode = !is_long->GetValueAsUnsigned(/*fail_value=*/0); | |
| size = size_sp->GetValueAsUnsigned(/*fail_value=*/0); | |
| } else { | |
| // The string mode is encoded in the size field. | |
| size_mode_value = size_sp->GetValueAsUnsigned(0); | |
| uint8_t mode_mask = layout == StringLayout::DSC ? 0x80 : 1; | |
| short_mode = (size_mode_value & mode_mask) == 0; | |
| } | |
| if (short_mode) { | |
| ValueObjectSP location_sp = short_sp->GetChildMemberWithName("__data_"); | |
| if (using_bitmasks) | |
| size = (layout == StringLayout::DSC) ? size_mode_value | |
| : ((size_mode_value >> 1) % 256); | |
| if (!location_sp) | |
| return {}; | |
| // When the small-string optimization takes place, the data must fit in the | |
| // inline string buffer (23 bytes on x86_64/Darwin). If it doesn't, it's | |
| // likely that the string isn't initialized and we're reading garbage. | |
| ExecutionContext exe_ctx(location_sp->GetExecutionContextRef()); | |
| const std::optional<uint64_t> max_bytes = | |
| llvm::expectedToOptional(location_sp->GetCompilerType().GetByteSize( | |
| exe_ctx.GetBestExecutionContextScope())); | |
| if (!max_bytes || size > *max_bytes) | |
| return {}; | |
| return std::make_pair(size, location_sp); | |
| } | |
| // we can use the layout_decider object as the data pointer | |
| ValueObjectSP location_sp = l->GetChildMemberWithName("__data_"); | |
| ValueObjectSP size_vo = l->GetChildMemberWithName("__size_"); | |
| ValueObjectSP capacity_vo = l->GetChildMemberWithName("__cap_"); | |
| if (!size_vo || !location_sp || !capacity_vo) | |
| return {}; | |
| size = size_vo->GetValueAsUnsigned(LLDB_INVALID_OFFSET); | |
| uint64_t capacity = capacity_vo->GetValueAsUnsigned(LLDB_INVALID_OFFSET); | |
| if (!using_bitmasks && layout == StringLayout::CSD) | |
| capacity *= 2; | |
| if (size == LLDB_INVALID_OFFSET || capacity == LLDB_INVALID_OFFSET || | |
| capacity < size) | |
| return {}; | |
| return std::make_pair(size, location_sp); | |
| } | |
| bool lldb_private::formatters::LibcxxWStringSummaryProvider( | |
| ValueObject &valobj, Stream &stream, | |
| const TypeSummaryOptions &summary_options) { | |
| auto string_info = ExtractLibcxxStringInfo(valobj); | |
| if (!string_info) | |
| return false; | |
| uint64_t size; | |
| ValueObjectSP location_sp; | |
| std::tie(size, location_sp) = *string_info; | |
| auto wchar_t_size = GetWCharByteSize(valobj); | |
| if (!wchar_t_size) | |
| return false; | |
| switch (*wchar_t_size) { | |
| case 1: | |
| return StringBufferSummaryProvider<StringPrinter::StringElementType::UTF8>( | |
| stream, summary_options, location_sp, size, "L"); | |
| case 2: | |
| return StringBufferSummaryProvider<StringPrinter::StringElementType::UTF16>( | |
| stream, summary_options, location_sp, size, "L"); | |
| case 4: | |
| return StringBufferSummaryProvider<StringPrinter::StringElementType::UTF32>( | |
| stream, summary_options, location_sp, size, "L"); | |
| } | |
| return false; | |
| } | |
| template <StringPrinter::StringElementType element_type> | |
| static bool | |
| LibcxxStringSummaryProvider(ValueObject &valobj, Stream &stream, | |
| const TypeSummaryOptions &summary_options, | |
| std::string prefix_token) { | |
| auto string_info = ExtractLibcxxStringInfo(valobj); | |
| if (!string_info) | |
| return false; | |
| uint64_t size; | |
| ValueObjectSP location_sp; | |
| std::tie(size, location_sp) = *string_info; | |
| return StringBufferSummaryProvider<element_type>( | |
| stream, summary_options, location_sp, size, prefix_token); | |
| } | |
| template <StringPrinter::StringElementType element_type> | |
| static bool formatStringImpl(ValueObject &valobj, Stream &stream, | |
| const TypeSummaryOptions &summary_options, | |
| std::string prefix_token) { | |
| StreamString scratch_stream; | |
| const bool success = LibcxxStringSummaryProvider<element_type>( | |
| valobj, scratch_stream, summary_options, prefix_token); | |
| if (success) | |
| stream << scratch_stream.GetData(); | |
| else | |
| stream << "Summary Unavailable"; | |
| return true; | |
| } | |
| bool lldb_private::formatters::LibcxxStringSummaryProviderASCII( | |
| ValueObject &valobj, Stream &stream, | |
| const TypeSummaryOptions &summary_options) { | |
| return formatStringImpl<StringPrinter::StringElementType::ASCII>( | |
| valobj, stream, summary_options, ""); | |
| } | |
| bool lldb_private::formatters::LibcxxStringSummaryProviderUTF16( | |
| ValueObject &valobj, Stream &stream, | |
| const TypeSummaryOptions &summary_options) { | |
| return formatStringImpl<StringPrinter::StringElementType::UTF16>( | |
| valobj, stream, summary_options, "u"); | |
| } | |
| bool lldb_private::formatters::LibcxxStringSummaryProviderUTF32( | |
| ValueObject &valobj, Stream &stream, | |
| const TypeSummaryOptions &summary_options) { | |
| return formatStringImpl<StringPrinter::StringElementType::UTF32>( | |
| valobj, stream, summary_options, "U"); | |
| } | |
| static std::tuple<bool, ValueObjectSP, size_t> | |
| LibcxxExtractStringViewData(ValueObject& valobj) { | |
| auto dataobj = GetChildMemberWithName( | |
| valobj, {ConstString("__data_"), ConstString("__data")}); | |
| auto sizeobj = GetChildMemberWithName( | |
| valobj, {ConstString("__size_"), ConstString("__size")}); | |
| if (!dataobj || !sizeobj) | |
| return std::make_tuple<bool,ValueObjectSP,size_t>(false, {}, {}); | |
| if (!dataobj->GetError().Success() || !sizeobj->GetError().Success()) | |
| return std::make_tuple<bool,ValueObjectSP,size_t>(false, {}, {}); | |
| bool success{false}; | |
| uint64_t size = sizeobj->GetValueAsUnsigned(0, &success); | |
| if (!success) | |
| return std::make_tuple<bool,ValueObjectSP,size_t>(false, {}, {}); | |
| return std::make_tuple(true,dataobj,size); | |
| } | |
| template <StringPrinter::StringElementType element_type> | |
| static bool formatStringViewImpl(ValueObject &valobj, Stream &stream, | |
| const TypeSummaryOptions &summary_options, | |
| std::string prefix_token) { | |
| bool success; | |
| ValueObjectSP dataobj; | |
| size_t size; | |
| std::tie(success, dataobj, size) = LibcxxExtractStringViewData(valobj); | |
| if (!success) { | |
| stream << "Summary Unavailable"; | |
| return true; | |
| } | |
| return StringBufferSummaryProvider<element_type>(stream, summary_options, | |
| dataobj, size, prefix_token); | |
| } | |
| bool lldb_private::formatters::LibcxxStringViewSummaryProviderASCII( | |
| ValueObject &valobj, Stream &stream, | |
| const TypeSummaryOptions &summary_options) { | |
| return formatStringViewImpl<StringPrinter::StringElementType::ASCII>( | |
| valobj, stream, summary_options, ""); | |
| } | |
| bool lldb_private::formatters::LibcxxStringViewSummaryProviderUTF16( | |
| ValueObject &valobj, Stream &stream, | |
| const TypeSummaryOptions &summary_options) { | |
| return formatStringViewImpl<StringPrinter::StringElementType::UTF16>( | |
| valobj, stream, summary_options, "u"); | |
| } | |
| bool lldb_private::formatters::LibcxxStringViewSummaryProviderUTF32( | |
| ValueObject &valobj, Stream &stream, | |
| const TypeSummaryOptions &summary_options) { | |
| return formatStringViewImpl<StringPrinter::StringElementType::UTF32>( | |
| valobj, stream, summary_options, "U"); | |
| } | |
| bool lldb_private::formatters::LibcxxWStringViewSummaryProvider( | |
| ValueObject &valobj, Stream &stream, | |
| const TypeSummaryOptions &summary_options) { | |
| bool success; | |
| ValueObjectSP dataobj; | |
| size_t size; | |
| std::tie(success, dataobj, size) = LibcxxExtractStringViewData(valobj); | |
| if (!success) { | |
| stream << "Summary Unavailable"; | |
| return true; | |
| } | |
| auto wchar_t_size = GetWCharByteSize(valobj); | |
| if (!wchar_t_size) | |
| return false; | |
| switch (*wchar_t_size) { | |
| case 1: | |
| return StringBufferSummaryProvider<StringPrinter::StringElementType::UTF8>( | |
| stream, summary_options, dataobj, size, "L"); | |
| case 2: | |
| return StringBufferSummaryProvider<StringPrinter::StringElementType::UTF16>( | |
| stream, summary_options, dataobj, size, "L"); | |
| case 4: | |
| return StringBufferSummaryProvider<StringPrinter::StringElementType::UTF32>( | |
| stream, summary_options, dataobj, size, "L"); | |
| } | |
| return false; | |
| } | |
| static bool | |
| LibcxxChronoTimePointSecondsSummaryProvider(ValueObject &valobj, Stream &stream, | |
| const TypeSummaryOptions &options, | |
| const char *fmt) { | |
| ValueObjectSP ptr_sp = valobj.GetChildMemberWithName("__d_"); | |
| if (!ptr_sp) | |
| return false; | |
| ptr_sp = ptr_sp->GetChildMemberWithName("__rep_"); | |
| if (!ptr_sp) | |
| return false; | |
| #ifndef _WIN32 | |
| // The date time in the chrono library is valid in the range | |
| // [-32767-01-01T00:00:00Z, 32767-12-31T23:59:59Z]. A 64-bit time_t has a | |
| // larger range, the function strftime is not able to format the entire range | |
| // of time_t. The exact point has not been investigated; it's limited to | |
| // chrono's range. | |
| const std::time_t chrono_timestamp_min = | |
| -1'096'193'779'200; // -32767-01-01T00:00:00Z | |
| const std::time_t chrono_timestamp_max = | |
| 971'890'963'199; // 32767-12-31T23:59:59Z | |
| #else | |
| const std::time_t chrono_timestamp_min = -43'200; // 1969-12-31T12:00:00Z | |
| const std::time_t chrono_timestamp_max = | |
| 32'536'850'399; // 3001-01-19T21:59:59 | |
| #endif | |
| const std::time_t seconds = ptr_sp->GetValueAsSigned(0); | |
| if (seconds < chrono_timestamp_min || seconds > chrono_timestamp_max) | |
| stream.Printf("timestamp=%" PRId64 " s", static_cast<int64_t>(seconds)); | |
| else { | |
| std::array<char, 128> str; | |
| std::size_t size = | |
| std::strftime(str.data(), str.size(), fmt, gmtime(&seconds)); | |
| if (size == 0) | |
| return false; | |
| stream.Printf("date/time=%s timestamp=%" PRId64 " s", str.data(), | |
| static_cast<int64_t>(seconds)); | |
| } | |
| return true; | |
| } | |
| bool lldb_private::formatters::LibcxxChronoSysSecondsSummaryProvider( | |
| ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) { | |
| return LibcxxChronoTimePointSecondsSummaryProvider(valobj, stream, options, | |
| "%FT%H:%M:%SZ"); | |
| } | |
| bool lldb_private::formatters::LibcxxChronoLocalSecondsSummaryProvider( | |
| ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) { | |
| return LibcxxChronoTimePointSecondsSummaryProvider(valobj, stream, options, | |
| "%FT%H:%M:%S"); | |
| } | |
| static bool | |
| LibcxxChronoTimepointDaysSummaryProvider(ValueObject &valobj, Stream &stream, | |
| const TypeSummaryOptions &options, | |
| const char *fmt) { | |
| ValueObjectSP ptr_sp = valobj.GetChildMemberWithName("__d_"); | |
| if (!ptr_sp) | |
| return false; | |
| ptr_sp = ptr_sp->GetChildMemberWithName("__rep_"); | |
| if (!ptr_sp) | |
| return false; | |
| #ifndef _WIN32 | |
| // The date time in the chrono library is valid in the range | |
| // [-32767-01-01Z, 32767-12-31Z]. A 32-bit time_t has a larger range, the | |
| // function strftime is not able to format the entire range of time_t. The | |
| // exact point has not been investigated; it's limited to chrono's range. | |
| const int chrono_timestamp_min = -12'687'428; // -32767-01-01Z | |
| const int chrono_timestamp_max = 11'248'737; // 32767-12-31Z | |
| #else | |
| const int chrono_timestamp_min = 0; // 1970-01-01Z | |
| const int chrono_timestamp_max = 376'583; // 3001-01-19Z | |
| #endif | |
| const int days = ptr_sp->GetValueAsSigned(0); | |
| if (days < chrono_timestamp_min || days > chrono_timestamp_max) | |
| stream.Printf("timestamp=%d days", days); | |
| else { | |
| const std::time_t seconds = std::time_t(86400) * days; | |
| std::array<char, 128> str; | |
| std::size_t size = | |
| std::strftime(str.data(), str.size(), fmt, gmtime(&seconds)); | |
| if (size == 0) | |
| return false; | |
| stream.Printf("date=%s timestamp=%d days", str.data(), days); | |
| } | |
| return true; | |
| } | |
| bool lldb_private::formatters::LibcxxChronoSysDaysSummaryProvider( | |
| ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) { | |
| return LibcxxChronoTimepointDaysSummaryProvider(valobj, stream, options, | |
| "%FZ"); | |
| } | |
| bool lldb_private::formatters::LibcxxChronoLocalDaysSummaryProvider( | |
| ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) { | |
| return LibcxxChronoTimepointDaysSummaryProvider(valobj, stream, options, | |
| "%F"); | |
| } | |
| bool lldb_private::formatters::LibcxxChronoMonthSummaryProvider( | |
| ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) { | |
| // FIXME: These are the names used in the C++20 ostream operator. Since LLVM | |
| // uses C++17 it's not possible to use the ostream operator directly. | |
| static const std::array<std::string_view, 12> months = { | |
| "January", "February", "March", "April", "May", "June", | |
| "July", "August", "September", "October", "November", "December"}; | |
| ValueObjectSP ptr_sp = valobj.GetChildMemberWithName("__m_"); | |
| if (!ptr_sp) | |
| return false; | |
| const unsigned month = ptr_sp->GetValueAsUnsigned(0); | |
| if (month >= 1 && month <= 12) | |
| stream << "month=" << months[month - 1]; | |
| else | |
| stream.Printf("month=%u", month); | |
| return true; | |
| } | |
| bool lldb_private::formatters::LibcxxChronoWeekdaySummaryProvider( | |
| ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) { | |
| // FIXME: These are the names used in the C++20 ostream operator. Since LLVM | |
| // uses C++17 it's not possible to use the ostream operator directly. | |
| static const std::array<std::string_view, 7> weekdays = { | |
| "Sunday", "Monday", "Tuesday", "Wednesday", | |
| "Thursday", "Friday", "Saturday"}; | |
| ValueObjectSP ptr_sp = valobj.GetChildMemberWithName("__wd_"); | |
| if (!ptr_sp) | |
| return false; | |
| const unsigned weekday = ptr_sp->GetValueAsUnsigned(0); | |
| if (weekday < 7) | |
| stream << "weekday=" << weekdays[weekday]; | |
| else | |
| stream.Printf("weekday=%u", weekday); | |
| return true; | |
| } | |
| bool lldb_private::formatters::LibcxxChronoYearMonthDaySummaryProvider( | |
| ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) { | |
| ValueObjectSP ptr_sp = valobj.GetChildMemberWithName("__y_"); | |
| if (!ptr_sp) | |
| return false; | |
| ptr_sp = ptr_sp->GetChildMemberWithName("__y_"); | |
| if (!ptr_sp) | |
| return false; | |
| int year = ptr_sp->GetValueAsSigned(0); | |
| ptr_sp = valobj.GetChildMemberWithName("__m_"); | |
| if (!ptr_sp) | |
| return false; | |
| ptr_sp = ptr_sp->GetChildMemberWithName("__m_"); | |
| if (!ptr_sp) | |
| return false; | |
| const unsigned month = ptr_sp->GetValueAsUnsigned(0); | |
| ptr_sp = valobj.GetChildMemberWithName("__d_"); | |
| if (!ptr_sp) | |
| return false; | |
| ptr_sp = ptr_sp->GetChildMemberWithName("__d_"); | |
| if (!ptr_sp) | |
| return false; | |
| const unsigned day = ptr_sp->GetValueAsUnsigned(0); | |
| stream << "date="; | |
| if (year < 0) { | |
| stream << '-'; | |
| year = -year; | |
| } | |
| stream.Printf("%04d-%02u-%02u", year, month, day); | |
| return true; | |
| } | |
| bool lldb_private::formatters::LibcxxSourceLocationSummaryProvider( | |
| ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) { | |
| ValueObjectSP ptr_sp = valobj.GetChildMemberWithName("__ptr_"); | |
| if (!ptr_sp) | |
| return false; | |
| ValueObjectSP file_sp = ptr_sp->GetChildMemberWithName("_M_file_name"); | |
| ValueObjectSP function_sp = | |
| ptr_sp->GetChildMemberWithName("_M_function_name"); | |
| ValueObjectSP line_sp = ptr_sp->GetChildMemberWithName("_M_line"); | |
| ValueObjectSP column_sp = ptr_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; | |
| } |