Skip to content

Commit 7f0f30d

Browse files
adrian-prantlpuja2196
authored andcommitted
[lldb] Inline expression evaluator error visualization (#106470)
This patch is a reworking of Pete Lawrence's (@PortalPete) proposal for better expression evaluator error messages: llvm/llvm-project#80938 Before: ``` $ lldb -o "expr a+b" (lldb) expr a+b error: <user expression 0>:1:1: use of undeclared identifier 'a' a+b ^ error: <user expression 0>:1:3: use of undeclared identifier 'b' a+b ^ ``` After: ``` (lldb) expr a+b ^ ^ │ ╰─ error: use of undeclared identifier 'b' ╰─ error: use of undeclared identifier 'a' ``` This eliminates the confusing `<user expression 0>:1:3` source location and avoids echoing the expression to the console again, which results in a cleaner presentation that makes it easier to grasp what's going on. You can't see it here, bug the word "error" is now also in color, if so desired. Depends on llvm/llvm-project#106442.
1 parent aabaab0 commit 7f0f30d

File tree

24 files changed

+315
-51
lines changed

24 files changed

+315
-51
lines changed

lldb/include/lldb/API/SBDebugger.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,8 @@ class LLDB_API SBDebugger {
304304

305305
bool GetUseColor() const;
306306

307+
bool SetShowInlineDiagnostics(bool);
308+
307309
bool SetUseSourceCache(bool use_source_cache);
308310

309311
bool GetUseSourceCache() const;

lldb/include/lldb/Core/Debugger.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,10 @@ class Debugger : public std::enable_shared_from_this<Debugger>,
364364

365365
const std::string &GetInstanceName() { return m_instance_name; }
366366

367+
bool GetShowInlineDiagnostics() const;
368+
369+
bool SetShowInlineDiagnostics(bool);
370+
367371
bool LoadPlugin(const FileSpec &spec, Status &error);
368372

369373
void RunIOHandlers();

lldb/include/lldb/Expression/DiagnosticManager.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ struct DiagnosticDetail {
3939
unsigned line = 0;
4040
uint16_t column = 0;
4141
uint16_t length = 0;
42+
bool hidden = false;
4243
bool in_user_input = false;
4344
};
4445
/// Contains {{}, 1, 3, 3, true} in the example above.

lldb/include/lldb/Interpreter/CommandObject.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,13 @@ class CommandObject : public std::enable_shared_from_this<CommandObject> {
340340
return false;
341341
}
342342

343+
/// Set the command input as it appeared in the terminal. This
344+
/// is used to have errors refer directly to the original command.
345+
void SetOriginalCommandString(std::string s) { m_original_command = s; }
346+
347+
/// \param offset_in_command is on what column \c args_string
348+
/// appears, if applicable. This enables diagnostics that refer back
349+
/// to the user input.
343350
virtual void Execute(const char *args_string,
344351
CommandReturnObject &result) = 0;
345352

@@ -404,6 +411,7 @@ class CommandObject : public std::enable_shared_from_this<CommandObject> {
404411
std::string m_cmd_help_short;
405412
std::string m_cmd_help_long;
406413
std::string m_cmd_syntax;
414+
std::string m_original_command;
407415
Flags m_flags;
408416
std::vector<CommandArgumentEntry> m_arguments;
409417
lldb::CommandOverrideCallback m_deprecated_command_override_callback;

lldb/source/API/SBDebugger.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1483,6 +1483,12 @@ bool SBDebugger::GetUseColor() const {
14831483
return (m_opaque_sp ? m_opaque_sp->GetUseColor() : false);
14841484
}
14851485

1486+
bool SBDebugger::SetShowInlineDiagnostics(bool value) {
1487+
LLDB_INSTRUMENT_VA(this, value);
1488+
1489+
return (m_opaque_sp ? m_opaque_sp->SetShowInlineDiagnostics(value) : false);
1490+
}
1491+
14861492
bool SBDebugger::SetUseSourceCache(bool value) {
14871493
LLDB_INSTRUMENT_VA(this, value);
14881494

lldb/source/Commands/CommandObjectExpression.cpp

Lines changed: 143 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
#include "CommandObjectExpression.h"
1212
#include "lldb/Core/Debugger.h"
13+
#include "lldb/Expression/DiagnosticManager.h"
1314
#include "lldb/Expression/ExpressionVariable.h"
1415
#include "lldb/Expression/REPL.h"
1516
#include "lldb/Expression/UserExpression.h"
@@ -398,6 +399,122 @@ CanBeUsedForElementCountPrinting(ValueObject &valobj) {
398399
return Status();
399400
}
400401

402+
static llvm::raw_ostream &PrintSeverity(Stream &stream,
403+
lldb::Severity severity) {
404+
llvm::HighlightColor color;
405+
llvm::StringRef text;
406+
switch (severity) {
407+
case eSeverityError:
408+
color = llvm::HighlightColor::Error;
409+
text = "error: ";
410+
break;
411+
case eSeverityWarning:
412+
color = llvm::HighlightColor::Warning;
413+
text = "warning: ";
414+
break;
415+
case eSeverityInfo:
416+
color = llvm::HighlightColor::Remark;
417+
text = "note: ";
418+
break;
419+
}
420+
return llvm::WithColor(stream.AsRawOstream(), color, llvm::ColorMode::Enable)
421+
<< text;
422+
}
423+
424+
namespace lldb_private {
425+
// Public for unittesting.
426+
void RenderDiagnosticDetails(Stream &stream,
427+
std::optional<uint16_t> offset_in_command,
428+
bool show_inline,
429+
llvm::ArrayRef<DiagnosticDetail> details) {
430+
if (details.empty())
431+
return;
432+
433+
if (!offset_in_command) {
434+
for (const DiagnosticDetail &detail : details) {
435+
PrintSeverity(stream, detail.severity);
436+
stream << detail.rendered << '\n';
437+
}
438+
return;
439+
}
440+
441+
// Print a line with caret indicator(s) below the lldb prompt + command.
442+
const size_t padding = *offset_in_command;
443+
stream << std::string(padding, ' ');
444+
445+
size_t offset = 1;
446+
std::vector<DiagnosticDetail> remaining_details, other_details,
447+
hidden_details;
448+
for (const DiagnosticDetail &detail : details) {
449+
if (!show_inline || !detail.source_location) {
450+
other_details.push_back(detail);
451+
continue;
452+
}
453+
if (detail.source_location->hidden) {
454+
hidden_details.push_back(detail);
455+
continue;
456+
}
457+
if (!detail.source_location->in_user_input) {
458+
other_details.push_back(detail);
459+
continue;
460+
}
461+
462+
auto &loc = *detail.source_location;
463+
remaining_details.push_back(detail);
464+
if (offset > loc.column)
465+
continue;
466+
stream << std::string(loc.column - offset, ' ') << '^';
467+
if (loc.length > 1)
468+
stream << std::string(loc.length - 1, '~');
469+
offset = loc.column + 1;
470+
}
471+
stream << '\n';
472+
473+
// Work through each detail in reverse order using the vector/stack.
474+
bool did_print = false;
475+
for (auto detail = remaining_details.rbegin();
476+
detail != remaining_details.rend();
477+
++detail, remaining_details.pop_back()) {
478+
// Get the information to print this detail and remove it from the stack.
479+
// Print all the lines for all the other messages first.
480+
stream << std::string(padding, ' ');
481+
size_t offset = 1;
482+
for (auto &remaining_detail :
483+
llvm::ArrayRef(remaining_details).drop_back(1)) {
484+
uint16_t column = remaining_detail.source_location->column;
485+
stream << std::string(column - offset, ' ') << "";
486+
offset = column + 1;
487+
}
488+
489+
// Print the line connecting the ^ with the error message.
490+
uint16_t column = detail->source_location->column;
491+
if (offset <= column)
492+
stream << std::string(column - offset, ' ') << "╰─ ";
493+
494+
// Print a colorized string based on the message's severity type.
495+
PrintSeverity(stream, detail->severity);
496+
497+
// Finally, print the message and start a new line.
498+
stream << detail->message << '\n';
499+
did_print = true;
500+
}
501+
502+
// Print the non-located details.
503+
for (const DiagnosticDetail &detail : other_details) {
504+
PrintSeverity(stream, detail.severity);
505+
stream << detail.rendered << '\n';
506+
did_print = true;
507+
}
508+
509+
// Print the hidden details as a last resort.
510+
if (!did_print)
511+
for (const DiagnosticDetail &detail : hidden_details) {
512+
PrintSeverity(stream, detail.severity);
513+
stream << detail.rendered << '\n';
514+
}
515+
}
516+
} // namespace lldb_private
517+
401518
bool CommandObjectExpression::EvaluateExpression(llvm::StringRef expr,
402519
Stream &output_stream,
403520
Stream &error_stream,
@@ -486,19 +603,34 @@ bool CommandObjectExpression::EvaluateExpression(llvm::StringRef expr,
486603

487604
result.SetStatus(eReturnStatusSuccessFinishResult);
488605
} else {
489-
const char *error_cstr = result_valobj_sp->GetError().AsCString();
490-
if (error_cstr && error_cstr[0]) {
491-
const size_t error_cstr_len = strlen(error_cstr);
492-
const bool ends_with_newline = error_cstr[error_cstr_len - 1] == '\n';
493-
if (strstr(error_cstr, "error:") != error_cstr)
494-
error_stream.PutCString("error: ");
495-
error_stream.Write(error_cstr, error_cstr_len);
496-
if (!ends_with_newline)
497-
error_stream.EOL();
606+
// Retrieve the diagnostics.
607+
std::vector<DiagnosticDetail> details;
608+
llvm::consumeError(llvm::handleErrors(
609+
result_valobj_sp->GetError().ToError(),
610+
[&](ExpressionError &error) { details = error.GetDetails(); }));
611+
// Find the position of the expression in the command.
612+
std::optional<uint16_t> expr_pos;
613+
size_t nchar = m_original_command.find(expr);
614+
if (nchar != std::string::npos)
615+
expr_pos = nchar + GetDebugger().GetPrompt().size();
616+
617+
if (!details.empty()) {
618+
bool show_inline =
619+
GetDebugger().GetShowInlineDiagnostics() && !expr.contains('\n');
620+
RenderDiagnosticDetails(error_stream, expr_pos, show_inline, details);
498621
} else {
499-
error_stream.PutCString("error: unknown error\n");
622+
const char *error_cstr = result_valobj_sp->GetError().AsCString();
623+
llvm::StringRef error(error_cstr);
624+
if (!error.empty()) {
625+
if (!error.starts_with("error:"))
626+
error_stream << "error: ";
627+
error_stream << error;
628+
if (!error.ends_with('\n'))
629+
error_stream.EOL();
630+
} else {
631+
error_stream << "error: unknown error\n";
632+
}
500633
}
501-
502634
result.SetStatus(eReturnStatusFailed);
503635
}
504636
}

lldb/source/Core/CoreProperties.td

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,4 +225,8 @@ let Definition = "debugger" in {
225225
DefaultEnumValue<"eDWIMPrintVerbosityNone">,
226226
EnumValues<"OptionEnumValues(g_dwim_print_verbosities)">,
227227
Desc<"The verbosity level used by dwim-print.">;
228+
def ShowInlineDiagnostics: Property<"show-inline-diagnostics", "Boolean">,
229+
Global,
230+
DefaultFalse,
231+
Desc<"Controls whether diagnostics can refer directly to the command input, drawing arrows to it. If false, diagnostics will echo the input.">;
228232
}

lldb/source/Core/Debugger.cpp

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -592,7 +592,18 @@ lldb::DWIMPrintVerbosity Debugger::GetDWIMPrintVerbosity() const {
592592
const uint32_t idx = ePropertyDWIMPrintVerbosity;
593593
return GetPropertyAtIndexAs<lldb::DWIMPrintVerbosity>(
594594
idx, static_cast<lldb::DWIMPrintVerbosity>(
595-
g_debugger_properties[idx].default_uint_value));
595+
g_debugger_properties[idx].default_uint_value != 0));
596+
}
597+
598+
bool Debugger::GetShowInlineDiagnostics() const {
599+
const uint32_t idx = ePropertyShowInlineDiagnostics;
600+
return GetPropertyAtIndexAs<bool>(
601+
idx, g_debugger_properties[idx].default_uint_value);
602+
}
603+
604+
bool Debugger::SetShowInlineDiagnostics(bool b) {
605+
const uint32_t idx = ePropertyShowInlineDiagnostics;
606+
return SetPropertyAtIndex(idx, b);
596607
}
597608

598609
#pragma mark Debugger

lldb/source/Expression/DiagnosticManager.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ ExpressionError::ExpressionError(lldb::ExpressionResults result,
3737
: ErrorInfo(std::error_code(result, expression_category())), m_message(msg),
3838
m_details(details) {}
3939

40-
static const char *StringForSeverity(lldb::Severity severity) {
40+
static llvm::StringRef StringForSeverity(lldb::Severity severity) {
4141
switch (severity) {
4242
// this should be exhaustive
4343
case lldb::eSeverityError:

lldb/source/Interpreter/CommandInterpreter.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1887,7 +1887,8 @@ bool CommandInterpreter::HandleCommand(const char *command_line,
18871887
CommandReturnObject &result,
18881888
bool force_repeat_command) {
18891889
std::string command_string(command_line);
1890-
std::string original_command_string(command_line);
1890+
std::string original_command_string(command_string);
1891+
std::string real_original_command_string(command_string);
18911892

18921893
Log *log = GetLog(LLDBLog::Commands);
18931894
llvm::PrettyStackTraceFormat stack_trace("HandleCommand(command = \"%s\")",
@@ -2076,6 +2077,7 @@ bool CommandInterpreter::HandleCommand(const char *command_line,
20762077
}
20772078

20782079
ElapsedTime elapsed(execute_time);
2080+
cmd_obj->SetOriginalCommandString(real_original_command_string);
20792081
cmd_obj->Execute(remainder.c_str(), result);
20802082
}
20812083

0 commit comments

Comments
 (0)