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 extra_line = 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 |
Definitions
- ID
- REPL
- ~REPL
- Create
- GetSourcePath
- GetIOHandler
- IOHandlerActivated
- IOHandlerInterrupt
- IOHandlerInputInterrupted
- IOHandlerGetFixIndentationCharacters
- IOHandlerGetControlSequence
- IOHandlerGetCommandPrefix
- IOHandlerGetHelpPrologue
- IOHandlerIsInputComplete
- CalculateActualIndentation
- IOHandlerFixIndentation
- ReadCode
- IOHandlerInputComplete
- IOHandlerComplete
- QuitCommandOverrideCallback
Improve your Profiling and Debugging skills
Find out more