diff options
Diffstat (limited to 'contrib/llvm-project/lldb/source/Expression/REPL.cpp')
-rw-r--r-- | contrib/llvm-project/lldb/source/Expression/REPL.cpp | 622 |
1 files changed, 622 insertions, 0 deletions
diff --git a/contrib/llvm-project/lldb/source/Expression/REPL.cpp b/contrib/llvm-project/lldb/source/Expression/REPL.cpp new file mode 100644 index 000000000000..a6a4ffb5e0af --- /dev/null +++ b/contrib/llvm-project/lldb/source/Expression/REPL.cpp @@ -0,0 +1,622 @@ +//===-- REPL.cpp ----------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#include "lldb/Expression/REPL.h" +#include "lldb/Core/Debugger.h" +#include "lldb/Core/PluginManager.h" +#include "lldb/Expression/ExpressionVariable.h" +#include "lldb/Expression/UserExpression.h" +#include "lldb/Host/HostInfo.h" +#include "lldb/Host/StreamFile.h" +#include "lldb/Interpreter/CommandInterpreter.h" +#include "lldb/Interpreter/CommandReturnObject.h" +#include "lldb/Target/Thread.h" +#include "lldb/Utility/AnsiTerminal.h" + +#include <memory> + +using namespace lldb_private; + +char REPL::ID; + +REPL::REPL(Target &target) : m_target(target) { + // Make sure all option values have sane defaults + Debugger &debugger = m_target.GetDebugger(); + debugger.SetShowProgress(false); + auto exe_ctx = debugger.GetCommandInterpreter().GetExecutionContext(); + m_format_options.OptionParsingStarting(&exe_ctx); + m_varobj_options.OptionParsingStarting(&exe_ctx); +} + +REPL::~REPL() = default; + +lldb::REPLSP REPL::Create(Status &err, lldb::LanguageType language, + Debugger *debugger, Target *target, + const char *repl_options) { + uint32_t idx = 0; + lldb::REPLSP ret; + + while (REPLCreateInstance create_instance = + PluginManager::GetREPLCreateCallbackAtIndex(idx)) { + LanguageSet supported_languages = + PluginManager::GetREPLSupportedLanguagesAtIndex(idx++); + if (!supported_languages[language]) + continue; + ret = (*create_instance)(err, language, debugger, target, repl_options); + if (ret) { + break; + } + } + + return ret; +} + +std::string REPL::GetSourcePath() { + llvm::StringRef file_basename = GetSourceFileBasename(); + FileSpec tmpdir_file_spec = HostInfo::GetProcessTempDir(); + if (tmpdir_file_spec) { + tmpdir_file_spec.SetFilename(file_basename); + m_repl_source_path = tmpdir_file_spec.GetPath(); + } else { + tmpdir_file_spec = FileSpec("/tmp"); + tmpdir_file_spec.AppendPathComponent(file_basename); + } + + return tmpdir_file_spec.GetPath(); +} + +lldb::IOHandlerSP REPL::GetIOHandler() { + if (!m_io_handler_sp) { + Debugger &debugger = m_target.GetDebugger(); + m_io_handler_sp = std::make_shared<IOHandlerEditline>( + debugger, IOHandler::Type::REPL, + "lldb-repl", // Name of input reader for history + llvm::StringRef("> "), // prompt + llvm::StringRef(". "), // Continuation prompt + true, // Multi-line + true, // The REPL prompt is always colored + 1, // Line number + *this); + + // Don't exit if CTRL+C is pressed + static_cast<IOHandlerEditline *>(m_io_handler_sp.get()) + ->SetInterruptExits(false); + + if (m_io_handler_sp->GetIsInteractive() && + m_io_handler_sp->GetIsRealTerminal()) { + m_indent_str.assign(debugger.GetTabSize(), ' '); + m_enable_auto_indent = debugger.GetAutoIndent(); + } else { + m_indent_str.clear(); + m_enable_auto_indent = false; + } + } + return m_io_handler_sp; +} + +void REPL::IOHandlerActivated(IOHandler &io_handler, bool interactive) { + lldb::ProcessSP process_sp = m_target.GetProcessSP(); + if (process_sp && process_sp->IsAlive()) + return; + lldb::StreamFileSP error_sp(io_handler.GetErrorStreamFileSP()); + error_sp->Printf("REPL requires a running target process.\n"); + io_handler.SetIsDone(true); +} + +bool REPL::IOHandlerInterrupt(IOHandler &io_handler) { return false; } + +void REPL::IOHandlerInputInterrupted(IOHandler &io_handler, std::string &line) { +} + +const char *REPL::IOHandlerGetFixIndentationCharacters() { + return (m_enable_auto_indent ? GetAutoIndentCharacters() : nullptr); +} + +llvm::StringRef REPL::IOHandlerGetControlSequence(char ch) { + static constexpr llvm::StringLiteral control_sequence(":quit\n"); + if (ch == 'd') + return control_sequence; + return {}; +} + +const char *REPL::IOHandlerGetCommandPrefix() { return ":"; } + +const char *REPL::IOHandlerGetHelpPrologue() { + return "\nThe REPL (Read-Eval-Print-Loop) acts like an interpreter. " + "Valid statements, expressions, and declarations are immediately " + "compiled and executed.\n\n" + "The complete set of LLDB debugging commands are also available as " + "described below.\n\nCommands " + "must be prefixed with a colon at the REPL prompt (:quit for " + "example.) Typing just a colon " + "followed by return will switch to the LLDB prompt.\n\n" + "Type “< path” to read in code from a text file “path”.\n\n"; +} + +bool REPL::IOHandlerIsInputComplete(IOHandler &io_handler, StringList &lines) { + // Check for meta command + const size_t num_lines = lines.GetSize(); + if (num_lines == 1) { + const char *first_line = lines.GetStringAtIndex(0); + if (first_line[0] == ':') + return true; // Meta command is a single line where that starts with ':' + } + + // Check if REPL input is done + std::string source_string(lines.CopyList()); + return SourceIsComplete(source_string); +} + +int REPL::CalculateActualIndentation(const StringList &lines) { + std::string last_line = lines[lines.GetSize() - 1]; + + int actual_indent = 0; + for (char &ch : last_line) { + if (ch != ' ') + break; + ++actual_indent; + } + + return actual_indent; +} + +int REPL::IOHandlerFixIndentation(IOHandler &io_handler, + const StringList &lines, + int cursor_position) { + if (!m_enable_auto_indent) + return 0; + + if (!lines.GetSize()) { + return 0; + } + + int tab_size = io_handler.GetDebugger().GetTabSize(); + + lldb::offset_t desired_indent = + GetDesiredIndentation(lines, cursor_position, tab_size); + + int actual_indent = REPL::CalculateActualIndentation(lines); + + if (desired_indent == LLDB_INVALID_OFFSET) + return 0; + + return (int)desired_indent - actual_indent; +} + +static bool ReadCode(const std::string &path, std::string &code, + lldb::StreamFileSP &error_sp) { + auto &fs = FileSystem::Instance(); + llvm::Twine pathTwine(path); + if (!fs.Exists(pathTwine)) { + error_sp->Printf("no such file at path '%s'\n", path.c_str()); + return false; + } + if (!fs.Readable(pathTwine)) { + error_sp->Printf("could not read file at path '%s'\n", path.c_str()); + return false; + } + const size_t file_size = fs.GetByteSize(pathTwine); + const size_t max_size = code.max_size(); + if (file_size > max_size) { + error_sp->Printf("file at path '%s' too large: " + "file_size = %zu, max_size = %zu\n", + path.c_str(), file_size, max_size); + return false; + } + auto data_sp = fs.CreateDataBuffer(pathTwine); + if (data_sp == nullptr) { + error_sp->Printf("could not create buffer for file at path '%s'\n", + path.c_str()); + return false; + } + code.assign((const char *)data_sp->GetBytes(), data_sp->GetByteSize()); + return true; +} + +void REPL::IOHandlerInputComplete(IOHandler &io_handler, std::string &code) { + lldb::StreamFileSP output_sp(io_handler.GetOutputStreamFileSP()); + lldb::StreamFileSP error_sp(io_handler.GetErrorStreamFileSP()); + bool extra_line = false; + bool did_quit = false; + + if (code.empty()) { + m_code.AppendString(""); + static_cast<IOHandlerEditline &>(io_handler) + .SetBaseLineNumber(m_code.GetSize() + 1); + } else { + Debugger &debugger = m_target.GetDebugger(); + CommandInterpreter &ci = debugger.GetCommandInterpreter(); + extra_line = ci.GetSpaceReplPrompts(); + + ExecutionContext exe_ctx(m_target.GetProcessSP() + ->GetThreadList() + .GetSelectedThread() + ->GetSelectedFrame(DoNoSelectMostRelevantFrame) + .get()); + + lldb::ProcessSP process_sp(exe_ctx.GetProcessSP()); + + if (code[0] == ':') { + // Meta command + // Strip the ':' + code.erase(0, 1); + if (!llvm::StringRef(code).trim().empty()) { + // "lldb" was followed by arguments, so just execute the command dump + // the results + + // Turn off prompt on quit in case the user types ":quit" + const bool saved_prompt_on_quit = ci.GetPromptOnQuit(); + if (saved_prompt_on_quit) + ci.SetPromptOnQuit(false); + + // Execute the command + CommandReturnObject result(debugger.GetUseColor()); + result.SetImmediateOutputStream(output_sp); + result.SetImmediateErrorStream(error_sp); + ci.HandleCommand(code.c_str(), eLazyBoolNo, result); + + if (saved_prompt_on_quit) + ci.SetPromptOnQuit(true); + + if (result.GetStatus() == lldb::eReturnStatusQuit) { + did_quit = true; + io_handler.SetIsDone(true); + if (debugger.CheckTopIOHandlerTypes( + IOHandler::Type::REPL, IOHandler::Type::CommandInterpreter)) { + // We typed "quit" or an alias to quit so we need to check if the + // command interpreter is above us and tell it that it is done as + // well so we don't drop back into the command interpreter if we + // have already quit + lldb::IOHandlerSP io_handler_sp(ci.GetIOHandler()); + if (io_handler_sp) + io_handler_sp->SetIsDone(true); + } + } + } else { + // ":" was followed by no arguments, so push the LLDB command prompt + if (debugger.CheckTopIOHandlerTypes( + IOHandler::Type::REPL, IOHandler::Type::CommandInterpreter)) { + // If the user wants to get back to the command interpreter and the + // command interpreter is what launched the REPL, then just let the + // REPL exit and fall back to the command interpreter. + io_handler.SetIsDone(true); + } else { + // The REPL wasn't launched the by the command interpreter, it is the + // base IOHandler, so we need to get the command interpreter and + lldb::IOHandlerSP io_handler_sp(ci.GetIOHandler()); + if (io_handler_sp) { + io_handler_sp->SetIsDone(false); + debugger.RunIOHandlerAsync(ci.GetIOHandler()); + } + } + } + } else { + if (code[0] == '<') { + // User wants to read code from a file. + // Interpret rest of line as a literal path. + auto path = llvm::StringRef(code.substr(1)).trim().str(); + if (!ReadCode(path, code, error_sp)) { + return; + } + } + + // Unwind any expression we might have been running in case our REPL + // expression crashed and the user was looking around + if (m_dedicated_repl_mode) { + Thread *thread = exe_ctx.GetThreadPtr(); + if (thread && thread->UnwindInnermostExpression().Success()) { + thread->SetSelectedFrameByIndex(0, false); + exe_ctx.SetFrameSP( + thread->GetSelectedFrame(DoNoSelectMostRelevantFrame)); + } + } + + const bool colorize_err = error_sp->GetFile().GetIsTerminalWithColors(); + + EvaluateExpressionOptions expr_options = m_expr_options; + expr_options.SetCoerceToId(m_varobj_options.use_objc); + expr_options.SetKeepInMemory(true); + expr_options.SetUseDynamic(m_varobj_options.use_dynamic); + expr_options.SetGenerateDebugInfo(true); + expr_options.SetREPLEnabled(true); + expr_options.SetColorizeErrors(colorize_err); + expr_options.SetPoundLine(m_repl_source_path.c_str(), + m_code.GetSize() + 1); + + expr_options.SetLanguage(GetLanguage()); + + PersistentExpressionState *persistent_state = + m_target.GetPersistentExpressionStateForLanguage(GetLanguage()); + if (!persistent_state) + return; + + const size_t var_count_before = persistent_state->GetSize(); + + const char *expr_prefix = nullptr; + lldb::ValueObjectSP result_valobj_sp; + Status error; + lldb::ExpressionResults execution_results = + UserExpression::Evaluate(exe_ctx, expr_options, code.c_str(), + expr_prefix, result_valobj_sp, error, + nullptr); // fixed expression + + if (llvm::Error err = OnExpressionEvaluated(exe_ctx, code, expr_options, + execution_results, + result_valobj_sp, error)) { + *error_sp << llvm::toString(std::move(err)) << "\n"; + } else if (process_sp && process_sp->IsAlive()) { + bool add_to_code = true; + bool handled = false; + if (result_valobj_sp) { + lldb::Format format = m_format_options.GetFormat(); + + if (result_valobj_sp->GetError().Success()) { + handled |= PrintOneVariable(debugger, output_sp, result_valobj_sp); + } else if (result_valobj_sp->GetError().GetError() == + UserExpression::kNoResult) { + if (format != lldb::eFormatVoid && debugger.GetNotifyVoid()) { + error_sp->PutCString("(void)\n"); + handled = true; + } + } + } + + if (debugger.GetPrintDecls()) { + for (size_t vi = var_count_before, ve = persistent_state->GetSize(); + vi != ve; ++vi) { + lldb::ExpressionVariableSP persistent_var_sp = + persistent_state->GetVariableAtIndex(vi); + lldb::ValueObjectSP valobj_sp = persistent_var_sp->GetValueObject(); + + PrintOneVariable(debugger, output_sp, valobj_sp, + persistent_var_sp.get()); + } + } + + if (!handled) { + bool useColors = error_sp->GetFile().GetIsTerminalWithColors(); + switch (execution_results) { + case lldb::eExpressionSetupError: + case lldb::eExpressionParseError: + add_to_code = false; + [[fallthrough]]; + case lldb::eExpressionDiscarded: + error_sp->Printf("%s\n", error.AsCString()); + break; + + case lldb::eExpressionCompleted: + break; + case lldb::eExpressionInterrupted: + if (useColors) { + error_sp->Printf(ANSI_ESCAPE1(ANSI_FG_COLOR_RED)); + error_sp->Printf(ANSI_ESCAPE1(ANSI_CTRL_BOLD)); + } + error_sp->Printf("Execution interrupted. "); + if (useColors) + error_sp->Printf(ANSI_ESCAPE1(ANSI_CTRL_NORMAL)); + error_sp->Printf("Enter code to recover and continue.\nEnter LLDB " + "commands to investigate (type :help for " + "assistance.)\n"); + break; + + case lldb::eExpressionHitBreakpoint: + // Breakpoint was hit, drop into LLDB command interpreter + if (useColors) { + error_sp->Printf(ANSI_ESCAPE1(ANSI_FG_COLOR_RED)); + error_sp->Printf(ANSI_ESCAPE1(ANSI_CTRL_BOLD)); + } + output_sp->Printf("Execution stopped at breakpoint. "); + if (useColors) + error_sp->Printf(ANSI_ESCAPE1(ANSI_CTRL_NORMAL)); + output_sp->Printf("Enter LLDB commands to investigate (type help " + "for assistance.)\n"); + { + lldb::IOHandlerSP io_handler_sp(ci.GetIOHandler()); + if (io_handler_sp) { + io_handler_sp->SetIsDone(false); + debugger.RunIOHandlerAsync(ci.GetIOHandler()); + } + } + break; + + case lldb::eExpressionTimedOut: + error_sp->Printf("error: timeout\n"); + if (error.AsCString()) + error_sp->Printf("error: %s\n", error.AsCString()); + break; + case lldb::eExpressionResultUnavailable: + // Shoulnd't happen??? + error_sp->Printf("error: could not fetch result -- %s\n", + error.AsCString()); + break; + case lldb::eExpressionStoppedForDebug: + // Shoulnd't happen??? + error_sp->Printf("error: stopped for debug -- %s\n", + error.AsCString()); + break; + case lldb::eExpressionThreadVanished: + // Shoulnd't happen??? + error_sp->Printf("error: expression thread vanished -- %s\n", + error.AsCString()); + break; + } + } + + if (add_to_code) { + const uint32_t new_default_line = m_code.GetSize() + 1; + + m_code.SplitIntoLines(code); + + // Update our code on disk + if (!m_repl_source_path.empty()) { + auto file = FileSystem::Instance().Open( + FileSpec(m_repl_source_path), + File::eOpenOptionWriteOnly | File::eOpenOptionTruncate | + File::eOpenOptionCanCreate, + lldb::eFilePermissionsFileDefault); + if (file) { + std::string code(m_code.CopyList()); + code.append(1, '\n'); + size_t bytes_written = code.size(); + file.get()->Write(code.c_str(), bytes_written); + file.get()->Close(); + } else { + std::string message = llvm::toString(file.takeError()); + error_sp->Printf("error: couldn't open %s: %s\n", + m_repl_source_path.c_str(), message.c_str()); + } + + // Now set the default file and line to the REPL source file + m_target.GetSourceManager().SetDefaultFileAndLine( + FileSpec(m_repl_source_path), new_default_line); + } + static_cast<IOHandlerEditline &>(io_handler) + .SetBaseLineNumber(m_code.GetSize() + 1); + } + if (extra_line) { + output_sp->Printf("\n"); + } + } + } + + // Don't complain about the REPL process going away if we are in the + // process of quitting. + if (!did_quit && (!process_sp || !process_sp->IsAlive())) { + error_sp->Printf( + "error: REPL process is no longer alive, exiting REPL\n"); + io_handler.SetIsDone(true); + } + } +} + +void REPL::IOHandlerComplete(IOHandler &io_handler, + CompletionRequest &request) { + // Complete an LLDB command if the first character is a colon... + if (request.GetRawLine().starts_with(":")) { + Debugger &debugger = m_target.GetDebugger(); + + // auto complete LLDB commands + llvm::StringRef new_line = request.GetRawLine().drop_front(); + CompletionResult sub_result; + CompletionRequest sub_request(new_line, request.GetRawCursorPos() - 1, + sub_result); + debugger.GetCommandInterpreter().HandleCompletion(sub_request); + StringList matches, descriptions; + sub_result.GetMatches(matches); + // Prepend command prefix that was excluded in the completion request. + if (request.GetCursorIndex() == 0) + for (auto &match : matches) + match.insert(0, 1, ':'); + sub_result.GetDescriptions(descriptions); + request.AddCompletions(matches, descriptions); + return; + } + + // Strip spaces from the line and see if we had only spaces + if (request.GetRawLine().trim().empty()) { + // Only spaces on this line, so just indent + request.AddCompletion(m_indent_str); + return; + } + + std::string current_code; + current_code.append(m_code.CopyList()); + + IOHandlerEditline &editline = static_cast<IOHandlerEditline &>(io_handler); + StringList current_lines = editline.GetCurrentLines(); + const uint32_t current_line_idx = editline.GetCurrentLineIndex(); + + if (current_line_idx < current_lines.GetSize()) { + for (uint32_t i = 0; i < current_line_idx; ++i) { + const char *line_cstr = current_lines.GetStringAtIndex(i); + if (line_cstr) { + current_code.append("\n"); + current_code.append(line_cstr); + } + } + } + + current_code.append("\n"); + current_code += request.GetRawLine(); + + CompleteCode(current_code, request); +} + +bool QuitCommandOverrideCallback(void *baton, const char **argv) { + Target *target = (Target *)baton; + lldb::ProcessSP process_sp(target->GetProcessSP()); + if (process_sp) { + process_sp->Destroy(false); + process_sp->GetTarget().GetDebugger().ClearIOHandlers(); + } + return false; +} + +Status REPL::RunLoop() { + Status error; + + error = DoInitialization(); + m_repl_source_path = GetSourcePath(); + + if (!error.Success()) + return error; + + Debugger &debugger = m_target.GetDebugger(); + + lldb::IOHandlerSP io_handler_sp(GetIOHandler()); + + FileSpec save_default_file; + uint32_t save_default_line = 0; + + if (!m_repl_source_path.empty()) { + // Save the current default file and line + m_target.GetSourceManager().GetDefaultFileAndLine(save_default_file, + save_default_line); + } + + debugger.RunIOHandlerAsync(io_handler_sp); + + // Check if we are in dedicated REPL mode where LLDB was start with the "-- + // repl" option from the command line. Currently we know this by checking if + // the debugger already has a IOHandler thread. + if (!debugger.HasIOHandlerThread()) { + // The debugger doesn't have an existing IOHandler thread, so this must be + // dedicated REPL mode... + m_dedicated_repl_mode = true; + debugger.StartIOHandlerThread(); + llvm::StringRef command_name_str("quit"); + CommandObject *cmd_obj = + debugger.GetCommandInterpreter().GetCommandObjectForCommand( + command_name_str); + if (cmd_obj) { + assert(command_name_str.empty()); + cmd_obj->SetOverrideCallback(QuitCommandOverrideCallback, &m_target); + } + } + + // Wait for the REPL command interpreter to get popped + io_handler_sp->WaitForPop(); + + if (m_dedicated_repl_mode) { + // If we were in dedicated REPL mode we would have started the IOHandler + // thread, and we should kill our process + lldb::ProcessSP process_sp = m_target.GetProcessSP(); + if (process_sp && process_sp->IsAlive()) + process_sp->Destroy(false); + + // Wait for the IO handler thread to exit (TODO: don't do this if the IO + // handler thread already exists...) + debugger.JoinIOHandlerThread(); + } + + // Restore the default file and line + if (save_default_file && save_default_line != 0) + m_target.GetSourceManager().SetDefaultFileAndLine(save_default_file, + save_default_line); + return error; +} |