Skip to content

Commit 8773b25

Browse files
committed
[lldb] Support alternatives for scope format entries
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.
1 parent a37e475 commit 8773b25

File tree

3 files changed

+128
-32
lines changed

3 files changed

+128
-32
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

@@ -157,9 +157,7 @@ struct Entry {
157157
}
158158

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

@@ -169,15 +167,19 @@ struct Entry {
169167

170168
void AppendText(const char *cstr);
171169

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

174174
void Clear() {
175175
string.clear();
176176
printf_format.clear();
177-
children.clear();
177+
children_stack.clear();
178+
children_stack.emplace_back();
178179
type = Type::Invalid;
179180
fmt = lldb::eFormatDefault;
180181
number = 0;
182+
level = 0;
181183
deref = false;
182184
}
183185

@@ -190,7 +192,7 @@ struct Entry {
190192
return false;
191193
if (printf_format != rhs.printf_format)
192194
return false;
193-
if (children != rhs.children)
195+
if (children_stack != rhs.children_stack)
194196
return false;
195197
if (type != rhs.type)
196198
return false;
@@ -201,9 +203,18 @@ struct Entry {
201203
return true;
202204
}
203205

206+
std::vector<Entry> &GetChildren();
207+
204208
std::string string;
205209
std::string printf_format;
206-
std::vector<Entry> children;
210+
211+
/// A stack of children entries, used by Scope entries to provide alterantive
212+
/// children. All other entries have a stack of size 1.
213+
/// @{
214+
llvm::SmallVector<std::vector<Entry>, 1> children_stack;
215+
size_t level = 0;
216+
/// @}
217+
207218
Type type;
208219
lldb::Format fmt = lldb::eFormatDefault;
209220
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>
@@ -280,31 +281,53 @@ constexpr Definition g_top_level_entries[] = {
280281
constexpr Definition g_root = Entry::DefinitionWithChildren(
281282
"<root>", EntryType::Root, g_top_level_entries);
282283

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

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

290300
void FormatEntity::Entry::AppendChar(char ch) {
291-
if (children.empty() || children.back().type != Entry::Type::String)
292-
children.push_back(Entry(ch));
301+
auto &entries = GetChildren();
302+
if (entries.empty() || entries.back().type != Entry::Type::String)
303+
entries.push_back(Entry(ch));
293304
else
294-
children.back().string.append(1, ch);
305+
entries.back().string.append(1, ch);
295306
}
296307

297308
void FormatEntity::Entry::AppendText(const llvm::StringRef &s) {
298-
if (children.empty() || children.back().type != Entry::Type::String)
299-
children.push_back(Entry(s));
309+
auto &entries = GetChildren();
310+
if (entries.empty() || entries.back().type != Entry::Type::String)
311+
entries.push_back(Entry(s));
300312
else
301-
children.back().string.append(s.data(), s.size());
313+
entries.back().string.append(s.data(), s.size());
302314
}
303315

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

320+
void FormatEntity::Entry::AppendEntry(const Entry &&entry) {
321+
auto &entries = GetChildren();
322+
entries.push_back(entry);
323+
}
324+
325+
void FormatEntity::Entry::StartAlternative() {
326+
assert(type == Entry::Type::Scope);
327+
children_stack.emplace_back();
328+
level++;
329+
}
330+
308331
#define ENUM_TO_CSTR(eee) \
309332
case FormatEntity::Entry::Type::eee: \
310333
return #eee
@@ -403,8 +426,9 @@ void FormatEntity::Entry::Dump(Stream &s, int depth) const {
403426
if (deref)
404427
s.Printf("deref = true, ");
405428
s.EOL();
406-
for (const auto &child : children) {
407-
child.Dump(s, depth + 1);
429+
for (const auto &children : children_stack) {
430+
for (const auto &child : children)
431+
child.Dump(s, depth + 1);
408432
}
409433
}
410434

@@ -1306,7 +1330,7 @@ bool FormatEntity::Format(const Entry &entry, Stream &s,
13061330
return true;
13071331

13081332
case Entry::Type::Root:
1309-
for (const auto &child : entry.children) {
1333+
for (const auto &child : entry.children_stack[0]) {
13101334
if (!Format(child, s, sc, exe_ctx, addr, valobj, function_changed,
13111335
initial_function)) {
13121336
return false; // If any item of root fails, then the formatting fails
@@ -1320,19 +1344,26 @@ bool FormatEntity::Format(const Entry &entry, Stream &s,
13201344

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

13371368
case Entry::Type::Variable:
13381369
case Entry::Type::VariableSynthetic:
@@ -2121,7 +2152,7 @@ static Status ParseInternal(llvm::StringRef &format, Entry &parent_entry,
21212152
uint32_t depth) {
21222153
Status error;
21232154
while (!format.empty() && error.Success()) {
2124-
const size_t non_special_chars = format.find_first_of("${}\\");
2155+
const size_t non_special_chars = format.find_first_of("${}\\|");
21252156

21262157
if (non_special_chars == llvm::StringRef::npos) {
21272158
// No special characters, just string bytes so add them and we are done
@@ -2158,6 +2189,14 @@ static Status ParseInternal(llvm::StringRef &format, Entry &parent_entry,
21582189
.drop_front(); // Skip the '}' as we are at the end of the scope
21592190
return error;
21602191

2192+
case '|':
2193+
format = format.drop_front(); // Skip the '|'
2194+
if (parent_entry.type == Entry::Type::Scope)
2195+
parent_entry.StartAlternative();
2196+
else
2197+
parent_entry.AppendChar('|');
2198+
break;
2199+
21612200
case '\\': {
21622201
format = format.drop_front(); // Skip the '\' character
21632202
if (format.empty()) {

lldb/unittests/Core/FormatEntityTest.cpp

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
#include "lldb/Core/FormatEntity.h"
1010
#include "lldb/Utility/Status.h"
11+
#include "lldb/Utility/StreamString.h"
1112

1213
#include "llvm/ADT/StringRef.h"
1314
#include "gtest/gtest.h"
@@ -160,3 +161,48 @@ TEST(FormatEntity, LookupAllEntriesInTree) {
160161
<< "Formatting " << testString << " did not succeed";
161162
}
162163
}
164+
165+
TEST(FormatEntity, ScopeAlt) {
166+
StreamString stream;
167+
FormatEntity::Entry format;
168+
Status status = FormatEntity::Parse("{${frame.pc}|foo}", format);
169+
ASSERT_TRUE(status.Success()) << status.AsCString();
170+
171+
FormatEntity::Format(format, stream, nullptr, nullptr, nullptr, nullptr,
172+
false, false);
173+
EXPECT_EQ(stream.GetString(), "foo");
174+
}
175+
176+
TEST(FormatEntity, EscapedPipeInScope) {
177+
StreamString stream;
178+
FormatEntity::Entry format;
179+
Status status = FormatEntity::Parse("{foo\\|bar}", format);
180+
ASSERT_TRUE(status.Success()) << status.AsCString();
181+
182+
FormatEntity::Format(format, stream, nullptr, nullptr, nullptr, nullptr,
183+
false, false);
184+
EXPECT_EQ(stream.GetString(), "foo|bar");
185+
}
186+
187+
TEST(FormatEntity, PipeOutsideScope) {
188+
StreamString stream;
189+
FormatEntity::Entry format;
190+
Status status = FormatEntity::Parse("foo|bar", format);
191+
ASSERT_TRUE(status.Success()) << status.AsCString();
192+
193+
FormatEntity::Format(format, stream, nullptr, nullptr, nullptr, nullptr,
194+
false, false);
195+
EXPECT_EQ(stream.GetString(), "foo|bar");
196+
}
197+
198+
TEST(FormatEntity, ScopeAltMultiple) {
199+
StreamString stream;
200+
FormatEntity::Entry format;
201+
Status status =
202+
FormatEntity::Parse("{${frame.pc}|${function.name}|foo}", format);
203+
ASSERT_TRUE(status.Success()) << status.AsCString();
204+
205+
FormatEntity::Format(format, stream, nullptr, nullptr, nullptr, nullptr,
206+
false, false);
207+
EXPECT_EQ(stream.GetString(), "foo");
208+
}

0 commit comments

Comments
 (0)