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
23using namespace lldb_private;
24
25char REPL::ID;
26
27REPL::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
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: 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(component: 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 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
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 lldb::StreamFileSP error_sp(io_handler.GetErrorStreamFileSP());
107 error_sp->Printf(format: "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 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
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(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
221void REPL::IOHandlerInputComplete(IOHandler &io_handler, std::string &code) {
222 lldb::StreamFileSP output_sp(io_handler.GetOutputStreamFileSP());
223 lldb::StreamFileSP error_sp(io_handler.GetErrorStreamFileSP());
224 bool extra_line = false;
225 bool did_quit = false;
226
227 if (code.empty()) {
228 m_code.AppendString(str: "");
229 static_cast<IOHandlerEditline &>(io_handler)
230 .SetBaseLineNumber(m_code.GetSize() + 1);
231 } else {
232 Debugger &debugger = m_target.GetDebugger();
233 CommandInterpreter &ci = debugger.GetCommandInterpreter();
234 extra_line = ci.GetSpaceReplPrompts();
235
236 ExecutionContext exe_ctx(m_target.GetProcessSP()
237 ->GetThreadList()
238 .GetSelectedThread()
239 ->GetSelectedFrame(select_most_relevant: DoNoSelectMostRelevantFrame)
240 .get());
241
242 lldb::ProcessSP process_sp(exe_ctx.GetProcessSP());
243
244 if (code[0] == ':') {
245 // Meta command
246 // Strip the ':'
247 code.erase(pos: 0, n: 1);
248 if (!llvm::StringRef(code).trim().empty()) {
249 // "lldb" was followed by arguments, so just execute the command dump
250 // the results
251
252 // Turn off prompt on quit in case the user types ":quit"
253 const bool saved_prompt_on_quit = ci.GetPromptOnQuit();
254 if (saved_prompt_on_quit)
255 ci.SetPromptOnQuit(false);
256
257 // Execute the command
258 CommandReturnObject result(debugger.GetUseColor());
259 result.SetImmediateOutputStream(output_sp);
260 result.SetImmediateErrorStream(error_sp);
261 ci.HandleCommand(command_line: code.c_str(), add_to_history: eLazyBoolNo, result);
262
263 if (saved_prompt_on_quit)
264 ci.SetPromptOnQuit(true);
265
266 if (result.GetStatus() == lldb::eReturnStatusQuit) {
267 did_quit = true;
268 io_handler.SetIsDone(true);
269 if (debugger.CheckTopIOHandlerTypes(
270 top_type: IOHandler::Type::REPL, second_top_type: IOHandler::Type::CommandInterpreter)) {
271 // We typed "quit" or an alias to quit so we need to check if the
272 // command interpreter is above us and tell it that it is done as
273 // well so we don't drop back into the command interpreter if we
274 // have already quit
275 lldb::IOHandlerSP io_handler_sp(ci.GetIOHandler());
276 if (io_handler_sp)
277 io_handler_sp->SetIsDone(true);
278 }
279 }
280 } else {
281 // ":" was followed by no arguments, so push the LLDB command prompt
282 if (debugger.CheckTopIOHandlerTypes(
283 top_type: IOHandler::Type::REPL, second_top_type: IOHandler::Type::CommandInterpreter)) {
284 // If the user wants to get back to the command interpreter and the
285 // command interpreter is what launched the REPL, then just let the
286 // REPL exit and fall back to the command interpreter.
287 io_handler.SetIsDone(true);
288 } else {
289 // The REPL wasn't launched the by the command interpreter, it is the
290 // base IOHandler, so we need to get the command interpreter and
291 lldb::IOHandlerSP io_handler_sp(ci.GetIOHandler());
292 if (io_handler_sp) {
293 io_handler_sp->SetIsDone(false);
294 debugger.RunIOHandlerAsync(reader_sp: ci.GetIOHandler());
295 }
296 }
297 }
298 } else {
299 if (code[0] == '<') {
300 // User wants to read code from a file.
301 // Interpret rest of line as a literal path.
302 auto path = llvm::StringRef(code.substr(pos: 1)).trim().str();
303 if (!ReadCode(path, code, error_sp)) {
304 return;
305 }
306 }
307
308 // Unwind any expression we might have been running in case our REPL
309 // expression crashed and the user was looking around
310 if (m_dedicated_repl_mode) {
311 Thread *thread = exe_ctx.GetThreadPtr();
312 if (thread && thread->UnwindInnermostExpression().Success()) {
313 thread->SetSelectedFrameByIndex(frame_idx: 0, broadcast: false);
314 exe_ctx.SetFrameSP(
315 thread->GetSelectedFrame(select_most_relevant: DoNoSelectMostRelevantFrame));
316 }
317 }
318
319 const bool colorize_err = error_sp->GetFile().GetIsTerminalWithColors();
320
321 EvaluateExpressionOptions expr_options = m_expr_options;
322 expr_options.SetCoerceToId(m_varobj_options.use_objc);
323 expr_options.SetKeepInMemory(true);
324 expr_options.SetUseDynamic(m_varobj_options.use_dynamic);
325 expr_options.SetGenerateDebugInfo(true);
326 expr_options.SetREPLEnabled(true);
327 expr_options.SetColorizeErrors(colorize_err);
328 expr_options.SetPoundLine(path: m_repl_source_path.c_str(),
329 line: m_code.GetSize() + 1);
330
331 expr_options.SetLanguage(GetLanguage());
332
333 PersistentExpressionState *persistent_state =
334 m_target.GetPersistentExpressionStateForLanguage(language: GetLanguage());
335 if (!persistent_state)
336 return;
337
338 const size_t var_count_before = persistent_state->GetSize();
339
340 const char *expr_prefix = nullptr;
341 lldb::ValueObjectSP result_valobj_sp;
342 Status error;
343 lldb::ExpressionResults execution_results =
344 UserExpression::Evaluate(exe_ctx, options: expr_options, expr_cstr: code.c_str(),
345 expr_prefix, result_valobj_sp, error,
346 fixed_expression: nullptr); // fixed expression
347
348 if (llvm::Error err = OnExpressionEvaluated(exe_ctx, code, expr_options,
349 execution_results,
350 result_valobj_sp, error)) {
351 *error_sp << llvm::toString(E: std::move(err)) << "\n";
352 } else if (process_sp && process_sp->IsAlive()) {
353 bool add_to_code = true;
354 bool handled = false;
355 if (result_valobj_sp) {
356 lldb::Format format = m_format_options.GetFormat();
357
358 if (result_valobj_sp->GetError().Success()) {
359 handled |= PrintOneVariable(debugger, output_sp, valobj_sp&: result_valobj_sp);
360 } else if (result_valobj_sp->GetError().GetError() ==
361 UserExpression::kNoResult) {
362 if (format != lldb::eFormatVoid && debugger.GetNotifyVoid()) {
363 error_sp->PutCString(cstr: "(void)\n");
364 handled = true;
365 }
366 }
367 }
368
369 if (debugger.GetPrintDecls()) {
370 for (size_t vi = var_count_before, ve = persistent_state->GetSize();
371 vi != ve; ++vi) {
372 lldb::ExpressionVariableSP persistent_var_sp =
373 persistent_state->GetVariableAtIndex(index: vi);
374 lldb::ValueObjectSP valobj_sp = persistent_var_sp->GetValueObject();
375
376 PrintOneVariable(debugger, output_sp, valobj_sp,
377 var: persistent_var_sp.get());
378 }
379 }
380
381 if (!handled) {
382 bool useColors = error_sp->GetFile().GetIsTerminalWithColors();
383 switch (execution_results) {
384 case lldb::eExpressionSetupError:
385 case lldb::eExpressionParseError:
386 add_to_code = false;
387 [[fallthrough]];
388 case lldb::eExpressionDiscarded:
389 error_sp->Printf(format: "%s\n", error.AsCString());
390 break;
391
392 case lldb::eExpressionCompleted:
393 break;
394 case lldb::eExpressionInterrupted:
395 if (useColors) {
396 error_sp->Printf(ANSI_ESCAPE1(ANSI_FG_COLOR_RED));
397 error_sp->Printf(ANSI_ESCAPE1(ANSI_CTRL_BOLD));
398 }
399 error_sp->Printf(format: "Execution interrupted. ");
400 if (useColors)
401 error_sp->Printf(ANSI_ESCAPE1(ANSI_CTRL_NORMAL));
402 error_sp->Printf(format: "Enter code to recover and continue.\nEnter LLDB "
403 "commands to investigate (type :help for "
404 "assistance.)\n");
405 break;
406
407 case lldb::eExpressionHitBreakpoint:
408 // Breakpoint was hit, drop into LLDB command interpreter
409 if (useColors) {
410 error_sp->Printf(ANSI_ESCAPE1(ANSI_FG_COLOR_RED));
411 error_sp->Printf(ANSI_ESCAPE1(ANSI_CTRL_BOLD));
412 }
413 output_sp->Printf(format: "Execution stopped at breakpoint. ");
414 if (useColors)
415 error_sp->Printf(ANSI_ESCAPE1(ANSI_CTRL_NORMAL));
416 output_sp->Printf(format: "Enter LLDB commands to investigate (type help "
417 "for assistance.)\n");
418 {
419 lldb::IOHandlerSP io_handler_sp(ci.GetIOHandler());
420 if (io_handler_sp) {
421 io_handler_sp->SetIsDone(false);
422 debugger.RunIOHandlerAsync(reader_sp: ci.GetIOHandler());
423 }
424 }
425 break;
426
427 case lldb::eExpressionTimedOut:
428 error_sp->Printf(format: "error: timeout\n");
429 if (error.AsCString())
430 error_sp->Printf(format: "error: %s\n", error.AsCString());
431 break;
432 case lldb::eExpressionResultUnavailable:
433 // Shoulnd't happen???
434 error_sp->Printf(format: "error: could not fetch result -- %s\n",
435 error.AsCString());
436 break;
437 case lldb::eExpressionStoppedForDebug:
438 // Shoulnd't happen???
439 error_sp->Printf(format: "error: stopped for debug -- %s\n",
440 error.AsCString());
441 break;
442 case lldb::eExpressionThreadVanished:
443 // Shoulnd't happen???
444 error_sp->Printf(format: "error: expression thread vanished -- %s\n",
445 error.AsCString());
446 break;
447 }
448 }
449
450 if (add_to_code) {
451 const uint32_t new_default_line = m_code.GetSize() + 1;
452
453 m_code.SplitIntoLines(lines: code);
454
455 // Update our code on disk
456 if (!m_repl_source_path.empty()) {
457 auto file = FileSystem::Instance().Open(
458 file_spec: FileSpec(m_repl_source_path),
459 options: File::eOpenOptionWriteOnly | File::eOpenOptionTruncate |
460 File::eOpenOptionCanCreate,
461 permissions: lldb::eFilePermissionsFileDefault);
462 if (file) {
463 std::string code(m_code.CopyList());
464 code.append(n: 1, c: '\n');
465 size_t bytes_written = code.size();
466 file.get()->Write(buf: code.c_str(), num_bytes&: bytes_written);
467 file.get()->Close();
468 } else {
469 std::string message = llvm::toString(E: file.takeError());
470 error_sp->Printf(format: "error: couldn't open %s: %s\n",
471 m_repl_source_path.c_str(), message.c_str());
472 }
473
474 // Now set the default file and line to the REPL source file
475 m_target.GetSourceManager().SetDefaultFileAndLine(
476 file_spec: FileSpec(m_repl_source_path), 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
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(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
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(force_kill: 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 FileSpec save_default_file;
574 uint32_t save_default_line = 0;
575
576 if (!m_repl_source_path.empty()) {
577 // Save the current default file and line
578 m_target.GetSourceManager().GetDefaultFileAndLine(file_spec&: save_default_file,
579 line&: save_default_line);
580 }
581
582 debugger.RunIOHandlerAsync(reader_sp: io_handler_sp);
583
584 // Check if we are in dedicated REPL mode where LLDB was start with the "--
585 // repl" option from the command line. Currently we know this by checking if
586 // the debugger already has a IOHandler thread.
587 if (!debugger.HasIOHandlerThread()) {
588 // The debugger doesn't have an existing IOHandler thread, so this must be
589 // dedicated REPL mode...
590 m_dedicated_repl_mode = true;
591 debugger.StartIOHandlerThread();
592 llvm::StringRef command_name_str("quit");
593 CommandObject *cmd_obj =
594 debugger.GetCommandInterpreter().GetCommandObjectForCommand(
595 command_line&: command_name_str);
596 if (cmd_obj) {
597 assert(command_name_str.empty());
598 cmd_obj->SetOverrideCallback(callback: QuitCommandOverrideCallback, baton: &m_target);
599 }
600 }
601
602 // Wait for the REPL command interpreter to get popped
603 io_handler_sp->WaitForPop();
604
605 if (m_dedicated_repl_mode) {
606 // If we were in dedicated REPL mode we would have started the IOHandler
607 // thread, and we should kill our process
608 lldb::ProcessSP process_sp = m_target.GetProcessSP();
609 if (process_sp && process_sp->IsAlive())
610 process_sp->Destroy(force_kill: false);
611
612 // Wait for the IO handler thread to exit (TODO: don't do this if the IO
613 // handler thread already exists...)
614 debugger.JoinIOHandlerThread();
615 }
616
617 // Restore the default file and line
618 if (save_default_file && save_default_line != 0)
619 m_target.GetSourceManager().SetDefaultFileAndLine(file_spec: save_default_file,
620 line: save_default_line);
621 return error;
622}
623

source code of lldb/source/Expression/REPL.cpp