blob: 4a3b788e3ea161673843dda6b2064c4ad4255104 [file] [log] [blame]
//===-- 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 <memory>
#include <mutex>
#include <string>
#include <vector>
#include <cstdint>
#include <cstdio>
namespace lldb_private {
class Debugger;
namespace repro {
class DataRecorder;
}
}
namespace curses {
class Application;
typedef std::unique_ptr<Application> 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<bool> 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<std::string> 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
bool IsInputCompleteCallback(Editline *editline, StringList &lines);
int FixIndentationCallback(Editline *editline, const StringList &lines,
int cursor_position);
llvm::Optional<std::string> SuggestionCallback(llvm::StringRef line);
void AutoCompleteCallback(CompletionRequest &request);
#endif
protected:
#if LLDB_ENABLE_LIBEDIT
std::unique_ptr<Editline> 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<std::recursive_mutex> guard(m_mutex);
return m_stack.size();
}
void Push(const lldb::IOHandlerSP &sp) {
if (sp) {
std::lock_guard<std::recursive_mutex> 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<std::recursive_mutex> guard(m_mutex);
return m_stack.empty();
}
lldb::IOHandlerSP Top() {
lldb::IOHandlerSP sp;
{
std::lock_guard<std::recursive_mutex> guard(m_mutex);
if (!m_stack.empty())
sp = m_stack.back();
}
return sp;
}
void Pop() {
std::lock_guard<std::recursive_mutex> 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<std::recursive_mutex> 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<lldb::IOHandlerSP> 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