https://github.com/kpdev updated https://github.com/llvm/llvm-project/pull/67782
>From ccc9fb6be2f390cd894e0632cfded98f329f3059 Mon Sep 17 00:00:00 2001 From: Pavel Kosov <kpde...@gmail.com> Date: Wed, 10 Apr 2024 14:45:49 +0300 Subject: [PATCH] [LLDB] Add ability to update string during debugging This is the last patch needed for adding an ability to update std::string/wstring/etc during debug process. std::string/std::wstring/std::u16(32)string synthetic frontend implemented Also tests for the frontend added. ~~ Huawei RRI, OS Lab --- .../lldb/DataFormatters/TypeSynthetic.h | 4 + lldb/source/Core/ValueObject.cpp | 2 +- .../Core/ValueObjectSyntheticFilter.cpp | 2 + lldb/source/DataFormatters/FormatManager.cpp | 10 +- .../Plugins/Language/CPlusPlus/CMakeLists.txt | 1 + .../Language/CPlusPlus/CPlusPlusLanguage.cpp | 88 +++++---- .../Plugins/Language/CPlusPlus/LibCxx.cpp | 96 +--------- .../Plugins/Language/CPlusPlus/LibCxx.h | 15 ++ .../Language/CPlusPlus/LibCxxString.cpp | 171 ++++++++++++++++++ .../CPlusPlus/LibCxxStringInfoExtractor.h | 119 ++++++++++++ .../change_values/libcxx/string/Makefile | 6 + .../libcxx/string/TestChangeStringValue.py | 56 ++++++ .../change_values/libcxx/string/main.cpp | 21 +++ 13 files changed, 461 insertions(+), 130 deletions(-) create mode 100644 lldb/source/Plugins/Language/CPlusPlus/LibCxxString.cpp create mode 100644 lldb/source/Plugins/Language/CPlusPlus/LibCxxStringInfoExtractor.h create mode 100644 lldb/test/API/python_api/value/change_values/libcxx/string/Makefile create mode 100644 lldb/test/API/python_api/value/change_values/libcxx/string/TestChangeStringValue.py create mode 100644 lldb/test/API/python_api/value/change_values/libcxx/string/main.cpp diff --git a/lldb/include/lldb/DataFormatters/TypeSynthetic.h b/lldb/include/lldb/DataFormatters/TypeSynthetic.h index ede7442a02bf6af..6de32eed79942b3 100644 --- a/lldb/include/lldb/DataFormatters/TypeSynthetic.h +++ b/lldb/include/lldb/DataFormatters/TypeSynthetic.h @@ -80,6 +80,10 @@ class SyntheticChildrenFrontEnd { // display purposes virtual ConstString GetSyntheticTypeName() { return ConstString(); } + virtual bool SetValueFromCString(const char *value_str, Status &error) { + return false; + } + typedef std::shared_ptr<SyntheticChildrenFrontEnd> SharedPointer; typedef std::unique_ptr<SyntheticChildrenFrontEnd> AutoPointer; diff --git a/lldb/source/Core/ValueObject.cpp b/lldb/source/Core/ValueObject.cpp index f39bd07a255366a..dca15e4d427b26d 100644 --- a/lldb/source/Core/ValueObject.cpp +++ b/lldb/source/Core/ValueObject.cpp @@ -1461,7 +1461,7 @@ bool ValueObject::SetValueFromCString(const char *value_str, Status &error) { if (value_type == Value::ValueType::Scalar) { // If the value is already a scalar, then let the scalar change itself: m_value.GetScalar().SetValueFromCString(value_str, encoding, byte_size); - } else if (byte_size <= 16) { + } else if (byte_size <= 16 && encoding != eEncodingInvalid) { // If the value fits in a scalar, then make a new scalar and again let the // scalar code do the conversion, then figure out where to put the new // value. diff --git a/lldb/source/Core/ValueObjectSyntheticFilter.cpp b/lldb/source/Core/ValueObjectSyntheticFilter.cpp index adac1b400705e20..f2d7e240200693f 100644 --- a/lldb/source/Core/ValueObjectSyntheticFilter.cpp +++ b/lldb/source/Core/ValueObjectSyntheticFilter.cpp @@ -379,6 +379,8 @@ bool ValueObjectSynthetic::CanProvideValue() { bool ValueObjectSynthetic::SetValueFromCString(const char *value_str, Status &error) { + if (m_synth_filter_up->SetValueFromCString(value_str, error)) + return true; return m_parent->SetValueFromCString(value_str, error); } diff --git a/lldb/source/DataFormatters/FormatManager.cpp b/lldb/source/DataFormatters/FormatManager.cpp index d7ba5b4b70c949c..8b2be03694ede56 100644 --- a/lldb/source/DataFormatters/FormatManager.cpp +++ b/lldb/source/DataFormatters/FormatManager.cpp @@ -504,9 +504,13 @@ bool FormatManager::ShouldPrintAsOneLiner(ValueObject &valobj) { // wait.. wat? just get out of here.. if (!synth_sp) return false; - // but if we only have them to provide a value, keep going - if (!synth_sp->MightHaveChildren() && - synth_sp->DoesProvideSyntheticValue()) + // but if they can fit in one line or ... + if (auto format = synth_sp->GetSummaryFormat()) { + is_synth_val = format->IsOneLiner(); + } + // ... if we only have them to provide a value, keep going + else if (!synth_sp->MightHaveChildren() && + synth_sp->DoesProvideSyntheticValue()) is_synth_val = true; else return false; diff --git a/lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt b/lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt index 0c6fdb2b9573152..6987838b758ebde 100644 --- a/lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt +++ b/lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt @@ -15,6 +15,7 @@ add_lldb_library(lldbPluginCPlusPlusLanguage PLUGIN LibCxxRangesRefView.cpp LibCxxSliceArray.cpp LibCxxSpan.cpp + LibCxxString.cpp LibCxxTuple.cpp LibCxxUnorderedMap.cpp LibCxxVariant.cpp diff --git a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp index 4a536096a066ffb..25c4f18ec3321d7 100644 --- a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp +++ b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp @@ -645,51 +645,52 @@ static void LoadLibCxxFormatters(lldb::TypeCategoryImplSP cpp_category_sp) { .SetShowMembersOneLiner(false) .SetHideItemNames(false); + static ConstString std_string_regex{ + "^std::__[[:alnum:]]+::string$" + "|" + "^std::__[[:alnum:]]+::basic_string<char, " + "std::__[[:alnum:]]+::char_traits<char>, " + "std::__[[:alnum:]]+::allocator<char> >$" + "|" + "^std::__[[:alnum:]]+::basic_string<unsigned char, " + "std::__[[:alnum:]]+::char_traits<unsigned char>, " + "std::__[[:alnum:]]+::allocator<unsigned char> >$"}; + + static ConstString std_u16string_regex{ + "^std::__[[:alnum:]]+::basic_string<char16_t, " + "std::__[[:alnum:]]+::char_traits<char16_t>, " + "std::__[[:alnum:]]+::allocator<char16_t> >$"}; + + static ConstString std_u32string_regex{ + "^std::__[[:alnum:]]+::basic_string<char32_t, " + "std::__[[:alnum:]]+::char_traits<char32_t>, " + "std::__[[:alnum:]]+::allocator<char32_t> >$"}; + + static ConstString std_wstring_regex{ + "^std::__[[:alnum:]]+::wstring$" + "|" + "^std::__[[:alnum:]]+::basic_string<wchar_t, " + "std::__[[:alnum:]]+::char_traits<wchar_t>, " + "std::__[[:alnum:]]+::allocator<wchar_t> >$"}; + AddCXXSummary(cpp_category_sp, lldb_private::formatters::LibcxxStringSummaryProviderASCII, - "std::string summary provider", "^std::__[[:alnum:]]+::string$", - stl_summary_flags, true); - AddCXXSummary(cpp_category_sp, - lldb_private::formatters::LibcxxStringSummaryProviderASCII, - "std::string summary provider", - "^std::__[[:alnum:]]+::basic_string<char, " - "std::__[[:alnum:]]+::char_traits<char>, " - "std::__[[:alnum:]]+::allocator<char> >$", - stl_summary_flags, true); - AddCXXSummary(cpp_category_sp, - lldb_private::formatters::LibcxxStringSummaryProviderASCII, - "std::string summary provider", - "^std::__[[:alnum:]]+::basic_string<unsigned char, " - "std::__[[:alnum:]]+::char_traits<unsigned char>, " - "std::__[[:alnum:]]+::allocator<unsigned char> >$", + "std::string summary provider", std_string_regex, stl_summary_flags, true); AddCXXSummary(cpp_category_sp, lldb_private::formatters::LibcxxStringSummaryProviderUTF16, - "std::u16string summary provider", - "^std::__[[:alnum:]]+::basic_string<char16_t, " - "std::__[[:alnum:]]+::char_traits<char16_t>, " - "std::__[[:alnum:]]+::allocator<char16_t> >$", + "std::u16string summary provider", std_u16string_regex, stl_summary_flags, true); AddCXXSummary(cpp_category_sp, lldb_private::formatters::LibcxxStringSummaryProviderUTF32, - "std::u32string summary provider", - "^std::__[[:alnum:]]+::basic_string<char32_t, " - "std::__[[:alnum:]]+::char_traits<char32_t>, " - "std::__[[:alnum:]]+::allocator<char32_t> >$", + "std::u32string summary provider", std_u32string_regex, stl_summary_flags, true); AddCXXSummary(cpp_category_sp, lldb_private::formatters::LibcxxWStringSummaryProvider, - "std::wstring summary provider", - "^std::__[[:alnum:]]+::wstring$", stl_summary_flags, true); - AddCXXSummary(cpp_category_sp, - lldb_private::formatters::LibcxxWStringSummaryProvider, - "std::wstring summary provider", - "^std::__[[:alnum:]]+::basic_string<wchar_t, " - "std::__[[:alnum:]]+::char_traits<wchar_t>, " - "std::__[[:alnum:]]+::allocator<wchar_t> >$", + "std::wstring summary provider", std_wstring_regex, stl_summary_flags, true); AddCXXSummary(cpp_category_sp, @@ -991,6 +992,31 @@ static void LoadLibCxxFormatters(lldb::TypeCategoryImplSP cpp_category_sp) { "std::unordered_map iterator synthetic children", "^std::__[[:alnum:]]+::__hash_map_(const_)?iterator<.+>$", stl_synth_flags, true); + + AddCXXSynthetic( + cpp_category_sp, + lldb_private::formatters::LibcxxStdStringSyntheticFrontEndCreator, + "std::string synthetic children", std_string_regex, stl_synth_flags, + true); + + AddCXXSynthetic( + cpp_category_sp, + lldb_private::formatters::LibcxxStdU16StringSyntheticFrontEndCreator, + "std::u16string synthetic children", std_u16string_regex, stl_synth_flags, + true); + + AddCXXSynthetic( + cpp_category_sp, + lldb_private::formatters::LibcxxStdU32StringSyntheticFrontEndCreator, + "std::u32string synthetic children", std_u32string_regex, stl_synth_flags, + true); + + AddCXXSynthetic( + cpp_category_sp, + lldb_private::formatters::LibcxxStdWStringSyntheticFrontEndCreator, + "std::wstring synthetic children", std_wstring_regex, stl_synth_flags, + true); + // Chrono duration typedefs cpp_category_sp->AddTypeSummary( "^std::__[[:alnum:]]+::chrono::nanoseconds", eFormatterMatchRegex, diff --git a/lldb/source/Plugins/Language/CPlusPlus/LibCxx.cpp b/lldb/source/Plugins/Language/CPlusPlus/LibCxx.cpp index d2d50152c07cf89..1d7c0b25d589ea5 100644 --- a/lldb/source/Plugins/Language/CPlusPlus/LibCxx.cpp +++ b/lldb/source/Plugins/Language/CPlusPlus/LibCxx.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include "LibCxx.h" +#include "LibCxxStringInfoExtractor.h" #include "lldb/Core/Debugger.h" #include "lldb/Core/FormatEntity.h" @@ -742,101 +743,6 @@ bool lldb_private::formatters::LibcxxContainerSummaryProvider( nullptr, nullptr, &valobj, false, false); } -/// The field layout in a libc++ string (cap, side, data or data, size, cap). -namespace { -enum class StringLayout { CSD, DSC }; -} - -/// 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_r_sp = valobj.GetChildMemberWithName("__r_"); - if (!valobj_r_sp || !valobj_r_sp->GetError().Success()) - return {}; - - // __r_ is a compressed_pair of the actual data and the allocator. The data we - // want is in the first base class. - ValueObjectSP valobj_r_base_sp = valobj_r_sp->GetChildAtIndex(0); - if (!valobj_r_base_sp) - return {}; - - ValueObjectSP valobj_rep_sp = - valobj_r_base_sp->GetChildMemberWithName("__value_"); - if (!valobj_rep_sp) - return {}; - - ValueObjectSP l = valobj_rep_sp->GetChildMemberWithName("__l"); - if (!l) - return {}; - - StringLayout layout = l->GetIndexOfChildWithName("__data_") == 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); - - // 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 = - location_sp->GetCompilerType().GetByteSize( - exe_ctx.GetBestExecutionContextScope()); - if (!max_bytes || size > *max_bytes || !location_sp) - 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); -} - static bool LibcxxWStringSummaryProvider(ValueObject &valobj, Stream &stream, const TypeSummaryOptions &summary_options, diff --git a/lldb/source/Plugins/Language/CPlusPlus/LibCxx.h b/lldb/source/Plugins/Language/CPlusPlus/LibCxx.h index d8b807d180e0683..893b478d5dab3f3 100644 --- a/lldb/source/Plugins/Language/CPlusPlus/LibCxx.h +++ b/lldb/source/Plugins/Language/CPlusPlus/LibCxx.h @@ -293,6 +293,21 @@ bool LibcxxChronoYearMonthDaySummaryProvider( ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options); // libc++ std::chrono::year_month_day +SyntheticChildrenFrontEnd * +LibcxxStdStringSyntheticFrontEndCreator(CXXSyntheticChildren *, + lldb::ValueObjectSP); + +SyntheticChildrenFrontEnd * +LibcxxStdWStringSyntheticFrontEndCreator(CXXSyntheticChildren *, + lldb::ValueObjectSP); + +SyntheticChildrenFrontEnd * +LibcxxStdU16StringSyntheticFrontEndCreator(CXXSyntheticChildren *, + lldb::ValueObjectSP); + +SyntheticChildrenFrontEnd * +LibcxxStdU32StringSyntheticFrontEndCreator(CXXSyntheticChildren *, + lldb::ValueObjectSP); } // namespace formatters } // namespace lldb_private diff --git a/lldb/source/Plugins/Language/CPlusPlus/LibCxxString.cpp b/lldb/source/Plugins/Language/CPlusPlus/LibCxxString.cpp new file mode 100644 index 000000000000000..2b0ef58ea99a59d --- /dev/null +++ b/lldb/source/Plugins/Language/CPlusPlus/LibCxxString.cpp @@ -0,0 +1,171 @@ +#include "LibCxx.h" +#include "LibCxxStringInfoExtractor.h" + +#include "lldb/DataFormatters/FormattersHelpers.h" +#include <unordered_map> + +using namespace lldb; +using namespace lldb_private; + +namespace { + +class StringFrontend : public SyntheticChildrenFrontEnd { + +public: + StringFrontend(ValueObject &valobj, const char *prefix = "") + : SyntheticChildrenFrontEnd(valobj), m_prefix(prefix) {} + + llvm::Expected<uint32_t> CalculateNumChildren() override { + return m_size + m_special_members_count; + } + + lldb::ValueObjectSP GetChildAtIndex(uint32_t idx) override { + + if (idx < m_special_members_count) { + return m_backend.GetChildMemberWithName(ConstString("__r_"), + /*can_create=*/true); + } + + idx -= m_special_members_count; + + if (!m_str_data_ptr || idx > m_size || !m_element_size) { + return {}; + } + + auto char_it = m_chars.find(idx); + if (char_it != m_chars.end()) { + return char_it->second; + } + + uint64_t offset = idx * m_element_size; + uint64_t address = m_str_data_ptr->GetValueAsUnsigned(0); + + if (!address) { + return {}; + } + + StreamString name; + name.Printf("[%" PRIu64 "]", (uint64_t)idx); + + m_chars[idx] = CreateValueObjectFromAddress( + name.GetString(), address + offset, m_backend.GetExecutionContextRef(), + m_element_type); + + return m_chars[idx]; + } + + size_t GetIndexOfChildWithName(ConstString name) override { + if (name == "__r_") { + return 0; + } + return formatters::ExtractIndexFromString(name.GetCString()) + + m_special_members_count; + } + + ChildCacheState Update() override { + + clear(); + + auto string_info = ExtractLibcxxStringInfo(m_backend); + if (!string_info) + return ChildCacheState::eRefetch; + std::tie(m_size, m_str_data_ptr) = *string_info; + + m_element_type = m_backend.GetCompilerType().GetTypeTemplateArgument(0); + m_element_size = m_element_type.GetByteSize(nullptr).value_or(0); + + if (m_str_data_ptr->IsArrayType()) { + // this means the string is in short-mode and the + // data is stored inline in array, + // so we need address of this array + Status status; + m_str_data_ptr = m_str_data_ptr->AddressOf(status); + } + + return ChildCacheState::eReuse; + } + + bool MightHaveChildren() override { return true; } + + bool SetValueFromCString(const char *value_str, Status &error) override { + + ValueObjectSP expr_value_sp; + + std::unique_lock<std::recursive_mutex> lock; + ExecutionContext exe_ctx(m_backend.GetExecutionContextRef(), lock); + + Target *target = exe_ctx.GetTargetPtr(); + StackFrame *frame = exe_ctx.GetFramePtr(); + + if (target && frame) { + EvaluateExpressionOptions options; + options.SetUseDynamic(frame->CalculateTarget()->GetPreferDynamicValue()); + options.SetIgnoreBreakpoints(true); + + if (target->GetLanguage() != eLanguageTypeUnknown) + options.SetLanguage(target->GetLanguage()); + else + options.SetLanguage(frame->GetLanguage()); + StreamString expr; + expr.Printf("%s = %s\"%s\"", m_backend.GetName().AsCString(), m_prefix, + value_str); + ExpressionResults result = target->EvaluateExpression( + expr.GetString(), frame, expr_value_sp, options); + if (result != eExpressionCompleted) + error.SetErrorStringWithFormat("Expression (%s) can't be evaluated.", + expr.GetData()); + } + + return error.Success(); + } + +private: + void clear() { + m_size = 0; + m_element_size = 0; + m_str_data_ptr = nullptr; + m_element_type.Clear(); + m_chars.clear(); + } + + std::unordered_map<uint32_t, ValueObjectSP> m_chars; + ValueObjectSP m_str_data_ptr; + CompilerType m_element_type; + uint32_t m_size = 0; + uint32_t m_element_size = 0; + const char *m_prefix = ""; + static const uint32_t m_special_members_count = + 1; // __r_ member needed for correct summaries +}; + +} // namespace + +SyntheticChildrenFrontEnd *formatters::LibcxxStdStringSyntheticFrontEndCreator( + CXXSyntheticChildren *, lldb::ValueObjectSP valobj_sp) { + if (valobj_sp) + return new StringFrontend(*valobj_sp); + return nullptr; +} + +SyntheticChildrenFrontEnd *formatters::LibcxxStdWStringSyntheticFrontEndCreator( + CXXSyntheticChildren *, lldb::ValueObjectSP valobj_sp) { + if (valobj_sp) + return new StringFrontend(*valobj_sp, "L"); + return nullptr; +} + +SyntheticChildrenFrontEnd * +formatters::LibcxxStdU16StringSyntheticFrontEndCreator( + CXXSyntheticChildren *, lldb::ValueObjectSP valobj_sp) { + if (valobj_sp) + return new StringFrontend(*valobj_sp, "u"); + return nullptr; +} + +SyntheticChildrenFrontEnd * +formatters::LibcxxStdU32StringSyntheticFrontEndCreator( + CXXSyntheticChildren *, lldb::ValueObjectSP valobj_sp) { + if (valobj_sp) + return new StringFrontend(*valobj_sp, "U"); + return nullptr; +} diff --git a/lldb/source/Plugins/Language/CPlusPlus/LibCxxStringInfoExtractor.h b/lldb/source/Plugins/Language/CPlusPlus/LibCxxStringInfoExtractor.h new file mode 100644 index 000000000000000..d140e37872721d3 --- /dev/null +++ b/lldb/source/Plugins/Language/CPlusPlus/LibCxxStringInfoExtractor.h @@ -0,0 +1,119 @@ + +#ifndef LLDB_SOURCE_PLUGINS_LANGUAGE_CPLUSPLUS_LIBCXXSTRINGINFOEXTRACTOR_H +#define LLDB_SOURCE_PLUGINS_LANGUAGE_CPLUSPLUS_LIBCXXSTRINGINFOEXTRACTOR_H + +#include "lldb/Core/ValueObject.h" +#include "lldb/lldb-forward.h" + +#include <optional> +#include <utility> + +using namespace lldb; +using namespace lldb_private; + +/// The field layout in a libc++ string (cap, side, data or data, size, cap). +namespace { +enum class StringLayout { CSD, DSC }; +} + +/// 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_r_sp = + valobj.GetChildMemberWithName(ConstString("__r_"), /*can_create=*/true); + if (!valobj_r_sp || !valobj_r_sp->GetError().Success()) + return {}; + + // __r_ is a compressed_pair of the actual data and the allocator. The data we + // want is in the first base class. + ValueObjectSP valobj_r_base_sp = + valobj_r_sp->GetChildAtIndex(0, /*can_create=*/true); + if (!valobj_r_base_sp) + return {}; + + ValueObjectSP valobj_rep_sp = valobj_r_base_sp->GetChildMemberWithName( + ConstString("__value_"), /*can_create=*/true); + if (!valobj_rep_sp) + return {}; + + ValueObjectSP l = valobj_rep_sp->GetChildMemberWithName(ConstString("__l"), + /*can_create=*/true); + if (!l) + return {}; + + StringLayout layout = l->GetIndexOfChildWithName(ConstString("__data_")) == 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( + ConstString("__s"), /*can_create=*/true); + if (!short_sp) + return {}; + + ValueObjectSP is_long = + short_sp->GetChildMemberWithName(ConstString("__is_long_"), true); + ValueObjectSP size_sp = + short_sp->GetChildAtNamePath({ConstString("__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(ConstString("__data_"), true); + if (using_bitmasks) + size = (layout == StringLayout::DSC) ? size_mode_value + : ((size_mode_value >> 1) % 256); + + // 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 = + location_sp->GetCompilerType().GetByteSize( + exe_ctx.GetBestExecutionContextScope()); + if (!max_bytes || size > *max_bytes || !location_sp) + return {}; + + return std::make_pair(size, location_sp); + } + + // we can use the layout_decider object as the data pointer + ValueObjectSP location_sp = + l->GetChildMemberWithName(ConstString("__data_"), /*can_create=*/true); + ValueObjectSP size_vo = + l->GetChildMemberWithName(ConstString("__size_"), /*can_create=*/true); + ValueObjectSP capacity_vo = + l->GetChildMemberWithName(ConstString("__cap_"), /*can_create=*/true); + 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); +} + +#endif // LLDB_SOURCE_PLUGINS_LANGUAGE_CPLUSPLUS_LIBCXXSTRINGINFOEXTRACTOR_H diff --git a/lldb/test/API/python_api/value/change_values/libcxx/string/Makefile b/lldb/test/API/python_api/value/change_values/libcxx/string/Makefile new file mode 100644 index 000000000000000..564cbada74e080e --- /dev/null +++ b/lldb/test/API/python_api/value/change_values/libcxx/string/Makefile @@ -0,0 +1,6 @@ +CXX_SOURCES := main.cpp + +USE_LIBCPP := 1 + +CXXFLAGS_EXTRAS := -O0 +include Makefile.rules diff --git a/lldb/test/API/python_api/value/change_values/libcxx/string/TestChangeStringValue.py b/lldb/test/API/python_api/value/change_values/libcxx/string/TestChangeStringValue.py new file mode 100644 index 000000000000000..33497a44f7b12ce --- /dev/null +++ b/lldb/test/API/python_api/value/change_values/libcxx/string/TestChangeStringValue.py @@ -0,0 +1,56 @@ +""" +Test change libc++ string values. +""" + +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil + +class LibcxxChangeStringValueTestCase(TestBase): + + def setUp(self): + # Call super's setUp(). + TestBase.setUp(self) + + def do_test_value(self, frame, var_name, new_value, str_prefix): + str_value = frame.FindVariable(var_name) + self.assertTrue(str_value.IsValid(), "Got the SBValue for {}".format(var_name)) + + # update whole string + err = lldb.SBError() + result = str_value.SetValueFromCString(new_value, err) + self.assertTrue(result, "Setting val returned error: {}".format(err)) + result = str_value.GetSummary() # str_value is a summary + expected = '{}"{}"'.format(str_prefix, new_value) + self.assertTrue(result == expected, "Got value: ({}), expected: ({})" + .format(result, expected)) + + @add_test_categories(["libc++"]) + @expectedFailureAll(oslist=["windows"], archs=["arm"], bugnumber="llvm.org/pr24772") + @expectedFailureAll(archs=["arm"]) # arm can't jit + def test(self): + """Test that we can change values of libc++ string.""" + self.build() + self.runCmd("file " + self.getBuildArtifact("a.out"), CURRENT_EXECUTABLE_SET) + bkpt = self.target().FindBreakpointByID( + lldbutil.run_break_set_by_source_regexp( + self, "Set break point at this line.")) + + self.runCmd("run", RUN_SUCCEEDED) + + # Get Frame #0. + target = self.dbg.GetSelectedTarget() + process = target.GetProcess() + self.assertState(process.GetState(), lldb.eStateStopped) + thread = lldbutil.get_stopped_thread( + process, lldb.eStopReasonBreakpoint) + self.assertTrue( + thread.IsValid(), + "There should be a thread stopped due to breakpoint condition") + frame0 = thread.GetFrameAtIndex(0) + self.assertTrue(frame0.IsValid(), "Got a valid frame.") + + for var_name, str_prefix in zip(("s", "l", "ws", "wl", "u16s", "u32s"), + ('', '', 'L', 'L', 'u', 'U')): + self.do_test_value(frame0, var_name, "new_value", str_prefix) diff --git a/lldb/test/API/python_api/value/change_values/libcxx/string/main.cpp b/lldb/test/API/python_api/value/change_values/libcxx/string/main.cpp new file mode 100644 index 000000000000000..7e3d143e97d8c88 --- /dev/null +++ b/lldb/test/API/python_api/value/change_values/libcxx/string/main.cpp @@ -0,0 +1,21 @@ +#include <string> + +using namespace std; + +int main() { + // Currently changing value for string requires + // string's operator= to be in debug executable + string s; + string l; + wstring ws; + wstring wl; + u16string u16s; + u32string u32s; + s = "small"; + l = "looooooooooooooooooooooooooooooooong"; + ws = L"wsmall"; + wl = L"wlooooooooooooooooooooooooooooooooong"; + u16s = u"small"; + u32s = U"looooooooooooooooooooooooooooooooong"; + return 0; // Set break point at this line. +} _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits