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