Skip to content

Commit

Permalink
[lldb-dap] Add feature to remember last non-empty expression. (llvm#1…
Browse files Browse the repository at this point in the history
…07485)

Update lldb-dap so if the user just presses return, which sends an empty
expression, it re-evaluates the most recent non-empty
expression/command. Also udpated test to test this case.
  • Loading branch information
cmtice authored Sep 20, 2024
1 parent 76bc1ed commit 2011cbc
Show file tree
Hide file tree
Showing 5 changed files with 49 additions and 5 deletions.
18 changes: 18 additions & 0 deletions lldb/test/API/tools/lldb-dap/evaluate/TestDAP_evaluate.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,22 @@ def run_test_evaluate_expressions(
line_number(source, "// breakpoint 5"),
line_number(source, "// breakpoint 6"),
line_number(source, "// breakpoint 7"),
line_number(source, "// breakpoint 8"),
],
)
self.continue_to_next_stop()

# Expressions at breakpoint 1, which is in main
self.assertEvaluate("var1", "20")
# Empty expression should equate to the previous expression.
if context == "repl":
self.assertEvaluate("", "20")
else:
self.assertEvaluateFailure("")
self.assertEvaluate("var2", "21")
if context == "repl":
self.assertEvaluate("", "21")
self.assertEvaluate("", "21")
self.assertEvaluate("static_int", "42")
self.assertEvaluate("non_static_int", "43")
self.assertEvaluate("struct1.foo", "15")
Expand Down Expand Up @@ -191,6 +200,15 @@ def run_test_evaluate_expressions(
self.continue_to_next_stop()
self.assertEvaluate("my_bool_vec", "size=2")

# Test memory read, especially with 'empty' repeat commands.
if context == "repl":
self.continue_to_next_stop()
self.assertEvaluate("memory read -c 1 &my_ints", ".* 05 .*\n")
self.assertEvaluate("", ".* 0a .*\n")
self.assertEvaluate("", ".* 0f .*\n")
self.assertEvaluate("", ".* 14 .*\n")
self.assertEvaluate("", ".* 19 .*\n")

@skipIfWindows
def test_generic_evaluate_expressions(self):
# Tests context-less expression evaluations
Expand Down
4 changes: 3 additions & 1 deletion lldb/test/API/tools/lldb-dap/evaluate/main.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "foo.h"

#include <cstdint>
#include <map>
#include <vector>

Expand Down Expand Up @@ -45,5 +46,6 @@ int main(int argc, char const *argv[]) {
my_bool_vec.push_back(false); // breakpoint 6
my_bool_vec.push_back(true); // breakpoint 7

return 0;
uint8_t my_ints[] = {5, 10, 15, 20, 25, 30};
return 0; // breakpoint 8
}
6 changes: 6 additions & 0 deletions lldb/tools/lldb-dap/DAP.h
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,12 @@ struct DAP {
std::string command_escape_prefix = "`";
lldb::SBFormat frame_format;
lldb::SBFormat thread_format;
// This is used to allow request_evaluate to handle empty expressions
// (ie the user pressed 'return' and expects the previous expression to
// repeat). If the previous expression was a command, this string will be
// empty; if the previous expression was a variable expression, this string
// will contain that expression.
std::string last_nonempty_var_expression;

DAP();
~DAP();
Expand Down
3 changes: 2 additions & 1 deletion lldb/tools/lldb-dap/LLDBUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ bool RunLLDBCommands(llvm::StringRef prefix,
// RunTerminateCommands.
static std::mutex handle_command_mutex;
std::lock_guard<std::mutex> locker(handle_command_mutex);
interp.HandleCommand(command.str().c_str(), result);
interp.HandleCommand(command.str().c_str(), result,
/*add_to_history=*/true);
}

const bool got_error = !result.Succeeded();
Expand Down
23 changes: 20 additions & 3 deletions lldb/tools/lldb-dap/lldb-dap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1568,9 +1568,16 @@ void request_evaluate(const llvm::json::Object &request) {
lldb::SBFrame frame = g_dap.GetLLDBFrame(*arguments);
std::string expression = GetString(arguments, "expression").str();
llvm::StringRef context = GetString(arguments, "context");

if (context == "repl" && g_dap.DetectExpressionContext(frame, expression) ==
ExpressionContext::Command) {
bool repeat_last_command =
expression.empty() && g_dap.last_nonempty_var_expression.empty();

if (context == "repl" && (repeat_last_command ||
(!expression.empty() &&
g_dap.DetectExpressionContext(frame, expression) ==
ExpressionContext::Command))) {
// Since the current expression is not for a variable, clear the
// last_nonempty_var_expression field.
g_dap.last_nonempty_var_expression.clear();
// If we're evaluating a command relative to the current frame, set the
// focus_tid to the current frame for any thread related events.
if (frame.IsValid()) {
Expand All @@ -1581,6 +1588,16 @@ void request_evaluate(const llvm::json::Object &request) {
EmplaceSafeString(body, "result", result);
body.try_emplace("variablesReference", (int64_t)0);
} else {
if (context == "repl") {
// If the expression is empty and the last expression was for a
// variable, set the expression to the previous expression (repeat the
// evaluation); otherwise save the current non-empty expression for the
// next (possibly empty) variable expression.
if (expression.empty())
expression = g_dap.last_nonempty_var_expression;
else
g_dap.last_nonempty_var_expression = expression;
}
// Always try to get the answer from the local variables if possible. If
// this fails, then if the context is not "hover", actually evaluate an
// expression using the expression parser.
Expand Down

0 comments on commit 2011cbc

Please sign in to comment.