Skip to content

[lldb] Add Substring type summary #5559

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Nov 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 95 additions & 6 deletions lldb/source/Plugins/Language/Swift/SwiftFormatters.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include "Plugins/Language/Swift/SwiftStringIndex.h"
#include "Plugins/LanguageRuntime/Swift/SwiftLanguageRuntime.h"
#include "Plugins/TypeSystem/Clang/TypeSystemClang.h"
#include "lldb/Core/ValueObject.h"
#include "lldb/DataFormatters/FormattersHelpers.h"
#include "lldb/DataFormatters/StringPrinter.h"
#include "lldb/Target/Process.h"
Expand All @@ -23,6 +24,7 @@
#include "lldb/Utility/Timer.h"
#include "swift/AST/Types.h"
#include "swift/Demangling/ManglingMacros.h"
#include "llvm/ADT/None.h"
#include "llvm/ADT/Optional.h"
#include "llvm/ADT/StringRef.h"

Expand Down Expand Up @@ -71,6 +73,33 @@ bool lldb_private::formatters::swift::SwiftSharedString_SummaryProvider(
StringPrinter::ReadStringAndDumpToStreamOptions());
}

struct StringSlice {
uint64_t start, end;
};

template <typename AddrT>
static void applySlice(AddrT &address, uint64_t &length,
Optional<StringSlice> slice) {
if (!slice)
return;

// No slicing is performed when the slice starts beyond the string's bounds.
if (slice->start > length)
return;

// The slicing logic does handle the corner case where slice->start == length.

auto offset = slice->start;
auto slice_length = slice->end - slice->start;

// Adjust from the start.
address += offset;
length -= offset;

// Reduce to the slice length, unless it's larger than the remaining length.
length = std::min(slice_length, length);
}

