[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
Fri Sep 29 03:19:33 PDT 2023


https://github.com/kpdev created https://github.com/llvm/llvm-project/pull/67782

Before this update strings has only DataFormatters, and therefore the way they were printed was slightly different (see comment here: https://github.com/llvm/llvm-project/blob/main/lldb/source/DataFormatters/FormatManager.cpp#L498), so tests which rely on this formatting also changed. std::string/std::wstring/std::u16(32)string synthetic frontend implemented Also tests for the frontend added.

~~

Huawei RRI, OS Lab

>From 433d3973716cc46e1ffd7044898189f845e536f6 Mon Sep 17 00:00:00 2001
From: Pavel Kosov <kpdev42 at gmail.com>
Date: Fri, 29 Sep 2023 13:09:00 +0300
Subject: [PATCH] [lldb] Add support for updating string during debug process

Before this update strings has only DataFormatters,
and therefore the way they were printed was slightly different (see comment here:
https://github.com/llvm/llvm-project/blob/main/lldb/source/DataFormatters/FormatManager.cpp#L498),
so tests which rely on this formatting also changed.
std::string/std::wstring/std::u16(32)string synthetic frontend implemented
Also tests for the frontend added.

~~

Huawei RRI, OS Lab
---
 lldb/include/lldb/Core/ValueObject.h          |  11 ++
 .../lldb/DataFormatters/TypeSynthetic.h       |   8 +-
 lldb/source/Core/ValueObject.cpp              |   8 +-
 .../Plugins/Language/CPlusPlus/CMakeLists.txt |   1 +
 .../Language/CPlusPlus/CPlusPlusLanguage.cpp  |  87 +++++----
 .../Plugins/Language/CPlusPlus/LibCxx.cpp     |  96 +---------
 .../Plugins/Language/CPlusPlus/LibCxx.h       |  15 ++
 .../Language/CPlusPlus/LibCxxString.cpp       | 171 ++++++++++++++++++
 .../CPlusPlus/LibCxxStringInfoExtractor.h     | 119 ++++++++++++
 lldb/source/Utility/Scalar.cpp                |  11 +-
 .../TestDataFormatterGenericMultiMap.py       | 105 +++++++----
 .../libcxx/map/TestDataFormatterLibccMap.py   | 112 ++++++++----
 .../TestDataFormatterLibcxxSharedPtr.py       |   2 +-
 .../TestDataFormatterLibcxxUniquePtr.py       |   2 +-
 .../change_values/libcxx/string/Makefile      |   6 +
 .../libcxx/string/TestChangeStringValue.py    |  71 ++++++++
 .../change_values/libcxx/string/main.cpp      |  21 +++
 17 files changed, 648 insertions(+), 198 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/Core/ValueObject.h b/lldb/include/lldb/Core/ValueObject.h
index 3af94f0a86e2fcc..892f5d0dea4f650 100644
--- a/lldb/include/lldb/Core/ValueObject.h
+++ b/lldb/include/lldb/Core/ValueObject.h
@@ -589,6 +589,14 @@ class ValueObject {
 
   virtual bool IsSynthetic() { return false; }
 
+  void SetSyntheticFrontend(SyntheticChildrenFrontEnd *synth_front) {
+    m_synthetic_frontend = synth_front;
+  }
+
+  SyntheticChildrenFrontEnd *GetSyntheticFrontend() const {
+    return m_synthetic_frontend;
+  }
+
   lldb::ValueObjectSP
   GetQualifiedRepresentationIfAvailable(lldb::DynamicValueType dynValue,
                                         bool synthValue);
@@ -898,6 +906,9 @@ class ValueObject {
   /// Unique identifier for every value object.
   UserID m_id;
 
+  // If frontend exist - we may try to update our value through it
+  SyntheticChildrenFrontEnd *m_synthetic_frontend = nullptr;
+
   // Utility class for initializing all bitfields in ValueObject's constructors.
   // FIXME: This could be done via default initializers once we have C++20.
   struct Bitflags {
diff --git a/lldb/include/lldb/DataFormatters/TypeSynthetic.h b/lldb/include/lldb/DataFormatters/TypeSynthetic.h
index 41be9b7efda8fdb..3a19804b22c196c 100644
--- a/lldb/include/lldb/DataFormatters/TypeSynthetic.h
+++ b/lldb/include/lldb/DataFormatters/TypeSynthetic.h
@@ -34,7 +34,9 @@ class SyntheticChildrenFrontEnd {
 
 public:
   SyntheticChildrenFrontEnd(ValueObject &backend)
-      : m_backend(backend), m_valid(true) {}
+      : m_backend(backend), m_valid(true) {
+    backend.SetSyntheticFrontend(this);
+  }
 
   virtual ~SyntheticChildrenFrontEnd() = default;
 
@@ -75,6 +77,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 ebfc1cf4d6fe9e1..ba88f848c0fb191 100644
--- a/lldb/source/Core/ValueObject.cpp
+++ b/lldb/source/Core/ValueObject.cpp
@@ -1479,7 +1479,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.
@@ -1535,7 +1535,11 @@ bool ValueObject::SetValueFromCString(const char *value_str, Status &error) {
     }
   } else {
     // We don't support setting things bigger than a scalar at present.
-    error.SetErrorString("unable to write aggregate data type");
+    // But maybe our frontend knows how to update the value.
+    SyntheticChildrenFrontEnd *frontend = GetSyntheticFrontend();
+    if (frontend) {
+      return frontend->SetValueFromCString(value_str, error);
+    }
     return false;
   }
 
diff --git a/lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt b/lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt
index 21108b27896a1a9..224f6bd205d7d2e 100644
--- a/lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt
+++ b/lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt
@@ -14,6 +14,7 @@ add_lldb_library(lldbPluginCPlusPlusLanguage PLUGIN
   LibCxxQueue.cpp
   LibCxxRangesRefView.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 3d709e3d6759556..f847774d1eff063 100644
--- a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
+++ b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
@@ -646,51 +646,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,
@@ -981,6 +982,30 @@ 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);
 }
 
 static void LoadLibStdcppFormatters(lldb::TypeCategoryImplSP cpp_category_sp) {
diff --git a/lldb/source/Plugins/Language/CPlusPlus/LibCxx.cpp b/lldb/source/Plugins/Language/CPlusPlus/LibCxx.cpp
index cae17ef992b215e..f733ed14c742a4b 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"
@@ -739,101 +740,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 f65801e2cb1b9cf..1e98d7017548fee 100644
--- a/lldb/source/Plugins/Language/CPlusPlus/LibCxx.h
+++ b/lldb/source/Plugins/Language/CPlusPlus/LibCxx.h
@@ -261,6 +261,21 @@ SyntheticChildrenFrontEnd *
 LibcxxStdRangesRefViewSyntheticFrontEndCreator(CXXSyntheticChildren *,
                                                lldb::ValueObjectSP);
 
+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..2df8a352d98186c
--- /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) {}
+
+  size_t CalculateNumChildren() override {
+    return m_size + m_special_members_count;
+  }
+
+  lldb::ValueObjectSP GetChildAtIndex(size_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;
+  }
+
+  bool Update() override {
+
+    clear();
+
+    auto string_info = ExtractLibcxxStringInfo(m_backend);
+    if (!string_info)
+      return false;
+    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 false;
+  }
+
+  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<char, ValueObjectSP> m_chars;
+  ValueObjectSP m_str_data_ptr;
+  CompilerType m_element_type;
+  size_t m_size = 0;
+  size_t m_element_size = 0;
+  const char *m_prefix = "";
+  static const size_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/source/Utility/Scalar.cpp b/lldb/source/Utility/Scalar.cpp
index 791c0fb74352913..33d0b832bfd9be6 100644
--- a/lldb/source/Utility/Scalar.cpp
+++ b/lldb/source/Utility/Scalar.cpp
@@ -18,6 +18,7 @@
 #include "llvm/ADT/SmallString.h"
 #include "llvm/ADT/StringExtras.h"
 
+#include <cctype> // for std::isalpha
 #include <cinttypes>
 #include <cstdio>
 
@@ -646,7 +647,15 @@ Status Scalar::SetValueFromCString(const char *value_str, Encoding encoding,
     bool is_signed = encoding == eEncodingSint;
     bool is_negative = is_signed && str.consume_front("-");
     APInt integer;
-    if (str.getAsInteger(0, integer)) {
+    if (str.size() == 1 && std::isalpha(static_cast<unsigned char>(str[0]))) {
+      // We can represent single character as Scalar -
+      // this is useful when working with symbols in string
+      // NOTE: it is okay to consider char size as 8-bit since we only have
+      // `SetValueFrom C String` api, not the `C Wstring` or something like
+      // that. If we can ever get wide characters here - we have to modify this
+      // behaviour somehow.
+      integer = APInt(8, static_cast<uint64_t>(str[0]));
+    } else if (str.getAsInteger(0, integer)) {
       error.SetErrorStringWithFormatv(
           "'{0}' is not a valid integer string value", value_str);
       break;
diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/multimap/TestDataFormatterGenericMultiMap.py b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/multimap/TestDataFormatterGenericMultiMap.py
index e7d00560ae39b9d..942d256d9050290 100644
--- a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/multimap/TestDataFormatterGenericMultiMap.py
+++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/multimap/TestDataFormatterGenericMultiMap.py
@@ -180,23 +180,36 @@ def cleanup():
             "frame variable si",
             substrs=[
                 multimap,
-                "size=4",
-                '[0] = (first = "one", second = 1)',
-                '[1] = (first = "three", second = 3)',
-                '[2] = (first = "two", second = 2)',
-                '[3] = (first = "zero", second = 0)',
+                'size=4',
+                '[0] = ',
+                'first = "one"',
+                'second = 1',
+                '[1] = ',
+                'first = "three"',
+                'second = 3',
+                '[2] = ',
+                'first = "two"',
+                'second = 2',
+                '[3] = ',
+                'first = "zero"',
+                'second = 0',
             ],
         )
 
-        self.expect(
-            "expression si",
-            substrs=[
-                multimap,
-                "size=4",
-                '[0] = (first = "one", second = 1)',
-                '[1] = (first = "three", second = 3)',
-                '[2] = (first = "two", second = 2)',
-                '[3] = (first = "zero", second = 0)',
+        self.expect("p si",
+                substrs=[multimap, 'size=4',
+                '[0] = ',
+                'first = "one"',
+                'second = 1',
+                '[1] = ',
+                'first = "three"',
+                'second = 3',
+                '[2] = ',
+                'first = "two"',
+                'second = 2',
+                '[3] = ',
+                'first = "zero"',
+                'second = 0',
             ],
         )
 
@@ -231,11 +244,19 @@ def cleanup():
             "frame variable is",
             substrs=[
                 multimap,
-                "size=4",
-                '[0] = (first = 1, second = "is")',
-                '[1] = (first = 2, second = "smart")',
-                '[2] = (first = 3, second = "!!!")',
-                '[3] = (first = 85, second = "goofy")',
+                'size=4',
+                '[0] = ',
+                'first = 1',
+                'second = "is"',
+                '[1] = ',
+                'first = 2',
+                'second = "smart"',
+                '[2] = ',
+                'first = 3',
+                'second = "!!!"',
+                '[3] = ',
+                'first = 85',
+                'second = "goofy"',
             ],
         )
 
@@ -243,11 +264,19 @@ def cleanup():
             "expression is",
             substrs=[
                 multimap,
-                "size=4",
-                '[0] = (first = 1, second = "is")',
-                '[1] = (first = 2, second = "smart")',
-                '[2] = (first = 3, second = "!!!")',
-                '[3] = (first = 85, second = "goofy")',
+                'size=4',
+                '[0] = ',
+                'first = 1',
+                'second = "is"',
+                '[1] = ',
+                'first = 2',
+                'second = "smart"',
+                '[2] = ',
+                'first = 3',
+                'second = "!!!"',
+                '[3] = ',
+                'first = 85',
+                'second = "goofy"',
             ],
         )
 
@@ -286,10 +315,16 @@ def cleanup():
             "frame variable ss",
             substrs=[
                 multimap,
-                "size=3",
-                '[0] = (first = "casa", second = "house")',
-                '[1] = (first = "ciao", second = "hello")',
-                '[2] = (first = "gatto", second = "cat")',
+                'size=3',
+                '[0] = ',
+                'first = "casa"',
+                'second = "house"',
+                '[1] = ',
+                'first = "ciao"',
+                'second = "hello"',
+                '[2] = ',
+                'first = "gatto"',
+                'second = "cat"',
             ],
         )
 
@@ -299,10 +334,16 @@ def cleanup():
             "expression ss",
             substrs=[
                 multimap,
-                "size=3",
-                '[0] = (first = "casa", second = "house")',
-                '[1] = (first = "ciao", second = "hello")',
-                '[2] = (first = "gatto", second = "cat")',
+                'size=3',
+                '[0] = ',
+                'first = "casa"',
+                'second = "house"',
+                '[1] = ',
+                'first = "ciao"',
+                'second = "hello"',
+                '[2] = ',
+                'first = "gatto"',
+                'second = "cat"',
             ],
         )
 
diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx/map/TestDataFormatterLibccMap.py b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx/map/TestDataFormatterLibccMap.py
index 7d81ec6a0cce1d1..7c78dd737bdb464 100644
--- a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx/map/TestDataFormatterLibccMap.py
+++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx/map/TestDataFormatterLibccMap.py
@@ -186,24 +186,40 @@ def cleanup():
         self.expect(
             "frame variable si",
             substrs=[
-                "%s::map" % ns,
-                "size=4",
-                '[0] = (first = "one", second = 1)',
-                '[1] = (first = "three", second = 3)',
-                '[2] = (first = "two", second = 2)',
-                '[3] = (first = "zero", second = 0)',
+                '%s::map' % ns,
+                'size=4',
+                '[0] = ',
+                'first = "one"',
+                'second = 1',
+                '[1] = ',
+                'first = "three"',
+                'second = 3',
+                '[2] = ',
+                'first = "two"',
+                'second = 2',
+                '[3] = ',
+                'first = "zero"',
+                'second = 0',
             ],
         )
 
         self.expect(
             "expression si",
             substrs=[
-                "%s::map" % ns,
-                "size=4",
-                '[0] = (first = "one", second = 1)',
-                '[1] = (first = "three", second = 3)',
-                '[2] = (first = "two", second = 2)',
-                '[3] = (first = "zero", second = 0)',
+                '%s::map' % ns,
+                'size=4',
+                '[0] = ',
+                'first = "one"',
+                'second = 1',
+                '[1] = ',
+                'first = "three"',
+                'second = 3',
+                '[2] = ',
+                'first = "two"',
+                'second = 2',
+                '[3] = ',
+                'first = "zero"',
+                'second = 0',
             ],
         )
 
@@ -237,24 +253,40 @@ def cleanup():
         self.expect(
             "frame variable is",
             substrs=[
-                "%s::map" % ns,
-                "size=4",
-                '[0] = (first = 1, second = "is")',
-                '[1] = (first = 2, second = "smart")',
-                '[2] = (first = 3, second = "!!!")',
-                '[3] = (first = 85, second = "goofy")',
+                '%s::map' % ns,
+                'size=4',
+                '[0] = ',
+                'first = 1',
+                'second = "is"',
+                '[1] = ',
+                'first = 2',
+                'second = "smart"',
+                '[2] = ',
+                'first = 3',
+                'second = "!!!"',
+                '[3] = ',
+                'first = 85',
+                'second = "goofy"',
             ],
         )
 
         self.expect(
             "expression is",
             substrs=[
-                "%s::map" % ns,
-                "size=4",
-                '[0] = (first = 1, second = "is")',
-                '[1] = (first = 2, second = "smart")',
-                '[2] = (first = 3, second = "!!!")',
-                '[3] = (first = 85, second = "goofy")',
+                '%s::map' % ns,
+                'size=4',
+                '[0] = ',
+                'first = 1',
+                'second = "is"',
+                '[1] = ',
+                'first = 2',
+                'second = "smart"',
+                '[2] = ',
+                'first = 3',
+                'second = "!!!"',
+                '[3] = ',
+                'first = 85',
+                'second = "goofy"',
             ],
         )
 
@@ -288,22 +320,34 @@ def cleanup():
         self.expect(
             "frame variable ss",
             substrs=[
-                "%s::map" % ns,
-                "size=3",
-                '[0] = (first = "casa", second = "house")',
-                '[1] = (first = "ciao", second = "hello")',
-                '[2] = (first = "gatto", second = "cat")',
+                '%s::map' % ns,
+                'size=3',
+                '[0] = ',
+                'first = "casa"',
+                'second = "house"',
+                '[1] = ',
+                'first = "ciao"',
+                'second = "hello"',
+                '[2] = ',
+                'first = "gatto"',
+                'second = "cat"',
             ],
         )
 
         self.expect(
             "expression ss",
             substrs=[
-                "%s::map" % ns,
-                "size=3",
-                '[0] = (first = "casa", second = "house")',
-                '[1] = (first = "ciao", second = "hello")',
-                '[2] = (first = "gatto", second = "cat")',
+                '%s::map' % ns,
+                'size=3',
+                '[0] = ',
+                'first = "casa"',
+                'second = "house"',
+                '[1] = ',
+                'first = "ciao"',
+                'second = "hello"',
+                '[2] = ',
+                'first = "gatto"',
+                'second = "cat"',
             ],
         )
 
diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx/shared_ptr/TestDataFormatterLibcxxSharedPtr.py b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx/shared_ptr/TestDataFormatterLibcxxSharedPtr.py
index 23011c951e213a3..fde8d83b7e45502 100644
--- a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx/shared_ptr/TestDataFormatterLibcxxSharedPtr.py
+++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx/shared_ptr/TestDataFormatterLibcxxSharedPtr.py
@@ -86,7 +86,7 @@ def test_shared_ptr_variables(self):
                 ValueCheck(name="name", summary='"steph"'),
             ],
         )
-        self.assertEqual(str(valobj), '(User) *__ptr_ = (id = 30, name = "steph")')
+        self.assertEqual(str(valobj), '(User) *__ptr_ = {\n  id = 30\n  name = "steph"\n}')
 
         self.expect_var_path("sp_user->id", type="int", value="30")
         self.expect_var_path("sp_user->name", type="std::string", summary='"steph"')
diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx/unique_ptr/TestDataFormatterLibcxxUniquePtr.py b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx/unique_ptr/TestDataFormatterLibcxxUniquePtr.py
index 6a726e0253482af..ac383d4066e8994 100644
--- a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx/unique_ptr/TestDataFormatterLibcxxUniquePtr.py
+++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx/unique_ptr/TestDataFormatterLibcxxUniquePtr.py
@@ -98,7 +98,7 @@ def test_unique_ptr_variables(self):
                 ValueCheck(name="name", summary='"steph"'),
             ],
         )
-        self.assertEqual(str(valobj), '(User) *pointer = (id = 30, name = "steph")')
+        self.assertEqual(str(valobj), '(User) *pointer = {\n  id = 30\n  name = "steph"\n}')
 
         valobj = self.expect_var_path(
             "up_non_empty_deleter",
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..a8443d45528a857
--- /dev/null
+++ b/lldb/test/API/python_api/value/change_values/libcxx/string/TestChangeStringValue.py
@@ -0,0 +1,71 @@
+"""
+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, new_first_letter):
+        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))
+
+        # update only first letter
+        first_letter = str_value.GetChildMemberWithName("[0]")
+        result = first_letter.SetValueFromCString(new_first_letter, err)
+        self.assertTrue(result, "Setting val returned error: {}".format(err))
+
+        # We could use `GetValue` or `GetSummary` (only non-ascii characters) here,
+        # but all of them will give us different representation of the same symbol,
+        # e.g. `GetValue` for `wstring` will give us "X\0\0\0\0", while for u16/u32
+        # strings it will give "U+0058"/"U+0x00000058", so it is easier to check symbol's code
+        result = first_letter.GetValueAsUnsigned()
+        expected = ord(new_first_letter)
+        self.assertTrue(result == expected, "Got value: ({}), expected: ({})"
+                                                        .format(result, expected))
+        self.assertTrue(first_letter.GetValueDidChange(), "LLDB noticed that value changed")
+
+    @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, "X")
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