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