Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/contrib/llvm-project/lldb/source/Expression/REPL.cpp
39587 views
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(&exe_ctx);
33
m_varobj_options.OptionParsingStarting(&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++);
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(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
debugger, IOHandler::Type::REPL,
78
"lldb-repl", // Name of input reader for history
79
llvm::StringRef("> "), // prompt
80
llvm::StringRef(". "), // Continuation prompt
81
true, // Multi-line
82
true, // The REPL prompt is always colored
83
1, // Line number
84
*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(debugger.GetTabSize(), ' ');
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("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(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_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(pathTwine)) {
196
error_sp->Printf("no such file at path '%s'\n", path.c_str());
197
return false;
198
}
199
if (!fs.Readable(pathTwine)) {
200
error_sp->Printf("could not read file at path '%s'\n", path.c_str());
201
return false;
202
}
203
const size_t file_size = fs.GetByteSize(pathTwine);
204
const size_t max_size = code.max_size();
205
if (file_size > max_size) {
206
error_sp->Printf("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(pathTwine);
212
if (data_sp == nullptr) {
213
error_sp->Printf("could not create buffer for file at path '%s'\n",
214
path.c_str());
215
return false;
216
}
217
code.assign((const char *)data_sp->GetBytes(), 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 extra_line = false;
225
bool did_quit = false;
226
227
if (code.empty()) {
228
m_code.AppendString("");
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(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(0, 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(code.c_str(), 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
IOHandler::Type::REPL, 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
IOHandler::Type::REPL, 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(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(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(0, false);
314
exe_ctx.SetFrameSP(
315
thread->GetSelectedFrame(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(m_repl_source_path.c_str(),
329
m_code.GetSize() + 1);
330
331
expr_options.SetLanguage(GetLanguage());
332
333
PersistentExpressionState *persistent_state =
334
m_target.GetPersistentExpressionStateForLanguage(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, expr_options, code.c_str(),
345
expr_prefix, result_valobj_sp, error,
346
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(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, 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("(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(vi);
374
lldb::ValueObjectSP valobj_sp = persistent_var_sp->GetValueObject();
375
376
PrintOneVariable(debugger, output_sp, valobj_sp,
377
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("%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("Execution interrupted. ");
400
if (useColors)
401
error_sp->Printf(ANSI_ESCAPE1(ANSI_CTRL_NORMAL));
402
error_sp->Printf("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("Execution stopped at breakpoint. ");
414
if (useColors)
415
error_sp->Printf(ANSI_ESCAPE1(ANSI_CTRL_NORMAL));
416
output_sp->Printf("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(ci.GetIOHandler());
423
}
424
}
425
break;
426
427
case lldb::eExpressionTimedOut:
428
error_sp->Printf("error: timeout\n");
429
if (error.AsCString())
430
error_sp->Printf("error: %s\n", error.AsCString());
431
break;
432
case lldb::eExpressionResultUnavailable:
433
// Shoulnd't happen???
434
error_sp->Printf("error: could not fetch result -- %s\n",
435
error.AsCString());
436
break;
437
case lldb::eExpressionStoppedForDebug:
438
// Shoulnd't happen???
439
error_sp->Printf("error: stopped for debug -- %s\n",
440
error.AsCString());
441
break;
442
case lldb::eExpressionThreadVanished:
443
// Shoulnd't happen???
444
error_sp->Printf("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(code);
454
455
// Update our code on disk
456
if (!m_repl_source_path.empty()) {
457
auto file = FileSystem::Instance().Open(
458
FileSpec(m_repl_source_path),
459
File::eOpenOptionWriteOnly | File::eOpenOptionTruncate |
460
File::eOpenOptionCanCreate,
461
lldb::eFilePermissionsFileDefault);
462
if (file) {
463
std::string code(m_code.CopyList());
464
code.append(1, '\n');
465
size_t bytes_written = code.size();
466
file.get()->Write(code.c_str(), bytes_written);
467
file.get()->Close();
468
} else {
469
std::string message = llvm::toString(file.takeError());
470
error_sp->Printf("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
FileSpec(m_repl_source_path), 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("\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
"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(":")) {
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(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(0, 1, ':');
515
sub_result.GetDescriptions(descriptions);
516
request.AddCompletions(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(m_indent_str);
524
return;
525
}
526
527
std::string current_code;
528
current_code.append(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(i);
537
if (line_cstr) {
538
current_code.append("\n");
539
current_code.append(line_cstr);
540
}
541
}
542
}
543
544
current_code.append("\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(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(save_default_file,
579
save_default_line);
580
}
581
582
debugger.RunIOHandlerAsync(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_name_str);
596
if (cmd_obj) {
597
assert(command_name_str.empty());
598
cmd_obj->SetOverrideCallback(QuitCommandOverrideCallback, &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(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(save_default_file,
620
save_default_line);
621
return error;
622
}
623
624