brintos

brintos / llvm-project-archived public Read only

0
0
Text · 22.1 KiB · c6cf6d8 Raw
621 lines · cpp
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-exception6//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 defaults29  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 history79        llvm::StringRef("> "), // prompt80        llvm::StringRef(". "), // Continuation prompt81        true,                  // Multi-line82        true,                  // The REPL prompt is always colored83        1,                     // Line number84        *this);85 86    // Don't exit if CTRL+C is pressed87    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  LockedStreamFile locked_stream = io_handler.GetErrorStreamFileSP()->Lock();107  locked_stream.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 command143  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 done151  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 = std::make_shared<StreamFile>(223      io_handler.GetOutputStreamFileSP()->GetUnlockedFileSP());224  lldb::StreamFileSP error_sp = std::make_shared<StreamFile>(225      io_handler.GetErrorStreamFileSP()->GetUnlockedFileSP());226  bool extra_line = false;227  bool did_quit = false;228 229  if (code.empty()) {230    m_code.AppendString("");231    static_cast<IOHandlerEditline &>(io_handler)232        .SetBaseLineNumber(m_code.GetSize() + 1);233  } else {234    Debugger &debugger = m_target.GetDebugger();235    CommandInterpreter &ci = debugger.GetCommandInterpreter();236    extra_line = ci.GetSpaceReplPrompts();237 238    ExecutionContext exe_ctx(m_target.GetProcessSP()239                                 ->GetThreadList()240                                 .GetSelectedThread()241                                 ->GetSelectedFrame(DoNoSelectMostRelevantFrame)242                                 .get());243 244    lldb::ProcessSP process_sp(exe_ctx.GetProcessSP());245 246    if (code[0] == ':') {247      // Meta command248      // Strip the ':'249      code.erase(0, 1);250      if (!llvm::StringRef(code).trim().empty()) {251        // "lldb" was followed by arguments, so just execute the command dump252        // the results253 254        // Turn off prompt on quit in case the user types ":quit"255        const bool saved_prompt_on_quit = ci.GetPromptOnQuit();256        if (saved_prompt_on_quit)257          ci.SetPromptOnQuit(false);258 259        // Execute the command260        CommandReturnObject result(debugger.GetUseColor());261        result.SetImmediateOutputStream(output_sp);262        result.SetImmediateErrorStream(error_sp);263        ci.HandleCommand(code.c_str(), eLazyBoolNo, result);264 265        if (saved_prompt_on_quit)266          ci.SetPromptOnQuit(true);267 268        if (result.GetStatus() == lldb::eReturnStatusQuit) {269          did_quit = true;270          io_handler.SetIsDone(true);271          if (debugger.CheckTopIOHandlerTypes(272                  IOHandler::Type::REPL, IOHandler::Type::CommandInterpreter)) {273            // We typed "quit" or an alias to quit so we need to check if the274            // command interpreter is above us and tell it that it is done as275            // well so we don't drop back into the command interpreter if we276            // have already quit277            lldb::IOHandlerSP io_handler_sp(ci.GetIOHandler());278            if (io_handler_sp)279              io_handler_sp->SetIsDone(true);280          }281        }282      } else {283        // ":" was followed by no arguments, so push the LLDB command prompt284        if (debugger.CheckTopIOHandlerTypes(285                IOHandler::Type::REPL, IOHandler::Type::CommandInterpreter)) {286          // If the user wants to get back to the command interpreter and the287          // command interpreter is what launched the REPL, then just let the288          // REPL exit and fall back to the command interpreter.289          io_handler.SetIsDone(true);290        } else {291          // The REPL wasn't launched the by the command interpreter, it is the292          // base IOHandler, so we need to get the command interpreter and293          lldb::IOHandlerSP io_handler_sp(ci.GetIOHandler());294          if (io_handler_sp) {295            io_handler_sp->SetIsDone(false);296            debugger.RunIOHandlerAsync(ci.GetIOHandler());297          }298        }299      }300    } else {301      if (code[0] == '<') {302        // User wants to read code from a file.303        // Interpret rest of line as a literal path.304        auto path = llvm::StringRef(code.substr(1)).trim().str();305        if (!ReadCode(path, code, error_sp)) {306          return;307        }308      }309 310      // Unwind any expression we might have been running in case our REPL311      // expression crashed and the user was looking around312      if (m_dedicated_repl_mode) {313        Thread *thread = exe_ctx.GetThreadPtr();314        if (thread && thread->UnwindInnermostExpression().Success()) {315          thread->SetSelectedFrameByIndex(0, false);316          exe_ctx.SetFrameSP(317              thread->GetSelectedFrame(DoNoSelectMostRelevantFrame));318        }319      }320 321      const bool colorize_err = error_sp->GetFile().GetIsTerminalWithColors();322 323      EvaluateExpressionOptions expr_options = m_expr_options;324      expr_options.SetCoerceToId(m_varobj_options.use_object_desc);325      expr_options.SetKeepInMemory(true);326      expr_options.SetUseDynamic(m_varobj_options.use_dynamic);327      expr_options.SetGenerateDebugInfo(true);328      expr_options.SetREPLEnabled(true);329      expr_options.SetColorizeErrors(colorize_err);330      expr_options.SetPoundLine(m_repl_source_path.c_str(),331                                m_code.GetSize() + 1);332 333      expr_options.SetLanguage(GetLanguage());334 335      PersistentExpressionState *persistent_state =336          m_target.GetPersistentExpressionStateForLanguage(GetLanguage());337      if (!persistent_state)338        return;339 340      const size_t var_count_before = persistent_state->GetSize();341 342      const char *expr_prefix = nullptr;343      lldb::ValueObjectSP result_valobj_sp;344      lldb::ExpressionResults execution_results = UserExpression::Evaluate(345          exe_ctx, expr_options, code.c_str(), expr_prefix, result_valobj_sp);346      Status error;347      if (llvm::Error err = OnExpressionEvaluated(exe_ctx, code, expr_options,348                                                  execution_results,349                                                  result_valobj_sp, error)) {350        *error_sp << llvm::toString(std::move(err)) << "\n";351      } else if (process_sp && process_sp->IsAlive()) {352        bool add_to_code = true;353        bool handled = false;354        if (result_valobj_sp) {355          lldb::Format format = m_format_options.GetFormat();356 357          if (result_valobj_sp->GetError().Success()) {358            handled |= PrintOneVariable(debugger, output_sp, result_valobj_sp);359          } else if (result_valobj_sp->GetError().GetError() ==360                     UserExpression::kNoResult) {361            if (format != lldb::eFormatVoid && debugger.GetNotifyVoid()) {362              error_sp->PutCString("(void)\n");363              handled = true;364            }365          }366        }367 368        if (debugger.GetPrintDecls()) {369          for (size_t vi = var_count_before, ve = persistent_state->GetSize();370               vi != ve; ++vi) {371            lldb::ExpressionVariableSP persistent_var_sp =372                persistent_state->GetVariableAtIndex(vi);373            lldb::ValueObjectSP valobj_sp = persistent_var_sp->GetValueObject();374 375            PrintOneVariable(debugger, output_sp, valobj_sp,376                             persistent_var_sp.get());377          }378        }379 380        if (!handled) {381          bool useColors = error_sp->GetFile().GetIsTerminalWithColors();382          switch (execution_results) {383          case lldb::eExpressionSetupError:384          case lldb::eExpressionParseError:385            add_to_code = false;386            [[fallthrough]];387          case lldb::eExpressionDiscarded:388            error_sp->Printf("%s\n", error.AsCString());389            break;390 391          case lldb::eExpressionCompleted:392            break;393          case lldb::eExpressionInterrupted:394            if (useColors) {395              error_sp->Printf(ANSI_ESCAPE1(ANSI_FG_COLOR_RED));396              error_sp->Printf(ANSI_ESCAPE1(ANSI_CTRL_BOLD));397            }398            error_sp->Printf("Execution interrupted. ");399            if (useColors)400              error_sp->Printf(ANSI_ESCAPE1(ANSI_CTRL_NORMAL));401            error_sp->Printf("Enter code to recover and continue.\nEnter LLDB "402                             "commands to investigate (type :help for "403                             "assistance.)\n");404            break;405 406          case lldb::eExpressionHitBreakpoint:407            // Breakpoint was hit, drop into LLDB command interpreter408            if (useColors) {409              error_sp->Printf(ANSI_ESCAPE1(ANSI_FG_COLOR_RED));410              error_sp->Printf(ANSI_ESCAPE1(ANSI_CTRL_BOLD));411            }412            output_sp->Printf("Execution stopped at breakpoint.  ");413            if (useColors)414              error_sp->Printf(ANSI_ESCAPE1(ANSI_CTRL_NORMAL));415            output_sp->Printf("Enter LLDB commands to investigate (type help "416                              "for assistance.)\n");417            {418              lldb::IOHandlerSP io_handler_sp(ci.GetIOHandler());419              if (io_handler_sp) {420                io_handler_sp->SetIsDone(false);421                debugger.RunIOHandlerAsync(ci.GetIOHandler());422              }423            }424            break;425 426          case lldb::eExpressionTimedOut:427            error_sp->Printf("error: timeout\n");428            if (error.AsCString())429              error_sp->Printf("error: %s\n", error.AsCString());430            break;431          case lldb::eExpressionResultUnavailable:432            // Shoulnd't happen???433            error_sp->Printf("error: could not fetch result -- %s\n",434                             error.AsCString());435            break;436          case lldb::eExpressionStoppedForDebug:437            // Shoulnd't happen???438            error_sp->Printf("error: stopped for debug -- %s\n",439                             error.AsCString());440            break;441          case lldb::eExpressionThreadVanished:442            // Shoulnd't happen???443            error_sp->Printf("error: expression thread vanished -- %s\n",444                             error.AsCString());445            break;446          }447        }448 449        if (add_to_code) {450          const uint32_t new_default_line = m_code.GetSize() + 1;451 452          m_code.SplitIntoLines(code);453 454          // Update our code on disk455          if (!m_repl_source_path.empty()) {456            auto file = FileSystem::Instance().Open(457                FileSpec(m_repl_source_path),458                File::eOpenOptionWriteOnly | File::eOpenOptionTruncate |459                    File::eOpenOptionCanCreate,460                lldb::eFilePermissionsFileDefault);461            if (file) {462              std::string code(m_code.CopyList());463              code.append(1, '\n');464              size_t bytes_written = code.size();465              file.get()->Write(code.c_str(), bytes_written);466              file.get()->Close();467            } else {468              std::string message = llvm::toString(file.takeError());469              error_sp->Printf("error: couldn't open %s: %s\n",470                               m_repl_source_path.c_str(), message.c_str());471            }472 473            // Now set the default file and line to the REPL source file474            m_target.GetSourceManager().SetDefaultFileAndLine(475                std::make_shared<SupportFile>(FileSpec(m_repl_source_path)),476                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 the488    // 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 commands504    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 spaces521  if (request.GetRawLine().trim().empty()) {522    // Only spaces on this line, so just indent523    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  std::optional<SourceManager::SupportFileAndLine> default_file_line;574 575  if (!m_repl_source_path.empty()) {576    // Save the current default file and line577    default_file_line = m_target.GetSourceManager().GetDefaultFileAndLine();578  }579 580  debugger.RunIOHandlerAsync(io_handler_sp);581 582  // Check if we are in dedicated REPL mode where LLDB was start with the "--583  // repl" option from the command line. Currently we know this by checking if584  // the debugger already has a IOHandler thread.585  if (!debugger.HasIOHandlerThread()) {586    // The debugger doesn't have an existing IOHandler thread, so this must be587    // dedicated REPL mode...588    m_dedicated_repl_mode = true;589    debugger.StartIOHandlerThread();590    llvm::StringRef command_name_str("quit");591    CommandObject *cmd_obj =592        debugger.GetCommandInterpreter().GetCommandObjectForCommand(593            command_name_str);594    if (cmd_obj) {595      assert(command_name_str.empty());596      cmd_obj->SetOverrideCallback(QuitCommandOverrideCallback, &m_target);597    }598  }599 600  // Wait for the REPL command interpreter to get popped601  io_handler_sp->WaitForPop();602 603  if (m_dedicated_repl_mode) {604    // If we were in dedicated REPL mode we would have started the IOHandler605    // thread, and we should kill our process606    lldb::ProcessSP process_sp = m_target.GetProcessSP();607    if (process_sp && process_sp->IsAlive())608      process_sp->Destroy(false);609 610    // Wait for the IO handler thread to exit (TODO: don't do this if the IO611    // handler thread already exists...)612    debugger.JoinIOHandlerThread();613  }614 615  // Restore the default file and line616  if (default_file_line)617    m_target.GetSourceManager().SetDefaultFileAndLine(618        default_file_line->support_file_nsp, default_file_line->line);619  return error;620}621