aboutsummaryrefslogtreecommitdiff
path: root/lldb/source/Commands/CommandObjectExpression.cpp
diff options
context:
space:
mode:
authorAdrian Prantl <aprantl@apple.com>2024-09-27 16:32:35 -0700
committerGitHub <noreply@github.com>2024-09-27 16:32:35 -0700
commit49372d1cccf50f404d52d40ae4b663db5604eb2c (patch)
treebf1c04f14b908bb46d6352647e1d1af37450969c /lldb/source/Commands/CommandObjectExpression.cpp
parent84fdfb9ca63ee4304b486d7e85545ee4e1a46f5d (diff)
downloadllvm-49372d1cccf50f404d52d40ae4b663db5604eb2c.zip
llvm-49372d1cccf50f404d52d40ae4b663db5604eb2c.tar.gz
llvm-49372d1cccf50f404d52d40ae4b663db5604eb2c.tar.bz2
[lldb] Inline expression evaluator error visualization (#106470)
This patch is a reworking of Pete Lawrence's (@PortalPete) proposal for better expression evaluator error messages: https://github.com/llvm/llvm-project/pull/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 https://github.com/llvm/llvm-project/pull/106442.
Diffstat (limited to 'lldb/source/Commands/CommandObjectExpression.cpp')
-rw-r--r--lldb/source/Commands/CommandObjectExpression.cpp154
1 files changed, 143 insertions, 11 deletions
diff --git a/lldb/source/Commands/CommandObjectExpression.cpp b/lldb/source/Commands/CommandObjectExpression.cpp
index 7711946..49f4421 100644
--- a/lldb/source/Commands/CommandObjectExpression.cpp
+++ b/lldb/source/Commands/CommandObjectExpression.cpp
@@ -10,6 +10,7 @@
#include "CommandObjectExpression.h"
#include "lldb/Core/Debugger.h"
+#include "lldb/Expression/DiagnosticManager.h"
#include "lldb/Expression/ExpressionVariable.h"
#include "lldb/Expression/REPL.h"
#include "lldb/Expression/UserExpression.h"
@@ -398,6 +399,122 @@ CanBeUsedForElementCountPrinting(ValueObject &valobj) {
return Status();
}
+static llvm::raw_ostream &PrintSeverity(Stream &stream,
+ lldb::Severity severity) {
+ llvm::HighlightColor color;
+ llvm::StringRef text;
+ switch (severity) {
+ case eSeverityError:
+ color = llvm::HighlightColor::Error;
+ text = "error: ";
+ break;
+ case eSeverityWarning:
+ color = llvm::HighlightColor::Warning;
+ text = "warning: ";
+ break;
+ case eSeverityInfo:
+ color = llvm::HighlightColor::Remark;
+ text = "note: ";
+ break;
+ }
+ return llvm::WithColor(stream.AsRawOstream(), color, llvm::ColorMode::Enable)
+ << text;
+}
+
+namespace lldb_private {
+// Public for unittesting.
+void RenderDiagnosticDetails(Stream &stream,
+ std::optional<uint16_t> offset_in_command,
+ bool show_inline,
+ llvm::ArrayRef<DiagnosticDetail> details) {
+ if (details.empty())
+ return;
+
+ if (!offset_in_command) {
+ for (const DiagnosticDetail &detail : details) {
+ PrintSeverity(stream, detail.severity);
+ stream << detail.rendered << '\n';
+ }
+ return;
+ }
+
+ // Print a line with caret indicator(s) below the lldb prompt + command.
+ const size_t padding = *offset_in_command;
+ stream << std::string(padding, ' ');
+
+ size_t offset = 1;
+ std::vector<DiagnosticDetail> remaining_details, other_details,
+ hidden_details;
+ for (const DiagnosticDetail &detail : details) {
+ if (!show_inline || !detail.source_location) {
+ other_details.push_back(detail);
+ continue;
+ }
+ if (detail.source_location->hidden) {
+ hidden_details.push_back(detail);
+ continue;
+ }
+ if (!detail.source_location->in_user_input) {
+ other_details.push_back(detail);
+ continue;
+ }
+
+ auto &loc = *detail.source_location;
+ remaining_details.push_back(detail);
+ if (offset > loc.column)
+ continue;
+ stream << std::string(loc.column - offset, ' ') << '^';
+ if (loc.length > 1)
+ stream << std::string(loc.length - 1, '~');
+ offset = loc.column + 1;
+ }
+ stream << '\n';
+
+ // Work through each detail in reverse order using the vector/stack.
+ bool did_print = false;
+ for (auto detail = remaining_details.rbegin();
+ detail != remaining_details.rend();
+ ++detail, remaining_details.pop_back()) {
+ // Get the information to print this detail and remove it from the stack.
+ // Print all the lines for all the other messages first.
+ stream << std::string(padding, ' ');
+ size_t offset = 1;
+ for (auto &remaining_detail :
+ llvm::ArrayRef(remaining_details).drop_back(1)) {
+ uint16_t column = remaining_detail.source_location->column;
+ stream << std::string(column - offset, ' ') << "│";
+ offset = column + 1;
+ }
+
+ // Print the line connecting the ^ with the error message.
+ uint16_t column = detail->source_location->column;
+ if (offset <= column)
+ stream << std::string(column - offset, ' ') << "╰─ ";
+
+ // Print a colorized string based on the message's severity type.
+ PrintSeverity(stream, detail->severity);
+
+ // Finally, print the message and start a new line.
+ stream << detail->message << '\n';
+ did_print = true;
+ }
+
+ // Print the non-located details.
+ for (const DiagnosticDetail &detail : other_details) {
+ PrintSeverity(stream, detail.severity);
+ stream << detail.rendered << '\n';
+ did_print = true;
+ }
+
+ // Print the hidden details as a last resort.
+ if (!did_print)
+ for (const DiagnosticDetail &detail : hidden_details) {
+ PrintSeverity(stream, detail.severity);
+ stream << detail.rendered << '\n';
+ }
+}
+} // namespace lldb_private
+
bool CommandObjectExpression::EvaluateExpression(llvm::StringRef expr,
Stream &output_stream,
Stream &error_stream,
@@ -486,19 +603,34 @@ bool CommandObjectExpression::EvaluateExpression(llvm::StringRef expr,
result.SetStatus(eReturnStatusSuccessFinishResult);
} else {
- const char *error_cstr = result_valobj_sp->GetError().AsCString();
- if (error_cstr && error_cstr[0]) {
- const size_t error_cstr_len = strlen(error_cstr);
- const bool ends_with_newline = error_cstr[error_cstr_len - 1] == '\n';
- if (strstr(error_cstr, "error:") != error_cstr)
- error_stream.PutCString("error: ");
- error_stream.Write(error_cstr, error_cstr_len);
- if (!ends_with_newline)
- error_stream.EOL();
+ // Retrieve the diagnostics.
+ std::vector<DiagnosticDetail> details;
+ llvm::consumeError(llvm::handleErrors(
+ result_valobj_sp->GetError().ToError(),
+ [&](ExpressionError &error) { details = error.GetDetails(); }));
+ // Find the position of the expression in the command.
+ std::optional<uint16_t> expr_pos;
+ size_t nchar = m_original_command.find(expr);
+ if (nchar != std::string::npos)
+ expr_pos = nchar + GetDebugger().GetPrompt().size();
+
+ if (!details.empty()) {
+ bool show_inline =
+ GetDebugger().GetShowInlineDiagnostics() && !expr.contains('\n');
+ RenderDiagnosticDetails(error_stream, expr_pos, show_inline, details);
} else {
- error_stream.PutCString("error: unknown error\n");
+ const char *error_cstr = result_valobj_sp->GetError().AsCString();
+ llvm::StringRef error(error_cstr);
+ if (!error.empty()) {
+ if (!error.starts_with("error:"))
+ error_stream << "error: ";
+ error_stream << error;
+ if (!error.ends_with('\n'))
+ error_stream.EOL();
+ } else {
+ error_stream << "error: unknown error\n";
+ }
}
-
result.SetStatus(eReturnStatusFailed);
}
}