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