Path: blob/main/contrib/llvm-project/lldb/source/Core/IOHandler.cpp
39587 views
//===-- IOHandler.cpp -----------------------------------------------------===//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//78#include "lldb/Core/IOHandler.h"910#if defined(__APPLE__)11#include <deque>12#endif13#include <string>1415#include "lldb/Core/Debugger.h"16#include "lldb/Host/Config.h"17#include "lldb/Host/File.h"18#include "lldb/Host/StreamFile.h"19#include "lldb/Utility/AnsiTerminal.h"20#include "lldb/Utility/Predicate.h"21#include "lldb/Utility/Status.h"22#include "lldb/Utility/StreamString.h"23#include "lldb/Utility/StringList.h"24#include "lldb/lldb-forward.h"2526#if LLDB_ENABLE_LIBEDIT27#include "lldb/Host/Editline.h"28#endif29#include "lldb/Interpreter/CommandCompletions.h"30#include "lldb/Interpreter/CommandInterpreter.h"31#include "llvm/ADT/StringRef.h"3233#ifdef _WIN3234#include "lldb/Host/windows/windows.h"35#endif3637#include <memory>38#include <mutex>39#include <optional>4041#include <cassert>42#include <cctype>43#include <cerrno>44#include <clocale>45#include <cstdint>46#include <cstdio>47#include <cstring>48#include <type_traits>4950using namespace lldb;51using namespace lldb_private;52using llvm::StringRef;5354IOHandler::IOHandler(Debugger &debugger, IOHandler::Type type)55: IOHandler(debugger, type,56FileSP(), // Adopt STDIN from top input reader57StreamFileSP(), // Adopt STDOUT from top input reader58StreamFileSP(), // Adopt STDERR from top input reader590 // Flags6061) {}6263IOHandler::IOHandler(Debugger &debugger, IOHandler::Type type,64const lldb::FileSP &input_sp,65const lldb::StreamFileSP &output_sp,66const lldb::StreamFileSP &error_sp, uint32_t flags)67: m_debugger(debugger), m_input_sp(input_sp), m_output_sp(output_sp),68m_error_sp(error_sp), m_popped(false), m_flags(flags), m_type(type),69m_user_data(nullptr), m_done(false), m_active(false) {70// If any files are not specified, then adopt them from the top input reader.71if (!m_input_sp || !m_output_sp || !m_error_sp)72debugger.AdoptTopIOHandlerFilesIfInvalid(m_input_sp, m_output_sp,73m_error_sp);74}7576IOHandler::~IOHandler() = default;7778int IOHandler::GetInputFD() {79return (m_input_sp ? m_input_sp->GetDescriptor() : -1);80}8182int IOHandler::GetOutputFD() {83return (m_output_sp ? m_output_sp->GetFile().GetDescriptor() : -1);84}8586int IOHandler::GetErrorFD() {87return (m_error_sp ? m_error_sp->GetFile().GetDescriptor() : -1);88}8990FILE *IOHandler::GetInputFILE() {91return (m_input_sp ? m_input_sp->GetStream() : nullptr);92}9394FILE *IOHandler::GetOutputFILE() {95return (m_output_sp ? m_output_sp->GetFile().GetStream() : nullptr);96}9798FILE *IOHandler::GetErrorFILE() {99return (m_error_sp ? m_error_sp->GetFile().GetStream() : nullptr);100}101102FileSP IOHandler::GetInputFileSP() { return m_input_sp; }103104StreamFileSP IOHandler::GetOutputStreamFileSP() { return m_output_sp; }105106StreamFileSP IOHandler::GetErrorStreamFileSP() { return m_error_sp; }107108bool IOHandler::GetIsInteractive() {109return GetInputFileSP() ? GetInputFileSP()->GetIsInteractive() : false;110}111112bool IOHandler::GetIsRealTerminal() {113return GetInputFileSP() ? GetInputFileSP()->GetIsRealTerminal() : false;114}115116void IOHandler::SetPopped(bool b) { m_popped.SetValue(b, eBroadcastOnChange); }117118void IOHandler::WaitForPop() { m_popped.WaitForValueEqualTo(true); }119120void IOHandler::PrintAsync(const char *s, size_t len, bool is_stdout) {121std::lock_guard<std::recursive_mutex> guard(m_output_mutex);122lldb::StreamFileSP stream = is_stdout ? m_output_sp : m_error_sp;123stream->Write(s, len);124stream->Flush();125}126127bool IOHandlerStack::PrintAsync(const char *s, size_t len, bool is_stdout) {128std::lock_guard<std::recursive_mutex> guard(m_mutex);129if (!m_top)130return false;131m_top->PrintAsync(s, len, is_stdout);132return true;133}134135IOHandlerConfirm::IOHandlerConfirm(Debugger &debugger, llvm::StringRef prompt,136bool default_response)137: IOHandlerEditline(138debugger, IOHandler::Type::Confirm,139nullptr, // nullptr editline_name means no history loaded/saved140llvm::StringRef(), // No prompt141llvm::StringRef(), // No continuation prompt142false, // Multi-line143false, // Don't colorize the prompt (i.e. the confirm message.)1440, *this),145m_default_response(default_response), m_user_response(default_response) {146StreamString prompt_stream;147prompt_stream.PutCString(prompt);148if (m_default_response)149prompt_stream.Printf(": [Y/n] ");150else151prompt_stream.Printf(": [y/N] ");152153SetPrompt(prompt_stream.GetString());154}155156IOHandlerConfirm::~IOHandlerConfirm() = default;157158void IOHandlerConfirm::IOHandlerComplete(IOHandler &io_handler,159CompletionRequest &request) {160if (request.GetRawCursorPos() != 0)161return;162request.AddCompletion(m_default_response ? "y" : "n");163}164165void IOHandlerConfirm::IOHandlerInputComplete(IOHandler &io_handler,166std::string &line) {167if (line.empty()) {168// User just hit enter, set the response to the default169m_user_response = m_default_response;170io_handler.SetIsDone(true);171return;172}173174if (line.size() == 1) {175switch (line[0]) {176case 'y':177case 'Y':178m_user_response = true;179io_handler.SetIsDone(true);180return;181case 'n':182case 'N':183m_user_response = false;184io_handler.SetIsDone(true);185return;186default:187break;188}189}190191if (line == "yes" || line == "YES" || line == "Yes") {192m_user_response = true;193io_handler.SetIsDone(true);194} else if (line == "no" || line == "NO" || line == "No") {195m_user_response = false;196io_handler.SetIsDone(true);197}198}199200std::optional<std::string>201IOHandlerDelegate::IOHandlerSuggestion(IOHandler &io_handler,202llvm::StringRef line) {203return io_handler.GetDebugger()204.GetCommandInterpreter()205.GetAutoSuggestionForCommand(line);206}207208void IOHandlerDelegate::IOHandlerComplete(IOHandler &io_handler,209CompletionRequest &request) {210switch (m_completion) {211case Completion::None:212break;213case Completion::LLDBCommand:214io_handler.GetDebugger().GetCommandInterpreter().HandleCompletion(request);215break;216case Completion::Expression:217lldb_private::CommandCompletions::InvokeCommonCompletionCallbacks(218io_handler.GetDebugger().GetCommandInterpreter(),219lldb::eVariablePathCompletion, request, nullptr);220break;221}222}223224IOHandlerEditline::IOHandlerEditline(225Debugger &debugger, IOHandler::Type type,226const char *editline_name, // Used for saving history files227llvm::StringRef prompt, llvm::StringRef continuation_prompt,228bool multi_line, bool color, uint32_t line_number_start,229IOHandlerDelegate &delegate)230: IOHandlerEditline(debugger, type,231FileSP(), // Inherit input from top input reader232StreamFileSP(), // Inherit output from top input reader233StreamFileSP(), // Inherit error from top input reader2340, // Flags235editline_name, // Used for saving history files236prompt, continuation_prompt, multi_line, color,237line_number_start, delegate) {}238239IOHandlerEditline::IOHandlerEditline(240Debugger &debugger, IOHandler::Type type, const lldb::FileSP &input_sp,241const lldb::StreamFileSP &output_sp, const lldb::StreamFileSP &error_sp,242uint32_t flags,243const char *editline_name, // Used for saving history files244llvm::StringRef prompt, llvm::StringRef continuation_prompt,245bool multi_line, bool color, uint32_t line_number_start,246IOHandlerDelegate &delegate)247: IOHandler(debugger, type, input_sp, output_sp, error_sp, flags),248#if LLDB_ENABLE_LIBEDIT249m_editline_up(),250#endif251m_delegate(delegate), m_prompt(), m_continuation_prompt(),252m_current_lines_ptr(nullptr), m_base_line_number(line_number_start),253m_curr_line_idx(UINT32_MAX), m_multi_line(multi_line), m_color(color),254m_interrupt_exits(true) {255SetPrompt(prompt);256257#if LLDB_ENABLE_LIBEDIT258bool use_editline = false;259260use_editline = GetInputFILE() && GetOutputFILE() && GetErrorFILE() &&261m_input_sp && m_input_sp->GetIsRealTerminal();262263if (use_editline) {264m_editline_up = std::make_unique<Editline>(editline_name, GetInputFILE(),265GetOutputFILE(), GetErrorFILE(),266GetOutputMutex());267m_editline_up->SetIsInputCompleteCallback(268[this](Editline *editline, StringList &lines) {269return this->IsInputCompleteCallback(editline, lines);270});271272m_editline_up->SetAutoCompleteCallback([this](CompletionRequest &request) {273this->AutoCompleteCallback(request);274});275276if (debugger.GetUseAutosuggestion()) {277m_editline_up->SetSuggestionCallback([this](llvm::StringRef line) {278return this->SuggestionCallback(line);279});280if (m_color) {281m_editline_up->SetSuggestionAnsiPrefix(ansi::FormatAnsiTerminalCodes(282debugger.GetAutosuggestionAnsiPrefix()));283m_editline_up->SetSuggestionAnsiSuffix(ansi::FormatAnsiTerminalCodes(284debugger.GetAutosuggestionAnsiSuffix()));285}286}287// See if the delegate supports fixing indentation288const char *indent_chars = delegate.IOHandlerGetFixIndentationCharacters();289if (indent_chars) {290// The delegate does support indentation, hook it up so when any291// indentation character is typed, the delegate gets a chance to fix it292FixIndentationCallbackType f = [this](Editline *editline,293const StringList &lines,294int cursor_position) {295return this->FixIndentationCallback(editline, lines, cursor_position);296};297m_editline_up->SetFixIndentationCallback(std::move(f), indent_chars);298}299}300#endif301SetBaseLineNumber(m_base_line_number);302SetPrompt(prompt);303SetContinuationPrompt(continuation_prompt);304}305306IOHandlerEditline::~IOHandlerEditline() {307#if LLDB_ENABLE_LIBEDIT308m_editline_up.reset();309#endif310}311312void IOHandlerEditline::Activate() {313IOHandler::Activate();314m_delegate.IOHandlerActivated(*this, GetIsInteractive());315}316317void IOHandlerEditline::Deactivate() {318IOHandler::Deactivate();319m_delegate.IOHandlerDeactivated(*this);320}321322void IOHandlerEditline::TerminalSizeChanged() {323#if LLDB_ENABLE_LIBEDIT324if (m_editline_up)325m_editline_up->TerminalSizeChanged();326#endif327}328329// Split out a line from the buffer, if there is a full one to get.330static std::optional<std::string> SplitLine(std::string &line_buffer) {331size_t pos = line_buffer.find('\n');332if (pos == std::string::npos)333return std::nullopt;334std::string line =335std::string(StringRef(line_buffer.c_str(), pos).rtrim("\n\r"));336line_buffer = line_buffer.substr(pos + 1);337return line;338}339340// If the final line of the file ends without a end-of-line, return341// it as a line anyway.342static std::optional<std::string> SplitLineEOF(std::string &line_buffer) {343if (llvm::all_of(line_buffer, llvm::isSpace))344return std::nullopt;345std::string line = std::move(line_buffer);346line_buffer.clear();347return line;348}349350bool IOHandlerEditline::GetLine(std::string &line, bool &interrupted) {351#if LLDB_ENABLE_LIBEDIT352if (m_editline_up) {353return m_editline_up->GetLine(line, interrupted);354}355#endif356357line.clear();358359if (GetIsInteractive()) {360const char *prompt = nullptr;361362if (m_multi_line && m_curr_line_idx > 0)363prompt = GetContinuationPrompt();364365if (prompt == nullptr)366prompt = GetPrompt();367368if (prompt && prompt[0]) {369if (m_output_sp) {370m_output_sp->Printf("%s", prompt);371m_output_sp->Flush();372}373}374}375376std::optional<std::string> got_line = SplitLine(m_line_buffer);377378if (!got_line && !m_input_sp) {379// No more input file, we are done...380SetIsDone(true);381return false;382}383384FILE *in = GetInputFILE();385char buffer[256];386387if (!got_line && !in && m_input_sp) {388// there is no FILE*, fall back on just reading bytes from the stream.389while (!got_line) {390size_t bytes_read = sizeof(buffer);391Status error = m_input_sp->Read((void *)buffer, bytes_read);392if (error.Success() && !bytes_read) {393got_line = SplitLineEOF(m_line_buffer);394break;395}396if (error.Fail())397break;398m_line_buffer += StringRef(buffer, bytes_read);399got_line = SplitLine(m_line_buffer);400}401}402403if (!got_line && in) {404while (!got_line) {405char *r = fgets(buffer, sizeof(buffer), in);406#ifdef _WIN32407// ReadFile on Windows is supposed to set ERROR_OPERATION_ABORTED408// according to the docs on MSDN. However, this has evidently been a409// known bug since Windows 8. Therefore, we can't detect if a signal410// interrupted in the fgets. So pressing ctrl-c causes the repl to end411// and the process to exit. A temporary workaround is just to attempt to412// fgets twice until this bug is fixed.413if (r == nullptr)414r = fgets(buffer, sizeof(buffer), in);415// this is the equivalent of EINTR for Windows416if (r == nullptr && GetLastError() == ERROR_OPERATION_ABORTED)417continue;418#endif419if (r == nullptr) {420if (ferror(in) && errno == EINTR)421continue;422if (feof(in))423got_line = SplitLineEOF(m_line_buffer);424break;425}426m_line_buffer += buffer;427got_line = SplitLine(m_line_buffer);428}429}430431if (got_line) {432line = *got_line;433}434435return (bool)got_line;436}437438#if LLDB_ENABLE_LIBEDIT439bool IOHandlerEditline::IsInputCompleteCallback(Editline *editline,440StringList &lines) {441return m_delegate.IOHandlerIsInputComplete(*this, lines);442}443444int IOHandlerEditline::FixIndentationCallback(Editline *editline,445const StringList &lines,446int cursor_position) {447return m_delegate.IOHandlerFixIndentation(*this, lines, cursor_position);448}449450std::optional<std::string>451IOHandlerEditline::SuggestionCallback(llvm::StringRef line) {452return m_delegate.IOHandlerSuggestion(*this, line);453}454455void IOHandlerEditline::AutoCompleteCallback(CompletionRequest &request) {456m_delegate.IOHandlerComplete(*this, request);457}458#endif459460const char *IOHandlerEditline::GetPrompt() {461#if LLDB_ENABLE_LIBEDIT462if (m_editline_up) {463return m_editline_up->GetPrompt();464} else {465#endif466if (m_prompt.empty())467return nullptr;468#if LLDB_ENABLE_LIBEDIT469}470#endif471return m_prompt.c_str();472}473474bool IOHandlerEditline::SetPrompt(llvm::StringRef prompt) {475m_prompt = std::string(prompt);476477#if LLDB_ENABLE_LIBEDIT478if (m_editline_up) {479m_editline_up->SetPrompt(m_prompt.empty() ? nullptr : m_prompt.c_str());480if (m_color) {481m_editline_up->SetPromptAnsiPrefix(482ansi::FormatAnsiTerminalCodes(m_debugger.GetPromptAnsiPrefix()));483m_editline_up->SetPromptAnsiSuffix(484ansi::FormatAnsiTerminalCodes(m_debugger.GetPromptAnsiSuffix()));485}486}487#endif488return true;489}490491const char *IOHandlerEditline::GetContinuationPrompt() {492return (m_continuation_prompt.empty() ? nullptr493: m_continuation_prompt.c_str());494}495496void IOHandlerEditline::SetContinuationPrompt(llvm::StringRef prompt) {497m_continuation_prompt = std::string(prompt);498499#if LLDB_ENABLE_LIBEDIT500if (m_editline_up)501m_editline_up->SetContinuationPrompt(m_continuation_prompt.empty()502? nullptr503: m_continuation_prompt.c_str());504#endif505}506507void IOHandlerEditline::SetBaseLineNumber(uint32_t line) {508m_base_line_number = line;509}510511uint32_t IOHandlerEditline::GetCurrentLineIndex() const {512#if LLDB_ENABLE_LIBEDIT513if (m_editline_up)514return m_editline_up->GetCurrentLine();515#endif516return m_curr_line_idx;517}518519StringList IOHandlerEditline::GetCurrentLines() const {520#if LLDB_ENABLE_LIBEDIT521if (m_editline_up)522return m_editline_up->GetInputAsStringList();523#endif524// When libedit is not used, the current lines can be gotten from525// `m_current_lines_ptr`, which is updated whenever a new line is processed.526// This doesn't happen when libedit is used, in which case527// `m_current_lines_ptr` is only updated when the full input is terminated.528529if (m_current_lines_ptr)530return *m_current_lines_ptr;531return StringList();532}533534bool IOHandlerEditline::GetLines(StringList &lines, bool &interrupted) {535m_current_lines_ptr = &lines;536537bool success = false;538#if LLDB_ENABLE_LIBEDIT539if (m_editline_up) {540return m_editline_up->GetLines(m_base_line_number, lines, interrupted);541} else {542#endif543bool done = false;544Status error;545546while (!done) {547// Show line numbers if we are asked to548std::string line;549if (m_base_line_number > 0 && GetIsInteractive()) {550if (m_output_sp) {551m_output_sp->Printf("%u%s",552m_base_line_number + (uint32_t)lines.GetSize(),553GetPrompt() == nullptr ? " " : "");554}555}556557m_curr_line_idx = lines.GetSize();558559bool interrupted = false;560if (GetLine(line, interrupted) && !interrupted) {561lines.AppendString(line);562done = m_delegate.IOHandlerIsInputComplete(*this, lines);563} else {564done = true;565}566}567success = lines.GetSize() > 0;568#if LLDB_ENABLE_LIBEDIT569}570#endif571return success;572}573574// Each IOHandler gets to run until it is done. It should read data from the575// "in" and place output into "out" and "err and return when done.576void IOHandlerEditline::Run() {577std::string line;578while (IsActive()) {579bool interrupted = false;580if (m_multi_line) {581StringList lines;582if (GetLines(lines, interrupted)) {583if (interrupted) {584m_done = m_interrupt_exits;585m_delegate.IOHandlerInputInterrupted(*this, line);586587} else {588line = lines.CopyList();589m_delegate.IOHandlerInputComplete(*this, line);590}591} else {592m_done = true;593}594} else {595if (GetLine(line, interrupted)) {596if (interrupted)597m_delegate.IOHandlerInputInterrupted(*this, line);598else599m_delegate.IOHandlerInputComplete(*this, line);600} else {601m_done = true;602}603}604}605}606607void IOHandlerEditline::Cancel() {608#if LLDB_ENABLE_LIBEDIT609if (m_editline_up)610m_editline_up->Cancel();611#endif612}613614bool IOHandlerEditline::Interrupt() {615// Let the delgate handle it first616if (m_delegate.IOHandlerInterrupt(*this))617return true;618619#if LLDB_ENABLE_LIBEDIT620if (m_editline_up)621return m_editline_up->Interrupt();622#endif623return false;624}625626void IOHandlerEditline::GotEOF() {627#if LLDB_ENABLE_LIBEDIT628if (m_editline_up)629m_editline_up->Interrupt();630#endif631}632633void IOHandlerEditline::PrintAsync(const char *s, size_t len, bool is_stdout) {634#if LLDB_ENABLE_LIBEDIT635if (m_editline_up) {636std::lock_guard<std::recursive_mutex> guard(m_output_mutex);637lldb::StreamFileSP stream = is_stdout ? m_output_sp : m_error_sp;638m_editline_up->PrintAsync(stream.get(), s, len);639} else640#endif641{642#ifdef _WIN32643const char *prompt = GetPrompt();644if (prompt) {645// Back up over previous prompt using Windows API646CONSOLE_SCREEN_BUFFER_INFO screen_buffer_info;647HANDLE console_handle = GetStdHandle(STD_OUTPUT_HANDLE);648GetConsoleScreenBufferInfo(console_handle, &screen_buffer_info);649COORD coord = screen_buffer_info.dwCursorPosition;650coord.X -= strlen(prompt);651if (coord.X < 0)652coord.X = 0;653SetConsoleCursorPosition(console_handle, coord);654}655#endif656IOHandler::PrintAsync(s, len, is_stdout);657#ifdef _WIN32658if (prompt)659IOHandler::PrintAsync(prompt, strlen(prompt), is_stdout);660#endif661}662}663664665