[llvm-branch-commits] [lldb] d853bd7 - [lldb/Lua] add support for multiline scripted breakpoints

Pedro Tammela via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Wed Jan 6 16:35:59 PST 2021


Author: Pedro Tammela
Date: 2021-01-07T00:31:36Z
New Revision: d853bd7a4e86a50f7d7e6a5f397fcbd1e7d844b4

URL: https://github.com/llvm/llvm-project/commit/d853bd7a4e86a50f7d7e6a5f397fcbd1e7d844b4
DIFF: https://github.com/llvm/llvm-project/commit/d853bd7a4e86a50f7d7e6a5f397fcbd1e7d844b4.diff

LOG: [lldb/Lua] add support for multiline scripted breakpoints

1 - Partial Statements

The interpreter loop runs every line it receives, so partial
Lua statements are not being handled properly. This is a problem for
multiline breakpoint scripts since the interpreter loop, for this
particular case, is just an abstraction to a partially parsed function
body declaration.

This patch addresses this issue and as a side effect improves the
general Lua interpreter loop as well. It's now possible to write partial
statements in the 'script' command.

Example:
   (lldb) script
   >>>   do
   ..>   local a = 123
   ..>   print(a)
   ..>   end
   123

The technique implemented is the same as the one employed by Lua's own REPL implementation.
Partial statements always errors out with the '<eof>' tag in the error
message.

2 - CheckSyntax in Lua.h

In order to support (1), we need an API for just checking the syntax of string buffers.

3 - Multiline scripted breakpoints

Finally, with all the base features implemented this feature is
straightforward. The interpreter loop behaves exactly the same, the
difference is that it will aggregate all Lua statements into the body of
the breakpoint function. An explicit 'quit' statement is needed to exit the
interpreter loop.

Example:
   (lldb) breakpoint command add -s lua
   Enter your Lua command(s). Type 'quit' to end.
   The commands are compiled as the body of the following Lua function
   function (frame, bp_loc, ...) end
   ..> print(456)
   ..> a = 123
   ..> quit

Differential Revision: https://reviews.llvm.org/D93481

Added: 
    lldb/test/Shell/ScriptInterpreter/Lua/partial_statements.test

Modified: 
    lldb/source/Plugins/ScriptInterpreter/Lua/Lua.cpp
    lldb/source/Plugins/ScriptInterpreter/Lua/Lua.h
    lldb/source/Plugins/ScriptInterpreter/Lua/ScriptInterpreterLua.cpp
    lldb/source/Plugins/ScriptInterpreter/Lua/ScriptInterpreterLua.h
    lldb/test/Shell/ScriptInterpreter/Lua/breakpoint_callback.test

Removed: 
    


################################################################################
diff  --git a/lldb/source/Plugins/ScriptInterpreter/Lua/Lua.cpp b/lldb/source/Plugins/ScriptInterpreter/Lua/Lua.cpp
index fb3628a3107c..ec946d1b97e4 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Lua/Lua.cpp
+++ b/lldb/source/Plugins/ScriptInterpreter/Lua/Lua.cpp
@@ -105,6 +105,23 @@ Lua::CallBreakpointCallback(void *baton, lldb::StackFrameSP stop_frame_sp,
                                                bp_loc_sp);
 }
 
