Path: blob/main/contrib/llvm-project/lldb/source/Expression/REPL.cpp
39587 views
//===-- REPL.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/Expression/REPL.h"9#include "lldb/Core/Debugger.h"10#include "lldb/Core/PluginManager.h"11#include "lldb/Expression/ExpressionVariable.h"12#include "lldb/Expression/UserExpression.h"13#include "lldb/Host/HostInfo.h"14#include "lldb/Host/StreamFile.h"15#include "lldb/Interpreter/CommandInterpreter.h"16#include "lldb/Interpreter/CommandReturnObject.h"17#include "lldb/Target/Thread.h"18#include "lldb/Utility/AnsiTerminal.h"1920#include <memory>2122using namespace lldb_private;2324char REPL::ID;2526REPL::REPL(Target &target) : m_target(target) {27// Make sure all option values have sane defaults28Debugger &debugger = m_target.GetDebugger();29debugger.SetShowProgress(false);30auto exe_ctx = debugger.GetCommandInterpreter().GetExecutionContext();31m_format_options.OptionParsingStarting(&exe_ctx);32m_varobj_options.OptionParsingStarting(&exe_ctx);33}3435REPL::~REPL() = default;3637lldb::REPLSP REPL::Create(Status &err, lldb::LanguageType language,38Debugger *debugger, Target *target,39const char *repl_options) {40uint32_t idx = 0;41lldb::REPLSP ret;4243while (REPLCreateInstance create_instance =44PluginManager::GetREPLCreateCallbackAtIndex(idx)) {45LanguageSet supported_languages =46PluginManager::GetREPLSupportedLanguagesAtIndex(idx++);47if (!supported_languages[language])48continue;49ret = (*create_instance)(err, language, debugger, target, repl_options);50if (ret) {51break;52}53}5455return ret;56}5758std::string REPL::GetSourcePath() {59llvm::StringRef file_basename = GetSourceFileBasename();60FileSpec tmpdir_file_spec = HostInfo::GetProcessTempDir();61if (tmpdir_file_spec) {62tmpdir_file_spec.SetFilename(file_basename);63m_repl_source_path = tmpdir_file_spec.GetPath();64} else {65tmpdir_file_spec = FileSpec("/tmp");66tmpdir_file_spec.AppendPathComponent(file_basename);67}6869return tmpdir_file_spec.GetPath();70}7172lldb::IOHandlerSP REPL::GetIOHandler() {73if (!m_io_handler_sp) {74Debugger &debugger = m_target.GetDebugger();75m_io_handler_sp = std::make_shared<IOHandlerEditline>(76debugger, IOHandler::Type::REPL,77"lldb-repl", // Name of input reader for history78llvm::StringRef("> "), // prompt79llvm::StringRef(". "), // Continuation prompt80true, // Multi-line81true, // The REPL prompt is always colored821, // Line number83*this);8485// Don't exit if CTRL+C is pressed86static_cast<IOHandlerEditline *>(m_io_handler_sp.get())87->SetInterruptExits(false);8889if (m_io_handler_sp->GetIsInteractive() &&90m_io_handler_sp->GetIsRealTerminal()) {91m_indent_str.assign(debugger.GetTabSize(), ' ');92m_enable_auto_indent = debugger.GetAutoIndent();93} else {94m_indent_str.clear();95m_enable_auto_indent = false;96}97}98return m_io_handler_sp;99}100101void REPL::IOHandlerActivated(IOHandler &io_handler, bool interactive) {102lldb::ProcessSP process_sp = m_target.GetProcessSP();103if (process_sp && process_sp->IsAlive())104return;105lldb::StreamFileSP error_sp(io_handler.GetErrorStreamFileSP());106error_sp->Printf("REPL requires a running target process.\n");107io_handler.SetIsDone(true);108}109110bool REPL::IOHandlerInterrupt(IOHandler &io_handler) { return false; }111112void REPL::IOHandlerInputInterrupted(IOHandler &io_handler, std::string &line) {113}114115const char *REPL::IOHandlerGetFixIndentationCharacters() {116return (m_enable_auto_indent ? GetAutoIndentCharacters() : nullptr);117}118119llvm::StringRef REPL::IOHandlerGetControlSequence(char ch) {120static constexpr llvm::StringLiteral control_sequence(":quit\n");121if (ch == 'd')122return control_sequence;123return {};124}125126const char *REPL::IOHandlerGetCommandPrefix() { return ":"; }127128const char *REPL::IOHandlerGetHelpPrologue() {129return "\nThe REPL (Read-Eval-Print-Loop) acts like an interpreter. "130"Valid statements, expressions, and declarations are immediately "131"compiled and executed.\n\n"132"The complete set of LLDB debugging commands are also available as "133"described below.\n\nCommands "134"must be prefixed with a colon at the REPL prompt (:quit for "135"example.) Typing just a colon "136"followed by return will switch to the LLDB prompt.\n\n"137"Type “< path” to read in code from a text file “path”.\n\n";138}139140bool REPL::IOHandlerIsInputComplete(IOHandler &io_handler, StringList &lines) {141// Check for meta command142const size_t num_lines = lines.GetSize();143if (num_lines == 1) {144const char *first_line = lines.GetStringAtIndex(0);145if (first_line[0] == ':')146return true; // Meta command is a single line where that starts with ':'147}148149// Check if REPL input is done150std::string source_string(lines.CopyList());151return SourceIsComplete(source_string);152}153154int REPL::CalculateActualIndentation(const StringList &lines) {155std::string last_line = lines[lines.GetSize() - 1];156157int actual_indent = 0;158for (char &ch : last_line) {159if (ch != ' ')160break;161++actual_indent;162}163164return actual_indent;165}166167int REPL::IOHandlerFixIndentation(IOHandler &io_handler,168const StringList &lines,169int cursor_position) {170if (!m_enable_auto_indent)171return 0;172173if (!lines.GetSize()) {174return 0;175}176177int tab_size = io_handler.GetDebugger().GetTabSize();178179lldb::offset_t desired_indent =180GetDesiredIndentation(lines, cursor_position, tab_size);181182int actual_indent = REPL::CalculateActualIndentation(lines);183184if (desired_indent == LLDB_INVALID_OFFSET)185return 0;186187return (int)desired_indent - actual_indent;188}189190static bool ReadCode(const std::string &path, std::string &code,191lldb::StreamFileSP &error_sp) {192auto &fs = FileSystem::Instance();193llvm::Twine pathTwine(path);194if (!fs.Exists(pathTwine)) {195error_sp->Printf("no such file at path '%s'\n", path.c_str());196return false;197}198if (!fs.Readable(pathTwine)) {199error_sp->Printf("could not read file at path '%s'\n", path.c_str());200return false;201}202const size_t file_size = fs.GetByteSize(pathTwine);203const size_t max_size = code.max_size();204if (file_size > max_size) {205error_sp->Printf("file at path '%s' too large: "206"file_size = %zu, max_size = %zu\n",207path.c_str(), file_size, max_size);208return false;209}210auto data_sp = fs.CreateDataBuffer(pathTwine);211if (data_sp == nullptr) {212error_sp->Printf("could not create buffer for file at path '%s'\n",213path.c_str());214return false;215}216code.assign((const char *)data_sp->GetBytes(), data_sp->GetByteSize());217return true;218}219220void REPL::IOHandlerInputComplete(IOHandler &io_handler, std::string &code) {221lldb::StreamFileSP output_sp(io_handler.GetOutputStreamFileSP());222lldb::StreamFileSP error_sp(io_handler.GetErrorStreamFileSP());223bool extra_line = false;224bool did_quit = false;225226if (code.empty()) {227m_code.AppendString("");228static_cast<IOHandlerEditline &>(io_handler)229.SetBaseLineNumber(m_code.GetSize() + 1);230} else {231Debugger &debugger = m_target.GetDebugger();232CommandInterpreter &ci = debugger.GetCommandInterpreter();233extra_line = ci.GetSpaceReplPrompts();234235ExecutionContext exe_ctx(m_target.GetProcessSP()236->GetThreadList()237.GetSelectedThread()238->GetSelectedFrame(DoNoSelectMostRelevantFrame)239.get());240241lldb::ProcessSP process_sp(exe_ctx.GetProcessSP());242243if (code[0] == ':') {244// Meta command245// Strip the ':'246code.erase(0, 1);247if (!llvm::StringRef(code).trim().empty()) {248// "lldb" was followed by arguments, so just execute the command dump249// the results250251// Turn off prompt on quit in case the user types ":quit"252const bool saved_prompt_on_quit = ci.GetPromptOnQuit();253if (saved_prompt_on_quit)254ci.SetPromptOnQuit(false);255256// Execute the command257CommandReturnObject result(debugger.GetUseColor());258result.SetImmediateOutputStream(output_sp);259result.SetImmediateErrorStream(error_sp);260ci.HandleCommand(code.c_str(), eLazyBoolNo, result);261262if (saved_prompt_on_quit)263ci.SetPromptOnQuit(true);264265if (result.GetStatus() == lldb::eReturnStatusQuit) {266did_quit = true;267io_handler.SetIsDone(true);268if (debugger.CheckTopIOHandlerTypes(269IOHandler::Type::REPL, IOHandler::Type::CommandInterpreter)) {270// We typed "quit" or an alias to quit so we need to check if the271// command interpreter is above us and tell it that it is done as272// well so we don't drop back into the command interpreter if we273// have already quit274lldb::IOHandlerSP io_handler_sp(ci.GetIOHandler());275if (io_handler_sp)276io_handler_sp->SetIsDone(true);277}278}279} else {280// ":" was followed by no arguments, so push the LLDB command prompt281if (debugger.CheckTopIOHandlerTypes(282IOHandler::Type::REPL, IOHandler::Type::CommandInterpreter)) {283// If the user wants to get back to the command interpreter and the284// command interpreter is what launched the REPL, then just let the285// REPL exit and fall back to the command interpreter.286io_handler.SetIsDone(true);287} else {288// The REPL wasn't launched the by the command interpreter, it is the289// base IOHandler, so we need to get the command interpreter and290lldb::IOHandlerSP io_handler_sp(ci.GetIOHandler());291if (io_handler_sp) {292io_handler_sp->SetIsDone(false);293debugger.RunIOHandlerAsync(ci.GetIOHandler());294}295}296}297} else {298if (code[0] == '<') {299// User wants to read code from a file.300// Interpret rest of line as a literal path.301auto path = llvm::StringRef(code.substr(1)).trim().str();302if (!ReadCode(path, code, error_sp)) {303return;304}305}306307// Unwind any expression we might have been running in case our REPL308// expression crashed and the user was looking around309if (m_dedicated_repl_mode) {310Thread *thread = exe_ctx.GetThreadPtr();311if (thread && thread->UnwindInnermostExpression().Success()) {312thread->SetSelectedFrameByIndex(0, false);313exe_ctx.SetFrameSP(314thread->GetSelectedFrame(DoNoSelectMostRelevantFrame));315}316}317318const bool colorize_err = error_sp->GetFile().GetIsTerminalWithColors();319320EvaluateExpressionOptions expr_options = m_expr_options;321expr_options.SetCoerceToId(m_varobj_options.use_objc);322expr_options.SetKeepInMemory(true);323expr_options.SetUseDynamic(m_varobj_options.use_dynamic);324expr_options.SetGenerateDebugInfo(true);325expr_options.SetREPLEnabled(true);326expr_options.SetColorizeErrors(colorize_err);327expr_options.SetPoundLine(m_repl_source_path.c_str(),328m_code.GetSize() + 1);329330expr_options.SetLanguage(GetLanguage());331332PersistentExpressionState *persistent_state =333m_target.GetPersistentExpressionStateForLanguage(GetLanguage());334if (!persistent_state)335return;336337const size_t var_count_before = persistent_state->GetSize();338339const char *expr_prefix = nullptr;340lldb::ValueObjectSP result_valobj_sp;341Status error;342lldb::ExpressionResults execution_results =343UserExpression::Evaluate(exe_ctx, expr_options, code.c_str(),344expr_prefix, result_valobj_sp, error,345nullptr); // fixed expression346347if (llvm::Error err = OnExpressionEvaluated(exe_ctx, code, expr_options,348execution_results,349result_valobj_sp, error)) {350*error_sp << llvm::toString(std::move(err)) << "\n";351} else if (process_sp && process_sp->IsAlive()) {352bool add_to_code = true;353bool handled = false;354if (result_valobj_sp) {355lldb::Format format = m_format_options.GetFormat();356357if (result_valobj_sp->GetError().Success()) {358handled |= PrintOneVariable(debugger, output_sp, result_valobj_sp);359} else if (result_valobj_sp->GetError().GetError() ==360UserExpression::kNoResult) {361if (format != lldb::eFormatVoid && debugger.GetNotifyVoid()) {362error_sp->PutCString("(void)\n");363handled = true;364}365}366}367368if (debugger.GetPrintDecls()) {369for (size_t vi = var_count_before, ve = persistent_state->GetSize();370vi != ve; ++vi) {371lldb::ExpressionVariableSP persistent_var_sp =372persistent_state->GetVariableAtIndex(vi);373lldb::ValueObjectSP valobj_sp = persistent_var_sp->GetValueObject();374375PrintOneVariable(debugger, output_sp, valobj_sp,376persistent_var_sp.get());377}378}379380if (!handled) {381bool useColors = error_sp->GetFile().GetIsTerminalWithColors();382switch (execution_results) {383case lldb::eExpressionSetupError:384case lldb::eExpressionParseError:385add_to_code = false;386[[fallthrough]];387case lldb::eExpressionDiscarded:388error_sp->Printf("%s\n", error.AsCString());389break;390391case lldb::eExpressionCompleted:392break;393case lldb::eExpressionInterrupted:394if (useColors) {395error_sp->Printf(ANSI_ESCAPE1(ANSI_FG_COLOR_RED));396error_sp->Printf(ANSI_ESCAPE1(ANSI_CTRL_BOLD));397}398error_sp->Printf("Execution interrupted. ");399if (useColors)400error_sp->Printf(ANSI_ESCAPE1(ANSI_CTRL_NORMAL));401error_sp->Printf("Enter code to recover and continue.\nEnter LLDB "402"commands to investigate (type :help for "403"assistance.)\n");404break;405406case lldb::eExpressionHitBreakpoint:407// Breakpoint was hit, drop into LLDB command interpreter408if (useColors) {409error_sp->Printf(ANSI_ESCAPE1(ANSI_FG_COLOR_RED));410error_sp->Printf(ANSI_ESCAPE1(ANSI_CTRL_BOLD));411}412output_sp->Printf("Execution stopped at breakpoint. ");413if (useColors)414error_sp->Printf(ANSI_ESCAPE1(ANSI_CTRL_NORMAL));415output_sp->Printf("Enter LLDB commands to investigate (type help "416"for assistance.)\n");417{418lldb::IOHandlerSP io_handler_sp(ci.GetIOHandler());419if (io_handler_sp) {420io_handler_sp->SetIsDone(false);421debugger.RunIOHandlerAsync(ci.GetIOHandler());422}423}424break;425426case lldb::eExpressionTimedOut:427error_sp->Printf("error: timeout\n");428if (error.AsCString())429error_sp->Printf("error: %s\n", error.AsCString());430break;431case lldb::eExpressionResultUnavailable:432// Shoulnd't happen???433error_sp->Printf("error: could not fetch result -- %s\n",434error.AsCString());435break;436case lldb::eExpressionStoppedForDebug:437// Shoulnd't happen???438error_sp->Printf("error: stopped for debug -- %s\n",439error.AsCString());440break;441case lldb::eExpressionThreadVanished:442// Shoulnd't happen???443error_sp->Printf("error: expression thread vanished -- %s\n",444error.AsCString());445break;446}447}448449if (add_to_code) {450const uint32_t new_default_line = m_code.GetSize() + 1;451452m_code.SplitIntoLines(code);453454// Update our code on disk455if (!m_repl_source_path.empty()) {456auto file = FileSystem::Instance().Open(457FileSpec(m_repl_source_path),458File::eOpenOptionWriteOnly | File::eOpenOptionTruncate |459File::eOpenOptionCanCreate,460lldb::eFilePermissionsFileDefault);461if (file) {462std::string code(m_code.CopyList());463code.append(1, '\n');464size_t bytes_written = code.size();465file.get()->Write(code.c_str(), bytes_written);466file.get()->Close();467} else {468std::string message = llvm::toString(file.takeError());469error_sp->Printf("error: couldn't open %s: %s\n",470m_repl_source_path.c_str(), message.c_str());471}472473// Now set the default file and line to the REPL source file474m_target.GetSourceManager().SetDefaultFileAndLine(475FileSpec(m_repl_source_path), new_default_line);476}477static_cast<IOHandlerEditline &>(io_handler)478.SetBaseLineNumber(m_code.GetSize() + 1);479}480if (extra_line) {481output_sp->Printf("\n");482}483}484}485486// Don't complain about the REPL process going away if we are in the487// process of quitting.488if (!did_quit && (!process_sp || !process_sp->IsAlive())) {489error_sp->Printf(490"error: REPL process is no longer alive, exiting REPL\n");491io_handler.SetIsDone(true);492}493}494}495496void REPL::IOHandlerComplete(IOHandler &io_handler,497CompletionRequest &request) {498// Complete an LLDB command if the first character is a colon...499if (request.GetRawLine().starts_with(":")) {500Debugger &debugger = m_target.GetDebugger();501502// auto complete LLDB commands503llvm::StringRef new_line = request.GetRawLine().drop_front();504CompletionResult sub_result;505CompletionRequest sub_request(new_line, request.GetRawCursorPos() - 1,506sub_result);507debugger.GetCommandInterpreter().HandleCompletion(sub_request);508StringList matches, descriptions;509sub_result.GetMatches(matches);510// Prepend command prefix that was excluded in the completion request.511if (request.GetCursorIndex() == 0)512for (auto &match : matches)513match.insert(0, 1, ':');514sub_result.GetDescriptions(descriptions);515request.AddCompletions(matches, descriptions);516return;517}518519// Strip spaces from the line and see if we had only spaces520if (request.GetRawLine().trim().empty()) {521// Only spaces on this line, so just indent522request.AddCompletion(m_indent_str);523return;524}525526std::string current_code;527current_code.append(m_code.CopyList());528529IOHandlerEditline &editline = static_cast<IOHandlerEditline &>(io_handler);530StringList current_lines = editline.GetCurrentLines();531const uint32_t current_line_idx = editline.GetCurrentLineIndex();532533if (current_line_idx < current_lines.GetSize()) {534for (uint32_t i = 0; i < current_line_idx; ++i) {535const char *line_cstr = current_lines.GetStringAtIndex(i);536if (line_cstr) {537current_code.append("\n");538current_code.append(line_cstr);539}540}541}542543current_code.append("\n");544current_code += request.GetRawLine();545546CompleteCode(current_code, request);547}548549bool QuitCommandOverrideCallback(void *baton, const char **argv) {550Target *target = (Target *)baton;551lldb::ProcessSP process_sp(target->GetProcessSP());552if (process_sp) {553process_sp->Destroy(false);554process_sp->GetTarget().GetDebugger().ClearIOHandlers();555}556return false;557}558559Status REPL::RunLoop() {560Status error;561562error = DoInitialization();563m_repl_source_path = GetSourcePath();564565if (!error.Success())566return error;567568Debugger &debugger = m_target.GetDebugger();569570lldb::IOHandlerSP io_handler_sp(GetIOHandler());571572FileSpec save_default_file;573uint32_t save_default_line = 0;574575if (!m_repl_source_path.empty()) {576// Save the current default file and line577m_target.GetSourceManager().GetDefaultFileAndLine(save_default_file,578save_default_line);579}580581debugger.RunIOHandlerAsync(io_handler_sp);582583// Check if we are in dedicated REPL mode where LLDB was start with the "--584// repl" option from the command line. Currently we know this by checking if585// the debugger already has a IOHandler thread.586if (!debugger.HasIOHandlerThread()) {587// The debugger doesn't have an existing IOHandler thread, so this must be588// dedicated REPL mode...589m_dedicated_repl_mode = true;590debugger.StartIOHandlerThread();591llvm::StringRef command_name_str("quit");592CommandObject *cmd_obj =593debugger.GetCommandInterpreter().GetCommandObjectForCommand(594command_name_str);595if (cmd_obj) {596assert(command_name_str.empty());597cmd_obj->SetOverrideCallback(QuitCommandOverrideCallback, &m_target);598}599}600601// Wait for the REPL command interpreter to get popped602io_handler_sp->WaitForPop();603604if (m_dedicated_repl_mode) {605// If we were in dedicated REPL mode we would have started the IOHandler606// thread, and we should kill our process607lldb::ProcessSP process_sp = m_target.GetProcessSP();608if (process_sp && process_sp->IsAlive())609process_sp->Destroy(false);610611// Wait for the IO handler thread to exit (TODO: don't do this if the IO612// handler thread already exists...)613debugger.JoinIOHandlerThread();614}615616// Restore the default file and line617if (save_default_file && save_default_line != 0)618m_target.GetSourceManager().SetDefaultFileAndLine(save_default_file,619save_default_line);620return error;621}622623624