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