Skip to content

Commit d22f12a

Browse files
committed
[lldb] Add Substring type summary (#5559)
Adds a type summary for `Substring` values. With this change, a summary string shows the substring slice. The substring's children show the base string, and start/end indexes. ``` (lldb) v substring (Substring) substring = "bcdefghijklmnopqrstuvwxy" { _slice = { _startIndex = 1[utf8] _endIndex = 25[utf8] _base = "abcdefghijklmnopqrstuvwxyz" } } ``` Before this change, a literal substring was not shown, only the full base string: ``` (lldb) v substring (Substring) substring = { _slice = { _startIndex = 1[utf8] _endIndex = 25[utf8] _base = "abcdefghijklmnopqrstuvwxyz" } } ``` #### Implementation Notes This supports Swift native (utf8) strings. NSString (utf16) are not yet supported, that will be a follow up change. Additionally, this change requires that the indexes be matching encoding. rdar://91431816 (cherry picked from commit a514263)
1 parent 4cdd7bd commit d22f12a

File tree

7 files changed

+196
-7
lines changed

7 files changed

+196
-7
lines changed

lldb/source/Plugins/Language/Swift/SwiftFormatters.cpp

Lines changed: 95 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "Plugins/Language/Swift/SwiftStringIndex.h"
1515
#include "Plugins/LanguageRuntime/Swift/SwiftLanguageRuntime.h"
1616
#include "Plugins/TypeSystem/Clang/TypeSystemClang.h"
17+
#include "lldb/Core/ValueObject.h"
1718
#include "lldb/DataFormatters/FormattersHelpers.h"
1819
#include "lldb/DataFormatters/StringPrinter.h"
1920
#include "lldb/Target/Process.h"
@@ -23,6 +24,7 @@
2324
#include "lldb/Utility/Timer.h"
2425
#include "swift/AST/Types.h"
2526
#include "swift/Demangling/ManglingMacros.h"
27+
#include "llvm/ADT/None.h"
2628
#include "llvm/ADT/Optional.h"
2729
#include "llvm/ADT/StringRef.h"
2830

@@ -71,6 +73,33 @@ bool lldb_private::formatters::swift::SwiftSharedString_SummaryProvider(
7173
StringPrinter::ReadStringAndDumpToStreamOptions());
7274
}
7375

