1 //===-- REPL.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 "lldb/Expression/REPL.h"
10 #include "lldb/Core/Debugger.h"
11 #include "lldb/Core/PluginManager.h"
12 #include "lldb/Core/StreamFile.h"
13 #include "lldb/Expression/ExpressionVariable.h"
14 #include "lldb/Expression/UserExpression.h"
15 #include "lldb/Host/HostInfo.h"
16 #include "lldb/Interpreter/CommandInterpreter.h"
17 #include "lldb/Interpreter/CommandReturnObject.h"
18 #include "lldb/Target/Thread.h"
19 #include "lldb/Utility/AnsiTerminal.h"
20
21 #include <memory>
22
23 using namespace lldb_private;
24
REPL(LLVMCastKind kind,Target & target)25 REPL::REPL(LLVMCastKind kind, Target &target) : m_target(target), m_kind(kind) {
26 // Make sure all option values have sane defaults
27 Debugger &debugger = m_target.GetDebugger();
28 auto exe_ctx = debugger.GetCommandInterpreter().GetExecutionContext();
29 m_format_options.OptionParsingStarting(&exe_ctx);
30 m_varobj_options.OptionParsingStarting(&exe_ctx);
31 }
32
33 REPL::~REPL() = default;
34
Create(Status & err,lldb::LanguageType language,Debugger * debugger,Target * target,const char * repl_options)35 lldb::REPLSP REPL::Create(Status &err, lldb::LanguageType language,
36 Debugger *debugger, Target *target,
37 const char *repl_options) {
38 uint32_t idx = 0;
39 lldb::REPLSP ret;
40
41 while (REPLCreateInstance create_instance =
42 PluginManager::GetREPLCreateCallbackAtIndex(idx++)) {
43 ret = (*create_instance)(err, language, debugger, target, repl_options);
44 if (ret) {
45 break;
46 }
47 }
48
49 return ret;
50 }
51
GetSourcePath()52 std::string REPL::GetSourcePath() {
53 ConstString file_basename = GetSourceFileBasename();
54 FileSpec tmpdir_file_spec = HostInfo::GetProcessTempDir();
55 if (tmpdir_file_spec) {
56 tmpdir_file_spec.GetFilename() = file_basename;
57 m_repl_source_path = tmpdir_file_spec.GetPath();
58 } else {
59 tmpdir_file_spec = FileSpec("/tmp");
60 tmpdir_file_spec.AppendPathComponent(file_basename.GetStringRef());
61 }
62
63 return tmpdir_file_spec.GetPath();
64 }
65
GetIOHandler()66 lldb::IOHandlerSP REPL::GetIOHandler() {
67 if (!m_io_handler_sp) {
68 Debugger &debugger = m_target.GetDebugger();
69 m_io_handler_sp = std::make_shared<IOHandlerEditline>(
70 debugger, IOHandler::Type::REPL,
71 "lldb-repl", // Name of input reader for history
72 llvm::StringRef("> "), // prompt
73 llvm::StringRef(". "), // Continuation prompt
74 true, // Multi-line
75 true, // The REPL prompt is always colored
76 1, // Line number
77 *this, nullptr);
78
79 // Don't exit if CTRL+C is pressed
80 static_cast<IOHandlerEditline *>(m_io_handler_sp.get())
81 ->SetInterruptExits(false);
82
83 if (m_io_handler_sp->GetIsInteractive() &&
84 m_io_handler_sp->GetIsRealTerminal()) {
85 m_indent_str.assign(debugger.GetTabSize(), ' ');
86 m_enable_auto_indent = debugger.GetAutoIndent();
87 } else {
88 m_indent_str.clear();
89 m_enable_auto_indent = false;
90 }
91 }
92 return m_io_handler_sp;
93 }
94
IOHandlerActivated(IOHandler & io_handler,bool interactive)95 void REPL::IOHandlerActivated(IOHandler &io_handler, bool interactive) {
96 lldb::ProcessSP process_sp = m_target.GetProcessSP();
97 if (process_sp && process_sp->IsAlive())
98 return;
99 lldb::StreamFileSP error_sp(io_handler.GetErrorStreamFileSP());
100 error_sp->Printf("REPL requires a running target process.\n");
101 io_handler.SetIsDone(true);
102 }
103
IOHandlerInterrupt(IOHandler & io_handler)104 bool REPL::IOHandlerInterrupt(IOHandler &io_handler) { return false; }
105
IOHandlerInputInterrupted(IOHandler & io_handler,std::string & line)106 void REPL::IOHandlerInputInterrupted(IOHandler &io_handler, std::string &line) {
107 }
108
IOHandlerGetFixIndentationCharacters()109 const char *REPL::IOHandlerGetFixIndentationCharacters() {
110 return (m_enable_auto_indent ? GetAutoIndentCharacters() : nullptr);
111 }
112
IOHandlerGetControlSequence(char ch)113 ConstString REPL::IOHandlerGetControlSequence(char ch) {
114 if (ch == 'd')
115 return ConstString(":quit\n");
116 return ConstString();
117 }
118
IOHandlerGetCommandPrefix()119 const char *REPL::IOHandlerGetCommandPrefix() { return ":"; }
120
IOHandlerGetHelpPrologue()121 const char *REPL::IOHandlerGetHelpPrologue() {
122 return "\nThe REPL (Read-Eval-Print-Loop) acts like an interpreter. "
123 "Valid statements, expressions, and declarations are immediately "
124 "compiled and executed.\n\n"
125 "The complete set of LLDB debugging commands are also available as "
126 "described below.\n\nCommands "
127 "must be prefixed with a colon at the REPL prompt (:quit for "
128 "example.) Typing just a colon "
129 "followed by return will switch to the LLDB prompt.\n\n"
130 "Type “< path” to read in code from a text file “path”.\n\n";
131 }
132
IOHandlerIsInputComplete(IOHandler & io_handler,StringList & lines)133 bool REPL::IOHandlerIsInputComplete(IOHandler &io_handler, StringList &lines) {
134 // Check for meta command
135 const size_t num_lines = lines.GetSize();
136 if (num_lines == 1) {
137 const char *first_line = lines.GetStringAtIndex(0);
138 if (first_line[0] == ':')
139 return true; // Meta command is a single line where that starts with ':'
140 }
141
142 // Check if REPL input is done
143 std::string source_string(lines.CopyList());
144 return SourceIsComplete(source_string);
145 }
146
CalculateActualIndentation(const StringList & lines)147 int REPL::CalculateActualIndentation(const StringList &lines) {
148 std::string last_line = lines[lines.GetSize() - 1];
149
150 int actual_indent = 0;
151 for (char &ch : last_line) {
152 if (ch != ' ')
153 break;
154 ++actual_indent;
155 }
156
157 return actual_indent;
158 }
159
IOHandlerFixIndentation(IOHandler & io_handler,const StringList & lines,int cursor_position)160 int REPL::IOHandlerFixIndentation(IOHandler &io_handler,
161 const StringList &lines,
162 int cursor_position) {
163 if (!m_enable_auto_indent)
164 return 0;
165
166 if (!lines.GetSize()) {
167 return 0;
168 }
169
170 int tab_size = io_handler.GetDebugger().GetTabSize();
171
172 lldb::offset_t desired_indent =
173 GetDesiredIndentation(lines, cursor_position, tab_size);
174
175 int actual_indent = REPL::CalculateActualIndentation(lines);
176
177 if (desired_indent == LLDB_INVALID_OFFSET)
178 return 0;
179
180 return (int)desired_indent - actual_indent;
181 }
182
ReadCode(const std::string & path,std::string & code,lldb::StreamFileSP & error_sp)183 static bool ReadCode(const std::string &path, std::string &code,
184 lldb::StreamFileSP &error_sp) {
185 auto &fs = FileSystem::Instance();
186 llvm::Twine pathTwine(path);
187 if (!fs.Exists(pathTwine)) {
188 error_sp->Printf("no such file at path '%s'\n", path.c_str());
189 return false;
190 }
191 if (!fs.Readable(pathTwine)) {
192 error_sp->Printf("could not read file at path '%s'\n", path.c_str());
193 return false;
194 }
195 const size_t file_size = fs.GetByteSize(pathTwine);
196 const size_t max_size = code.max_size();
197 if (file_size > max_size) {
198 error_sp->Printf("file at path '%s' too large: "
199 "file_size = %zu, max_size = %zu\n",
200 path.c_str(), file_size, max_size);
201 return false;
202 }
203 auto data_sp = fs.CreateDataBuffer(pathTwine);
204 if (data_sp == nullptr) {
205 error_sp->Printf("could not create buffer for file at path '%s'\n",
206 path.c_str());
207 return false;
208 }
209 code.assign((const char *)data_sp->GetBytes(), data_sp->GetByteSize());
210 return true;
211 }
212
IOHandlerInputComplete(IOHandler & io_handler,std::string & code)213 void REPL::IOHandlerInputComplete(IOHandler &io_handler, std::string &code) {
214 lldb::StreamFileSP output_sp(io_handler.GetOutputStreamFileSP());
215 lldb::StreamFileSP error_sp(io_handler.GetErrorStreamFileSP());
216 bool extra_line = false;
217 bool did_quit = false;
218
219 if (code.empty()) {
220 m_code.AppendString("");
221 static_cast<IOHandlerEditline &>(io_handler)
222 .SetBaseLineNumber(m_code.GetSize() + 1);
223 } else {
224 Debugger &debugger = m_target.GetDebugger();
225 CommandInterpreter &ci = debugger.GetCommandInterpreter();
226 extra_line = ci.GetSpaceReplPrompts();
227
228 ExecutionContext exe_ctx(m_target.GetProcessSP()
229 ->GetThreadList()
230 .GetSelectedThread()
231 ->GetSelectedFrame()
232 .get());
233
234 lldb::ProcessSP process_sp(exe_ctx.GetProcessSP());
235
236 if (code[0] == ':') {
237 // Meta command
238 // Strip the ':'
239 code.erase(0, 1);
240 if (!llvm::StringRef(code).trim().empty()) {
241 // "lldb" was followed by arguments, so just execute the command dump
242 // the results
243
244 // Turn off prompt on quit in case the user types ":quit"
245 const bool saved_prompt_on_quit = ci.GetPromptOnQuit();
246 if (saved_prompt_on_quit)
247 ci.SetPromptOnQuit(false);
248
249 // Execute the command
250 CommandReturnObject result(debugger.GetUseColor());
251 result.SetImmediateOutputStream(output_sp);
252 result.SetImmediateErrorStream(error_sp);
253 ci.HandleCommand(code.c_str(), eLazyBoolNo, result);
254
255 if (saved_prompt_on_quit)
256 ci.SetPromptOnQuit(true);
257
258 if (result.GetStatus() == lldb::eReturnStatusQuit) {
259 did_quit = true;
260 io_handler.SetIsDone(true);
261 if (debugger.CheckTopIOHandlerTypes(
262 IOHandler::Type::REPL, IOHandler::Type::CommandInterpreter)) {
263 // We typed "quit" or an alias to quit so we need to check if the
264 // command interpreter is above us and tell it that it is done as
265 // well so we don't drop back into the command interpreter if we
266 // have already quit
267 lldb::IOHandlerSP io_handler_sp(ci.GetIOHandler());
268 if (io_handler_sp)
269 io_handler_sp->SetIsDone(true);
270 }
271 }
272 } else {
273 // ":" was followed by no arguments, so push the LLDB command prompt
274 if (debugger.CheckTopIOHandlerTypes(
275 IOHandler::Type::REPL, IOHandler::Type::CommandInterpreter)) {
276 // If the user wants to get back to the command interpreter and the
277 // command interpreter is what launched the REPL, then just let the
278 // REPL exit and fall back to the command interpreter.
279 io_handler.SetIsDone(true);
280 } else {
281 // The REPL wasn't launched the by the command interpreter, it is the
282 // base IOHandler, so we need to get the command interpreter and
283 lldb::IOHandlerSP io_handler_sp(ci.GetIOHandler());
284 if (io_handler_sp) {
285 io_handler_sp->SetIsDone(false);
286 debugger.RunIOHandlerAsync(ci.GetIOHandler());
287 }
288 }
289 }
290 } else {
291 if (code[0] == '<') {
292 // User wants to read code from a file.
293 // Interpret rest of line as a literal path.
294 auto path = llvm::StringRef(code.substr(1)).trim().str();
295 if (!ReadCode(path, code, error_sp)) {
296 return;
297 }
298 }
299
300 // Unwind any expression we might have been running in case our REPL
301 // expression crashed and the user was looking around
302 if (m_dedicated_repl_mode) {
303 Thread *thread = exe_ctx.GetThreadPtr();
304 if (thread && thread->UnwindInnermostExpression().Success()) {
305 thread->SetSelectedFrameByIndex(0, false);
306 exe_ctx.SetFrameSP(thread->GetSelectedFrame());
307 }
308 }
309
310 const bool colorize_err = error_sp->GetFile().GetIsTerminalWithColors();
311
312 EvaluateExpressionOptions expr_options = m_expr_options;
313 expr_options.SetCoerceToId(m_varobj_options.use_objc);
314 expr_options.SetKeepInMemory(true);
315 expr_options.SetUseDynamic(m_varobj_options.use_dynamic);
316 expr_options.SetGenerateDebugInfo(true);
317 expr_options.SetREPLEnabled(true);
318 expr_options.SetColorizeErrors(colorize_err);
319 expr_options.SetPoundLine(m_repl_source_path.c_str(),
320 m_code.GetSize() + 1);
321
322 expr_options.SetLanguage(GetLanguage());
323
324 PersistentExpressionState *persistent_state =
325 m_target.GetPersistentExpressionStateForLanguage(GetLanguage());
326 if (!persistent_state)
327 return;
328
329 const size_t var_count_before = persistent_state->GetSize();
330
331 const char *expr_prefix = nullptr;
332 lldb::ValueObjectSP result_valobj_sp;
333 Status error;
334 lldb::ExpressionResults execution_results =
335 UserExpression::Evaluate(exe_ctx, expr_options, code.c_str(),
336 expr_prefix, result_valobj_sp, error,
337 nullptr); // fixed expression
338
339 // CommandInterpreter &ci = debugger.GetCommandInterpreter();
340
341 if (process_sp && process_sp->IsAlive()) {
342 bool add_to_code = true;
343 bool handled = false;
344 if (result_valobj_sp) {
345 lldb::Format format = m_format_options.GetFormat();
346
347 if (result_valobj_sp->GetError().Success()) {
348 handled |= PrintOneVariable(debugger, output_sp, result_valobj_sp);
349 } else if (result_valobj_sp->GetError().GetError() ==
350 UserExpression::kNoResult) {
351 if (format != lldb::eFormatVoid && debugger.GetNotifyVoid()) {
352 error_sp->PutCString("(void)\n");
353 handled = true;
354 }
355 }
356 }
357
358 if (debugger.GetPrintDecls()) {
359 for (size_t vi = var_count_before, ve = persistent_state->GetSize();
360 vi != ve; ++vi) {
361 lldb::ExpressionVariableSP persistent_var_sp =
362 persistent_state->GetVariableAtIndex(vi);
363 lldb::ValueObjectSP valobj_sp = persistent_var_sp->GetValueObject();
364
365 PrintOneVariable(debugger, output_sp, valobj_sp,
366 persistent_var_sp.get());
367 }
368 }
369
370 if (!handled) {
371 bool useColors = error_sp->GetFile().GetIsTerminalWithColors();
372 switch (execution_results) {
373 case lldb::eExpressionSetupError:
374 case lldb::eExpressionParseError:
375 add_to_code = false;
376 LLVM_FALLTHROUGH;
377 case lldb::eExpressionDiscarded:
378 error_sp->Printf("%s\n", error.AsCString());
379 break;
380
381 case lldb::eExpressionCompleted:
382 break;
383 case lldb::eExpressionInterrupted:
384 if (useColors) {
385 error_sp->Printf(ANSI_ESCAPE1(ANSI_FG_COLOR_RED));
386 error_sp->Printf(ANSI_ESCAPE1(ANSI_CTRL_BOLD));
387 }
388 error_sp->Printf("Execution interrupted. ");
389 if (useColors)
390 error_sp->Printf(ANSI_ESCAPE1(ANSI_CTRL_NORMAL));
391 error_sp->Printf("Enter code to recover and continue.\nEnter LLDB "
392 "commands to investigate (type :help for "
393 "assistance.)\n");
394 break;
395
396 case lldb::eExpressionHitBreakpoint:
397 // Breakpoint was hit, drop into LLDB command interpreter
398 if (useColors) {
399 error_sp->Printf(ANSI_ESCAPE1(ANSI_FG_COLOR_RED));
400 error_sp->Printf(ANSI_ESCAPE1(ANSI_CTRL_BOLD));
401 }
402 output_sp->Printf("Execution stopped at breakpoint. ");
403 if (useColors)
404 error_sp->Printf(ANSI_ESCAPE1(ANSI_CTRL_NORMAL));
405 output_sp->Printf("Enter LLDB commands to investigate (type help "
406 "for assistance.)\n");
407 {
408 lldb::IOHandlerSP io_handler_sp(ci.GetIOHandler());
409 if (io_handler_sp) {
410 io_handler_sp->SetIsDone(false);
411 debugger.RunIOHandlerAsync(ci.GetIOHandler());
412 }
413 }
414 break;
415
416 case lldb::eExpressionTimedOut:
417 error_sp->Printf("error: timeout\n");
418 if (error.AsCString())
419 error_sp->Printf("error: %s\n", error.AsCString());
420 break;
421 case lldb::eExpressionResultUnavailable:
422 // Shoulnd't happen???
423 error_sp->Printf("error: could not fetch result -- %s\n",
424 error.AsCString());
425 break;
426 case lldb::eExpressionStoppedForDebug:
427 // Shoulnd't happen???
428 error_sp->Printf("error: stopped for debug -- %s\n",
429 error.AsCString());
430 break;
431 case lldb::eExpressionThreadVanished:
432 // Shoulnd't happen???
433 error_sp->Printf("error: expression thread vanished -- %s\n",
434 error.AsCString());
435 break;
436 }
437 }
438
439 if (add_to_code) {
440 const uint32_t new_default_line = m_code.GetSize() + 1;
441
442 m_code.SplitIntoLines(code);
443
444 // Update our code on disk
445 if (!m_repl_source_path.empty()) {
446 auto file = FileSystem::Instance().Open(
447 FileSpec(m_repl_source_path),
448 File::eOpenOptionWrite | File::eOpenOptionTruncate |
449 File::eOpenOptionCanCreate,
450 lldb::eFilePermissionsFileDefault);
451 if (file) {
452 std::string code(m_code.CopyList());
453 code.append(1, '\n');
454 size_t bytes_written = code.size();
455 file.get()->Write(code.c_str(), bytes_written);
456 file.get()->Close();
457 } else {
458 std::string message = llvm::toString(file.takeError());
459 error_sp->Printf("error: couldn't open %s: %s\n",
460 m_repl_source_path.c_str(), message.c_str());
461 }
462
463 // Now set the default file and line to the REPL source file
464 m_target.GetSourceManager().SetDefaultFileAndLine(
465 FileSpec(m_repl_source_path), new_default_line);
466 }
467 static_cast<IOHandlerEditline &>(io_handler)
468 .SetBaseLineNumber(m_code.GetSize() + 1);
469 }
470 if (extra_line) {
471 output_sp->Printf("\n");
472 }
473 }
474 }
475
476 // Don't complain about the REPL process going away if we are in the
477 // process of quitting.
478 if (!did_quit && (!process_sp || !process_sp->IsAlive())) {
479 error_sp->Printf(
480 "error: REPL process is no longer alive, exiting REPL\n");
481 io_handler.SetIsDone(true);
482 }
483 }
484 }
485
IOHandlerComplete(IOHandler & io_handler,CompletionRequest & request)486 void REPL::IOHandlerComplete(IOHandler &io_handler,
487 CompletionRequest &request) {
488 // Complete an LLDB command if the first character is a colon...
489 if (request.GetRawLine().startswith(":")) {
490 Debugger &debugger = m_target.GetDebugger();
491
492 // auto complete LLDB commands
493 llvm::StringRef new_line = request.GetRawLine().drop_front();
494 CompletionResult sub_result;
495 CompletionRequest sub_request(new_line, request.GetRawCursorPos() - 1,
496 sub_result);
497 debugger.GetCommandInterpreter().HandleCompletion(sub_request);
498 StringList matches, descriptions;
499 sub_result.GetMatches(matches);
500 // Prepend command prefix that was excluded in the completion request.
501 if (request.GetCursorIndex() == 0)
502 for (auto &match : matches)
503 match.insert(0, 1, ':');
504 sub_result.GetDescriptions(descriptions);
505 request.AddCompletions(matches, descriptions);
506 return;
507 }
508
509 // Strip spaces from the line and see if we had only spaces
510 if (request.GetRawLine().trim().empty()) {
511 // Only spaces on this line, so just indent
512 request.AddCompletion(m_indent_str);
513 return;
514 }
515
516 std::string current_code;
517 current_code.append(m_code.CopyList());
518
519 IOHandlerEditline &editline = static_cast<IOHandlerEditline &>(io_handler);
520 const StringList *current_lines = editline.GetCurrentLines();
521 if (current_lines) {
522 const uint32_t current_line_idx = editline.GetCurrentLineIndex();
523
524 if (current_line_idx < current_lines->GetSize()) {
525 for (uint32_t i = 0; i < current_line_idx; ++i) {
526 const char *line_cstr = current_lines->GetStringAtIndex(i);
527 if (line_cstr) {
528 current_code.append("\n");
529 current_code.append(line_cstr);
530 }
531 }
532 }
533 }
534
535 current_code.append("\n");
536 current_code += request.GetRawLine();
537
538 CompleteCode(current_code, request);
539 }
540
QuitCommandOverrideCallback(void * baton,const char ** argv)541 bool QuitCommandOverrideCallback(void *baton, const char **argv) {
542 Target *target = (Target *)baton;
543 lldb::ProcessSP process_sp(target->GetProcessSP());
544 if (process_sp) {
545 process_sp->Destroy(false);
546 process_sp->GetTarget().GetDebugger().ClearIOHandlers();
547 }
548 return false;
549 }
550
RunLoop()551 Status REPL::RunLoop() {
552 Status error;
553
554 error = DoInitialization();
555 m_repl_source_path = GetSourcePath();
556
557 if (!error.Success())
558 return error;
559
560 Debugger &debugger = m_target.GetDebugger();
561
562 lldb::IOHandlerSP io_handler_sp(GetIOHandler());
563
564 FileSpec save_default_file;
565 uint32_t save_default_line = 0;
566
567 if (!m_repl_source_path.empty()) {
568 // Save the current default file and line
569 m_target.GetSourceManager().GetDefaultFileAndLine(save_default_file,
570 save_default_line);
571 }
572
573 debugger.RunIOHandlerAsync(io_handler_sp);
574
575 // Check if we are in dedicated REPL mode where LLDB was start with the "--
576 // repl" option from the command line. Currently we know this by checking if
577 // the debugger already has a IOHandler thread.
578 if (!debugger.HasIOHandlerThread()) {
579 // The debugger doesn't have an existing IOHandler thread, so this must be
580 // dedicated REPL mode...
581 m_dedicated_repl_mode = true;
582 debugger.StartIOHandlerThread();
583 llvm::StringRef command_name_str("quit");
584 CommandObject *cmd_obj =
585 debugger.GetCommandInterpreter().GetCommandObjectForCommand(
586 command_name_str);
587 if (cmd_obj) {
588 assert(command_name_str.empty());
589 cmd_obj->SetOverrideCallback(QuitCommandOverrideCallback, &m_target);
590 }
591 }
592
593 // Wait for the REPL command interpreter to get popped
594 io_handler_sp->WaitForPop();
595
596 if (m_dedicated_repl_mode) {
597 // If we were in dedicated REPL mode we would have started the IOHandler
598 // thread, and we should kill our process
599 lldb::ProcessSP process_sp = m_target.GetProcessSP();
600 if (process_sp && process_sp->IsAlive())
601 process_sp->Destroy(false);
602
603 // Wait for the IO handler thread to exit (TODO: don't do this if the IO
604 // handler thread already exists...)
605 debugger.JoinIOHandlerThread();
606 }
607
608 // Restore the default file and line
609 if (save_default_file && save_default_line != 0)
610 m_target.GetSourceManager().SetDefaultFileAndLine(save_default_file,
611 save_default_line);
612 return error;
613 }
614