Skip to content

Commit 1505c40

Browse files
committed
[lldb] Support alternatives for scope format entries (llvm#137751)
This PR implements support for specifying multiple alternatives for scope format entries. Scopes are used to enclose things that should only be printed when everything in the scope resolves. For example, the following scope only resolves if both `${line.file.basename}` and `${line.number}` resolve. ` ``` { at ${line.file.basename}:${line.number}} ``` However, the current implementation doesn't let you specify what to print when they don't resolve. This PR adds support for specifying multiple alternative scopes, which are evaluated left-to-right. For example: ``` { at ${line.file.basename}:${line.number}| in ${function.name}| <unknown location>} ``` This will resolve to: - ` at ${line.file.basename}:${line.number}` if the corresponding variables resolve. - Otherwise, this resolves to ` in ${function.name}` if `${function.name}` resolves. - Otherwise, this resolves to ` <unknown location>` which always resolves. This PR makes the `|` character a special character within a scope, but allows it to be escaped. I ended up with this approach because it fit quite nicely in the existing architecture of the format entries and by limiting the functionality to scopes, it sidesteps some complexity, like dealing with recursion. (cherry picked from commit 3e235a7)
1 parent aebeca4 commit 1505c40

File tree

3 files changed

+126
-34
lines changed

3 files changed

+126
-34
lines changed

lldb/include/lldb/Core/FormatEntity.h

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@
1111

1212
#include "lldb/lldb-enumerations.h"
1313
#include "lldb/lldb-types.h"
14+
#include "llvm/ADT/SmallVector.h"
1415
#include <algorithm>
1516
#include <cstddef>
1617
#include <cstdint>
17-
1818
#include <string>
1919
#include <vector>
2020

@@ -158,9 +158,7 @@ struct Entry {
158158
}
159159

160160
Entry(Type t = Type::Invalid, const char *s = nullptr,
161-
const char *f = nullptr)
162-
: string(s ? s : ""), printf_format(f ? f : ""), type(t) {}
163-
161+
const char *f = nullptr);
164162
Entry(llvm::StringRef s);
165163
Entry(char ch);
166164

@@ -170,15 +168,19 @@ struct Entry {
170168

171169
void AppendText(const char *cstr);
172170

173-
void AppendEntry(const Entry &&entry) { children.push_back(entry); }
171+
void AppendEntry(const Entry &&entry);
172+
173+
void StartAlternative();
174174

175175
void Clear() {
176176
string.clear();
177177
printf_format.clear();
178-
children.clear();
178+
children_stack.clear();
179+
children_stack.emplace_back();
179180
type = Type::Invalid;
180181
fmt = lldb::eFormatDefault;
181182
number = 0;
183+
level = 0;
182184
deref = false;
183185
}
184186

@@ -191,7 +193,7 @@ struct Entry {
191193
return false;
192194
if (printf_format != rhs.printf_format)
193195
return false;
194-
if (children != rhs.children)
196+
if (children_stack != rhs.children_stack)
195197
return false;
196198
if (type != rhs.type)
197199
return false;
@@ -202,9 +204,18 @@ struct Entry {
202204
return true;
203205
}
204206

207+
std::vector<Entry> &GetChildren();
208+
205209
std::string string;
206210
std::string printf_format;
207-
std::vector<Entry> children;
211+
212+
/// A stack of children entries, used by Scope entries to provide alterantive
213+
/// children. All other entries have a stack of size 1.
214+
/// @{
215+
llvm::SmallVector<std::vector<Entry>, 1> children_stack;
216+
size_t level = 0;
217+
/// @}
218+
208219
Type type;
209220
lldb::Format fmt = lldb::eFormatDefault;
210221
lldb::addr_t number = 0;

lldb/source/Core/FormatEntity.cpp

Lines changed: 63 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
#include "llvm/Support/Regex.h"
6161
#include "llvm/TargetParser/Triple.h"
6262

63+
#include <cassert>
6364
#include <cctype>
6465
#include <cinttypes>
6566
#include <cstdio>
@@ -281,31 +282,53 @@ constexpr Definition g_top_level_entries[] = {
281282
constexpr Definition g_root = Entry::DefinitionWithChildren(
282283
"<root>", EntryType::Root, g_top_level_entries);
283284

285+
FormatEntity::Entry::Entry(Type t, const char *s, const char *f)
286+
: string(s ? s : ""), printf_format(f ? f : ""), children_stack({{}}),
287+
type(t) {}
288+
284289
FormatEntity::Entry::Entry(llvm::StringRef s)
285-
: string(s.data(), s.size()), printf_format(), children(),
286-
type(Type::String) {}
290+
: string(s.data(), s.size()), children_stack({{}}), type(Type::String) {}
287291

288292
FormatEntity::Entry::Entry(char ch)
289-
: string(1, ch), printf_format(), children(), type(Type::String) {}
293+
: string(1, ch), printf_format(), children_stack({{}}), type(Type::String) {
294+
}
295+
296+
std::vector<Entry> &FormatEntity::Entry::GetChildren() {
297+
assert(level < children_stack.size());
298+
return children_stack[level];
299+
}
290300

291301
void FormatEntity::Entry::AppendChar(char ch) {
292-
if (children.empty() || children.back().type != Entry::Type::String)
293-
children.push_back(Entry(ch));
302+
auto &entries = GetChildren();
303+
if (entries.empty() || entries.back().type != Entry::Type::String)
304+
entries.push_back(Entry(ch));
294305
else
295-
children.back().string.append(1, ch);
306+
entries.back().string.append(1, ch);
296307
}
297308

298309
void FormatEntity::Entry::AppendText(const llvm::StringRef &s) {
299-
if (children.empty() || children.back().type != Entry::Type::String)
300-
children.push_back(Entry(s));
310+
auto &entries = GetChildren();
311+
if (entries.empty() || entries.back().type != Entry::Type::String)
312+
entries.push_back(Entry(s));
301313
else
302-
children.back().string.append(s.data(), s.size());
314+
entries.back().string.append(s.data(), s.size());
303315
}
304316

305317
void FormatEntity::Entry::AppendText(const char *cstr) {
306318
return AppendText(llvm::StringRef(cstr));
307319
}
308320

321+
void FormatEntity::Entry::AppendEntry(const Entry &&entry) {
322+
auto &entries = GetChildren();
323+
entries.push_back(entry);
324+
}
325+
326+
void FormatEntity::Entry::StartAlternative() {
327+
assert(type == Entry::Type::Scope);
328+
children_stack.emplace_back();
329+
level++;
330+
}
331+
309332
#define ENUM_TO_CSTR(eee) \
310333
case FormatEntity::Entry::Type::eee: \
311334
return #eee
@@ -405,8 +428,9 @@ void FormatEntity::Entry::Dump(Stream &s, int depth) const {
405428
if (deref)
406429
s.Printf("deref = true, ");
407430
s.EOL();
408-
for (const auto &child : children) {
409-
child.Dump(s, depth + 1);
431+
for (const auto &children : children_stack) {
432+
for (const auto &child : children)
433+
child.Dump(s, depth + 1);
410434
}
411435
}
412436

@@ -1308,7 +1332,7 @@ bool FormatEntity::Format(const Entry &entry, Stream &s,
13081332
return true;
13091333

13101334
case Entry::Type::Root:
1311-
for (const auto &child : entry.children) {
1335+
for (const auto &child : entry.children_stack[0]) {
13121336
if (!Format(child, s, sc, exe_ctx, addr, valobj, function_changed,
13131337
initial_function)) {
13141338
return false; // If any item of root fails, then the formatting fails
@@ -1322,19 +1346,26 @@ bool FormatEntity::Format(const Entry &entry, Stream &s,
13221346

13231347
case Entry::Type::Scope: {
13241348
StreamString scope_stream;
1325-
bool success = false;
1326-
for (const auto &child : entry.children) {
1327-
success = Format(child, scope_stream, sc, exe_ctx, addr, valobj,
1328-
function_changed, initial_function);
1329-
if (!success)
1330-
break;
1349+
auto format_children = [&](const std::vector<Entry> &children) {
1350+
scope_stream.Clear();
1351+
for (const auto &child : children) {
1352+
if (!Format(child, scope_stream, sc, exe_ctx, addr, valobj,
1353+
function_changed, initial_function))
1354+
return false;
1355+
}
1356+
return true;
1357+
};
1358+
1359+
for (auto &children : entry.children_stack) {
1360+
if (format_children(children)) {
1361+
s.Write(scope_stream.GetString().data(),
1362+
scope_stream.GetString().size());
1363+
return true;
1364+
}
13311365
}
1332-
// Only if all items in a scope succeed, then do we print the output into
1333-
// the main stream
1334-
if (success)
1335-
s.Write(scope_stream.GetString().data(), scope_stream.GetString().size());
1336-
}
1366+
13371367
return true; // Scopes always successfully print themselves
1368+
}
13381369

13391370
case Entry::Type::Variable:
13401371
case Entry::Type::VariableSynthetic:
@@ -2132,7 +2163,7 @@ static Status ParseInternal(llvm::StringRef &format, Entry &parent_entry,
21322163
uint32_t depth) {
21332164
Status error;
21342165
while (!format.empty() && error.Success()) {
2135-
const size_t non_special_chars = format.find_first_of("${}\\");
2166+
const size_t non_special_chars = format.find_first_of("${}\\|");
21362167

21372168
if (non_special_chars == llvm::StringRef::npos) {
21382169
// No special characters, just string bytes so add them and we are done
@@ -2169,6 +2200,14 @@ static Status ParseInternal(llvm::StringRef &format, Entry &parent_entry,
21692200
.drop_front(); // Skip the '}' as we are at the end of the scope
21702201
return error;
21712202

2203+
case '|':
2204+
format = format.drop_front(); // Skip the '|'
2205+
if (parent_entry.type == Entry::Type::Scope)
2206+
parent_entry.StartAlternative();
2207+
else
2208+
parent_entry.AppendChar('|');
2209+
break;
2210+
21722211
case '\\': {
21732212
format = format.drop_front(); // Skip the '\' character
21742213
if (format.empty()) {

lldb/unittests/Core/FormatEntityTest.cpp

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,30 @@
88

99
#include "lldb/Core/FormatEntity.h"
1010
#include "lldb/Utility/Status.h"
11-
11+
#include "lldb/Utility/StreamString.h"
1212
#include "llvm/ADT/StringRef.h"
13+
#include "llvm/Support/Error.h"
14+
#include "llvm/Testing/Support/Error.h"
1315
#include "gtest/gtest.h"
1416

1517
using namespace lldb_private;
18+
using namespace llvm;
1619

1720
using Definition = FormatEntity::Entry::Definition;
1821
using Entry = FormatEntity::Entry;
1922

23+
static Expected<std::string> Format(StringRef format_str) {
24+
StreamString stream;
25+
FormatEntity::Entry format;
26+
Status status = FormatEntity::Parse(format_str, format);
27+
if (status.Fail())
28+
return status.ToError();
29+
30+
FormatEntity::Format(format, stream, nullptr, nullptr, nullptr, nullptr,
31+
false, false);
32+
return stream.GetString().str();
33+
}
34+
2035
TEST(FormatEntityTest, DefinitionConstructionNameAndType) {
2136
Definition d("foo", FormatEntity::Entry::Type::Invalid);
2237

@@ -153,10 +168,37 @@ constexpr llvm::StringRef lookupStrings[] = {
153168
"${target.file.fullpath}",
154169
"${var.dummy-var-to-test-wildcard}"};
155170

156-
TEST(FormatEntity, LookupAllEntriesInTree) {
171+
TEST(FormatEntityTest, LookupAllEntriesInTree) {
157172
for (const llvm::StringRef testString : lookupStrings) {
158173
Entry e;
159174
EXPECT_TRUE(FormatEntity::Parse(testString, e).Success())
160175
<< "Formatting " << testString << " did not succeed";
161176
}
162177
}
178+
179+
TEST(FormatEntityTest, Scope) {
180+
// Scope with one alternative.
181+
EXPECT_THAT_EXPECTED(Format("{${frame.pc}|foo}"), HasValue("foo"));
182+
183+
// Scope with multiple alternatives.
184+
EXPECT_THAT_EXPECTED(Format("{${frame.pc}|${function.name}|foo}"),
185+
HasValue("foo"));
186+
187+
// Escaped pipe inside a scope.
188+
EXPECT_THAT_EXPECTED(Format("{foo\\|bar}"), HasValue("foo|bar"));
189+
190+
// Unescaped pipe outside a scope.
191+
EXPECT_THAT_EXPECTED(Format("foo|bar"), HasValue("foo|bar"));
192+
193+
// Nested scopes. Note that scopes always resolve.
194+
EXPECT_THAT_EXPECTED(Format("{{${frame.pc}|foo}|{bar}}"), HasValue("foo"));
195+
EXPECT_THAT_EXPECTED(Format("{{${frame.pc}}|{bar}}"), HasValue(""));
196+
197+
// Pipe between scopes.
198+
EXPECT_THAT_EXPECTED(Format("{foo}|{bar}"), HasValue("foo|bar"));
199+
EXPECT_THAT_EXPECTED(Format("{foo}||{bar}"), HasValue("foo||bar"));
200+
201+
// Empty space between pipes.
202+
EXPECT_THAT_EXPECTED(Format("{{foo}||{bar}}"), HasValue("foo"));
203+
EXPECT_THAT_EXPECTED(Format("{${frame.pc}||{bar}}"), HasValue(""));
204+
}

0 commit comments

Comments
 (0)