| //===-- REPL.cpp ------------------------------------------------*- C++ -*-===// |
| // |
| // The LLVM Compiler Infrastructure |
| // |
| // This file is distributed under the University of Illinois Open Source |
| // License. See LICENSE.TXT for details. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| // C Includes |
| // C++ Includes |
| // Other libraries and framework includes |
| // Project includes |
| #include "lldb/Expression/REPL.h" |
| #include "lldb/Core/Debugger.h" |
| #include "lldb/Core/PluginManager.h" |
| #include "lldb/Core/StreamFile.h" |
| #include "lldb/Expression/ExpressionVariable.h" |
| #include "lldb/Expression/UserExpression.h" |
| #include "lldb/Host/HostInfo.h" |
| #include "lldb/Interpreter/CommandInterpreter.h" |
| #include "lldb/Interpreter/CommandReturnObject.h" |
| #include "lldb/Target/Target.h" |
| #include "lldb/Target/Thread.h" |
| #include "lldb/Utility/AnsiTerminal.h" |
| |
| using namespace lldb_private; |
| |
| REPL::REPL(LLVMCastKind kind, Target &target) : m_target(target), m_kind(kind) { |
| // Make sure all option values have sane defaults |
| Debugger &debugger = m_target.GetDebugger(); |
| auto exe_ctx = debugger.GetCommandInterpreter().GetExecutionContext(); |
| m_format_options.OptionParsingStarting(&exe_ctx); |
| m_varobj_options.OptionParsingStarting(&exe_ctx); |
| m_command_options.OptionParsingStarting(&exe_ctx); |
| |
| // Default certain settings for REPL regardless of the global settings. |
| m_command_options.unwind_on_error = false; |
| m_command_options.ignore_breakpoints = false; |
| m_command_options.debug = false; |
| } |
| |
| 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++)) { |
| ret = (*create_instance)(err, language, debugger, target, repl_options); |
| if (ret) { |
| break; |
| } |
| } |
| |
| return ret; |
| } |
| |
| std::string REPL::GetSourcePath() { |
| ConstString file_basename = GetSourceFileBasename(); |
| FileSpec tmpdir_file_spec = HostInfo::GetProcessTempDir(); |
| if (tmpdir_file_spec) { |
| tmpdir_file_spec.GetFilename().SetCString(file_basename.AsCString()); |
| m_repl_source_path = tmpdir_file_spec.GetPath(); |
| } else { |
| tmpdir_file_spec = FileSpec("/tmp", false); |
| tmpdir_file_spec.AppendPathComponent(file_basename.AsCString()); |
| } |
| |
| return tmpdir_file_spec.GetPath(); |
| } |
| |
| lldb::IOHandlerSP REPL::GetIOHandler() { |
| if (!m_io_handler_sp) { |
| Debugger &debugger = m_target.GetDebugger(); |
| m_io_handler_sp.reset( |
| new 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) { |
| lldb::ProcessSP process_sp = m_target.GetProcessSP(); |
| if (process_sp && process_sp->IsAlive()) |
| return; |
| lldb::StreamFileSP error_sp(io_handler.GetErrorStreamFile()); |
| 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); |
| } |
| |
| ConstString REPL::IOHandlerGetControlSequence(char ch) { |
| if (ch == 'd') |
| return ConstString(":quit\n"); |
| return ConstString(); |
| } |
| |
| 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. Commands " |
| "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"; |
| } |
| |
| 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; |
| } |
| |
| void REPL::IOHandlerInputComplete(IOHandler &io_handler, std::string &code) { |
| lldb::StreamFileSP output_sp(io_handler.GetOutputStreamFile()); |
| lldb::StreamFileSP error_sp(io_handler.GetErrorStreamFile()); |
| 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() |
| .get()); |
| |
| lldb::ProcessSP process_sp(exe_ctx.GetProcessSP()); |
| |
| if (code[0] == ':') { |
| // Meta command |
| // Strip the ':' |
| code.erase(0, 1); |
| if (Args::StripSpaces(code)) { |
| // "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; |
| 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.PushIOHandler(ci.GetIOHandler()); |
| } |
| } |
| } |
| } else { |
| // 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()); |
| } |
| } |
| |
| const bool colorize_err = error_sp->GetFile().GetIsTerminalWithColors(); |
| |
| EvaluateExpressionOptions expr_options; |
| expr_options.SetCoerceToId(m_varobj_options.use_objc); |
| expr_options.SetUnwindOnError(m_command_options.unwind_on_error); |
| expr_options.SetIgnoreBreakpoints(m_command_options.ignore_breakpoints); |
| expr_options.SetKeepInMemory(true); |
| expr_options.SetUseDynamic(m_varobj_options.use_dynamic); |
| expr_options.SetTryAllThreads(m_command_options.try_all_threads); |
| 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); |
| if (m_command_options.timeout > 0) |
| expr_options.SetTimeout(std::chrono::microseconds(m_command_options.timeout)); |
| else |
| expr_options.SetTimeout(llvm::None); |
| |
| expr_options.SetLanguage(GetLanguage()); |
| |
| PersistentExpressionState *persistent_state = |
| m_target.GetPersistentExpressionStateForLanguage(GetLanguage()); |
| |
| const size_t var_count_before = persistent_state->GetSize(); |
| |
| const char *expr_prefix = nullptr; |
| lldb::ValueObjectSP result_valobj_sp; |
| Status error; |
| lldb::ModuleSP jit_module_sp; |
| lldb::ExpressionResults execution_results = |
| UserExpression::Evaluate(exe_ctx, expr_options, code.c_str(), |
| expr_prefix, result_valobj_sp, error, |
| 0, // Line offset |
| nullptr, // Fixed Expression |
| &jit_module_sp); |
| |
| // CommandInterpreter &ci = debugger.GetCommandInterpreter(); |
| |
| 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; |
| LLVM_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.PushIOHandler(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; |
| } |
| } |
| |
| 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()) { |
| lldb_private::File file(m_repl_source_path.c_str(), |
| File::eOpenOptionWrite | |
| File::eOpenOptionTruncate | |
| File::eOpenOptionCanCreate, |
| lldb::eFilePermissionsFileDefault); |
| std::string code(m_code.CopyList()); |
| code.append(1, '\n'); |
| size_t bytes_written = code.size(); |
| file.Write(code.c_str(), bytes_written); |
| file.Close(); |
| |
| // Now set the default file and line to the REPL source file |
| m_target.GetSourceManager().SetDefaultFileAndLine( |
| FileSpec(m_repl_source_path, false), new_default_line); |
| } |
| static_cast<IOHandlerEditline &>(io_handler) |
| .SetBaseLineNumber(m_code.GetSize() + 1); |
| } |
| if (extra_line) { |
| fprintf(output_sp->GetFile().GetStream(), "\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); |
| } |
| } |
| } |
| |
| int REPL::IOHandlerComplete(IOHandler &io_handler, const char *current_line, |
| const char *cursor, const char *last_char, |
| int skip_first_n_matches, int max_matches, |
| StringList &matches) { |
| matches.Clear(); |
| |
| llvm::StringRef line(current_line, cursor - current_line); |
| |
| // Complete an LLDB command if the first character is a colon... |
| if (!line.empty() && line[0] == ':') { |
| Debugger &debugger = m_target.GetDebugger(); |
| |
| // auto complete LLDB commands |
| const char *lldb_current_line = line.substr(1).data(); |
| return debugger.GetCommandInterpreter().HandleCompletion( |
| lldb_current_line, cursor, last_char, skip_first_n_matches, max_matches, |
| matches); |
| } |
| |
| // Strip spaces from the line and see if we had only spaces |
| line = line.ltrim(); |
| if (line.empty()) { |
| // Only spaces on this line, so just indent |
| matches.AppendString(m_indent_str); |
| return 1; |
| } |
| |
| std::string current_code; |
| current_code.append(m_code.CopyList()); |
| |
| IOHandlerEditline &editline = static_cast<IOHandlerEditline &>(io_handler); |
| const StringList *current_lines = editline.GetCurrentLines(); |
| if (current_lines) { |
| 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); |
| } |
| } |
| } |
| } |
| |
| if (cursor > current_line) { |
| current_code.append("\n"); |
| current_code.append(current_line, cursor - current_line); |
| } |
| |
| return CompleteCode(current_code, matches); |
| } |
| |
| 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.PushIOHandler(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; |
| } |