+llvm::Error Lua::CheckSyntax(llvm::StringRef buffer) {
+  int error =
+      luaL_loadbuffer(m_lua_state, buffer.data(), buffer.size(), "buffer");
+  if (error == LUA_OK) {
+    // Pop buffer
+    lua_pop(m_lua_state, 1);
+    return llvm::Error::success();
+  }
+
+  llvm::Error e = llvm::make_error<llvm::StringError>(
+      llvm::formatv("{0}\n", lua_tostring(m_lua_state, -1)),
+      llvm::inconvertibleErrorCode());
+  // Pop error message from the stack.
+  lua_pop(m_lua_state, 1);
+  return e;
+}
+
 llvm::Error Lua::LoadModule(llvm::StringRef filename) {
   FileSpec file(filename);
   if (!FileSystem::Instance().Exists(file)) {

diff  --git a/lldb/source/Plugins/ScriptInterpreter/Lua/Lua.h b/lldb/source/Plugins/ScriptInterpreter/Lua/Lua.h
index 39c39e1c43b8..84cc0148a7eb 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Lua/Lua.h
+++ b/lldb/source/Plugins/ScriptInterpreter/Lua/Lua.h
@@ -36,6 +36,7 @@ class Lua {
   CallBreakpointCallback(void *baton, lldb::StackFrameSP stop_frame_sp,
                          lldb::BreakpointLocationSP bp_loc_sp);
   llvm::Error LoadModule(llvm::StringRef filename);
+  llvm::Error CheckSyntax(llvm::StringRef buffer);
   llvm::Error ChangeIO(FILE *out, FILE *err);
 
 private:

diff  --git a/lldb/source/Plugins/ScriptInterpreter/Lua/ScriptInterpreterLua.cpp b/lldb/source/Plugins/ScriptInterpreter/Lua/ScriptInterpreterLua.cpp
index 239b409ac695..1dbadb90813c 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Lua/ScriptInterpreterLua.cpp
+++ b/lldb/source/Plugins/ScriptInterpreter/Lua/ScriptInterpreterLua.cpp
@@ -17,23 +17,33 @@
 #include "lldb/Utility/Stream.h"
 #include "lldb/Utility/StringList.h"
 #include "lldb/Utility/Timer.h"
+#include "llvm/ADT/StringRef.h"
 #include "llvm/Support/FormatAdapters.h"
 #include <memory>
+#include <vector>
 
 using namespace lldb;
 using namespace lldb_private;
 
 LLDB_PLUGIN_DEFINE(ScriptInterpreterLua)
 
+enum ActiveIOHandler {
+  eIOHandlerNone,
+  eIOHandlerBreakpoint,
+  eIOHandlerWatchpoint
+};
+
 class IOHandlerLuaInterpreter : public IOHandlerDelegate,
                                 public IOHandlerEditline {
 public:
   IOHandlerLuaInterpreter(Debugger &debugger,
-                          ScriptInterpreterLua &script_interpreter)
+                          ScriptInterpreterLua &script_interpreter,
+                          ActiveIOHandler active_io_handler = eIOHandlerNone)
       : IOHandlerEditline(debugger, IOHandler::Type::LuaInterpreter, "lua",
                           ">>> ", "..> ", true, debugger.GetUseColor(), 0,
                           *this, nullptr),
-        m_script_interpreter(script_interpreter) {
+        m_script_interpreter(script_interpreter),
+        m_active_io_handler(active_io_handler) {
     llvm::cantFail(m_script_interpreter.GetLua().ChangeIO(
         debugger.GetOutputFile().GetStream(),
         debugger.GetErrorFile().GetStream()));
@@ -44,20 +54,79 @@ class IOHandlerLuaInterpreter : public IOHandlerDelegate,
     llvm::cantFail(m_script_interpreter.LeaveSession());
   }
 
-  void IOHandlerInputComplete(IOHandler &io_handler,
-                              std::string &data) override {
-    if (llvm::StringRef(data).rtrim() == "quit") {
-      io_handler.SetIsDone(true);
+  void IOHandlerActivated(IOHandler &io_handler, bool interactive) override {
+    const char *instructions = nullptr;
+    switch (m_active_io_handler) {
+    case eIOHandlerNone:
+    case eIOHandlerWatchpoint:
+      break;
+    case eIOHandlerBreakpoint:
+      instructions = "Enter your Lua command(s). Type 'quit' to end.\n"
+                     "The commands are compiled as the body of the following "
+                     "Lua function\n"
+                     "function (frame, bp_loc, ...) end\n";
+      SetPrompt(llvm::StringRef("..> "));
+      break;
+    }
+    if (instructions == nullptr)
       return;
+    if (interactive)
+      *io_handler.GetOutputStreamFileSP() << instructions;
+  }
+
+  bool IOHandlerIsInputComplete(IOHandler &io_handler,
+                                StringList &lines) override {
+    size_t last = lines.GetSize() - 1;
+    if (IsQuitCommand(lines.GetStringAtIndex(last))) {
+      if (m_active_io_handler == eIOHandlerBreakpoint)
+        lines.DeleteStringAtIndex(last);
+      return true;
+    }
+    StreamString str;
+    lines.Join("\n", str);
+    if (llvm::Error E =
+            m_script_interpreter.GetLua().CheckSyntax(str.GetString())) {
+      std::string error_str = toString(std::move(E));
+      // Lua always errors out to incomplete code with '<eof>'
+      return error_str.find("<eof>") == std::string::npos;
     }
+    // The breakpoint handler only exits with a explicit 'quit'
+    return m_active_io_handler != eIOHandlerBreakpoint;
+  }
 
-    if (llvm::Error error = m_script_interpreter.GetLua().Run(data)) {
-      *GetOutputStreamFileSP() << llvm::toString(std::move(error));
+  void IOHandlerInputComplete(IOHandler &io_handler,
+                              std::string &data) override {
+    switch (m_active_io_handler) {
+    case eIOHandlerBreakpoint: {
+      auto *bp_options_vec = static_cast<std::vector<BreakpointOptions *> *>(
+          io_handler.GetUserData());
+      for (auto *bp_options : *bp_options_vec) {
+        Status error = m_script_interpreter.SetBreakpointCommandCallback(
+            bp_options, data.c_str());
+        if (error.Fail())
+          *io_handler.GetErrorStreamFileSP() << error.AsCString() << '\n';
+      }
+      io_handler.SetIsDone(true);
+    } break;
+    case eIOHandlerWatchpoint:
+      io_handler.SetIsDone(true);
+      break;
+    case eIOHandlerNone:
+      if (IsQuitCommand(data)) {
+        io_handler.SetIsDone(true);
+        return;
+      }
+      if (llvm::Error error = m_script_interpreter.GetLua().Run(data))
+        *io_handler.GetErrorStreamFileSP() << toString(std::move(error));
+      break;
     }
   }
 
 private:
   ScriptInterpreterLua &m_script_interpreter;
+  ActiveIOHandler m_active_io_handler;
+
+  bool IsQuitCommand(llvm::StringRef cmd) { return cmd.rtrim() == "quit"; }
 };
 
 ScriptInterpreterLua::ScriptInterpreterLua(Debugger &debugger)
@@ -205,6 +274,15 @@ bool ScriptInterpreterLua::BreakpointCallbackFunction(
   return *BoolOrErr;
 }
 
+void ScriptInterpreterLua::CollectDataForBreakpointCommandCallback(
+    std::vector<BreakpointOptions *> &bp_options_vec,
+    CommandReturnObject &result) {
+  IOHandlerSP io_handler_sp(
+      new IOHandlerLuaInterpreter(m_debugger, *this, eIOHandlerBreakpoint));
+  io_handler_sp->SetUserData(&bp_options_vec);
+  m_debugger.RunIOHandlerAsync(io_handler_sp);
+}
+
 Status ScriptInterpreterLua::SetBreakpointCommandCallback(
     BreakpointOptions *bp_options, const char *command_body_text) {
   Status error;

diff  --git a/lldb/source/Plugins/ScriptInterpreter/Lua/ScriptInterpreterLua.h b/lldb/source/Plugins/ScriptInterpreter/Lua/ScriptInterpreterLua.h
index 1238b8922bc1..6c3fc01a4607 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Lua/ScriptInterpreterLua.h
+++ b/lldb/source/Plugins/ScriptInterpreter/Lua/ScriptInterpreterLua.h
@@ -65,6 +65,10 @@ class ScriptInterpreterLua : public ScriptInterpreter {
   llvm::Error EnterSession(lldb::user_id_t debugger_id);
   llvm::Error LeaveSession();
 
+  void CollectDataForBreakpointCommandCallback(
+      std::vector<BreakpointOptions *> &bp_options_vec,
+      CommandReturnObject &result) override;
+
   Status SetBreakpointCommandCallback(BreakpointOptions *bp_options,
                                       const char *command_body_text) override;
 

diff  --git a/lldb/test/Shell/ScriptInterpreter/Lua/breakpoint_callback.test b/lldb/test/Shell/ScriptInterpreter/Lua/breakpoint_callback.test
index 4dbf286a693e..65997d8af099 100644
--- a/lldb/test/Shell/ScriptInterpreter/Lua/breakpoint_callback.test
+++ b/lldb/test/Shell/ScriptInterpreter/Lua/breakpoint_callback.test
@@ -1,5 +1,13 @@
 # REQUIRES: lua
-# RUN: %lldb -s %s --script-language lua 2>&1 | FileCheck %s
+# RUN: echo "int main() { return 0; }" | %clang_host -x c - -o %t
+# RUN: %lldb -s %s --script-language lua %t 2>&1 | FileCheck %s
 b main
 breakpoint command add -s lua
-# CHECK: error: This script interpreter does not support breakpoint callbacks
+local a = 123
+print(a)
+quit
+run
+# CHECK: 123
+breakpoint command add -s lua
+789_nil
+# CHECK: unexpected symbol near '789'

diff  --git a/lldb/test/Shell/ScriptInterpreter/Lua/partial_statements.test b/lldb/test/Shell/ScriptInterpreter/Lua/partial_statements.test
new file mode 100644
index 000000000000..3b764d7ba4a9
--- /dev/null
+++ b/lldb/test/Shell/ScriptInterpreter/Lua/partial_statements.test
@@ -0,0 +1,15 @@
+# REQUIRES: lua
+# RUN: %lldb -s %s --script-language lua 2>&1 | FileCheck %s
+script
+do
+local a = 123
+print(a)
+end
+# CHECK: 123
+str = 'hello there!'
+function callme()
+print(str)
+end
+callme()
+# CHECK: hello there!
+# CHECK-NOT: error


        


More information about the llvm-branch-commits mailing list