[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