tammela created this revision.
tammela requested review of this revision.
Herald added a project: LLDB.
Herald added a subscriber: lldb-commits.

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 - LoadBuffer in Lua.h

In order to support (1), we need an API for just loading string buffers. This
was not available as so far we only needed to run 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


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D93481

Files:
  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
  lldb/test/Shell/ScriptInterpreter/Lua/partial_statements.test

Index: lldb/test/Shell/ScriptInterpreter/Lua/partial_statements.test
===================================================================
--- /dev/null
+++ 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
Index: lldb/test/Shell/ScriptInterpreter/Lua/breakpoint_callback.test
===================================================================
--- lldb/test/Shell/ScriptInterpreter/Lua/breakpoint_callback.test
+++ 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'
Index: lldb/source/Plugins/ScriptInterpreter/Lua/ScriptInterpreterLua.h
===================================================================
--- lldb/source/Plugins/ScriptInterpreter/Lua/ScriptInterpreterLua.h
+++ lldb/source/Plugins/ScriptInterpreter/Lua/ScriptInterpreterLua.h
@@ -65,6 +65,10 @@
   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;
 
Index: lldb/source/Plugins/ScriptInterpreter/Lua/ScriptInterpreterLua.cpp
===================================================================
--- lldb/source/Plugins/ScriptInterpreter/Lua/ScriptInterpreterLua.cpp
+++ 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,78 @@
     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;
+    llvm::StringRef last_str = lines.GetStringAtIndex(last);
+    if (last_str.rtrim() == "quit") {
+      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().LoadBuffer(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 (llvm::StringRef(data).rtrim() == "quit") {
+        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;
 };
 
 ScriptInterpreterLua::ScriptInterpreterLua(Debugger &debugger)
@@ -206,6 +274,15 @@
   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;
Index: lldb/source/Plugins/ScriptInterpreter/Lua/Lua.h
===================================================================
--- lldb/source/Plugins/ScriptInterpreter/Lua/Lua.h
+++ lldb/source/Plugins/ScriptInterpreter/Lua/Lua.h
@@ -36,6 +36,7 @@
   CallBreakpointCallback(void *baton, lldb::StackFrameSP stop_frame_sp,
                          lldb::BreakpointLocationSP bp_loc_sp);
   llvm::Error LoadModule(llvm::StringRef filename);
+  llvm::Error LoadBuffer(llvm::StringRef buffer, bool pop_result = true);
   llvm::Error ChangeIO(FILE *out, FILE *err);
 
 private:
Index: lldb/source/Plugins/ScriptInterpreter/Lua/Lua.cpp
===================================================================
--- lldb/source/Plugins/ScriptInterpreter/Lua/Lua.cpp
+++ lldb/source/Plugins/ScriptInterpreter/Lua/Lua.cpp
@@ -66,9 +66,10 @@
 }
 
 llvm::Error Lua::Run(llvm::StringRef buffer) {
-  int error =
-      luaL_loadbuffer(m_lua_state, buffer.data(), buffer.size(), "buffer") ||
-      lua_pcall(m_lua_state, 0, 0, 0);
+  if (llvm::Error e = LoadBuffer(buffer, false))
+    return e;
+
+  int error = lua_pcall(m_lua_state, 0, 0, 0);
   if (error == LUA_OK)
     return llvm::Error::success();
 
@@ -105,6 +106,22 @@
                                                bp_loc_sp);
 }
 
+llvm::Error Lua::LoadBuffer(llvm::StringRef buffer, bool pop_result) {
+  int error =
+      luaL_loadbuffer(m_lua_state, buffer.data(), buffer.size(), "buffer");
+  if (error == LUA_OK) {
+    lua_pop(m_lua_state, pop_result ? 1 : 0);
+    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)) {
_______________________________________________
lldb-commits mailing list
lldb-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to