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