[Lldb-commits] [lldb] [lldb] Add support for updating string during debug process (PR #67782)
Pavel Kosov via lldb-commits
lldb-commits at lists.llvm.org
Wed Apr 10 04:49:19 PDT 2024
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 <kpdev42 at 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.
+}
More information about the lldb-commits
mailing list