621 lines · cpp
1//===-- REPL.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/Expression/REPL.h"10#include "lldb/Core/Debugger.h"11#include "lldb/Core/PluginManager.h"12#include "lldb/Expression/ExpressionVariable.h"13#include "lldb/Expression/UserExpression.h"14#include "lldb/Host/HostInfo.h"15#include "lldb/Host/StreamFile.h"16#include "lldb/Interpreter/CommandInterpreter.h"17#include "lldb/Interpreter/CommandReturnObject.h"18#include "lldb/Target/Thread.h"19#include "lldb/Utility/AnsiTerminal.h"20 21#include <memory>22 23using namespace lldb_private;24 25char REPL::ID;26 27REPL::REPL(Target &target) : m_target(target) {28 // Make sure all option values have sane defaults29 Debugger &debugger = m_target.GetDebugger();30 debugger.SetShowProgress(false);31 auto exe_ctx = debugger.GetCommandInterpreter().GetExecutionContext();32 m_format_options.OptionParsingStarting(&exe_ctx);33 m_varobj_options.OptionParsingStarting(&exe_ctx);34}35 36REPL::~REPL() = default;37 38lldb::REPLSP REPL::Create(Status &err, lldb::LanguageType language,39 Debugger *debugger, Target *target,40 const char *repl_options) {41 uint32_t idx = 0;42 lldb::REPLSP ret;43 44 while (REPLCreateInstance create_instance =45 PluginManager::GetREPLCreateCallbackAtIndex(idx)) {46 LanguageSet supported_languages =47 PluginManager::GetREPLSupportedLanguagesAtIndex(idx++);48 if (!supported_languages[language])49 continue;50 ret = (*create_instance)(err, language, debugger, target, repl_options);51 if (ret) {52 break;53 }54 }55 56 return ret;57}58 59std::string REPL::GetSourcePath() {60 llvm::StringRef file_basename = GetSourceFileBasename();61 FileSpec tmpdir_file_spec = HostInfo::GetProcessTempDir();62 if (tmpdir_file_spec) {63 tmpdir_file_spec.SetFilename(file_basename);64 m_repl_source_path = tmpdir_file_spec.GetPath();65 } else {66 tmpdir_file_spec = FileSpec("/tmp");67 tmpdir_file_spec.AppendPathComponent(file_basename);68 }69 70 return tmpdir_file_spec.GetPath();71}72 73lldb::IOHandlerSP REPL::GetIOHandler() {74 if (!m_io_handler_sp) {75 Debugger &debugger = m_target.GetDebugger();76 m_io_handler_sp = std::make_shared<IOHandlerEditline>(77 debugger, IOHandler::Type::REPL,78 "lldb-repl", // Name of input reader for history79 llvm::StringRef("> "), // prompt80 llvm::StringRef(". "), // Continuation prompt81 true, // Multi-line82 true, // The REPL prompt is always colored83 1, // Line number84 *this);85 86 // Don't exit if CTRL+C is pressed87 static_cast<IOHandlerEditline *>(m_io_handler_sp.get())88 ->SetInterruptExits(false);89 90 if (m_io_handler_sp->GetIsInteractive() &&91 m_io_handler_sp->GetIsRealTerminal()) {92 m_indent_str.assign(debugger.GetTabSize(), ' ');93 m_enable_auto_indent = debugger.GetAutoIndent();94 } else {95 m_indent_str.clear();96 m_enable_auto_indent = false;97 }98 }99 return m_io_handler_sp;100}101 102void REPL::IOHandlerActivated(IOHandler &io_handler, bool interactive) {103 lldb::ProcessSP process_sp = m_target.GetProcessSP();104 if (process_sp && process_sp->IsAlive())105 return;106 LockedStreamFile locked_stream = io_handler.GetErrorStreamFileSP()->Lock();107 locked_stream.Printf("REPL requires a running target process.\n");108 io_handler.SetIsDone(true);109}110 111bool REPL::IOHandlerInterrupt(IOHandler &io_handler) { return false; }112 113void REPL::IOHandlerInputInterrupted(IOHandler &io_handler, std::string &line) {114}115 116const char *REPL::IOHandlerGetFixIndentationCharacters() {117 return (m_enable_auto_indent ? GetAutoIndentCharacters() : nullptr);118}119 120llvm::StringRef REPL::IOHandlerGetControlSequence(char ch) {121 static constexpr llvm::StringLiteral control_sequence(":quit\n");122 if (ch == 'd')123 return control_sequence;124 return {};125}126 127const char *REPL::IOHandlerGetCommandPrefix() { return ":"; }128 129const char *REPL::IOHandlerGetHelpPrologue() {130 return "\nThe REPL (Read-Eval-Print-Loop) acts like an interpreter. "131 "Valid statements, expressions, and declarations are immediately "132 "compiled and executed.\n\n"133 "The complete set of LLDB debugging commands are also available as "134 "described below.\n\nCommands "135 "must be prefixed with a colon at the REPL prompt (:quit for "136 "example.) Typing just a colon "137 "followed by return will switch to the LLDB prompt.\n\n"138 "Type “< path” to read in code from a text file “path”.\n\n";139}140 141bool REPL::IOHandlerIsInputComplete(IOHandler &io_handler, StringList &lines) {142 // Check for meta command143 const size_t num_lines = lines.GetSize();144 if (num_lines == 1) {145 const char *first_line = lines.GetStringAtIndex(0);146 if (first_line[0] == ':')147 return true; // Meta command is a single line where that starts with ':'148 }149 150 // Check if REPL input is done151 std::string source_string(lines.CopyList());152 return SourceIsComplete(source_string);153}154 155int REPL::CalculateActualIndentation(const StringList &lines) {156 std::string last_line = lines[lines.GetSize() - 1];157 158 int actual_indent = 0;159 for (char &ch : last_line) {160 if (ch != ' ')161 break;162 ++actual_indent;163 }164 165 return actual_indent;166}167 168int REPL::IOHandlerFixIndentation(IOHandler &io_handler,169 const StringList &lines,170 int cursor_position) {171 if (!m_enable_auto_indent)172 return 0;173 174 if (!lines.GetSize()) {175 return 0;176 }177 178 int tab_size = io_handler.GetDebugger().GetTabSize();179 180 lldb::offset_t desired_indent =181 GetDesiredIndentation(lines, cursor_position, tab_size);182 183 int actual_indent = REPL::CalculateActualIndentation(lines);184 185 if (desired_indent == LLDB_INVALID_OFFSET)186 return 0;187 188 return (int)desired_indent - actual_indent;189}190 191static bool ReadCode(const std::string &path, std::string &code,192 lldb::StreamFileSP &error_sp) {193 auto &fs = FileSystem::Instance();194 llvm::Twine pathTwine(path);195 if (!fs.Exists(pathTwine)) {196 error_sp->Printf("no such file at path '%s'\n", path.c_str());197 return false;198 }199 if (!fs.Readable(pathTwine)) {200 error_sp->Printf("could not read file at path '%s'\n", path.c_str());201 return false;202 }203 const size_t file_size = fs.GetByteSize(pathTwine);204 const size_t max_size = code.max_size();205 if (file_size > max_size) {206 error_sp->Printf("file at path '%s' too large: "207 "file_size = %zu, max_size = %zu\n",208 path.c_str(), file_size, max_size);209 return false;210 }211 auto data_sp = fs.CreateDataBuffer(pathTwine);212 if (data_sp == nullptr) {213 error_sp->Printf("could not create buffer for file at path '%s'\n",214 path.c_str());215 return false;216 }217 code.assign((const char *)data_sp->GetBytes(), data_sp->GetByteSize());218 return true;219}220 221void REPL::IOHandlerInputComplete(IOHandler &io_handler, std::string &code) {222 lldb::StreamFileSP output_sp = std::make_shared<StreamFile>(223 io_handler.GetOutputStreamFileSP()->GetUnlockedFileSP());224 lldb::StreamFileSP error_sp = std::make_shared<StreamFile>(225 io_handler.GetErrorStreamFileSP()->GetUnlockedFileSP());226 bool extra_line = false;227 bool did_quit = false;228 229 if (code.empty()) {230 m_code.AppendString("");231 static_cast<IOHandlerEditline &>(io_handler)232 .SetBaseLineNumber(m_code.GetSize() + 1);233 } else {234 Debugger &debugger = m_target.GetDebugger();235 CommandInterpreter &ci = debugger.GetCommandInterpreter();236 extra_line = ci.GetSpaceReplPrompts();237 238 ExecutionContext exe_ctx(m_target.GetProcessSP()239 ->GetThreadList()240 .GetSelectedThread()241 ->GetSelectedFrame(DoNoSelectMostRelevantFrame)242 .get());243 244 lldb::ProcessSP process_sp(exe_ctx.GetProcessSP());245 246 if (code[0] == ':') {247 // Meta command248 // Strip the ':'249 code.erase(0, 1);250 if (!llvm::StringRef(code).trim().empty()) {251 // "lldb" was followed by arguments, so just execute the command dump252 // the results253 254 // Turn off prompt on quit in case the user types ":quit"255 const bool saved_prompt_on_quit = ci.GetPromptOnQuit();256 if (saved_prompt_on_quit)257 ci.SetPromptOnQuit(false);258 259 // Execute the command260 CommandReturnObject result(debugger.GetUseColor());261 result.SetImmediateOutputStream(output_sp);262 result.SetImmediateErrorStream(error_sp);263 ci.HandleCommand(code.c_str(), eLazyBoolNo, result);264 265 if (saved_prompt_on_quit)266 ci.SetPromptOnQuit(true);267 268 if (result.GetStatus() == lldb::eReturnStatusQuit) {269 did_quit = true;270 io_handler.SetIsDone(true);271 if (debugger.CheckTopIOHandlerTypes(272 IOHandler::Type::REPL, IOHandler::Type::CommandInterpreter)) {273 // We typed "quit" or an alias to quit so we need to check if the274 // command interpreter is above us and tell it that it is done as275 // well so we don't drop back into the command interpreter if we276 // have already quit277 lldb::IOHandlerSP io_handler_sp(ci.GetIOHandler());278 if (io_handler_sp)279 io_handler_sp->SetIsDone(true);280 }281 }282 } else {283 // ":" was followed by no arguments, so push the LLDB command prompt284 if (debugger.CheckTopIOHandlerTypes(285 IOHandler::Type::REPL, IOHandler::Type::CommandInterpreter)) {286 // If the user wants to get back to the command interpreter and the287 // command interpreter is what launched the REPL, then just let the288 // REPL exit and fall back to the command interpreter.289 io_handler.SetIsDone(true);290 } else {291 // The REPL wasn't launched the by the command interpreter, it is the292 // base IOHandler, so we need to get the command interpreter and293 lldb::IOHandlerSP io_handler_sp(ci.GetIOHandler());294 if (io_handler_sp) {295 io_handler_sp->SetIsDone(false);296 debugger.RunIOHandlerAsync(ci.GetIOHandler());297 }298 }299 }300 } else {301 if (code[0] == '<') {302 // User wants to read code from a file.303 // Interpret rest of line as a literal path.304 auto path = llvm::StringRef(code.substr(1)).trim().str();305 if (!ReadCode(path, code, error_sp)) {306 return;307 }308 }309 310 // Unwind any expression we might have been running in case our REPL311 // expression crashed and the user was looking around312 if (m_dedicated_repl_mode) {313 Thread *thread = exe_ctx.GetThreadPtr();314 if (thread && thread->UnwindInnermostExpression().Success()) {315 thread->SetSelectedFrameByIndex(0, false);316 exe_ctx.SetFrameSP(317 thread->GetSelectedFrame(DoNoSelectMostRelevantFrame));318 }319 }320 321 const bool colorize_err = error_sp->GetFile().GetIsTerminalWithColors();322 323 EvaluateExpressionOptions expr_options = m_expr_options;324 expr_options.SetCoerceToId(m_varobj_options.use_object_desc);325 expr_options.SetKeepInMemory(true);326 expr_options.SetUseDynamic(m_varobj_options.use_dynamic);327 expr_options.SetGenerateDebugInfo(true);328 expr_options.SetREPLEnabled(true);329 expr_options.SetColorizeErrors(colorize_err);330 expr_options.SetPoundLine(m_repl_source_path.c_str(),331 m_code.GetSize() + 1);332 333 expr_options.SetLanguage(GetLanguage());334 335 PersistentExpressionState *persistent_state =336 m_target.GetPersistentExpressionStateForLanguage(GetLanguage());337 if (!persistent_state)338 return;339 340 const size_t var_count_before = persistent_state->GetSize();341 342 const char *expr_prefix = nullptr;343 lldb::ValueObjectSP result_valobj_sp;344 lldb::ExpressionResults execution_results = UserExpression::Evaluate(345 exe_ctx, expr_options, code.c_str(), expr_prefix, result_valobj_sp);346 Status error;347 if (llvm::Error err = OnExpressionEvaluated(exe_ctx, code, expr_options,348 execution_results,349 result_valobj_sp, error)) {350 *error_sp << llvm::toString(std::move(err)) << "\n";351 } else if (process_sp && process_sp->IsAlive()) {352 bool add_to_code = true;353 bool handled = false;354 if (result_valobj_sp) {355 lldb::Format format = m_format_options.GetFormat();356 357 if (result_valobj_sp->GetError().Success()) {358 handled |= PrintOneVariable(debugger, output_sp, result_valobj_sp);359 } else if (result_valobj_sp->GetError().GetError() ==360 UserExpression::kNoResult) {361 if (format != lldb::eFormatVoid && debugger.GetNotifyVoid()) {362 error_sp->PutCString("(void)\n");363 handled = true;364 }365 }366 }367 368 if (debugger.GetPrintDecls()) {369 for (size_t vi = var_count_before, ve = persistent_state->GetSize();370 vi != ve; ++vi) {371 lldb::ExpressionVariableSP persistent_var_sp =372 persistent_state->GetVariableAtIndex(vi);373 lldb::ValueObjectSP valobj_sp = persistent_var_sp->GetValueObject();374 375 PrintOneVariable(debugger, output_sp, valobj_sp,376 persistent_var_sp.get());377 }378 }379 380 if (!handled) {381 bool useColors = error_sp->GetFile().GetIsTerminalWithColors();382 switch (execution_results) {383 case lldb::eExpressionSetupError:384 case lldb::eExpressionParseError:385 add_to_code = false;386 [[fallthrough]];387 case lldb::eExpressionDiscarded:388 error_sp->Printf("%s\n", error.AsCString());389 break;390 391 case lldb::eExpressionCompleted:392 break;393 case lldb::eExpressionInterrupted:394 if (useColors) {395 error_sp->Printf(ANSI_ESCAPE1(ANSI_FG_COLOR_RED));396 error_sp->Printf(ANSI_ESCAPE1(ANSI_CTRL_BOLD));397 }398 error_sp->Printf("Execution interrupted. ");399 if (useColors)400 error_sp->Printf(ANSI_ESCAPE1(ANSI_CTRL_NORMAL));401 error_sp->Printf("Enter code to recover and continue.\nEnter LLDB "402 "commands to investigate (type :help for "403 "assistance.)\n");404 break;405 406 case lldb::eExpressionHitBreakpoint:407 // Breakpoint was hit, drop into LLDB command interpreter408 if (useColors) {409 error_sp->Printf(ANSI_ESCAPE1(ANSI_FG_COLOR_RED));410 error_sp->Printf(ANSI_ESCAPE1(ANSI_CTRL_BOLD));411 }412 output_sp->Printf("Execution stopped at breakpoint. ");413 if (useColors)414 error_sp->Printf(ANSI_ESCAPE1(ANSI_CTRL_NORMAL));415 output_sp->Printf("Enter LLDB commands to investigate (type help "416 "for assistance.)\n");417 {418 lldb::IOHandlerSP io_handler_sp(ci.GetIOHandler());419 if (io_handler_sp) {420 io_handler_sp->SetIsDone(false);421 debugger.RunIOHandlerAsync(ci.GetIOHandler());422 }423 }424 break;425 426 case lldb::eExpressionTimedOut:427 error_sp->Printf("error: timeout\n");428 if (error.AsCString())429 error_sp->Printf("error: %s\n", error.AsCString());430 break;431 case lldb::eExpressionResultUnavailable:432 // Shoulnd't happen???433 error_sp->Printf("error: could not fetch result -- %s\n",434 error.AsCString());435 break;436 case lldb::eExpressionStoppedForDebug:437 // Shoulnd't happen???438 error_sp->Printf("error: stopped for debug -- %s\n",439 error.AsCString());440 break;441 case lldb::eExpressionThreadVanished:442 // Shoulnd't happen???443 error_sp->Printf("error: expression thread vanished -- %s\n",444 error.AsCString());445 break;446 }447 }448 449 if (add_to_code) {450 const uint32_t new_default_line = m_code.GetSize() + 1;451 452 m_code.SplitIntoLines(code);453 454 // Update our code on disk455 if (!m_repl_source_path.empty()) {456 auto file = FileSystem::Instance().Open(457 FileSpec(m_repl_source_path),458 File::eOpenOptionWriteOnly | File::eOpenOptionTruncate |459 File::eOpenOptionCanCreate,460 lldb::eFilePermissionsFileDefault);461 if (file) {462 std::string code(m_code.CopyList());463 code.append(1, '\n');464 size_t bytes_written = code.size();465 file.get()->Write(code.c_str(), bytes_written);466 file.get()->Close();467 } else {468 std::string message = llvm::toString(file.takeError());469 error_sp->Printf("error: couldn't open %s: %s\n",470 m_repl_source_path.c_str(), message.c_str());471 }472 473 // Now set the default file and line to the REPL source file474 m_target.GetSourceManager().SetDefaultFileAndLine(475 std::make_shared<SupportFile>(FileSpec(m_repl_source_path)),476 new_default_line);477 }478 static_cast<IOHandlerEditline &>(io_handler)479 .SetBaseLineNumber(m_code.GetSize() + 1);480 }481 if (extra_line) {482 output_sp->Printf("\n");483 }484 }485 }486 487 // Don't complain about the REPL process going away if we are in the488 // process of quitting.489 if (!did_quit && (!process_sp || !process_sp->IsAlive())) {490 error_sp->Printf(491 "error: REPL process is no longer alive, exiting REPL\n");492 io_handler.SetIsDone(true);493 }494 }495}496 497void REPL::IOHandlerComplete(IOHandler &io_handler,498 CompletionRequest &request) {499 // Complete an LLDB command if the first character is a colon...500 if (request.GetRawLine().starts_with(":")) {501 Debugger &debugger = m_target.GetDebugger();502 503 // auto complete LLDB commands504 llvm::StringRef new_line = request.GetRawLine().drop_front();505 CompletionResult sub_result;506 CompletionRequest sub_request(new_line, request.GetRawCursorPos() - 1,507 sub_result);508 debugger.GetCommandInterpreter().HandleCompletion(sub_request);509 StringList matches, descriptions;510 sub_result.GetMatches(matches);511 // Prepend command prefix that was excluded in the completion request.512 if (request.GetCursorIndex() == 0)513 for (auto &match : matches)514 match.insert(0, 1, ':');515 sub_result.GetDescriptions(descriptions);516 request.AddCompletions(matches, descriptions);517 return;518 }519 520 // Strip spaces from the line and see if we had only spaces521 if (request.GetRawLine().trim().empty()) {522 // Only spaces on this line, so just indent523 request.AddCompletion(m_indent_str);524 return;525 }526 527 std::string current_code;528 current_code.append(m_code.CopyList());529 530 IOHandlerEditline &editline = static_cast<IOHandlerEditline &>(io_handler);531 StringList current_lines = editline.GetCurrentLines();532 const uint32_t current_line_idx = editline.GetCurrentLineIndex();533 534 if (current_line_idx < current_lines.GetSize()) {535 for (uint32_t i = 0; i < current_line_idx; ++i) {536 const char *line_cstr = current_lines.GetStringAtIndex(i);537 if (line_cstr) {538 current_code.append("\n");539 current_code.append(line_cstr);540 }541 }542 }543 544 current_code.append("\n");545 current_code += request.GetRawLine();546 547 CompleteCode(current_code, request);548}549 550bool QuitCommandOverrideCallback(void *baton, const char **argv) {551 Target *target = (Target *)baton;552 lldb::ProcessSP process_sp(target->GetProcessSP());553 if (process_sp) {554 process_sp->Destroy(false);555 process_sp->GetTarget().GetDebugger().ClearIOHandlers();556 }557 return false;558}559 560Status REPL::RunLoop() {561 Status error;562 563 error = DoInitialization();564 m_repl_source_path = GetSourcePath();565 566 if (!error.Success())567 return error;568 569 Debugger &debugger = m_target.GetDebugger();570 571 lldb::IOHandlerSP io_handler_sp(GetIOHandler());572 573 std::optional<SourceManager::SupportFileAndLine> default_file_line;574 575 if (!m_repl_source_path.empty()) {576 // Save the current default file and line577 default_file_line = m_target.GetSourceManager().GetDefaultFileAndLine();578 }579 580 debugger.RunIOHandlerAsync(io_handler_sp);581 582 // Check if we are in dedicated REPL mode where LLDB was start with the "--583 // repl" option from the command line. Currently we know this by checking if584 // the debugger already has a IOHandler thread.585 if (!debugger.HasIOHandlerThread()) {586 // The debugger doesn't have an existing IOHandler thread, so this must be587 // dedicated REPL mode...588 m_dedicated_repl_mode = true;589 debugger.StartIOHandlerThread();590 llvm::StringRef command_name_str("quit");591 CommandObject *cmd_obj =592 debugger.GetCommandInterpreter().GetCommandObjectForCommand(593 command_name_str);594 if (cmd_obj) {595 assert(command_name_str.empty());596 cmd_obj->SetOverrideCallback(QuitCommandOverrideCallback, &m_target);597 }598 }599 600 // Wait for the REPL command interpreter to get popped601 io_handler_sp->WaitForPop();602 603 if (m_dedicated_repl_mode) {604 // If we were in dedicated REPL mode we would have started the IOHandler605 // thread, and we should kill our process606 lldb::ProcessSP process_sp = m_target.GetProcessSP();607 if (process_sp && process_sp->IsAlive())608 process_sp->Destroy(false);609 610 // Wait for the IO handler thread to exit (TODO: don't do this if the IO611 // handler thread already exists...)612 debugger.JoinIOHandlerThread();613 }614 615 // Restore the default file and line616 if (default_file_line)617 m_target.GetSourceManager().SetDefaultFileAndLine(618 default_file_line->support_file_nsp, default_file_line->line);619 return error;620}621