static bool readStringFromAddress(
uint64_t startAddress, uint64_t length, ValueObject &valobj, Stream &stream,
const TypeSummaryOptions &summary_options,
Expand All @@ -95,10 +124,11 @@ static bool readStringFromAddress(
StringPrinter::StringElementType::UTF8>(read_options);
};

bool lldb_private::formatters::swift::StringGuts_SummaryProvider(
static bool makeStringGutsSummary(
ValueObject &valobj, Stream &stream,
const TypeSummaryOptions &summary_options,
StringPrinter::ReadStringAndDumpToStreamOptions read_options) {
StringPrinter::ReadStringAndDumpToStreamOptions read_options,
Optional<StringSlice> slice = None) {
LLDB_SCOPED_TIMER();

static ConstString g__object("_object");
Expand Down Expand Up @@ -278,11 +308,13 @@ bool lldb_private::formatters::swift::StringGuts_SummaryProvider(
if (count > maxCount)
return false;

uint64_t buffer[2] = {raw0, raw1};
uint64_t rawBuffer[2] = {raw0, raw1};
auto *buffer = (uint8_t *)&rawBuffer;
applySlice(buffer, count, slice);
Copy link

@Michael137 Michael137 Nov 14, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct me if I'm wrong but AFAIU this is technically UB. I don't think any compiler will break this kind of type punning but the recommended way of reading a type through a uint8_t* is using memcpy.

The standard doesn't technically permit doing pointer arithmetic on something that isn't pointer-interconvertible. Chances are memcpy will get optimised away into exactly the same code that reinterpret_cast produces because memcpy is compiler magic.


StringPrinter::ReadBufferAndDumpToStreamOptions options(read_options);
options.SetData(
DataExtractor(buffer, count, process->GetByteOrder(), ptrSize));
options.SetData(lldb_private::DataExtractor(
buffer, count, process->GetByteOrder(), ptrSize));
options.SetStream(&stream);
options.SetSourceSize(count);
options.SetBinaryZeroIsTerminator(false);
Expand All @@ -300,6 +332,7 @@ bool lldb_private::formatters::swift::StringGuts_SummaryProvider(
return false;
uint64_t bias = (ptrSize == 8 ? 32 : 20);
auto address = objectAddress + bias;
applySlice(address, count, slice);
return readStringFromAddress(
address, count, valobj, stream, summary_options, read_options);
}
Expand All @@ -315,6 +348,7 @@ bool lldb_private::formatters::swift::StringGuts_SummaryProvider(
if (error.Fail())
return false;

applySlice(address, count, slice);
return readStringFromAddress(
start, count, valobj, stream, summary_options, read_options);
}
Expand All @@ -333,7 +367,8 @@ bool lldb_private::formatters::swift::StringGuts_SummaryProvider(
lldb::eBasicTypeObjCID);

// We may have an NSString pointer inline, so try formatting it directly.
DataExtractor DE(&objectAddress, ptrSize, process->GetByteOrder(), ptrSize);
lldb_private::DataExtractor DE(&objectAddress, ptrSize,
process->GetByteOrder(), ptrSize);
auto nsstring = ValueObject::CreateValueObjectFromData(
"nsstring", DE, valobj.GetExecutionContextRef(), id_type);
if (!nsstring || nsstring->GetError().Fail())
Expand All @@ -352,6 +387,13 @@ bool lldb_private::formatters::swift::StringGuts_SummaryProvider(
return false;
}

bool lldb_private::formatters::swift::StringGuts_SummaryProvider(
ValueObject &valobj, Stream &stream,
const TypeSummaryOptions &summary_options,
StringPrinter::ReadStringAndDumpToStreamOptions read_options) {
return makeStringGutsSummary(valobj, stream, summary_options, read_options);
}

bool lldb_private::formatters::swift::String_SummaryProvider(
ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) {
return String_SummaryProvider(
Expand All @@ -371,6 +413,53 @@ bool lldb_private::formatters::swift::String_SummaryProvider(
return false;
}

bool lldb_private::formatters::swift::Substring_SummaryProvider(
ValueObject &valobj, Stream &stream,
const TypeSummaryOptions &summary_options) {
static ConstString g__slice("_slice");
static ConstString g__base("_base");
static ConstString g__startIndex("_startIndex");
static ConstString g__endIndex("_endIndex");
static ConstString g__rawBits("_rawBits");
auto slice_sp = valobj.GetChildMemberWithName(g__slice, true);
if (!slice_sp)
return false;
auto base_sp = slice_sp->GetChildMemberWithName(g__base, true);
if (!base_sp)
return false;

auto get_index =
[&slice_sp](ConstString index_name) -> Optional<StringIndex> {
auto raw_bits_sp = slice_sp->GetChildAtNamePath({index_name, g__rawBits});
if (!raw_bits_sp)
return None;
bool success = false;
StringIndex index =
raw_bits_sp->GetSyntheticValue()->GetValueAsUnsigned(0, &success);
if (!success)
return None;
return index;
};

Optional<StringIndex> start_index = get_index(g__startIndex);
Optional<StringIndex> end_index = get_index(g__endIndex);
if (!start_index || !end_index)
return false;

if (!start_index->matchesEncoding(*end_index))
return false;

static ConstString g_guts("_guts");
auto guts_sp = base_sp->GetChildMemberWithName(g_guts, true);
if (!guts_sp)
return false;

StringPrinter::ReadStringAndDumpToStreamOptions read_options;
StringSlice slice{start_index->encodedOffset(), end_index->encodedOffset()};
return makeStringGutsSummary(*guts_sp, stream, summary_options, read_options,
slice);
}

bool lldb_private::formatters::swift::StringIndex_SummaryProvider(
ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) {
static ConstString g__rawBits("_rawBits");
Expand Down
3 changes: 3 additions & 0 deletions lldb/source/Plugins/Language/Swift/SwiftFormatters.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ bool String_SummaryProvider(ValueObject &valobj, Stream &stream,
const TypeSummaryOptions &,
StringPrinter::ReadStringAndDumpToStreamOptions);

bool Substring_SummaryProvider(ValueObject &, Stream &,
const TypeSummaryOptions &);

bool StringIndex_SummaryProvider(ValueObject &valobj, Stream &stream,
const TypeSummaryOptions &options);

Expand Down
8 changes: 8 additions & 0 deletions lldb/source/Plugins/Language/Swift/SwiftLanguage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,14 @@ static void LoadSwiftFormatters(lldb::TypeCategoryImplSP swift_category_sp) {
bool (*staticstring_summary_provider)(ValueObject &, Stream &,
const TypeSummaryOptions &) =
lldb_private::formatters::swift::StaticString_SummaryProvider;
{
TypeSummaryImpl::Flags substring_summary_flags = summary_flags;
substring_summary_flags.SetDontShowChildren(false);
AddCXXSummary(swift_category_sp,
lldb_private::formatters::swift::Substring_SummaryProvider,
"Swift.Substring summary provider",
ConstString("Swift.Substring"), substring_summary_flags);
}
AddCXXSummary(swift_category_sp, staticstring_summary_provider,
"Swift.StaticString summary provider",
ConstString("Swift.StaticString"), summary_flags);
Expand Down
14 changes: 13 additions & 1 deletion lldb/source/Plugins/Language/Swift/SwiftStringIndex.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class StringIndex {
uint64_t encodedOffset() { return _rawBits >> 16; }

const char *encodingName() {
uint8_t flags = _rawBits & 0b1111;
auto flags = _flags();
bool canBeUTF8 = flags & Flags::CanBeUTF8;
bool canBeUTF16 = flags & Flags::CanBeUTF16;
if (canBeUTF8 && canBeUTF16)
Expand All @@ -56,6 +56,18 @@ class StringIndex {
}

uint8_t transcodedOffset() { return (_rawBits >> 14) & 0b11; }

bool matchesEncoding(StringIndex other) {
// Either both are valid utf8 indexes, or valid utf16 indexes.
return (_utfFlags() & other._utfFlags()) != 0;
}

private:
uint8_t _flags() const { return _rawBits & 0b1111; }

uint8_t _utfFlags() const {
return _flags() & (Flags::CanBeUTF8 | Flags::CanBeUTF16);
}
};

}; // namespace swift
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
SWIFT_SOURCES := main.swift

include Makefile.rules
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
"""
Test Substring summary strings.
"""

import lldb
from lldbsuite.test.lldbtest import *
from lldbsuite.test.decorators import *
import lldbsuite.test.lldbutil as lldbutil


class TestCase(TestBase):
@swiftTest
def test_swift_substring_formatters(self):
"""Test Subtring summary strings."""
self.build()

# First stop tests small strings.

_, process, _, _, = lldbutil.run_to_source_breakpoint(
self, "break here", lldb.SBFileSpec("main.swift")
)

self.expect(
"v substrings",
substrs=[
'[0] = "abc"',
'[1] = "bc"',
'[2] = "c"',
'[3] = ""',
],
)

# Continue to the second stop, to test non-small strings.

process.Continue()

# Strings larger than 15 bytes (64-bit) or 10 bytes (32-bit) cannot be
# stored directly inside the String struct.
alphabet = "abcdefghijklmnopqrstuvwxyz"

# Including the first full substring, and the final empty substring,
# there are 27 substrings.
subalphabets = [alphabet[i:] for i in range(27)]

self.expect(
"v substrings",
substrs=[
f'[{i}] = "{substring}"'
for i, substring in enumerate(subalphabets)
],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
func main() {
exerciseSmallString()
exerciseString()
}

func exerciseSmallString() {
exercise("abc")
}

func exerciseString() {
exercise("abcdefghijklmnopqrstuvwxyz")
}

func exercise(_ string: String) {
let substrings = allIndices(string).map { string[$0..<string.endIndex] }
// break here
}

func allIndices<T: Collection>(_ collection: T) -> [T.Index] {
return Array(collection.indices) + [collection.endIndex]
}

main()