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 | 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 | |
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(io_handler.GetOutputStreamFileSP()); |
223 | lldb::StreamFileSP error_sp(io_handler.GetErrorStreamFileSP()); |
224 | bool = 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 | |
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 | 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 | |