1 | //===-- ScriptInterpreterLua.cpp ------------------------------------------===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | |
9 | #include "ScriptInterpreterLua.h" |
10 | #include "Lua.h" |
11 | #include "lldb/Breakpoint/StoppointCallbackContext.h" |
12 | #include "lldb/Core/Debugger.h" |
13 | #include "lldb/Core/PluginManager.h" |
14 | #include "lldb/Host/StreamFile.h" |
15 | #include "lldb/Interpreter/CommandReturnObject.h" |
16 | #include "lldb/Target/ExecutionContext.h" |
17 | #include "lldb/Utility/Stream.h" |
18 | #include "lldb/Utility/StringList.h" |
19 | #include "lldb/Utility/Timer.h" |
20 | #include "llvm/ADT/StringRef.h" |
21 | #include "llvm/Support/FormatAdapters.h" |
22 | #include <memory> |
23 | #include <vector> |
24 | |
25 | using namespace lldb; |
26 | using namespace lldb_private; |
27 | |
28 | LLDB_PLUGIN_DEFINE(ScriptInterpreterLua) |
29 | |
30 | enum ActiveIOHandler { |
31 | eIOHandlerNone, |
32 | eIOHandlerBreakpoint, |
33 | eIOHandlerWatchpoint |
34 | }; |
35 | |
36 | class IOHandlerLuaInterpreter : public IOHandlerDelegate, |
37 | public IOHandlerEditline { |
38 | public: |
39 | IOHandlerLuaInterpreter(Debugger &debugger, |
40 | ScriptInterpreterLua &script_interpreter, |
41 | ActiveIOHandler active_io_handler = eIOHandlerNone) |
42 | : IOHandlerEditline(debugger, IOHandler::Type::LuaInterpreter, "lua" , |
43 | llvm::StringRef(">>> " ), llvm::StringRef("..> " ), |
44 | true, debugger.GetUseColor(), 0, *this), |
45 | m_script_interpreter(script_interpreter), |
46 | m_active_io_handler(active_io_handler) { |
47 | llvm::cantFail(Err: m_script_interpreter.GetLua().ChangeIO( |
48 | out: debugger.GetOutputFile().GetStream(), |
49 | err: debugger.GetErrorFile().GetStream())); |
50 | llvm::cantFail(Err: m_script_interpreter.EnterSession(debugger_id: debugger.GetID())); |
51 | } |
52 | |
53 | ~IOHandlerLuaInterpreter() override { |
54 | llvm::cantFail(Err: m_script_interpreter.LeaveSession()); |
55 | } |
56 | |
57 | void IOHandlerActivated(IOHandler &io_handler, bool interactive) override { |
58 | const char *instructions = nullptr; |
59 | switch (m_active_io_handler) { |
60 | case eIOHandlerNone: |
61 | break; |
62 | case eIOHandlerWatchpoint: |
63 | instructions = "Enter your Lua command(s). Type 'quit' to end.\n" |
64 | "The commands are compiled as the body of the following " |
65 | "Lua function\n" |
66 | "function (frame, wp) end\n" ; |
67 | SetPrompt(llvm::StringRef("..> " )); |
68 | break; |
69 | case eIOHandlerBreakpoint: |
70 | instructions = "Enter your Lua command(s). Type 'quit' to end.\n" |
71 | "The commands are compiled as the body of the following " |
72 | "Lua function\n" |
73 | "function (frame, bp_loc, ...) end\n" ; |
74 | SetPrompt(llvm::StringRef("..> " )); |
75 | break; |
76 | } |
77 | if (instructions == nullptr) |
78 | return; |
79 | if (interactive) |
80 | *io_handler.GetOutputStreamFileSP() << instructions; |
81 | } |
82 | |
83 | bool IOHandlerIsInputComplete(IOHandler &io_handler, |
84 | StringList &lines) override { |
85 | size_t last = lines.GetSize() - 1; |
86 | if (IsQuitCommand(cmd: lines.GetStringAtIndex(idx: last))) { |
87 | if (m_active_io_handler == eIOHandlerBreakpoint || |
88 | m_active_io_handler == eIOHandlerWatchpoint) |
89 | lines.DeleteStringAtIndex(id: last); |
90 | return true; |
91 | } |
92 | StreamString str; |
93 | lines.Join(separator: "\n" , strm&: str); |
94 | if (llvm::Error E = |
95 | m_script_interpreter.GetLua().CheckSyntax(buffer: str.GetString())) { |
96 | std::string error_str = toString(E: std::move(E)); |
97 | // Lua always errors out to incomplete code with '<eof>' |
98 | return error_str.find(s: "<eof>" ) == std::string::npos; |
99 | } |
100 | // The breakpoint and watchpoint handler only exits with a explicit 'quit' |
101 | return m_active_io_handler != eIOHandlerBreakpoint && |
102 | m_active_io_handler != eIOHandlerWatchpoint; |
103 | } |
104 | |
105 | void IOHandlerInputComplete(IOHandler &io_handler, |
106 | std::string &data) override { |
107 | switch (m_active_io_handler) { |
108 | case eIOHandlerBreakpoint: { |
109 | auto *bp_options_vec = |
110 | static_cast<std::vector<std::reference_wrapper<BreakpointOptions>> *>( |
111 | io_handler.GetUserData()); |
112 | for (BreakpointOptions &bp_options : *bp_options_vec) { |
113 | Status error = m_script_interpreter.SetBreakpointCommandCallback( |
114 | bp_options, command_body_text: data.c_str(), /*is_callback=*/false); |
115 | if (error.Fail()) |
116 | *io_handler.GetErrorStreamFileSP() << error.AsCString() << '\n'; |
117 | } |
118 | io_handler.SetIsDone(true); |
119 | } break; |
120 | case eIOHandlerWatchpoint: { |
121 | auto *wp_options = |
122 | static_cast<WatchpointOptions *>(io_handler.GetUserData()); |
123 | m_script_interpreter.SetWatchpointCommandCallback(wp_options, |
124 | command_body_text: data.c_str(), |
125 | /*is_callback=*/false); |
126 | io_handler.SetIsDone(true); |
127 | } break; |
128 | case eIOHandlerNone: |
129 | if (IsQuitCommand(cmd: data)) { |
130 | io_handler.SetIsDone(true); |
131 | return; |
132 | } |
133 | if (llvm::Error error = m_script_interpreter.GetLua().Run(buffer: data)) |
134 | *io_handler.GetErrorStreamFileSP() << toString(E: std::move(error)); |
135 | break; |
136 | } |
137 | } |
138 | |
139 | private: |
140 | ScriptInterpreterLua &m_script_interpreter; |
141 | ActiveIOHandler m_active_io_handler; |
142 | |
143 | bool IsQuitCommand(llvm::StringRef cmd) { return cmd.rtrim() == "quit" ; } |
144 | }; |
145 | |
146 | ScriptInterpreterLua::ScriptInterpreterLua(Debugger &debugger) |
147 | : ScriptInterpreter(debugger, eScriptLanguageLua), |
148 | m_lua(std::make_unique<Lua>()) {} |
149 | |
150 | ScriptInterpreterLua::~ScriptInterpreterLua() = default; |
151 | |
152 | StructuredData::DictionarySP ScriptInterpreterLua::GetInterpreterInfo() { |
153 | auto info = std::make_shared<StructuredData::Dictionary>(); |
154 | info->AddStringItem(key: "language" , value: "lua" ); |
155 | return info; |
156 | } |
157 | |
158 | bool ScriptInterpreterLua::ExecuteOneLine(llvm::StringRef command, |
159 | CommandReturnObject *result, |
160 | const ExecuteScriptOptions &options) { |
161 | if (command.empty()) { |
162 | if (result) |
163 | result->AppendError(in_string: "empty command passed to lua\n" ); |
164 | return false; |
165 | } |
166 | |
167 | llvm::Expected<std::unique_ptr<ScriptInterpreterIORedirect>> |
168 | io_redirect_or_error = ScriptInterpreterIORedirect::Create( |
169 | enable_io: options.GetEnableIO(), debugger&: m_debugger, result); |
170 | if (!io_redirect_or_error) { |
171 | if (result) |
172 | result->AppendErrorWithFormatv( |
173 | format: "failed to redirect I/O: {0}\n" , |
174 | args: llvm::fmt_consume(Item: io_redirect_or_error.takeError())); |
175 | else |
176 | llvm::consumeError(Err: io_redirect_or_error.takeError()); |
177 | return false; |
178 | } |
179 | |
180 | ScriptInterpreterIORedirect &io_redirect = **io_redirect_or_error; |
181 | |
182 | if (llvm::Error e = |
183 | m_lua->ChangeIO(out: io_redirect.GetOutputFile()->GetStream(), |
184 | err: io_redirect.GetErrorFile()->GetStream())) { |
185 | result->AppendErrorWithFormatv(format: "lua failed to redirect I/O: {0}\n" , |
186 | args: llvm::toString(E: std::move(e))); |
187 | return false; |
188 | } |
189 | |
190 | if (llvm::Error e = m_lua->Run(buffer: command)) { |
191 | result->AppendErrorWithFormatv( |
192 | format: "lua failed attempting to evaluate '{0}': {1}\n" , args&: command, |
193 | args: llvm::toString(E: std::move(e))); |
194 | return false; |
195 | } |
196 | |
197 | io_redirect.Flush(); |
198 | return true; |
199 | } |
200 | |
201 | void ScriptInterpreterLua::ExecuteInterpreterLoop() { |
202 | LLDB_SCOPED_TIMER(); |
203 | |
204 | // At the moment, the only time the debugger does not have an input file |
205 | // handle is when this is called directly from lua, in which case it is |
206 | // both dangerous and unnecessary (not to mention confusing) to try to embed |
207 | // a running interpreter loop inside the already running lua interpreter |
208 | // loop, so we won't do it. |
209 | if (!m_debugger.GetInputFile().IsValid()) |
210 | return; |
211 | |
212 | IOHandlerSP io_handler_sp(new IOHandlerLuaInterpreter(m_debugger, *this)); |
213 | m_debugger.RunIOHandlerAsync(reader_sp: io_handler_sp); |
214 | } |
215 | |
216 | bool ScriptInterpreterLua::LoadScriptingModule( |
217 | const char *filename, const LoadScriptOptions &options, |
218 | lldb_private::Status &error, StructuredData::ObjectSP *module_sp, |
219 | FileSpec ) { |
220 | |
221 | if (llvm::Error e = m_lua->LoadModule(filename)) { |
222 | error.SetErrorStringWithFormatv(format: "lua failed to import '{0}': {1}\n" , |
223 | args&: filename, args: llvm::toString(E: std::move(e))); |
224 | return false; |
225 | } |
226 | return true; |
227 | } |
228 | |
229 | void ScriptInterpreterLua::Initialize() { |
230 | static llvm::once_flag g_once_flag; |
231 | |
232 | llvm::call_once(flag&: g_once_flag, F: []() { |
233 | PluginManager::RegisterPlugin(name: GetPluginNameStatic(), |
234 | description: GetPluginDescriptionStatic(), |
235 | script_lang: lldb::eScriptLanguageLua, create_callback: CreateInstance); |
236 | }); |
237 | } |
238 | |
239 | void ScriptInterpreterLua::Terminate() {} |
240 | |
241 | llvm::Error ScriptInterpreterLua::EnterSession(user_id_t debugger_id) { |
242 | if (m_session_is_active) |
243 | return llvm::Error::success(); |
244 | |
245 | const char *fmt_str = |
246 | "lldb.debugger = lldb.SBDebugger.FindDebuggerWithID({0}); " |
247 | "lldb.target = lldb.debugger:GetSelectedTarget(); " |
248 | "lldb.process = lldb.target:GetProcess(); " |
249 | "lldb.thread = lldb.process:GetSelectedThread(); " |
250 | "lldb.frame = lldb.thread:GetSelectedFrame()" ; |
251 | return m_lua->Run(buffer: llvm::formatv(Fmt: fmt_str, Vals&: debugger_id).str()); |
252 | } |
253 | |
254 | llvm::Error ScriptInterpreterLua::LeaveSession() { |
255 | if (!m_session_is_active) |
256 | return llvm::Error::success(); |
257 | |
258 | m_session_is_active = false; |
259 | |
260 | llvm::StringRef str = "lldb.debugger = nil; " |
261 | "lldb.target = nil; " |
262 | "lldb.process = nil; " |
263 | "lldb.thread = nil; " |
264 | "lldb.frame = nil" ; |
265 | return m_lua->Run(buffer: str); |
266 | } |
267 | |
268 | bool ScriptInterpreterLua::BreakpointCallbackFunction( |
269 | void *baton, StoppointCallbackContext *context, user_id_t break_id, |
270 | user_id_t break_loc_id) { |
271 | assert(context); |
272 | |
273 | ExecutionContext exe_ctx(context->exe_ctx_ref); |
274 | Target *target = exe_ctx.GetTargetPtr(); |
275 | if (target == nullptr) |
276 | return true; |
277 | |
278 | StackFrameSP stop_frame_sp(exe_ctx.GetFrameSP()); |
279 | BreakpointSP breakpoint_sp = target->GetBreakpointByID(break_id); |
280 | BreakpointLocationSP bp_loc_sp(breakpoint_sp->FindLocationByID(bp_loc_id: break_loc_id)); |
281 | |
282 | Debugger &debugger = target->GetDebugger(); |
283 | ScriptInterpreterLua *lua_interpreter = static_cast<ScriptInterpreterLua *>( |
284 | debugger.GetScriptInterpreter(can_create: true, language: eScriptLanguageLua)); |
285 | Lua &lua = lua_interpreter->GetLua(); |
286 | |
287 | CommandDataLua *bp_option_data = static_cast<CommandDataLua *>(baton); |
288 | llvm::Expected<bool> BoolOrErr = lua.CallBreakpointCallback( |
289 | baton, stop_frame_sp, bp_loc_sp, extra_args_sp: bp_option_data->m_extra_args_sp); |
290 | if (llvm::Error E = BoolOrErr.takeError()) { |
291 | debugger.GetErrorStream() << toString(E: std::move(E)); |
292 | return true; |
293 | } |
294 | |
295 | return *BoolOrErr; |
296 | } |
297 | |
298 | bool ScriptInterpreterLua::WatchpointCallbackFunction( |
299 | void *baton, StoppointCallbackContext *context, user_id_t watch_id) { |
300 | assert(context); |
301 | |
302 | ExecutionContext exe_ctx(context->exe_ctx_ref); |
303 | Target *target = exe_ctx.GetTargetPtr(); |
304 | if (target == nullptr) |
305 | return true; |
306 | |
307 | StackFrameSP stop_frame_sp(exe_ctx.GetFrameSP()); |
308 | WatchpointSP wp_sp = target->GetWatchpointList().FindByID(watchID: watch_id); |
309 | |
310 | Debugger &debugger = target->GetDebugger(); |
311 | ScriptInterpreterLua *lua_interpreter = static_cast<ScriptInterpreterLua *>( |
312 | debugger.GetScriptInterpreter(can_create: true, language: eScriptLanguageLua)); |
313 | Lua &lua = lua_interpreter->GetLua(); |
314 | |
315 | llvm::Expected<bool> BoolOrErr = |
316 | lua.CallWatchpointCallback(baton, stop_frame_sp, wp_sp); |
317 | if (llvm::Error E = BoolOrErr.takeError()) { |
318 | debugger.GetErrorStream() << toString(E: std::move(E)); |
319 | return true; |
320 | } |
321 | |
322 | return *BoolOrErr; |
323 | } |
324 | |
325 | void ScriptInterpreterLua::CollectDataForBreakpointCommandCallback( |
326 | std::vector<std::reference_wrapper<BreakpointOptions>> &bp_options_vec, |
327 | CommandReturnObject &result) { |
328 | IOHandlerSP io_handler_sp( |
329 | new IOHandlerLuaInterpreter(m_debugger, *this, eIOHandlerBreakpoint)); |
330 | io_handler_sp->SetUserData(&bp_options_vec); |
331 | m_debugger.RunIOHandlerAsync(reader_sp: io_handler_sp); |
332 | } |
333 | |
334 | void ScriptInterpreterLua::CollectDataForWatchpointCommandCallback( |
335 | WatchpointOptions *wp_options, CommandReturnObject &result) { |
336 | IOHandlerSP io_handler_sp( |
337 | new IOHandlerLuaInterpreter(m_debugger, *this, eIOHandlerWatchpoint)); |
338 | io_handler_sp->SetUserData(wp_options); |
339 | m_debugger.RunIOHandlerAsync(reader_sp: io_handler_sp); |
340 | } |
341 | |
342 | Status ScriptInterpreterLua::SetBreakpointCommandCallbackFunction( |
343 | BreakpointOptions &bp_options, const char *function_name, |
344 | StructuredData::ObjectSP ) { |
345 | const char *fmt_str = "return {0}(frame, bp_loc, ...)" ; |
346 | std::string oneliner = llvm::formatv(Fmt: fmt_str, Vals&: function_name).str(); |
347 | return RegisterBreakpointCallback(bp_options, command_body_text: oneliner.c_str(), |
348 | extra_args_sp); |
349 | } |
350 | |
351 | Status ScriptInterpreterLua::SetBreakpointCommandCallback( |
352 | BreakpointOptions &bp_options, const char *command_body_text, |
353 | bool is_callback) { |
354 | return RegisterBreakpointCallback(bp_options, command_body_text, extra_args_sp: {}); |
355 | } |
356 | |
357 | Status ScriptInterpreterLua::RegisterBreakpointCallback( |
358 | BreakpointOptions &bp_options, const char *command_body_text, |
359 | StructuredData::ObjectSP ) { |
360 | Status error; |
361 | auto data_up = std::make_unique<CommandDataLua>(args&: extra_args_sp); |
362 | error = m_lua->RegisterBreakpointCallback(baton: data_up.get(), body: command_body_text); |
363 | if (error.Fail()) |
364 | return error; |
365 | auto baton_sp = |
366 | std::make_shared<BreakpointOptions::CommandBaton>(args: std::move(data_up)); |
367 | bp_options.SetCallback(callback: ScriptInterpreterLua::BreakpointCallbackFunction, |
368 | command_baton_sp: baton_sp); |
369 | return error; |
370 | } |
371 | |
372 | void ScriptInterpreterLua::SetWatchpointCommandCallback( |
373 | WatchpointOptions *wp_options, const char *command_body_text, |
374 | bool is_callback) { |
375 | RegisterWatchpointCallback(wp_options, command_body_text, extra_args_sp: {}); |
376 | } |
377 | |
378 | Status ScriptInterpreterLua::RegisterWatchpointCallback( |
379 | WatchpointOptions *wp_options, const char *command_body_text, |
380 | StructuredData::ObjectSP ) { |
381 | Status error; |
382 | auto data_up = std::make_unique<WatchpointOptions::CommandData>(); |
383 | error = m_lua->RegisterWatchpointCallback(baton: data_up.get(), body: command_body_text); |
384 | if (error.Fail()) |
385 | return error; |
386 | auto baton_sp = |
387 | std::make_shared<WatchpointOptions::CommandBaton>(args: std::move(data_up)); |
388 | wp_options->SetCallback(callback: ScriptInterpreterLua::WatchpointCallbackFunction, |
389 | baton_sp); |
390 | return error; |
391 | } |
392 | |
393 | lldb::ScriptInterpreterSP |
394 | ScriptInterpreterLua::CreateInstance(Debugger &debugger) { |
395 | return std::make_shared<ScriptInterpreterLua>(args&: debugger); |
396 | } |
397 | |
398 | llvm::StringRef ScriptInterpreterLua::GetPluginDescriptionStatic() { |
399 | return "Lua script interpreter" ; |
400 | } |
401 | |
402 | Lua &ScriptInterpreterLua::GetLua() { return *m_lua; } |
403 | |