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