Skip to content

[lldb] Change the statusline format to print "no target" #10650

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
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
33 changes: 19 additions & 14 deletions lldb/include/lldb/Core/FormatEntity.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@

#include "lldb/lldb-enumerations.h"
#include "lldb/lldb-types.h"
#include "llvm/ADT/SmallVector.h"
#include <algorithm>
#include <cstddef>
#include <cstdint>

#include <string>
#include <vector>

Expand Down Expand Up @@ -158,9 +158,7 @@ struct Entry {
}

Entry(Type t = Type::Invalid, const char *s = nullptr,
const char *f = nullptr)
: string(s ? s : ""), printf_format(f ? f : ""), type(t) {}

const char *f = nullptr);
Entry(llvm::StringRef s);
Entry(char ch);

Expand All @@ -170,15 +168,19 @@ struct Entry {

void AppendText(const char *cstr);

void AppendEntry(const Entry &&entry) { children.push_back(entry); }
void AppendEntry(const Entry &&entry);

void StartAlternative();

void Clear() {
string.clear();
printf_format.clear();
children.clear();
children_stack.clear();
children_stack.emplace_back();
type = Type::Invalid;
fmt = lldb::eFormatDefault;
number = 0;
level = 0;
deref = false;
}

Expand All @@ -191,13 +193,7 @@ struct Entry {
return false;
if (printf_format != rhs.printf_format)
return false;
const size_t n = children.size();
const size_t m = rhs.children.size();
for (size_t i = 0; i < std::min<size_t>(n, m); ++i) {
if (!(children[i] == rhs.children[i]))
return false;
}
if (children != rhs.children)
if (children_stack != rhs.children_stack)
return false;
if (type != rhs.type)
return false;
Expand All @@ -208,9 +204,18 @@ struct Entry {
return true;
}

std::vector<Entry> &GetChildren();

std::string string;
std::string printf_format;
std::vector<Entry> children;

/// A stack of children entries, used by Scope entries to provide alterantive
/// children. All other entries have a stack of size 1.
/// @{
llvm::SmallVector<std::vector<Entry>, 1> children_stack;
size_t level = 0;
/// @}

Type type;
lldb::Format fmt = lldb::eFormatDefault;
lldb::addr_t number = 0;
Expand Down
2 changes: 1 addition & 1 deletion lldb/source/Core/CoreProperties.td
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ let Definition = "debugger" in {
: Property<"statusline-format", "FormatEntity">,
Global,
DefaultStringValue<
"${ansi.negative}{${target.file.basename}}{ "
"${ansi.negative}{${target.file.basename}|no target}{ "
"${separator}${line.file.basename}:${line.number}:${line.column}}{ "
"${separator}${thread.stop-reason}}{ "
"${separator}{${progress.count} }${progress.message}}">,
Expand Down
87 changes: 63 additions & 24 deletions lldb/source/Core/FormatEntity.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
#include "llvm/Support/Regex.h"
#include "llvm/TargetParser/Triple.h"

#include <cassert>
#include <cctype>
#include <cinttypes>
#include <cstdio>
Expand Down Expand Up @@ -281,31 +282,53 @@ constexpr Definition g_top_level_entries[] = {
constexpr Definition g_root = Entry::DefinitionWithChildren(
"<root>", EntryType::Root, g_top_level_entries);

FormatEntity::Entry::Entry(Type t, const char *s, const char *f)
: string(s ? s : ""), printf_format(f ? f : ""), children_stack({{}}),
type(t) {}

FormatEntity::Entry::Entry(llvm::StringRef s)
: string(s.data(), s.size()), printf_format(), children(),
type(Type::String) {}
: string(s.data(), s.size()), children_stack({{}}), type(Type::String) {}

FormatEntity::Entry::Entry(char ch)
: string(1, ch), printf_format(), children(), type(Type::String) {}
: string(1, ch), printf_format(), children_stack({{}}), type(Type::String) {
}

std::vector<Entry> &FormatEntity::Entry::GetChildren() {
assert(level < children_stack.size());
return children_stack[level];
}

void FormatEntity::Entry::AppendChar(char ch) {
if (children.empty() || children.back().type != Entry::Type::String)
children.push_back(Entry(ch));
auto &entries = GetChildren();
if (entries.empty() || entries.back().type != Entry::Type::String)
entries.push_back(Entry(ch));
else
children.back().string.append(1, ch);
entries.back().string.append(1, ch);
}

void FormatEntity::Entry::AppendText(const llvm::StringRef &s) {
if (children.empty() || children.back().type != Entry::Type::String)
children.push_back(Entry(s));
auto &entries = GetChildren();
if (entries.empty() || entries.back().type != Entry::Type::String)
entries.push_back(Entry(s));
else
children.back().string.append(s.data(), s.size());
entries.back().string.append(s.data(), s.size());
}

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

void FormatEntity::Entry::AppendEntry(const Entry &&entry) {
auto &entries = GetChildren();
entries.push_back(entry);
}

void FormatEntity::Entry::StartAlternative() {
assert(type == Entry::Type::Scope);
children_stack.emplace_back();
level++;
}

#define ENUM_TO_CSTR(eee) \
case FormatEntity::Entry::Type::eee: \
return #eee
Expand Down Expand Up @@ -405,8 +428,9 @@ void FormatEntity::Entry::Dump(Stream &s, int depth) const {
if (deref)
s.Printf("deref = true, ");
s.EOL();
for (const auto &child : children) {
child.Dump(s, depth + 1);
for (const auto &children : children_stack) {
for (const auto &child : children)
child.Dump(s, depth + 1);
}
}

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

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

case Entry::Type::Scope: {
StreamString scope_stream;
bool success = false;
for (const auto &child : entry.children) {
success = Format(child, scope_stream, sc, exe_ctx, addr, valobj,
function_changed, initial_function);
if (!success)
break;
auto format_children = [&](const std::vector<Entry> &children) {
scope_stream.Clear();
for (const auto &child : children) {
if (!Format(child, scope_stream, sc, exe_ctx, addr, valobj,
function_changed, initial_function))
return false;
}
return true;
};

for (auto &children : entry.children_stack) {
if (format_children(children)) {
s.Write(scope_stream.GetString().data(),
scope_stream.GetString().size());
return true;
}
}
// Only if all items in a scope succeed, then do we print the output into
// the main stream
if (success)
s.Write(scope_stream.GetString().data(), scope_stream.GetString().size());
}

return true; // Scopes always successfully print themselves
}

case Entry::Type::Variable:
case Entry::Type::VariableSynthetic:
Expand Down Expand Up @@ -2132,7 +2163,7 @@ static Status ParseInternal(llvm::StringRef &format, Entry &parent_entry,
uint32_t depth) {
Status error;
while (!format.empty() && error.Success()) {
const size_t non_special_chars = format.find_first_of("${}\\");
const size_t non_special_chars = format.find_first_of("${}\\|");

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

case '|':
format = format.drop_front(); // Skip the '|'
if (parent_entry.type == Entry::Type::Scope)
parent_entry.StartAlternative();
else
parent_entry.AppendChar('|');
break;

case '\\': {
format = format.drop_front(); // Skip the '\' character
if (format.empty()) {
Expand Down
61 changes: 33 additions & 28 deletions lldb/test/API/functionalities/statusline/TestStatusline.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,17 @@
from lldbsuite.test.lldbpexpect import PExpectTest


# PExpect uses many timeouts internally and doesn't play well
# under ASAN on a loaded machine..
@skipIfAsan
class TestStatusline(PExpectTest):
# Change this value to something smaller to make debugging this test less
# tedious.
TIMEOUT = 60

TERMINAL_HEIGHT = 10
TERMINAL_WIDTH = 60

def do_setup(self):
# Create a target and run to a breakpoint.
exe = self.getBuildArtifact("a.out")
Expand All @@ -15,36 +25,34 @@ def do_setup(self):
)
self.expect('breakpoint set -p "Break here"', substrs=["Breakpoint 1"])
self.expect("run", substrs=["stop reason"])
self.resize()

def resize(self):
# Change the terminal dimensions. When we launch the tests, we reset
# all the settings, leaving the terminal dimensions unset.
self.child.setwinsize(self.TERMINAL_HEIGHT, self.TERMINAL_WIDTH)

# PExpect uses many timeouts internally and doesn't play well
# under ASAN on a loaded machine..
@skipIfAsan
def test(self):
"""Basic test for the statusline."""
self.build()
self.launch()
self.launch(timeout=self.TIMEOUT)
self.do_setup()

# Change the terminal dimensions.
terminal_height = 10
terminal_width = 60
self.child.setwinsize(terminal_height, terminal_width)

# Enable the statusline and check for the control character and that we
# can see the target, the location and the stop reason.
self.expect('set set separator "| "')
self.expect(
"set set show-statusline true",
[
"\x1b[0;{}r".format(terminal_height - 1),
"\x1b[0;{}r".format(self.TERMINAL_HEIGHT - 1),
"a.out | main.c:2:11 | breakpoint 1.1 ",
],
)

# Change the terminal dimensions and make sure it's reflected immediately.
self.child.setwinsize(terminal_height, 25)
self.child.setwinsize(self.TERMINAL_HEIGHT, 25)
self.child.expect(re.escape("a.out | main.c:2:11 | bre"))
self.child.setwinsize(terminal_height, terminal_width)
self.child.setwinsize(self.TERMINAL_HEIGHT, self.TERMINAL_WIDTH)

# Change the separator.
self.expect('set set separator "S "', ["a.out S main.c:2:11"])
Expand All @@ -58,23 +66,15 @@ def test(self):

# Hide the statusline and check or the control character.
self.expect(
"set set show-statusline false", ["\x1b[0;{}r".format(terminal_height)]
"set set show-statusline false", ["\x1b[0;{}r".format(self.TERMINAL_HEIGHT)]
)

# PExpect uses many timeouts internally and doesn't play well
# under ASAN on a loaded machine..
@skipIfAsan
def test_no_color(self):
"""Basic test for the statusline with colors disabled."""
self.build()
self.launch(use_colors=False)
self.launch(use_colors=False, timeout=self.TIMEOUT)
self.do_setup()

# Change the terminal dimensions.
terminal_height = 10
terminal_width = 60
self.child.setwinsize(terminal_height, terminal_width)

# Enable the statusline and check for the "reverse video" control character.
self.expect(
"set set show-statusline true",
Expand All @@ -87,15 +87,20 @@ def test_deadlock(self):
"""Regression test for lock inversion between the statusline mutex and
the output mutex."""
self.build()
self.launch(extra_args=["-o", "settings set use-color false"])
self.launch(
extra_args=["-o", "settings set use-color false"], timeout=self.TIMEOUT
)
self.child.expect("(lldb)")

# Change the terminal dimensions.
terminal_height = 10
terminal_width = 60
self.child.setwinsize(terminal_height, terminal_width)
self.resize()

exe = self.getBuildArtifact("a.out")

self.expect("file {}".format(exe), ["Current executable"])
self.expect("help", ["Debugger commands"])

def test_no_target(self):
"""Test that we print "no target" when launched without a target."""
self.launch(timeout=self.TIMEOUT)
self.resize()

self.expect("set set show-statusline true", ["no target"])
Loading