[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