brintos

brintos / llvm-project-archived public Read only

0
0
Text · 20.5 KiB · c2530aa Raw
674 lines · cpp
1//===-- IOHandler.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/Core/IOHandler.h"10 11#if defined(__APPLE__)12#include <deque>13#endif14#include <string>15 16#include "lldb/Core/Debugger.h"17#include "lldb/Host/Config.h"18#include "lldb/Host/File.h"19#include "lldb/Host/StreamFile.h"20#include "lldb/Utility/AnsiTerminal.h"21#include "lldb/Utility/Predicate.h"22#include "lldb/Utility/Status.h"23#include "lldb/Utility/StreamString.h"24#include "lldb/Utility/StringList.h"25#include "lldb/lldb-forward.h"26 27#if LLDB_ENABLE_LIBEDIT28#include "lldb/Host/Editline.h"29#endif30#include "lldb/Interpreter/CommandCompletions.h"31#include "lldb/Interpreter/CommandInterpreter.h"32#include "llvm/ADT/StringRef.h"33 34#ifdef _WIN3235#include "lldb/Host/windows/windows.h"36#endif37 38#include <memory>39#include <mutex>40#include <optional>41 42#include <cassert>43#include <cctype>44#include <cerrno>45#include <clocale>46#include <cstdint>47#include <cstdio>48#include <cstring>49#include <type_traits>50 51using namespace lldb;52using namespace lldb_private;53using llvm::StringRef;54 55IOHandler::IOHandler(Debugger &debugger, IOHandler::Type type)56    : IOHandler(debugger, type,57                FileSP(),               // Adopt STDIN from top input reader58                LockableStreamFileSP(), // Adopt STDOUT from top input reader59                LockableStreamFileSP(), // Adopt STDERR from top input reader60                0                       // Flags61 62      ) {}63 64IOHandler::IOHandler(Debugger &debugger, IOHandler::Type type,65                     const lldb::FileSP &input_sp,66                     const lldb::LockableStreamFileSP &output_sp,67                     const lldb::LockableStreamFileSP &error_sp, uint32_t flags)68    : m_debugger(debugger), m_input_sp(input_sp), m_output_sp(output_sp),69      m_error_sp(error_sp), m_popped(false), m_flags(flags), m_type(type),70      m_user_data(nullptr), m_done(false), m_active(false) {71  // If any files are not specified, then adopt them from the top input reader.72  if (!m_input_sp || !m_output_sp || !m_error_sp)73    debugger.AdoptTopIOHandlerFilesIfInvalid(m_input_sp, m_output_sp,74                                             m_error_sp);75}76 77IOHandler::~IOHandler() = default;78 79int IOHandler::GetInputFD() {80  return (m_input_sp ? m_input_sp->GetDescriptor() : -1);81}82 83int IOHandler::GetOutputFD() {84  return (m_output_sp ? m_output_sp->GetUnlockedFile().GetDescriptor() : -1);85}86 87int IOHandler::GetErrorFD() {88  return (m_error_sp ? m_error_sp->GetUnlockedFile().GetDescriptor() : -1);89}90 91FileSP IOHandler::GetInputFileSP() { return m_input_sp; }92 93LockableStreamFileSP IOHandler::GetOutputStreamFileSP() { return m_output_sp; }94 95LockableStreamFileSP IOHandler::GetErrorStreamFileSP() { return m_error_sp; }96 97bool IOHandler::GetIsInteractive() {98  return GetInputFileSP() ? GetInputFileSP()->GetIsInteractive() : false;99}100 101bool IOHandler::GetIsRealTerminal() {102  return GetInputFileSP() ? GetInputFileSP()->GetIsRealTerminal() : false;103}104 105void IOHandler::SetPopped(bool b) { m_popped.SetValue(b, eBroadcastOnChange); }106 107void IOHandler::WaitForPop() { m_popped.WaitForValueEqualTo(true); }108 109void IOHandler::PrintAsync(const char *s, size_t len, bool is_stdout) {110  lldb::LockableStreamFileSP stream_sp = is_stdout ? m_output_sp : m_error_sp;111  LockedStreamFile locked_Stream = stream_sp->Lock();112  locked_Stream.Write(s, len);113}114 115bool IOHandlerStack::PrintAsync(const char *s, size_t len, bool is_stdout) {116  std::lock_guard<std::recursive_mutex> guard(m_mutex);117  if (!m_top)118    return false;119  m_top->PrintAsync(s, len, is_stdout);120  return true;121}122 123IOHandlerConfirm::IOHandlerConfirm(Debugger &debugger, llvm::StringRef prompt,124                                   bool default_response)125    : IOHandlerEditline(126          debugger, IOHandler::Type::Confirm,127          nullptr, // nullptr editline_name means no history loaded/saved128          llvm::StringRef(), // No prompt129          llvm::StringRef(), // No continuation prompt130          false,             // Multi-line131          false, // Don't colorize the prompt (i.e. the confirm message.)132          0, *this),133      m_default_response(default_response), m_user_response(default_response) {134  StreamString prompt_stream;135  prompt_stream.PutCString(prompt);136  if (m_default_response)137    prompt_stream.Printf(": [Y/n] ");138  else139    prompt_stream.Printf(": [y/N] ");140 141  SetPrompt(prompt_stream.GetString());142}143 144IOHandlerConfirm::~IOHandlerConfirm() = default;145 146void IOHandlerConfirm::IOHandlerComplete(IOHandler &io_handler,147                                         CompletionRequest &request) {148  if (request.GetRawCursorPos() != 0)149    return;150  request.AddCompletion(m_default_response ? "y" : "n");151}152 153void IOHandlerConfirm::IOHandlerInputComplete(IOHandler &io_handler,154                                              std::string &line) {155  const llvm::StringRef input = llvm::StringRef(line).rtrim();156  if (input.empty()) {157    // User just hit enter, set the response to the default158    m_user_response = m_default_response;159    io_handler.SetIsDone(true);160    return;161  }162 163  if (input.size() == 1) {164    switch (input[0]) {165    case 'y':166    case 'Y':167      m_user_response = true;168      io_handler.SetIsDone(true);169      return;170    case 'n':171    case 'N':172      m_user_response = false;173      io_handler.SetIsDone(true);174      return;175    default:176      break;177    }178  }179 180  if (input.equals_insensitive("yes")) {181    m_user_response = true;182    io_handler.SetIsDone(true);183  } else if (input.equals_insensitive("no")) {184    m_user_response = false;185    io_handler.SetIsDone(true);186  }187}188 189std::optional<std::string>190IOHandlerDelegate::IOHandlerSuggestion(IOHandler &io_handler,191                                       llvm::StringRef line) {192  return io_handler.GetDebugger()193      .GetCommandInterpreter()194      .GetAutoSuggestionForCommand(line);195}196 197void IOHandlerDelegate::IOHandlerComplete(IOHandler &io_handler,198                                          CompletionRequest &request) {199  switch (m_completion) {200  case Completion::None:201    break;202  case Completion::LLDBCommand:203    io_handler.GetDebugger().GetCommandInterpreter().HandleCompletion(request);204    break;205  case Completion::Expression:206    lldb_private::CommandCompletions::InvokeCommonCompletionCallbacks(207        io_handler.GetDebugger().GetCommandInterpreter(),208        lldb::eVariablePathCompletion, request, nullptr);209    break;210  }211}212 213IOHandlerEditline::IOHandlerEditline(214    Debugger &debugger, IOHandler::Type type,215    const char *editline_name, // Used for saving history files216    llvm::StringRef prompt, llvm::StringRef continuation_prompt,217    bool multi_line, bool color, uint32_t line_number_start,218    IOHandlerDelegate &delegate)219    : IOHandlerEditline(220          debugger, type,221          FileSP(),               // Inherit input from top input reader222          LockableStreamFileSP(), // Inherit output from top input reader223          LockableStreamFileSP(), // Inherit error from top input reader224          0,                      // Flags225          editline_name,          // Used for saving history files226          prompt, continuation_prompt, multi_line, color, line_number_start,227          delegate) {}228 229IOHandlerEditline::IOHandlerEditline(230    Debugger &debugger, IOHandler::Type type, const lldb::FileSP &input_sp,231    const lldb::LockableStreamFileSP &output_sp,232    const lldb::LockableStreamFileSP &error_sp, uint32_t flags,233    const char *editline_name, // Used for saving history files234    llvm::StringRef prompt, llvm::StringRef continuation_prompt,235    bool multi_line, bool color, uint32_t line_number_start,236    IOHandlerDelegate &delegate)237    : IOHandler(debugger, type, input_sp, output_sp, error_sp, flags),238#if LLDB_ENABLE_LIBEDIT239      m_editline_up(),240#endif241      m_delegate(delegate), m_prompt(), m_continuation_prompt(),242      m_current_lines_ptr(nullptr), m_base_line_number(line_number_start),243      m_curr_line_idx(UINT32_MAX), m_multi_line(multi_line), m_color(color),244      m_interrupt_exits(true) {245  SetPrompt(prompt);246 247#if LLDB_ENABLE_LIBEDIT248  const bool use_editline = m_input_sp && m_output_sp && m_error_sp &&249                            m_input_sp->GetIsRealTerminal();250  if (use_editline) {251    m_editline_up = std::make_unique<Editline>(252        editline_name, m_input_sp ? m_input_sp->GetStream() : nullptr,253        m_output_sp, m_error_sp, m_color);254    m_editline_up->SetIsInputCompleteCallback(255        [this](Editline *editline, StringList &lines) {256          return this->IsInputCompleteCallback(editline, lines);257        });258 259    m_editline_up->SetAutoCompleteCallback([this](CompletionRequest &request) {260      this->AutoCompleteCallback(request);261    });262    m_editline_up->SetRedrawCallback([this]() { this->RedrawCallback(); });263 264    if (debugger.GetUseAutosuggestion()) {265      m_editline_up->SetSuggestionCallback([this](llvm::StringRef line) {266        return this->SuggestionCallback(line);267      });268      m_editline_up->SetSuggestionAnsiPrefix(ansi::FormatAnsiTerminalCodes(269          debugger.GetAutosuggestionAnsiPrefix()));270      m_editline_up->SetSuggestionAnsiSuffix(ansi::FormatAnsiTerminalCodes(271          debugger.GetAutosuggestionAnsiSuffix()));272    }273    // See if the delegate supports fixing indentation274    const char *indent_chars = delegate.IOHandlerGetFixIndentationCharacters();275    if (indent_chars) {276      // The delegate does support indentation, hook it up so when any277      // indentation character is typed, the delegate gets a chance to fix it278      FixIndentationCallbackType f = [this](Editline *editline,279                                            const StringList &lines,280                                            int cursor_position) {281        return this->FixIndentationCallback(editline, lines, cursor_position);282      };283      m_editline_up->SetFixIndentationCallback(std::move(f), indent_chars);284    }285  }286#endif287  SetBaseLineNumber(m_base_line_number);288  SetPrompt(prompt);289  SetContinuationPrompt(continuation_prompt);290}291 292IOHandlerEditline::~IOHandlerEditline() {293#if LLDB_ENABLE_LIBEDIT294  m_editline_up.reset();295#endif296}297 298void IOHandlerEditline::Activate() {299  IOHandler::Activate();300  m_delegate.IOHandlerActivated(*this, GetIsInteractive());301}302 303void IOHandlerEditline::Deactivate() {304  IOHandler::Deactivate();305  m_delegate.IOHandlerDeactivated(*this);306}307 308void IOHandlerEditline::TerminalSizeChanged() {309#if LLDB_ENABLE_LIBEDIT310  if (m_editline_up)311    m_editline_up->TerminalSizeChanged();312#endif313}314 315// Split out a line from the buffer, if there is a full one to get.316static std::optional<std::string> SplitLine(std::string &line_buffer) {317  size_t pos = line_buffer.find('\n');318  if (pos == std::string::npos)319    return std::nullopt;320  std::string line =321      std::string(StringRef(line_buffer.c_str(), pos).rtrim("\n\r"));322  line_buffer = line_buffer.substr(pos + 1);323  return line;324}325 326// If the final line of the file ends without a end-of-line, return327// it as a line anyway.328static std::optional<std::string> SplitLineEOF(std::string &line_buffer) {329  if (llvm::all_of(line_buffer, llvm::isSpace))330    return std::nullopt;331  std::string line = std::move(line_buffer);332  line_buffer.clear();333  return line;334}335 336bool IOHandlerEditline::GetLine(std::string &line, bool &interrupted) {337#if LLDB_ENABLE_LIBEDIT338  if (m_editline_up) {339    return m_editline_up->GetLine(line, interrupted);340  }341#endif342 343  line.clear();344 345  if (GetIsInteractive()) {346    const char *prompt = nullptr;347 348    if (m_multi_line && m_curr_line_idx > 0)349      prompt = GetContinuationPrompt();350 351    if (prompt == nullptr)352      prompt = GetPrompt();353 354    if (prompt && prompt[0]) {355      if (m_output_sp) {356        LockedStreamFile locked_stream = m_output_sp->Lock();357        locked_stream.Printf("%s", prompt);358      }359    }360  }361 362  std::optional<std::string> got_line = SplitLine(m_line_buffer);363 364  if (!got_line && !m_input_sp) {365    // No more input file, we are done...366    SetIsDone(true);367    return false;368  }369 370  FILE *in = m_input_sp ? m_input_sp->GetStream() : nullptr;371  char buffer[256];372 373  if (!got_line && !in && m_input_sp) {374    // there is no FILE*, fall back on just reading bytes from the stream.375    while (!got_line) {376      size_t bytes_read = sizeof(buffer);377      Status error = m_input_sp->Read((void *)buffer, bytes_read);378      if (error.Success() && !bytes_read) {379        got_line = SplitLineEOF(m_line_buffer);380        break;381      }382      if (error.Fail())383        break;384      m_line_buffer += StringRef(buffer, bytes_read);385      got_line = SplitLine(m_line_buffer);386    }387  }388 389  if (!got_line && in) {390    while (!got_line) {391      char *r = fgets(buffer, sizeof(buffer), in);392#ifdef _WIN32393      // ReadFile on Windows is supposed to set ERROR_OPERATION_ABORTED394      // according to the docs on MSDN. However, this has evidently been a395      // known bug since Windows 8. Therefore, we can't detect if a signal396      // interrupted in the fgets. So pressing ctrl-c causes the repl to end397      // and the process to exit. A temporary workaround is just to attempt to398      // fgets twice until this bug is fixed.399      if (r == nullptr)400        r = fgets(buffer, sizeof(buffer), in);401      // this is the equivalent of EINTR for Windows402      if (r == nullptr && GetLastError() == ERROR_OPERATION_ABORTED)403        continue;404#endif405      if (r == nullptr) {406        if (ferror(in) && errno == EINTR)407          continue;408        if (feof(in))409          got_line = SplitLineEOF(m_line_buffer);410        break;411      }412      m_line_buffer += buffer;413      got_line = SplitLine(m_line_buffer);414    }415  }416 417  if (got_line) {418    line = *got_line;419  }420 421  return (bool)got_line;422}423 424#if LLDB_ENABLE_LIBEDIT425bool IOHandlerEditline::IsInputCompleteCallback(Editline *editline,426                                                StringList &lines) {427  return m_delegate.IOHandlerIsInputComplete(*this, lines);428}429 430int IOHandlerEditline::FixIndentationCallback(Editline *editline,431                                              const StringList &lines,432                                              int cursor_position) {433  return m_delegate.IOHandlerFixIndentation(*this, lines, cursor_position);434}435 436std::optional<std::string>437IOHandlerEditline::SuggestionCallback(llvm::StringRef line) {438  return m_delegate.IOHandlerSuggestion(*this, line);439}440 441void IOHandlerEditline::AutoCompleteCallback(CompletionRequest &request) {442  m_delegate.IOHandlerComplete(*this, request);443}444 445void IOHandlerEditline::RedrawCallback() {446  m_debugger.RedrawStatusline(std::nullopt);447}448 449#endif450 451const char *IOHandlerEditline::GetPrompt() {452#if LLDB_ENABLE_LIBEDIT453  if (m_editline_up) {454    return m_editline_up->GetPrompt();455  } else {456#endif457    if (m_prompt.empty())458      return nullptr;459#if LLDB_ENABLE_LIBEDIT460  }461#endif462  return m_prompt.c_str();463}464 465bool IOHandlerEditline::SetPrompt(llvm::StringRef prompt) {466  m_prompt = std::string(prompt);467 468#if LLDB_ENABLE_LIBEDIT469  if (m_editline_up) {470    m_editline_up->SetPrompt(m_prompt.empty() ? nullptr : m_prompt.c_str());471    m_editline_up->SetPromptAnsiPrefix(472        ansi::FormatAnsiTerminalCodes(m_debugger.GetPromptAnsiPrefix()));473    m_editline_up->SetPromptAnsiSuffix(474        ansi::FormatAnsiTerminalCodes(m_debugger.GetPromptAnsiSuffix()));475  }476#endif477  return true;478}479 480bool IOHandlerEditline::SetUseColor(bool use_color) {481  m_color = use_color;482 483#if LLDB_ENABLE_LIBEDIT484  if (m_editline_up) {485    m_editline_up->UseColor(use_color);486    m_editline_up->SetSuggestionAnsiPrefix(ansi::FormatAnsiTerminalCodes(487        m_debugger.GetAutosuggestionAnsiPrefix()));488    m_editline_up->SetSuggestionAnsiSuffix(ansi::FormatAnsiTerminalCodes(489        m_debugger.GetAutosuggestionAnsiSuffix()));490  }491#endif492  return true;493}494 495const char *IOHandlerEditline::GetContinuationPrompt() {496  return (m_continuation_prompt.empty() ? nullptr497                                        : m_continuation_prompt.c_str());498}499 500void IOHandlerEditline::SetContinuationPrompt(llvm::StringRef prompt) {501  m_continuation_prompt = std::string(prompt);502 503#if LLDB_ENABLE_LIBEDIT504  if (m_editline_up)505    m_editline_up->SetContinuationPrompt(m_continuation_prompt.empty()506                                             ? nullptr507                                             : m_continuation_prompt.c_str());508#endif509}510 511void IOHandlerEditline::SetBaseLineNumber(uint32_t line) {512  m_base_line_number = line;513}514 515uint32_t IOHandlerEditline::GetCurrentLineIndex() const {516#if LLDB_ENABLE_LIBEDIT517  if (m_editline_up)518    return m_editline_up->GetCurrentLine();519#endif520  return m_curr_line_idx;521}522 523StringList IOHandlerEditline::GetCurrentLines() const {524#if LLDB_ENABLE_LIBEDIT525  if (m_editline_up)526    return m_editline_up->GetInputAsStringList();527#endif528  // When libedit is not used, the current lines can be gotten from529  // `m_current_lines_ptr`, which is updated whenever a new line is processed.530  // This doesn't happen when libedit is used, in which case531  // `m_current_lines_ptr` is only updated when the full input is terminated.532 533  if (m_current_lines_ptr)534    return *m_current_lines_ptr;535  return StringList();536}537 538bool IOHandlerEditline::GetLines(StringList &lines, bool &interrupted) {539  m_current_lines_ptr = &lines;540 541  bool success = false;542#if LLDB_ENABLE_LIBEDIT543  if (m_editline_up) {544    return m_editline_up->GetLines(m_base_line_number, lines, interrupted);545  } else {546#endif547    bool done = false;548    Status error;549 550    while (!done) {551      // Show line numbers if we are asked to552      std::string line;553      if (m_base_line_number > 0 && GetIsInteractive()) {554        if (m_output_sp) {555          LockedStreamFile locked_stream = m_output_sp->Lock();556          locked_stream.Printf("%u%s",557                               m_base_line_number + (uint32_t)lines.GetSize(),558                               GetPrompt() == nullptr ? " " : "");559        }560      }561 562      m_curr_line_idx = lines.GetSize();563 564      bool interrupted = false;565      if (GetLine(line, interrupted) && !interrupted) {566        lines.AppendString(line);567        done = m_delegate.IOHandlerIsInputComplete(*this, lines);568      } else {569        done = true;570      }571    }572    success = lines.GetSize() > 0;573#if LLDB_ENABLE_LIBEDIT574  }575#endif576  return success;577}578 579// Each IOHandler gets to run until it is done. It should read data from the580// "in" and place output into "out" and "err and return when done.581void IOHandlerEditline::Run() {582  std::string line;583  while (IsActive()) {584    bool interrupted = false;585    if (m_multi_line) {586      StringList lines;587      if (GetLines(lines, interrupted)) {588        if (interrupted) {589          m_done = m_interrupt_exits;590          m_delegate.IOHandlerInputInterrupted(*this, line);591 592        } else {593          line = lines.CopyList();594          m_delegate.IOHandlerInputComplete(*this, line);595        }596      } else {597        m_done = true;598      }599    } else {600      if (GetLine(line, interrupted)) {601        if (interrupted)602          m_delegate.IOHandlerInputInterrupted(*this, line);603        else604          m_delegate.IOHandlerInputComplete(*this, line);605      } else {606        m_done = true;607      }608    }609  }610}611 612void IOHandlerEditline::Cancel() {613#if LLDB_ENABLE_LIBEDIT614  if (m_editline_up)615    m_editline_up->Cancel();616#endif617}618 619bool IOHandlerEditline::Interrupt() {620  // Let the delgate handle it first621  if (m_delegate.IOHandlerInterrupt(*this))622    return true;623 624#if LLDB_ENABLE_LIBEDIT625  if (m_editline_up)626    return m_editline_up->Interrupt();627#endif628  return false;629}630 631void IOHandlerEditline::GotEOF() {632#if LLDB_ENABLE_LIBEDIT633  if (m_editline_up)634    m_editline_up->Interrupt();635#endif636}637 638void IOHandlerEditline::PrintAsync(const char *s, size_t len, bool is_stdout) {639#if LLDB_ENABLE_LIBEDIT640  if (m_editline_up) {641    lldb::LockableStreamFileSP stream_sp = is_stdout ? m_output_sp : m_error_sp;642    m_editline_up->PrintAsync(stream_sp, s, len);643  } else644#endif645  {646#ifdef _WIN32647    const char *prompt = GetPrompt();648    if (prompt) {649      // Back up over previous prompt using Windows API650      CONSOLE_SCREEN_BUFFER_INFO screen_buffer_info;651      HANDLE console_handle = GetStdHandle(STD_OUTPUT_HANDLE);652      GetConsoleScreenBufferInfo(console_handle, &screen_buffer_info);653      COORD coord = screen_buffer_info.dwCursorPosition;654      coord.X -= strlen(prompt);655      if (coord.X < 0)656        coord.X = 0;657      SetConsoleCursorPosition(console_handle, coord);658    }659#endif660    IOHandler::PrintAsync(s, len, is_stdout);661#ifdef _WIN32662    if (prompt)663      IOHandler::PrintAsync(prompt, strlen(prompt), is_stdout);664#endif665  }666}667 668void IOHandlerEditline::Refresh() {669#if LLDB_ENABLE_LIBEDIT670  if (m_editline_up)671    m_editline_up->Refresh();672#endif673}674