180814287SRaphael Isemann //===-- ScriptInterpreterLua.cpp ------------------------------------------===//
267de8962SJonas Devlieghere //
367de8962SJonas Devlieghere // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
467de8962SJonas Devlieghere // See https://llvm.org/LICENSE.txt for license information.
567de8962SJonas Devlieghere // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
667de8962SJonas Devlieghere //
767de8962SJonas Devlieghere //===----------------------------------------------------------------------===//
867de8962SJonas Devlieghere 
967de8962SJonas Devlieghere #include "ScriptInterpreterLua.h"
1028613242SJonas Devlieghere #include "Lua.h"
11a0d7406aSPedro Tammela #include "lldb/Breakpoint/StoppointCallbackContext.h"
1267de8962SJonas Devlieghere #include "lldb/Core/Debugger.h"
1367de8962SJonas Devlieghere #include "lldb/Core/PluginManager.h"
1467de8962SJonas Devlieghere #include "lldb/Core/StreamFile.h"
1528613242SJonas Devlieghere #include "lldb/Interpreter/CommandReturnObject.h"
16a0d7406aSPedro Tammela #include "lldb/Target/ExecutionContext.h"
1767de8962SJonas Devlieghere #include "lldb/Utility/Stream.h"
1867de8962SJonas Devlieghere #include "lldb/Utility/StringList.h"
1928613242SJonas Devlieghere #include "lldb/Utility/Timer.h"
20*d853bd7aSPedro Tammela #include "llvm/ADT/StringRef.h"
21ed8184b7SJonas Devlieghere #include "llvm/Support/FormatAdapters.h"
22a0d7406aSPedro Tammela #include <memory>
23*d853bd7aSPedro Tammela #include <vector>
2467de8962SJonas Devlieghere 
2567de8962SJonas Devlieghere using namespace lldb;
2667de8962SJonas Devlieghere using namespace lldb_private;
2767de8962SJonas Devlieghere 
28bba9ba8dSJonas Devlieghere LLDB_PLUGIN_DEFINE(ScriptInterpreterLua)
29fbb4d1e4SJonas Devlieghere 
30*d853bd7aSPedro Tammela enum ActiveIOHandler {
31*d853bd7aSPedro Tammela   eIOHandlerNone,
32*d853bd7aSPedro Tammela   eIOHandlerBreakpoint,
33*d853bd7aSPedro Tammela   eIOHandlerWatchpoint
34*d853bd7aSPedro Tammela };
35*d853bd7aSPedro Tammela 
3628613242SJonas Devlieghere class IOHandlerLuaInterpreter : public IOHandlerDelegate,
3728613242SJonas Devlieghere                                 public IOHandlerEditline {
3828613242SJonas Devlieghere public:
394164be72SJonas Devlieghere   IOHandlerLuaInterpreter(Debugger &debugger,
40*d853bd7aSPedro Tammela                           ScriptInterpreterLua &script_interpreter,
41*d853bd7aSPedro Tammela                           ActiveIOHandler active_io_handler = eIOHandlerNone)
4228613242SJonas Devlieghere       : IOHandlerEditline(debugger, IOHandler::Type::LuaInterpreter, "lua",
4328613242SJonas Devlieghere                           ">>> ", "..> ", true, debugger.GetUseColor(), 0,
4428613242SJonas Devlieghere                           *this, nullptr),
45*d853bd7aSPedro Tammela         m_script_interpreter(script_interpreter),
46*d853bd7aSPedro Tammela         m_active_io_handler(active_io_handler) {
47fa1b4a96SJonas Devlieghere     llvm::cantFail(m_script_interpreter.GetLua().ChangeIO(
48fa1b4a96SJonas Devlieghere         debugger.GetOutputFile().GetStream(),
49fa1b4a96SJonas Devlieghere         debugger.GetErrorFile().GetStream()));
5045c971f7SJonas Devlieghere     llvm::cantFail(m_script_interpreter.EnterSession(debugger.GetID()));
5145c971f7SJonas Devlieghere   }
5245c971f7SJonas Devlieghere 
535ddd4fc5SJonas Devlieghere   ~IOHandlerLuaInterpreter() override {
5445c971f7SJonas Devlieghere     llvm::cantFail(m_script_interpreter.LeaveSession());
5545c971f7SJonas Devlieghere   }
5628613242SJonas Devlieghere 
57*d853bd7aSPedro Tammela   void IOHandlerActivated(IOHandler &io_handler, bool interactive) override {
58*d853bd7aSPedro Tammela     const char *instructions = nullptr;
59*d853bd7aSPedro Tammela     switch (m_active_io_handler) {
60*d853bd7aSPedro Tammela     case eIOHandlerNone:
61*d853bd7aSPedro Tammela     case eIOHandlerWatchpoint:
62*d853bd7aSPedro Tammela       break;
63*d853bd7aSPedro Tammela     case eIOHandlerBreakpoint:
64*d853bd7aSPedro Tammela       instructions = "Enter your Lua command(s). Type 'quit' to end.\n"
65*d853bd7aSPedro Tammela                      "The commands are compiled as the body of the following "
66*d853bd7aSPedro Tammela                      "Lua function\n"
67*d853bd7aSPedro Tammela                      "function (frame, bp_loc, ...) end\n";
68*d853bd7aSPedro Tammela       SetPrompt(llvm::StringRef("..> "));
69*d853bd7aSPedro Tammela       break;
70*d853bd7aSPedro Tammela     }
71*d853bd7aSPedro Tammela     if (instructions == nullptr)
72*d853bd7aSPedro Tammela       return;
73*d853bd7aSPedro Tammela     if (interactive)
74*d853bd7aSPedro Tammela       *io_handler.GetOutputStreamFileSP() << instructions;
75*d853bd7aSPedro Tammela   }
76*d853bd7aSPedro Tammela 
77*d853bd7aSPedro Tammela   bool IOHandlerIsInputComplete(IOHandler &io_handler,
78*d853bd7aSPedro Tammela                                 StringList &lines) override {
79*d853bd7aSPedro Tammela     size_t last = lines.GetSize() - 1;
80*d853bd7aSPedro Tammela     if (IsQuitCommand(lines.GetStringAtIndex(last))) {
81*d853bd7aSPedro Tammela       if (m_active_io_handler == eIOHandlerBreakpoint)
82*d853bd7aSPedro Tammela         lines.DeleteStringAtIndex(last);
83*d853bd7aSPedro Tammela       return true;
84*d853bd7aSPedro Tammela     }
85*d853bd7aSPedro Tammela     StreamString str;
86*d853bd7aSPedro Tammela     lines.Join("\n", str);
87*d853bd7aSPedro Tammela     if (llvm::Error E =
88*d853bd7aSPedro Tammela             m_script_interpreter.GetLua().CheckSyntax(str.GetString())) {
89*d853bd7aSPedro Tammela       std::string error_str = toString(std::move(E));
90*d853bd7aSPedro Tammela       // Lua always errors out to incomplete code with '<eof>'
91*d853bd7aSPedro Tammela       return error_str.find("<eof>") == std::string::npos;
92*d853bd7aSPedro Tammela     }
93*d853bd7aSPedro Tammela     // The breakpoint handler only exits with a explicit 'quit'
94*d853bd7aSPedro Tammela     return m_active_io_handler != eIOHandlerBreakpoint;
95*d853bd7aSPedro Tammela   }
96*d853bd7aSPedro Tammela 
9728613242SJonas Devlieghere   void IOHandlerInputComplete(IOHandler &io_handler,
9828613242SJonas Devlieghere                               std::string &data) override {
99*d853bd7aSPedro Tammela     switch (m_active_io_handler) {
100*d853bd7aSPedro Tammela     case eIOHandlerBreakpoint: {
101*d853bd7aSPedro Tammela       auto *bp_options_vec = static_cast<std::vector<BreakpointOptions *> *>(
102*d853bd7aSPedro Tammela           io_handler.GetUserData());
103*d853bd7aSPedro Tammela       for (auto *bp_options : *bp_options_vec) {
104*d853bd7aSPedro Tammela         Status error = m_script_interpreter.SetBreakpointCommandCallback(
105*d853bd7aSPedro Tammela             bp_options, data.c_str());
106*d853bd7aSPedro Tammela         if (error.Fail())
107*d853bd7aSPedro Tammela           *io_handler.GetErrorStreamFileSP() << error.AsCString() << '\n';
108*d853bd7aSPedro Tammela       }
109*d853bd7aSPedro Tammela       io_handler.SetIsDone(true);
110*d853bd7aSPedro Tammela     } break;
111*d853bd7aSPedro Tammela     case eIOHandlerWatchpoint:
112*d853bd7aSPedro Tammela       io_handler.SetIsDone(true);
113*d853bd7aSPedro Tammela       break;
114*d853bd7aSPedro Tammela     case eIOHandlerNone:
115*d853bd7aSPedro Tammela       if (IsQuitCommand(data)) {
1161728dec2SJonas Devlieghere         io_handler.SetIsDone(true);
1171728dec2SJonas Devlieghere         return;
1181728dec2SJonas Devlieghere       }
119*d853bd7aSPedro Tammela       if (llvm::Error error = m_script_interpreter.GetLua().Run(data))
120*d853bd7aSPedro Tammela         *io_handler.GetErrorStreamFileSP() << toString(std::move(error));
121*d853bd7aSPedro Tammela       break;
12228613242SJonas Devlieghere     }
12328613242SJonas Devlieghere   }
12428613242SJonas Devlieghere 
12528613242SJonas Devlieghere private:
1264164be72SJonas Devlieghere   ScriptInterpreterLua &m_script_interpreter;
127*d853bd7aSPedro Tammela   ActiveIOHandler m_active_io_handler;
128*d853bd7aSPedro Tammela 
129*d853bd7aSPedro Tammela   bool IsQuitCommand(llvm::StringRef cmd) { return cmd.rtrim() == "quit"; }
13028613242SJonas Devlieghere };
13128613242SJonas Devlieghere 
13267de8962SJonas Devlieghere ScriptInterpreterLua::ScriptInterpreterLua(Debugger &debugger)
1334164be72SJonas Devlieghere     : ScriptInterpreter(debugger, eScriptLanguageLua),
1344164be72SJonas Devlieghere       m_lua(std::make_unique<Lua>()) {}
13567de8962SJonas Devlieghere 
13667de8962SJonas Devlieghere ScriptInterpreterLua::~ScriptInterpreterLua() {}
13767de8962SJonas Devlieghere 
13867de8962SJonas Devlieghere bool ScriptInterpreterLua::ExecuteOneLine(llvm::StringRef command,
13928613242SJonas Devlieghere                                           CommandReturnObject *result,
14028613242SJonas Devlieghere                                           const ExecuteScriptOptions &options) {
141ed8184b7SJonas Devlieghere   if (command.empty()) {
142ed8184b7SJonas Devlieghere     if (result)
143ed8184b7SJonas Devlieghere       result->AppendError("empty command passed to lua\n");
144ed8184b7SJonas Devlieghere     return false;
145ed8184b7SJonas Devlieghere   }
146ed8184b7SJonas Devlieghere 
147ed8184b7SJonas Devlieghere   llvm::Expected<std::unique_ptr<ScriptInterpreterIORedirect>>
148ed8184b7SJonas Devlieghere       io_redirect_or_error = ScriptInterpreterIORedirect::Create(
149ed8184b7SJonas Devlieghere           options.GetEnableIO(), m_debugger, result);
150ed8184b7SJonas Devlieghere   if (!io_redirect_or_error) {
151ed8184b7SJonas Devlieghere     if (result)
152ed8184b7SJonas Devlieghere       result->AppendErrorWithFormatv(
153ed8184b7SJonas Devlieghere           "failed to redirect I/O: {0}\n",
154ed8184b7SJonas Devlieghere           llvm::fmt_consume(io_redirect_or_error.takeError()));
155ed8184b7SJonas Devlieghere     else
156ed8184b7SJonas Devlieghere       llvm::consumeError(io_redirect_or_error.takeError());
157ed8184b7SJonas Devlieghere     return false;
158ed8184b7SJonas Devlieghere   }
159ed8184b7SJonas Devlieghere 
160ed8184b7SJonas Devlieghere   ScriptInterpreterIORedirect &io_redirect = **io_redirect_or_error;
161ed8184b7SJonas Devlieghere 
162ed8184b7SJonas Devlieghere   if (llvm::Error e =
163ed8184b7SJonas Devlieghere           m_lua->ChangeIO(io_redirect.GetOutputFile()->GetStream(),
164ed8184b7SJonas Devlieghere                           io_redirect.GetErrorFile()->GetStream())) {
165ed8184b7SJonas Devlieghere     result->AppendErrorWithFormatv("lua failed to redirect I/O: {0}\n",
166ed8184b7SJonas Devlieghere                                    llvm::toString(std::move(e)));
167ed8184b7SJonas Devlieghere     return false;
168ed8184b7SJonas Devlieghere   }
169ed8184b7SJonas Devlieghere 
1704164be72SJonas Devlieghere   if (llvm::Error e = m_lua->Run(command)) {
17128613242SJonas Devlieghere     result->AppendErrorWithFormatv(
17228613242SJonas Devlieghere         "lua failed attempting to evaluate '{0}': {1}\n", command,
17328613242SJonas Devlieghere         llvm::toString(std::move(e)));
17467de8962SJonas Devlieghere     return false;
17567de8962SJonas Devlieghere   }
176ed8184b7SJonas Devlieghere 
177ed8184b7SJonas Devlieghere   io_redirect.Flush();
17828613242SJonas Devlieghere   return true;
17928613242SJonas Devlieghere }
18067de8962SJonas Devlieghere 
18167de8962SJonas Devlieghere void ScriptInterpreterLua::ExecuteInterpreterLoop() {
1825c1c8443SJonas Devlieghere   LLDB_SCOPED_TIMER();
18328613242SJonas Devlieghere 
18428613242SJonas Devlieghere   // At the moment, the only time the debugger does not have an input file
18528613242SJonas Devlieghere   // handle is when this is called directly from lua, in which case it is
18628613242SJonas Devlieghere   // both dangerous and unnecessary (not to mention confusing) to try to embed
18728613242SJonas Devlieghere   // a running interpreter loop inside the already running lua interpreter
18828613242SJonas Devlieghere   // loop, so we won't do it.
1896e3faaebSJonas Devlieghere   if (!m_debugger.GetInputFile().IsValid())
19028613242SJonas Devlieghere     return;
19128613242SJonas Devlieghere 
1926e3faaebSJonas Devlieghere   IOHandlerSP io_handler_sp(new IOHandlerLuaInterpreter(m_debugger, *this));
1936e3faaebSJonas Devlieghere   m_debugger.RunIOHandlerAsync(io_handler_sp);
19467de8962SJonas Devlieghere }
19567de8962SJonas Devlieghere 
196572b9f46SJonas Devlieghere bool ScriptInterpreterLua::LoadScriptingModule(
197572b9f46SJonas Devlieghere     const char *filename, bool init_session, lldb_private::Status &error,
19800bb397bSJonas Devlieghere     StructuredData::ObjectSP *module_sp, FileSpec extra_search_dir) {
199572b9f46SJonas Devlieghere 
2001f80e515SJonas Devlieghere   FileSystem::Instance().Collect(filename);
201572b9f46SJonas Devlieghere   if (llvm::Error e = m_lua->LoadModule(filename)) {
202572b9f46SJonas Devlieghere     error.SetErrorStringWithFormatv("lua failed to import '{0}': {1}\n",
203572b9f46SJonas Devlieghere                                     filename, llvm::toString(std::move(e)));
204572b9f46SJonas Devlieghere     return false;
205572b9f46SJonas Devlieghere   }
206572b9f46SJonas Devlieghere   return true;
207572b9f46SJonas Devlieghere }
208572b9f46SJonas Devlieghere 
20967de8962SJonas Devlieghere void ScriptInterpreterLua::Initialize() {
21067de8962SJonas Devlieghere   static llvm::once_flag g_once_flag;
21167de8962SJonas Devlieghere 
21267de8962SJonas Devlieghere   llvm::call_once(g_once_flag, []() {
21367de8962SJonas Devlieghere     PluginManager::RegisterPlugin(GetPluginNameStatic(),
21467de8962SJonas Devlieghere                                   GetPluginDescriptionStatic(),
21567de8962SJonas Devlieghere                                   lldb::eScriptLanguageLua, CreateInstance);
21667de8962SJonas Devlieghere   });
21767de8962SJonas Devlieghere }
21867de8962SJonas Devlieghere 
21967de8962SJonas Devlieghere void ScriptInterpreterLua::Terminate() {}
22067de8962SJonas Devlieghere 
22145c971f7SJonas Devlieghere llvm::Error ScriptInterpreterLua::EnterSession(user_id_t debugger_id) {
22245c971f7SJonas Devlieghere   if (m_session_is_active)
22345c971f7SJonas Devlieghere     return llvm::Error::success();
22445c971f7SJonas Devlieghere 
22545c971f7SJonas Devlieghere   const char *fmt_str =
22645c971f7SJonas Devlieghere       "lldb.debugger = lldb.SBDebugger.FindDebuggerWithID({0}); "
22745c971f7SJonas Devlieghere       "lldb.target = lldb.debugger:GetSelectedTarget(); "
22845c971f7SJonas Devlieghere       "lldb.process = lldb.target:GetProcess(); "
22945c971f7SJonas Devlieghere       "lldb.thread = lldb.process:GetSelectedThread(); "
23045c971f7SJonas Devlieghere       "lldb.frame = lldb.thread:GetSelectedFrame()";
23145c971f7SJonas Devlieghere   return m_lua->Run(llvm::formatv(fmt_str, debugger_id).str());
23245c971f7SJonas Devlieghere }
23345c971f7SJonas Devlieghere 
23445c971f7SJonas Devlieghere llvm::Error ScriptInterpreterLua::LeaveSession() {
23545c971f7SJonas Devlieghere   if (!m_session_is_active)
23645c971f7SJonas Devlieghere     return llvm::Error::success();
23745c971f7SJonas Devlieghere 
23845c971f7SJonas Devlieghere   m_session_is_active = false;
23945c971f7SJonas Devlieghere 
24045c971f7SJonas Devlieghere   llvm::StringRef str = "lldb.debugger = nil; "
24145c971f7SJonas Devlieghere                         "lldb.target = nil; "
24245c971f7SJonas Devlieghere                         "lldb.process = nil; "
24345c971f7SJonas Devlieghere                         "lldb.thread = nil; "
24445c971f7SJonas Devlieghere                         "lldb.frame = nil";
24545c971f7SJonas Devlieghere   return m_lua->Run(str);
24645c971f7SJonas Devlieghere }
24745c971f7SJonas Devlieghere 
248a0d7406aSPedro Tammela bool ScriptInterpreterLua::BreakpointCallbackFunction(
249a0d7406aSPedro Tammela     void *baton, StoppointCallbackContext *context, user_id_t break_id,
250a0d7406aSPedro Tammela     user_id_t break_loc_id) {
251a0d7406aSPedro Tammela   assert(context);
252a0d7406aSPedro Tammela 
253a0d7406aSPedro Tammela   ExecutionContext exe_ctx(context->exe_ctx_ref);
254a0d7406aSPedro Tammela   Target *target = exe_ctx.GetTargetPtr();
255a0d7406aSPedro Tammela   if (target == nullptr)
256a0d7406aSPedro Tammela     return true;
257a0d7406aSPedro Tammela 
258a0d7406aSPedro Tammela   StackFrameSP stop_frame_sp(exe_ctx.GetFrameSP());
259a0d7406aSPedro Tammela   BreakpointSP breakpoint_sp = target->GetBreakpointByID(break_id);
260a0d7406aSPedro Tammela   BreakpointLocationSP bp_loc_sp(breakpoint_sp->FindLocationByID(break_loc_id));
261a0d7406aSPedro Tammela 
262a0d7406aSPedro Tammela   Debugger &debugger = target->GetDebugger();
263a0d7406aSPedro Tammela   ScriptInterpreterLua *lua_interpreter = static_cast<ScriptInterpreterLua *>(
264a0d7406aSPedro Tammela       debugger.GetScriptInterpreter(true, eScriptLanguageLua));
265a0d7406aSPedro Tammela   Lua &lua = lua_interpreter->GetLua();
266a0d7406aSPedro Tammela 
267a0d7406aSPedro Tammela   llvm::Expected<bool> BoolOrErr =
268a0d7406aSPedro Tammela       lua.CallBreakpointCallback(baton, stop_frame_sp, bp_loc_sp);
269a0d7406aSPedro Tammela   if (llvm::Error E = BoolOrErr.takeError()) {
270a0d7406aSPedro Tammela     debugger.GetErrorStream() << toString(std::move(E));
271a0d7406aSPedro Tammela     return true;
272a0d7406aSPedro Tammela   }
273a0d7406aSPedro Tammela 
274a0d7406aSPedro Tammela   return *BoolOrErr;
275a0d7406aSPedro Tammela }
276a0d7406aSPedro Tammela 
277*d853bd7aSPedro Tammela void ScriptInterpreterLua::CollectDataForBreakpointCommandCallback(
278*d853bd7aSPedro Tammela     std::vector<BreakpointOptions *> &bp_options_vec,
279*d853bd7aSPedro Tammela     CommandReturnObject &result) {
280*d853bd7aSPedro Tammela   IOHandlerSP io_handler_sp(
281*d853bd7aSPedro Tammela       new IOHandlerLuaInterpreter(m_debugger, *this, eIOHandlerBreakpoint));
282*d853bd7aSPedro Tammela   io_handler_sp->SetUserData(&bp_options_vec);
283*d853bd7aSPedro Tammela   m_debugger.RunIOHandlerAsync(io_handler_sp);
284*d853bd7aSPedro Tammela }
285*d853bd7aSPedro Tammela 
286a0d7406aSPedro Tammela Status ScriptInterpreterLua::SetBreakpointCommandCallback(
287a0d7406aSPedro Tammela     BreakpointOptions *bp_options, const char *command_body_text) {
288a0d7406aSPedro Tammela   Status error;
289a0d7406aSPedro Tammela   auto data_up = std::make_unique<CommandDataLua>();
290a0d7406aSPedro Tammela   error = m_lua->RegisterBreakpointCallback(data_up.get(), command_body_text);
291a0d7406aSPedro Tammela   if (error.Fail())
292a0d7406aSPedro Tammela     return error;
293a0d7406aSPedro Tammela   auto baton_sp =
294a0d7406aSPedro Tammela       std::make_shared<BreakpointOptions::CommandBaton>(std::move(data_up));
295a0d7406aSPedro Tammela   bp_options->SetCallback(ScriptInterpreterLua::BreakpointCallbackFunction,
296a0d7406aSPedro Tammela                           baton_sp);
297a0d7406aSPedro Tammela   return error;
298a0d7406aSPedro Tammela }
299a0d7406aSPedro Tammela 
30067de8962SJonas Devlieghere lldb::ScriptInterpreterSP
30167de8962SJonas Devlieghere ScriptInterpreterLua::CreateInstance(Debugger &debugger) {
30267de8962SJonas Devlieghere   return std::make_shared<ScriptInterpreterLua>(debugger);
30367de8962SJonas Devlieghere }
30467de8962SJonas Devlieghere 
30567de8962SJonas Devlieghere lldb_private::ConstString ScriptInterpreterLua::GetPluginNameStatic() {
30667de8962SJonas Devlieghere   static ConstString g_name("script-lua");
30767de8962SJonas Devlieghere   return g_name;
30867de8962SJonas Devlieghere }
30967de8962SJonas Devlieghere 
31067de8962SJonas Devlieghere const char *ScriptInterpreterLua::GetPluginDescriptionStatic() {
31167de8962SJonas Devlieghere   return "Lua script interpreter";
31267de8962SJonas Devlieghere }
31367de8962SJonas Devlieghere 
31467de8962SJonas Devlieghere lldb_private::ConstString ScriptInterpreterLua::GetPluginName() {
31567de8962SJonas Devlieghere   return GetPluginNameStatic();
31667de8962SJonas Devlieghere }
31767de8962SJonas Devlieghere 
31867de8962SJonas Devlieghere uint32_t ScriptInterpreterLua::GetPluginVersion() { return 1; }
3194164be72SJonas Devlieghere 
3204164be72SJonas Devlieghere Lua &ScriptInterpreterLua::GetLua() { return *m_lua; }
321