Skip to content

Commit bad95e9

Browse files
committed
[lldb] Inline expression evaluator error visualization
This patch is a reworking of Pete Lawrence's (@PortalPete) proposal for better expression evaluator error messages: #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.
1 parent 9f4ba3f commit bad95e9

File tree

25 files changed

+315
-54
lines changed

25 files changed

+315
-54
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: 2 additions & 1 deletion
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.
@@ -64,7 +65,7 @@ class ExpressionError
6465
ExpressionError(lldb::ExpressionResults result, std::string msg,
6566
std::vector<DiagnosticDetail> details = {});
6667
std::string message() const override;
67-
llvm::ArrayRef<DiagnosticDetail> GetDetail() const { return m_details; }
68+
llvm::ArrayRef<DiagnosticDetail> GetDetails() const { return m_details; }
6869
std::error_code convertToErrorCode() const override;
6970
void log(llvm::raw_ostream &OS) const override;
7071
std::unique_ptr<CloneableError> Clone() const override;

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

lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.cpp

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -176,15 +176,22 @@ class ClangDiagnosticManagerAdapter : public clang::DiagnosticConsumer {
176176
m_manager = manager;
177177
}
178178

179-
/// Returns the last ClangDiagnostic message that the DiagnosticManager
180-
/// received or a nullptr if the DiagnosticMangager hasn't seen any
181-
/// Clang diagnostics yet.
179+
/// Returns the last error ClangDiagnostic message that the
180+
/// DiagnosticManager received or a nullptr.
182181
ClangDiagnostic *MaybeGetLastClangDiag() const {
183182
if (m_manager->Diagnostics().empty())
184183
return nullptr;
185-
lldb_private::Diagnostic *diag = m_manager->Diagnostics().back().get();
186-
ClangDiagnostic *clang_diag = dyn_cast<ClangDiagnostic>(diag);
187-
return clang_diag;
184+
auto &diags = m_manager->Diagnostics();
185+
for (auto it = diags.rbegin(); it != diags.rend(); it++) {
186+
lldb_private::Diagnostic *diag = it->get();
187+
if (ClangDiagnostic *clang_diag = dyn_cast<ClangDiagnostic>(diag)) {
188+
if (clang_diag->GetSeverity() == lldb::eSeverityWarning)
189+
return nullptr;
190+
if (clang_diag->GetSeverity() == lldb::eSeverityError)
191+
return clang_diag;
192+
}
193+
}
194+
return nullptr;
188195
}
189196

190197
void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel,
@@ -215,8 +222,6 @@ class ClangDiagnosticManagerAdapter : public clang::DiagnosticConsumer {
215222
m_os->flush();
216223

217224
DiagnosticDetail detail;
218-
bool make_new_diagnostic = true;
219-
220225
switch (DiagLevel) {
221226
case DiagnosticsEngine::Level::Fatal:
222227
case DiagnosticsEngine::Level::Error:
@@ -230,9 +235,6 @@ class ClangDiagnosticManagerAdapter : public clang::DiagnosticConsumer {
230235
detail.severity = lldb::eSeverityInfo;
231236
break;
232237
case DiagnosticsEngine::Level::Note:
233-
m_manager->AppendMessageToDiagnostic(m_output);
234-
make_new_diagnostic = false;
235-
236238
// 'note:' diagnostics for errors and warnings can also contain Fix-Its.
237239
// We add these Fix-Its to the last error diagnostic to make sure
238240
// that we later have all Fix-Its related to an 'error' diagnostic when
@@ -250,7 +252,6 @@ class ClangDiagnosticManagerAdapter : public clang::DiagnosticConsumer {
250252
AddAllFixIts(clang_diag, Info);
251253
break;
252254
}
253-
if (make_new_diagnostic) {
254255
// ClangDiagnostic messages are expected to have no whitespace/newlines
255256
// around them.
256257
std::string stripped_output =
@@ -270,6 +271,7 @@ class ClangDiagnosticManagerAdapter : public clang::DiagnosticConsumer {
270271
loc.line = fsloc.getSpellingLineNumber();
271272
loc.column = fsloc.getSpellingColumnNumber();
272273
loc.in_user_input = filename == m_filename;
274+
loc.hidden = filename.starts_with("<lldb wrapper ");
273275

274276
// Find the range of the primary location.
275277
for (const auto &range : Info.getRanges()) {
@@ -299,7 +301,6 @@ class ClangDiagnosticManagerAdapter : public clang::DiagnosticConsumer {
299301
AddAllFixIts(new_diagnostic.get(), Info);
300302

301303
m_manager->AddDiagnostic(std::move(new_diagnostic));
302-
}
303304
}
304305

305306
void BeginSourceFile(const LangOptions &LO, const Preprocessor *PP) override {

lldb/source/Plugins/TraceExporter/ctf/CommandObjectThreadTraceExportCTF.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ class CommandObjectThreadTraceExportCTF : public CommandObjectParsed {
4848
Options *GetOptions() override { return &m_options; }
4949

5050
protected:
51-
void DoExecute(Args &command, CommandReturnObject &result) override;
51+
void DoExecute(Args &args, CommandReturnObject &result) override;
5252

5353
CommandOptions m_options;
5454
};

lldb/source/Utility/Status.cpp

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -250,8 +250,6 @@ ErrorType Status::GetType() const {
250250
else if (error.convertToErrorCode().category() == lldb_generic_category() ||
251251
error.convertToErrorCode() == llvm::inconvertibleErrorCode())
252252
result = eErrorTypeGeneric;
253-
else
254-
result = eErrorTypeInvalid;
255253
});
256254
return result;
257255
}

0 commit comments

Comments
 (0)