76+
struct StringSlice {
77+
uint64_t start, end;
78+
};
79+
80+
template <typename AddrT>
81+
static void applySlice(AddrT &address, uint64_t &length,
82+
Optional<StringSlice> slice) {
83+
if (!slice)
84+
return;
85+
86+
// No slicing is performed when the slice starts beyond the string's bounds.
87+
if (slice->start > length)
88+
return;
89+
90+
// The slicing logic does handle the corner case where slice->start == length.
91+
92+
auto offset = slice->start;
93+
auto slice_length = slice->end - slice->start;
94+
95+
// Adjust from the start.
96+
address += offset;
97+
length -= offset;
98+
99+
// Reduce to the slice length, unless it's larger than the remaining length.
100+
length = std::min(slice_length, length);
101+
}
102+
74103
static bool readStringFromAddress(
75104
uint64_t startAddress, uint64_t length, ValueObject &valobj, Stream &stream,
76105
const TypeSummaryOptions &summary_options,
@@ -95,10 +124,11 @@ static bool readStringFromAddress(
95124
StringPrinter::StringElementType::UTF8>(read_options);
96125
};
97126

98-
bool lldb_private::formatters::swift::StringGuts_SummaryProvider(
127+
static bool makeStringGutsSummary(
99128
ValueObject &valobj, Stream &stream,
100129
const TypeSummaryOptions &summary_options,
101-
StringPrinter::ReadStringAndDumpToStreamOptions read_options) {
130+
StringPrinter::ReadStringAndDumpToStreamOptions read_options,
131+
Optional<StringSlice> slice = None) {
102132
LLDB_SCOPED_TIMER();
103133

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

281-
uint64_t buffer[2] = {raw0, raw1};
311+
uint64_t rawBuffer[2] = {raw0, raw1};
312+
auto *buffer = (uint8_t *)&rawBuffer;
313+
applySlice(buffer, count, slice);
282314

283315
StringPrinter::ReadBufferAndDumpToStreamOptions options(read_options);
284-
options.SetData(
285-
DataExtractor(buffer, count, process->GetByteOrder(), ptrSize));
316+
options.SetData(lldb_private::DataExtractor(
317+
buffer, count, process->GetByteOrder(), ptrSize));
286318
options.SetStream(&stream);
287319
options.SetSourceSize(count);
288320
options.SetBinaryZeroIsTerminator(false);
@@ -300,6 +332,7 @@ bool lldb_private::formatters::swift::StringGuts_SummaryProvider(
300332
return false;
301333
uint64_t bias = (ptrSize == 8 ? 32 : 20);
302334
auto address = objectAddress + bias;
335+
applySlice(address, count, slice);
303336
return readStringFromAddress(
304337
address, count, valobj, stream, summary_options, read_options);
305338
}
@@ -315,6 +348,7 @@ bool lldb_private::formatters::swift::StringGuts_SummaryProvider(
315348
if (error.Fail())
316349
return false;
317350

351+
applySlice(address, count, slice);
318352
return readStringFromAddress(
319353
start, count, valobj, stream, summary_options, read_options);
320354
}
@@ -333,7 +367,8 @@ bool lldb_private::formatters::swift::StringGuts_SummaryProvider(
333367
lldb::eBasicTypeObjCID);
334368

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

390+
bool lldb_private::formatters::swift::StringGuts_SummaryProvider(
391+
ValueObject &valobj, Stream &stream,
392+
const TypeSummaryOptions &summary_options,
393+
StringPrinter::ReadStringAndDumpToStreamOptions read_options) {
394+
return makeStringGutsSummary(valobj, stream, summary_options, read_options);
395+
}
396+
355397
bool lldb_private::formatters::swift::String_SummaryProvider(
356398
ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) {
357399
return String_SummaryProvider(
@@ -371,6 +413,53 @@ bool lldb_private::formatters::swift::String_SummaryProvider(
371413
return false;
372414
}
373415

416+
bool lldb_private::formatters::swift::Substring_SummaryProvider(
417+
ValueObject &valobj, Stream &stream,
418+
const TypeSummaryOptions &summary_options) {
419+
static ConstString g__slice("_slice");
420+
static ConstString g__base("_base");
421+
static ConstString g__startIndex("_startIndex");
422+
static ConstString g__endIndex("_endIndex");
423+
static ConstString g__rawBits("_rawBits");
424+
auto slice_sp = valobj.GetChildMemberWithName(g__slice, true);
425+
if (!slice_sp)
426+
return false;
427+
auto base_sp = slice_sp->GetChildMemberWithName(g__base, true);
428+
if (!base_sp)
429+
return false;
430+
431+
auto get_index =
432+
[&slice_sp](ConstString index_name) -> Optional<StringIndex> {
433+
auto raw_bits_sp = slice_sp->GetChildAtNamePath({index_name, g__rawBits});
434+
if (!raw_bits_sp)
435+
return None;
436+
bool success = false;
437+
StringIndex index =
438+
raw_bits_sp->GetSyntheticValue()->GetValueAsUnsigned(0, &success);
439+
if (!success)
440+
return None;
441+
return index;
442+
};
443+
444+
Optional<StringIndex> start_index = get_index(g__startIndex);
445+
Optional<StringIndex> end_index = get_index(g__endIndex);
446+
if (!start_index || !end_index)
447+
return false;
448+
449+
if (!start_index->matchesEncoding(*end_index))
450+
return false;
451+
452+
static ConstString g_guts("_guts");
453+
auto guts_sp = base_sp->GetChildMemberWithName(g_guts, true);
454+
if (!guts_sp)
455+
return false;
456+
457+
StringPrinter::ReadStringAndDumpToStreamOptions read_options;
458+
StringSlice slice{start_index->encodedOffset(), end_index->encodedOffset()};
459+
return makeStringGutsSummary(*guts_sp, stream, summary_options, read_options,
460+
slice);
461+
}
462+
374463
bool lldb_private::formatters::swift::StringIndex_SummaryProvider(
375464
ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) {
376465
static ConstString g__rawBits("_rawBits");

lldb/source/Plugins/Language/Swift/SwiftFormatters.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ bool String_SummaryProvider(ValueObject &valobj, Stream &stream,
5454
const TypeSummaryOptions &,
5555
StringPrinter::ReadStringAndDumpToStreamOptions);
5656

57+
bool Substring_SummaryProvider(ValueObject &, Stream &,
58+
const TypeSummaryOptions &);
59+
5760
bool StringIndex_SummaryProvider(ValueObject &valobj, Stream &stream,
5861
const TypeSummaryOptions &options);
5962

lldb/source/Plugins/Language/Swift/SwiftLanguage.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,14 @@ static void LoadSwiftFormatters(lldb::TypeCategoryImplSP swift_category_sp) {
425425
bool (*staticstring_summary_provider)(ValueObject &, Stream &,
426426
const TypeSummaryOptions &) =
427427
lldb_private::formatters::swift::StaticString_SummaryProvider;
428+
{
429+
TypeSummaryImpl::Flags substring_summary_flags = summary_flags;
430+
substring_summary_flags.SetDontShowChildren(false);
431+
AddCXXSummary(swift_category_sp,
432+
lldb_private::formatters::swift::Substring_SummaryProvider,
433+
"Swift.Substring summary provider",
434+
ConstString("Swift.Substring"), substring_summary_flags);
435+
}
428436
AddCXXSummary(swift_category_sp, staticstring_summary_provider,
429437
"Swift.StaticString summary provider",
430438
ConstString("Swift.StaticString"), summary_flags);

lldb/source/Plugins/Language/Swift/SwiftStringIndex.h

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ class StringIndex {
4242
uint64_t encodedOffset() { return _rawBits >> 16; }
4343

4444
const char *encodingName() {
45-
uint8_t flags = _rawBits & 0b1111;
45+
auto flags = _flags();
4646
bool canBeUTF8 = flags & Flags::CanBeUTF8;
4747
bool canBeUTF16 = flags & Flags::CanBeUTF16;
4848
if (canBeUTF8 && canBeUTF16)
@@ -56,6 +56,18 @@ class StringIndex {
5656
}
5757

5858
uint8_t transcodedOffset() { return (_rawBits >> 14) & 0b11; }
59+
60+
bool matchesEncoding(StringIndex other) {
61+
// Either both are valid utf8 indexes, or valid utf16 indexes.
62+
return (_utfFlags() & other._utfFlags()) != 0;
63+
}
64+
65+
private:
66+
uint8_t _flags() const { return _rawBits & 0b1111; }
67+
68+
uint8_t _utfFlags() const {
69+
return _flags() & (Flags::CanBeUTF8 | Flags::CanBeUTF16);
70+
}
5971
};
6072

6173
}; // namespace swift
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
SWIFT_SOURCES := main.swift
2+
3+
include Makefile.rules
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
"""
2+
Test Substring summary strings.
3+
"""
4+
5+
import lldb
6+
from lldbsuite.test.lldbtest import *
7+
from lldbsuite.test.decorators import *
8+
import lldbsuite.test.lldbutil as lldbutil
9+
10+
11+
class TestCase(TestBase):
12+
@swiftTest
13+
def test_swift_substring_formatters(self):
14+
"""Test Subtring summary strings."""
15+
self.build()
16+
17+
# First stop tests small strings.
18+
19+
_, process, _, _, = lldbutil.run_to_source_breakpoint(
20+
self, "break here", lldb.SBFileSpec("main.swift")
21+
)
22+
23+
self.expect(
24+
"v substrings",
25+
substrs=[
26+
'[0] = "abc"',
27+
'[1] = "bc"',
28+
'[2] = "c"',
29+
'[3] = ""',
30+
],
31+
)
32+
33+
# Continue to the second stop, to test non-small strings.
34+
35+
process.Continue()
36+
37+
# Strings larger than 15 bytes (64-bit) or 10 bytes (32-bit) cannot be
38+
# stored directly inside the String struct.
39+
alphabet = "abcdefghijklmnopqrstuvwxyz"
40+
41+
# Including the first full substring, and the final empty substring,
42+
# there are 27 substrings.
43+
subalphabets = [alphabet[i:] for i in range(27)]
44+
45+
self.expect(
46+
"v substrings",
47+
substrs=[
48+
f'[{i}] = "{substring}"'
49+
for i, substring in enumerate(subalphabets)
50+
],
51+
)
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
func main() {
2+
exerciseSmallString()
3+
exerciseString()
4+
}
5+
6+
func exerciseSmallString() {
7+
exercise("abc")
8+
}
9+
10+
func exerciseString() {
11+
exercise("abcdefghijklmnopqrstuvwxyz")
12+
}
13+
14+
func exercise(_ string: String) {
15+
let substrings = allIndices(string).map { string[$0..<string.endIndex] }
16+
// break here
17+
}
18+
19+
func allIndices<T: Collection>(_ collection: T) -> [T.Index] {
20+
return Array(collection.indices) + [collection.endIndex]
21+
}
22+
23+
main()

0 commit comments

Comments
 (0)