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