//===-- IOHandler.h ---------------------------------------------*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #ifndef LLDB_CORE_IOHANDLER_H #define LLDB_CORE_IOHANDLER_H #include "lldb/Core/ValueObjectList.h" #include "lldb/Host/Config.h" #include "lldb/Utility/CompletionRequest.h" #include "lldb/Utility/ConstString.h" #include "lldb/Utility/Flags.h" #include "lldb/Utility/Predicate.h" #include "lldb/Utility/Stream.h" #include "lldb/Utility/StringList.h" #include "lldb/lldb-defines.h" #include "lldb/lldb-forward.h" #include "llvm/ADT/StringRef.h" #include #include #include #include #include #include namespace lldb_private { class Debugger; namespace repro { class DataRecorder; } } namespace curses { class Application; typedef std::unique_ptr ApplicationAP; } // namespace curses namespace lldb_private { class IOHandler { public: enum class Type { CommandInterpreter, CommandList, Confirm, Curses, Expression, REPL, ProcessIO, PythonInterpreter, LuaInterpreter, PythonCode, Other }; IOHandler(Debugger &debugger, IOHandler::Type type); IOHandler(Debugger &debugger, IOHandler::Type type, const lldb::FileSP &input_sp, const lldb::StreamFileSP &output_sp, const lldb::StreamFileSP &error_sp, uint32_t flags, repro::DataRecorder *data_recorder); virtual ~IOHandler(); // Each IOHandler gets to run until it is done. It should read data from the // "in" and place output into "out" and "err and return when done. virtual void Run() = 0; // Called when an input reader should relinquish its control so another can // be pushed onto the IO handler stack, or so the current IO handler can pop // itself off the stack virtual void Cancel() = 0; // Called when CTRL+C is pressed which usually causes // Debugger::DispatchInputInterrupt to be called. virtual bool Interrupt() = 0; virtual void GotEOF() = 0; virtual bool IsActive() { return m_active && !m_done; } virtual void SetIsDone(bool b) { m_done = b; } virtual bool GetIsDone() { return m_done; } Type GetType() const { return m_type; } virtual void Activate() { m_active = true; } virtual void Deactivate() { m_active = false; } virtual void TerminalSizeChanged() {} virtual const char *GetPrompt() { // Prompt support isn't mandatory return nullptr; } virtual bool SetPrompt(llvm::StringRef prompt) { // Prompt support isn't mandatory return false; } bool SetPrompt(const char *) = delete; virtual ConstString GetControlSequence(char ch) { return ConstString(); } virtual const char *GetCommandPrefix() { return nullptr; } virtual const char *GetHelpPrologue() { return nullptr; } int GetInputFD(); int GetOutputFD(); int GetErrorFD(); FILE *GetInputFILE(); FILE *GetOutputFILE(); FILE *GetErrorFILE(); lldb::FileSP GetInputFileSP(); lldb::StreamFileSP GetOutputStreamFileSP(); lldb::StreamFileSP GetErrorStreamFileSP(); Debugger &GetDebugger() { return m_debugger; } void *GetUserData() { return m_user_data; } void SetUserData(void *user_data) { m_user_data = user_data; } Flags &GetFlags() { return m_flags; } const Flags &GetFlags() const { return m_flags; } /// Check if the input is being supplied interactively by a user /// /// This will return true if the input stream is a terminal (tty or /// pty) and can cause IO handlers to do different things (like /// for a confirmation when deleting all breakpoints). bool GetIsInteractive(); /// Check if the input is coming from a real terminal. /// /// A real terminal has a valid size with a certain number of rows /// and columns. If this function returns true, then terminal escape /// sequences are expected to work (cursor movement escape sequences, /// clearing lines, etc). bool GetIsRealTerminal(); void SetPopped(bool b); void WaitForPop(); virtual void PrintAsync(Stream *stream, const char *s, size_t len) { stream->Write(s, len); stream->Flush(); } protected: Debugger &m_debugger; lldb::FileSP m_input_sp; lldb::StreamFileSP m_output_sp; lldb::StreamFileSP m_error_sp; repro::DataRecorder *m_data_recorder; Predicate m_popped; Flags m_flags; Type m_type; void *m_user_data; bool m_done; bool m_active; private: IOHandler(const IOHandler &) = delete; const IOHandler &operator=(const IOHandler &) = delete; }; /// A delegate class for use with IOHandler subclasses. /// /// The IOHandler delegate is designed to be mixed into classes so /// they can use an IOHandler subclass to fetch input and notify the /// object that inherits from this delegate class when a token is /// received. class IOHandlerDelegate { public: enum class Completion { None, LLDBCommand, Expression }; IOHandlerDelegate(Completion completion = Completion::None) : m_completion(completion) {} virtual ~IOHandlerDelegate() = default; virtual void IOHandlerActivated(IOHandler &io_handler, bool interactive) {} virtual void IOHandlerDeactivated(IOHandler &io_handler) {} virtual llvm::Optional IOHandlerSuggestion(IOHandler &io_handler, llvm::StringRef line); virtual void IOHandlerComplete(IOHandler &io_handler, CompletionRequest &request); virtual const char *IOHandlerGetFixIndentationCharacters() { return nullptr; } /// Called when a new line is created or one of an identified set of /// indentation characters is typed. /// /// This function determines how much indentation should be added /// or removed to match the recommended amount for the final line. /// /// \param[in] io_handler /// The IOHandler that responsible for input. /// /// \param[in] lines /// The current input up to the line to be corrected. Lines /// following the line containing the cursor are not included. /// /// \param[in] cursor_position /// The number of characters preceding the cursor on the final /// line at the time. /// /// \return /// Returns an integer describing the number of spaces needed /// to correct the indentation level. Positive values indicate /// that spaces should be added, while negative values represent /// spaces that should be removed. virtual int IOHandlerFixIndentation(IOHandler &io_handler, const StringList &lines, int cursor_position) { return 0; } /// Called when a line or lines have been retrieved. /// /// This function can handle the current line and possibly call /// IOHandler::SetIsDone(true) when the IO handler is done like when /// "quit" is entered as a command, of when an empty line is /// received. It is up to the delegate to determine when a line /// should cause a IOHandler to exit. virtual void IOHandlerInputComplete(IOHandler &io_handler, std::string &data) = 0; virtual void IOHandlerInputInterrupted(IOHandler &io_handler, std::string &data) {} /// Called to determine whether typing enter after the last line in /// \a lines should end input. This function will not be called on /// IOHandler objects that are getting single lines. /// \param[in] io_handler /// The IOHandler that responsible for updating the lines. /// /// \param[in] lines /// The current multi-line content. May be altered to provide /// alternative input when complete. /// /// \return /// Return an boolean to indicate whether input is complete, /// true indicates that no additional input is necessary, while /// false indicates that more input is required. virtual bool IOHandlerIsInputComplete(IOHandler &io_handler, StringList &lines) { // Impose no requirements for input to be considered complete. subclasses // should do something more intelligent. return true; } virtual ConstString IOHandlerGetControlSequence(char ch) { return ConstString(); } virtual const char *IOHandlerGetCommandPrefix() { return nullptr; } virtual const char *IOHandlerGetHelpPrologue() { return nullptr; } // Intercept the IOHandler::Interrupt() calls and do something. // // Return true if the interrupt was handled, false if the IOHandler should // continue to try handle the interrupt itself. virtual bool IOHandlerInterrupt(IOHandler &io_handler) { return false; } protected: Completion m_completion; // Support for common builtin completions }; // IOHandlerDelegateMultiline // // A IOHandlerDelegate that handles terminating multi-line input when // the last line is equal to "end_line" which is specified in the constructor. class IOHandlerDelegateMultiline : public IOHandlerDelegate { public: IOHandlerDelegateMultiline(const char *end_line, Completion completion = Completion::None) : IOHandlerDelegate(completion), m_end_line((end_line && end_line[0]) ? end_line : "") {} ~IOHandlerDelegateMultiline() override = default; ConstString IOHandlerGetControlSequence(char ch) override { if (ch == 'd') return ConstString(m_end_line + "\n"); return ConstString(); } bool IOHandlerIsInputComplete(IOHandler &io_handler, StringList &lines) override { // Determine whether the end of input signal has been entered const size_t num_lines = lines.GetSize(); if (num_lines > 0 && lines[num_lines - 1] == m_end_line) { // Remove the terminal line from "lines" so it doesn't appear in the // resulting input and return true to indicate we are done getting lines lines.PopBack(); return true; } return false; } protected: const std::string m_end_line; }; class IOHandlerEditline : public IOHandler { public: IOHandlerEditline(Debugger &debugger, IOHandler::Type type, const char *editline_name, // Used for saving history files llvm::StringRef prompt, llvm::StringRef continuation_prompt, bool multi_line, bool color_prompts, uint32_t line_number_start, // If non-zero show line numbers // starting at // 'line_number_start' IOHandlerDelegate &delegate, repro::DataRecorder *data_recorder); IOHandlerEditline(Debugger &debugger, IOHandler::Type type, const lldb::FileSP &input_sp, const lldb::StreamFileSP &output_sp, const lldb::StreamFileSP &error_sp, uint32_t flags, const char *editline_name, // Used for saving history files llvm::StringRef prompt, llvm::StringRef continuation_prompt, bool multi_line, bool color_prompts, uint32_t line_number_start, // If non-zero show line numbers // starting at // 'line_number_start' IOHandlerDelegate &delegate, repro::DataRecorder *data_recorder); IOHandlerEditline(Debugger &, IOHandler::Type, const char *, const char *, const char *, bool, bool, uint32_t, IOHandlerDelegate &) = delete; IOHandlerEditline(Debugger &, IOHandler::Type, const lldb::FileSP &, const lldb::StreamFileSP &, const lldb::StreamFileSP &, uint32_t, const char *, const char *, const char *, bool, bool, uint32_t, IOHandlerDelegate &) = delete; ~IOHandlerEditline() override; void Run() override; void Cancel() override; bool Interrupt() override; void GotEOF() override; void Activate() override; void Deactivate() override; void TerminalSizeChanged() override; ConstString GetControlSequence(char ch) override { return m_delegate.IOHandlerGetControlSequence(ch); } const char *GetCommandPrefix() override { return m_delegate.IOHandlerGetCommandPrefix(); } const char *GetHelpPrologue() override { return m_delegate.IOHandlerGetHelpPrologue(); } const char *GetPrompt() override; bool SetPrompt(llvm::StringRef prompt) override; bool SetPrompt(const char *prompt) = delete; const char *GetContinuationPrompt(); void SetContinuationPrompt(llvm::StringRef prompt); void SetContinuationPrompt(const char *) = delete; bool GetLine(std::string &line, bool &interrupted); bool GetLines(StringList &lines, bool &interrupted); void SetBaseLineNumber(uint32_t line); bool GetInterruptExits() { return m_interrupt_exits; } void SetInterruptExits(bool b) { m_interrupt_exits = b; } const StringList *GetCurrentLines() const { return m_current_lines_ptr; } uint32_t GetCurrentLineIndex() const; void PrintAsync(Stream *stream, const char *s, size_t len) override; private: #if LLDB_ENABLE_LIBEDIT static bool IsInputCompleteCallback(Editline *editline, StringList &lines, void *baton); static int FixIndentationCallback(Editline *editline, const StringList &lines, int cursor_position, void *baton); static llvm::Optional SuggestionCallback(llvm::StringRef line, void *baton); static void AutoCompleteCallback(CompletionRequest &request, void *baton); #endif protected: #if LLDB_ENABLE_LIBEDIT std::unique_ptr m_editline_up; #endif IOHandlerDelegate &m_delegate; std::string m_prompt; std::string m_continuation_prompt; StringList *m_current_lines_ptr; uint32_t m_base_line_number; // If non-zero, then show line numbers in prompt uint32_t m_curr_line_idx; bool m_multi_line; bool m_color_prompts; bool m_interrupt_exits; bool m_editing; // Set to true when fetching a line manually (not using // libedit) std::string m_line_buffer; }; // The order of base classes is important. Look at the constructor of // IOHandlerConfirm to see how. class IOHandlerConfirm : public IOHandlerDelegate, public IOHandlerEditline { public: IOHandlerConfirm(Debugger &debugger, llvm::StringRef prompt, bool default_response); ~IOHandlerConfirm() override; bool GetResponse() const { return m_user_response; } void IOHandlerComplete(IOHandler &io_handler, CompletionRequest &request) override; void IOHandlerInputComplete(IOHandler &io_handler, std::string &data) override; protected: const bool m_default_response; bool m_user_response; }; class IOHandlerStack { public: IOHandlerStack() = default; size_t GetSize() const { std::lock_guard guard(m_mutex); return m_stack.size(); } void Push(const lldb::IOHandlerSP &sp) { if (sp) { std::lock_guard guard(m_mutex); sp->SetPopped(false); m_stack.push_back(sp); // Set m_top the non-locking IsTop() call m_top = sp.get(); } } bool IsEmpty() const { std::lock_guard guard(m_mutex); return m_stack.empty(); } lldb::IOHandlerSP Top() { lldb::IOHandlerSP sp; { std::lock_guard guard(m_mutex); if (!m_stack.empty()) sp = m_stack.back(); } return sp; } void Pop() { std::lock_guard guard(m_mutex); if (!m_stack.empty()) { lldb::IOHandlerSP sp(m_stack.back()); m_stack.pop_back(); sp->SetPopped(true); } // Set m_top the non-locking IsTop() call m_top = (m_stack.empty() ? nullptr : m_stack.back().get()); } std::recursive_mutex &GetMutex() { return m_mutex; } bool IsTop(const lldb::IOHandlerSP &io_handler_sp) const { return m_top == io_handler_sp.get(); } bool CheckTopIOHandlerTypes(IOHandler::Type top_type, IOHandler::Type second_top_type) { std::lock_guard guard(m_mutex); const size_t num_io_handlers = m_stack.size(); return (num_io_handlers >= 2 && m_stack[num_io_handlers - 1]->GetType() == top_type && m_stack[num_io_handlers - 2]->GetType() == second_top_type); } ConstString GetTopIOHandlerControlSequence(char ch) { return ((m_top != nullptr) ? m_top->GetControlSequence(ch) : ConstString()); } const char *GetTopIOHandlerCommandPrefix() { return ((m_top != nullptr) ? m_top->GetCommandPrefix() : nullptr); } const char *GetTopIOHandlerHelpPrologue() { return ((m_top != nullptr) ? m_top->GetHelpPrologue() : nullptr); } void PrintAsync(Stream *stream, const char *s, size_t len); protected: typedef std::vector collection; collection m_stack; mutable std::recursive_mutex m_mutex; IOHandler *m_top = nullptr; private: IOHandlerStack(const IOHandlerStack &) = delete; const IOHandlerStack &operator=(const IOHandlerStack &) = delete; }; } // namespace lldb_private #endif // LLDB_CORE_IOHANDLER_H