| 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-exception |
| 6 | // |
| 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 | |
| 23 | using namespace lldb_private; |
| 24 | |
| 25 | char REPL::ID; |
| 26 | |
| 27 | REPL::REPL(Target &target) : m_target(target) { |
| 28 | // Make sure all option values have sane defaults |
| 29 | Debugger &debugger = m_target.GetDebugger(); |
| 30 | debugger.SetShowProgress(false); |
| 31 | auto exe_ctx = debugger.GetCommandInterpreter().GetExecutionContext(); |
| 32 | m_format_options.OptionParsingStarting(execution_context: &exe_ctx); |
| 33 | m_varobj_options.OptionParsingStarting(execution_context: &exe_ctx); |
| 34 | } |
| 35 | |
| 36 | REPL::~REPL() = default; |
| 37 | |
| 38 | lldb::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: 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 | |
| 59 | std::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(component: file_basename); |
| 68 | } |
| 69 | |
| 70 | return tmpdir_file_spec.GetPath(); |
| 71 | } |
| 72 | |
| 73 | lldb::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 | args&: debugger, args: IOHandler::Type::REPL, |
| 78 | args: "lldb-repl" , // Name of input reader for history |
| 79 | args: llvm::StringRef("> " ), // prompt |
| 80 | args: llvm::StringRef(". " ), // Continuation prompt |
| 81 | args: true, // Multi-line |
| 82 | args: true, // The REPL prompt is always colored |
| 83 | args: 1, // Line number |
| 84 | args&: *this); |
| 85 | |
| 86 | // Don't exit if CTRL+C is pressed |
| 87 | 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(n: debugger.GetTabSize(), c: ' '); |
| 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 | |
| 102 | void 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(format: "REPL requires a running target process.\n" ); |
| 108 | io_handler.SetIsDone(true); |
| 109 | } |
| 110 | |
| 111 | bool REPL::IOHandlerInterrupt(IOHandler &io_handler) { return false; } |
| 112 | |
| 113 | void REPL::IOHandlerInputInterrupted(IOHandler &io_handler, std::string &line) { |
| 114 | } |
| 115 | |
| 116 | const char *REPL::IOHandlerGetFixIndentationCharacters() { |
| 117 | return (m_enable_auto_indent ? GetAutoIndentCharacters() : nullptr); |
| 118 | } |
| 119 | |
| 120 | llvm::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 | |
| 127 | const char *REPL::IOHandlerGetCommandPrefix() { return ":" ; } |
| 128 | |
| 129 | const 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 | |
| 141 | bool REPL::IOHandlerIsInputComplete(IOHandler &io_handler, StringList &lines) { |
| 142 | // Check for meta command |
| 143 | const size_t num_lines = lines.GetSize(); |
| 144 | if (num_lines == 1) { |
| 145 | const char *first_line = lines.GetStringAtIndex(idx: 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 done |
| 151 | std::string source_string(lines.CopyList()); |
| 152 | return SourceIsComplete(source: source_string); |
| 153 | } |
| 154 | |
| 155 | int 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 | |
| 168 | int 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 | |
| 191 | static 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(path: pathTwine)) { |
| 196 | error_sp->Printf(format: "no such file at path '%s'\n" , path.c_str()); |
| 197 | return false; |
| 198 | } |
| 199 | if (!fs.Readable(path: pathTwine)) { |
| 200 | error_sp->Printf(format: "could not read file at path '%s'\n" , path.c_str()); |
| 201 | return false; |
| 202 | } |
| 203 | const size_t file_size = fs.GetByteSize(path: pathTwine); |
| 204 | const size_t max_size = code.max_size(); |
| 205 | if (file_size > max_size) { |
| 206 | error_sp->Printf(format: "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(path: pathTwine); |
| 212 | if (data_sp == nullptr) { |
| 213 | error_sp->Printf(format: "could not create buffer for file at path '%s'\n" , |
| 214 | path.c_str()); |
| 215 | return false; |
| 216 | } |
| 217 | code.assign(s: (const char *)data_sp->GetBytes(), n: data_sp->GetByteSize()); |
| 218 | return true; |
| 219 | } |
| 220 | |
| 221 | void REPL::IOHandlerInputComplete(IOHandler &io_handler, std::string &code) { |
| 222 | lldb::StreamFileSP output_sp = std::make_shared<StreamFile>( |
| 223 | args: io_handler.GetOutputStreamFileSP()->GetUnlockedFileSP()); |
| 224 | lldb::StreamFileSP error_sp = std::make_shared<StreamFile>( |
| 225 | args: io_handler.GetErrorStreamFileSP()->GetUnlockedFileSP()); |
| 226 | bool = false; |
| 227 | bool did_quit = false; |
| 228 | |
| 229 | if (code.empty()) { |
| 230 | m_code.AppendString(str: "" ); |
| 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(select_most_relevant: DoNoSelectMostRelevantFrame) |
| 242 | .get()); |
| 243 | |
| 244 | lldb::ProcessSP process_sp(exe_ctx.GetProcessSP()); |
| 245 | |
| 246 | if (code[0] == ':') { |
| 247 | // Meta command |
| 248 | // Strip the ':' |
| 249 | code.erase(pos: 0, n: 1); |
| 250 | if (!llvm::StringRef(code).trim().empty()) { |
| 251 | // "lldb" was followed by arguments, so just execute the command dump |
| 252 | // the results |
| 253 | |
| 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 command |
| 260 | CommandReturnObject result(debugger.GetUseColor()); |
| 261 | result.SetImmediateOutputStream(output_sp); |
| 262 | result.SetImmediateErrorStream(error_sp); |
| 263 | ci.HandleCommand(command_line: code.c_str(), add_to_history: 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 | top_type: IOHandler::Type::REPL, second_top_type: IOHandler::Type::CommandInterpreter)) { |
| 273 | // We typed "quit" or an alias to quit so we need to check if the |
| 274 | // command interpreter is above us and tell it that it is done as |
| 275 | // well so we don't drop back into the command interpreter if we |
| 276 | // have already quit |
| 277 | 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 prompt |
| 284 | if (debugger.CheckTopIOHandlerTypes( |
| 285 | top_type: IOHandler::Type::REPL, second_top_type: IOHandler::Type::CommandInterpreter)) { |
| 286 | // If the user wants to get back to the command interpreter and the |
| 287 | // command interpreter is what launched the REPL, then just let the |
| 288 | // 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 the |
| 292 | // base IOHandler, so we need to get the command interpreter and |
| 293 | lldb::IOHandlerSP io_handler_sp(ci.GetIOHandler()); |
| 294 | if (io_handler_sp) { |
| 295 | io_handler_sp->SetIsDone(false); |
| 296 | debugger.RunIOHandlerAsync(reader_sp: 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(pos: 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 REPL |
| 311 | // expression crashed and the user was looking around |
| 312 | if (m_dedicated_repl_mode) { |
| 313 | Thread *thread = exe_ctx.GetThreadPtr(); |
| 314 | if (thread && thread->UnwindInnermostExpression().Success()) { |
| 315 | thread->SetSelectedFrameByIndex(frame_idx: 0, broadcast: false); |
| 316 | exe_ctx.SetFrameSP( |
| 317 | thread->GetSelectedFrame(select_most_relevant: 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_objc); |
| 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(path: m_repl_source_path.c_str(), |
| 331 | line: m_code.GetSize() + 1); |
| 332 | |
| 333 | expr_options.SetLanguage(GetLanguage()); |
| 334 | |
| 335 | PersistentExpressionState *persistent_state = |
| 336 | m_target.GetPersistentExpressionStateForLanguage(language: 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, options: expr_options, expr_cstr: 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(E: 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, valobj_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(cstr: "(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(index: vi); |
| 373 | lldb::ValueObjectSP valobj_sp = persistent_var_sp->GetValueObject(); |
| 374 | |
| 375 | PrintOneVariable(debugger, output_sp, valobj_sp, |
| 376 | var: 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(format: "%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(format: "Execution interrupted. " ); |
| 399 | if (useColors) |
| 400 | error_sp->Printf(ANSI_ESCAPE1(ANSI_CTRL_NORMAL)); |
| 401 | error_sp->Printf(format: "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 interpreter |
| 408 | 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(format: "Execution stopped at breakpoint. " ); |
| 413 | if (useColors) |
| 414 | error_sp->Printf(ANSI_ESCAPE1(ANSI_CTRL_NORMAL)); |
| 415 | output_sp->Printf(format: "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(reader_sp: ci.GetIOHandler()); |
| 422 | } |
| 423 | } |
| 424 | break; |
| 425 | |
| 426 | case lldb::eExpressionTimedOut: |
| 427 | error_sp->Printf(format: "error: timeout\n" ); |
| 428 | if (error.AsCString()) |
| 429 | error_sp->Printf(format: "error: %s\n" , error.AsCString()); |
| 430 | break; |
| 431 | case lldb::eExpressionResultUnavailable: |
| 432 | // Shoulnd't happen??? |
| 433 | error_sp->Printf(format: "error: could not fetch result -- %s\n" , |
| 434 | error.AsCString()); |
| 435 | break; |
| 436 | case lldb::eExpressionStoppedForDebug: |
| 437 | // Shoulnd't happen??? |
| 438 | error_sp->Printf(format: "error: stopped for debug -- %s\n" , |
| 439 | error.AsCString()); |
| 440 | break; |
| 441 | case lldb::eExpressionThreadVanished: |
| 442 | // Shoulnd't happen??? |
| 443 | error_sp->Printf(format: "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(lines: code); |
| 453 | |
| 454 | // Update our code on disk |
| 455 | if (!m_repl_source_path.empty()) { |
| 456 | auto file = FileSystem::Instance().Open( |
| 457 | file_spec: FileSpec(m_repl_source_path), |
| 458 | options: File::eOpenOptionWriteOnly | File::eOpenOptionTruncate | |
| 459 | File::eOpenOptionCanCreate, |
| 460 | permissions: lldb::eFilePermissionsFileDefault); |
| 461 | if (file) { |
| 462 | std::string code(m_code.CopyList()); |
| 463 | code.append(n: 1, c: '\n'); |
| 464 | size_t bytes_written = code.size(); |
| 465 | file.get()->Write(buf: code.c_str(), num_bytes&: bytes_written); |
| 466 | file.get()->Close(); |
| 467 | } else { |
| 468 | std::string message = llvm::toString(E: file.takeError()); |
| 469 | error_sp->Printf(format: "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 file |
| 474 | m_target.GetSourceManager().SetDefaultFileAndLine( |
| 475 | support_file_sp: std::make_shared<SupportFile>(args: FileSpec(m_repl_source_path)), |
| 476 | line: 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(format: "\n" ); |
| 483 | } |
| 484 | } |
| 485 | } |
| 486 | |
| 487 | // Don't complain about the REPL process going away if we are in the |
| 488 | // process of quitting. |
| 489 | if (!did_quit && (!process_sp || !process_sp->IsAlive())) { |
| 490 | error_sp->Printf( |
| 491 | format: "error: REPL process is no longer alive, exiting REPL\n" ); |
| 492 | io_handler.SetIsDone(true); |
| 493 | } |
| 494 | } |
| 495 | } |
| 496 | |
| 497 | void 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(Prefix: ":" )) { |
| 501 | Debugger &debugger = m_target.GetDebugger(); |
| 502 | |
| 503 | // auto complete LLDB commands |
| 504 | 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(request&: 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(pos: 0, n: 1, c: ':'); |
| 515 | sub_result.GetDescriptions(descriptions); |
| 516 | request.AddCompletions(completions: matches, descriptions); |
| 517 | return; |
| 518 | } |
| 519 | |
| 520 | // Strip spaces from the line and see if we had only spaces |
| 521 | if (request.GetRawLine().trim().empty()) { |
| 522 | // Only spaces on this line, so just indent |
| 523 | request.AddCompletion(completion: m_indent_str); |
| 524 | return; |
| 525 | } |
| 526 | |
| 527 | std::string current_code; |
| 528 | current_code.append(str: 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(idx: i); |
| 537 | if (line_cstr) { |
| 538 | current_code.append(s: "\n" ); |
| 539 | current_code.append(s: line_cstr); |
| 540 | } |
| 541 | } |
| 542 | } |
| 543 | |
| 544 | current_code.append(s: "\n" ); |
| 545 | current_code += request.GetRawLine(); |
| 546 | |
| 547 | CompleteCode(current_code, request); |
| 548 | } |
| 549 | |
| 550 | bool 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(force_kill: false); |
| 555 | process_sp->GetTarget().GetDebugger().ClearIOHandlers(); |
| 556 | } |
| 557 | return false; |
| 558 | } |
| 559 | |
| 560 | Status 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 line |
| 577 | default_file_line = m_target.GetSourceManager().GetDefaultFileAndLine(); |
| 578 | } |
| 579 | |
| 580 | debugger.RunIOHandlerAsync(reader_sp: 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 if |
| 584 | // the debugger already has a IOHandler thread. |
| 585 | if (!debugger.HasIOHandlerThread()) { |
| 586 | // The debugger doesn't have an existing IOHandler thread, so this must be |
| 587 | // 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_line&: command_name_str); |
| 594 | if (cmd_obj) { |
| 595 | assert(command_name_str.empty()); |
| 596 | cmd_obj->SetOverrideCallback(callback: QuitCommandOverrideCallback, baton: &m_target); |
| 597 | } |
| 598 | } |
| 599 | |
| 600 | // Wait for the REPL command interpreter to get popped |
| 601 | io_handler_sp->WaitForPop(); |
| 602 | |
| 603 | if (m_dedicated_repl_mode) { |
| 604 | // If we were in dedicated REPL mode we would have started the IOHandler |
| 605 | // thread, and we should kill our process |
| 606 | lldb::ProcessSP process_sp = m_target.GetProcessSP(); |
| 607 | if (process_sp && process_sp->IsAlive()) |
| 608 | process_sp->Destroy(force_kill: false); |
| 609 | |
| 610 | // Wait for the IO handler thread to exit (TODO: don't do this if the IO |
| 611 | // handler thread already exists...) |
| 612 | debugger.JoinIOHandlerThread(); |
| 613 | } |
| 614 | |
| 615 | // Restore the default file and line |
| 616 | if (default_file_line) |
| 617 | m_target.GetSourceManager().SetDefaultFileAndLine( |
| 618 | support_file_sp: default_file_line->support_file_sp, line: default_file_line->line); |
| 619 | return error; |
| 620 | } |
| 621 | |