diff --git a/lldb/source/Plugins/ScriptInterpreter/Lua/Lua.h b/lldb/source/Plugins/ScriptInterpreter/Lua/Lua.h --- a/lldb/source/Plugins/ScriptInterpreter/Lua/Lua.h +++ b/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: diff --git a/lldb/source/Plugins/ScriptInterpreter/Lua/Lua.cpp b/lldb/source/Plugins/ScriptInterpreter/Lua/Lua.cpp --- a/lldb/source/Plugins/ScriptInterpreter/Lua/Lua.cpp +++ b/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::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/ScriptInterpreterLua.h b/lldb/source/Plugins/ScriptInterpreter/Lua/ScriptInterpreterLua.h --- a/lldb/source/Plugins/ScriptInterpreter/Lua/ScriptInterpreterLua.h +++ b/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 &bp_options_vec, + CommandReturnObject &result) override; + Status SetBreakpointCommandCallback(BreakpointOptions *bp_options, const char *command_body_text) override; diff --git a/lldb/source/Plugins/ScriptInterpreter/Lua/ScriptInterpreterLua.cpp b/lldb/source/Plugins/ScriptInterpreter/Lua/ScriptInterpreterLua.cpp --- 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 +#include 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 '' + return error_str.find("") == 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 *>( + 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 &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/test/Shell/ScriptInterpreter/Lua/breakpoint_callback.test b/lldb/test/Shell/ScriptInterpreter/Lua/breakpoint_callback.test --- 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 --- /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