Skip to content

Commit 8c682fc

Browse files
committed
[lldb] Store expression evaluator diagnostics in an llvm::Error (NFC)
This patch is a reworking of Pete Lawrence's (@PortalPete) proposal for better expression evaluator error messages: #80938 This patch is preparatory patch for improving the rendering of expression evaluator diagnostics. Currently diagnostics are rendered into a string and the command interpreter layer then textually parses words like "error:" to (sometimes) color the output accordingly. In order to enable user interfaces to do better with diagnostics, we need to store them in a machine-readable fromat. This patch does this by adding a new llvm::Error kind wrapping a DiagnosticDetail struct that is used when the error type is eErrorTypeExpression. Multiple diagnostics are modeled using llvm::ErrorList. Right now the extra information is not used by the CommandInterpreter, this will be added in a follow-up patch!
1 parent e584278 commit 8c682fc

File tree

17 files changed

+267
-118
lines changed

17 files changed

+267
-118
lines changed

lldb/include/lldb/Expression/DiagnosticManager.h

Lines changed: 68 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
#include "lldb/lldb-defines.h"
1313
#include "lldb/lldb-types.h"
1414

15+
#include "lldb/Utility/FileSpec.h"
16+
#include "lldb/Utility/Status.h"
17+
1518
#include "llvm/ADT/STLExtras.h"
1619
#include "llvm/ADT/StringRef.h"
1720

@@ -20,6 +23,52 @@
2023

2124
namespace lldb_private {
2225

26+
/// A compiler-independent representation of a Diagnostic. Expression
27+
/// evaluation failures often have more than one diagnostic that a UI
28+
/// layer might want to render differently, for example to colorize
29+
/// it.
30+
///
31+
/// Running example:
32+
/// (lldb) expr 1+foo
33+
/// error: <user expression 0>:1:3: use of undeclared identifier 'foo'
34+
/// 1+foo
35+
/// ^
36+
struct DiagnosticDetail {
37+
struct SourceLocation {
38+
FileSpec file;
39+
unsigned line = 0;
40+
uint16_t column = 0;
41+
uint16_t length = 0;
42+
bool in_user_input = false;
43+
};
44+
/// Contains {{}, 1, 3, 3, true} in the example above.
45+
std::optional<SourceLocation> source_location;
46+
/// Contains eSeverityError in the example above.
47+
lldb::Severity severity = lldb::eSeverityInfo;
48+
/// Contains "use of undeclared identifier 'x'" in the example above.
49+
std::string message;
50+
/// Contains the fully rendered error message.
51+
std::string rendered;
52+
};
53+
54+
/// An llvm::Error used to communicate diagnostics in Status. Multiple
55+
/// diagnostics may be chained in an llvm::ErrorList.
56+
class DetailedExpressionError
57+
: public llvm::ErrorInfo<DetailedExpressionError, CloneableError> {
58+
DiagnosticDetail m_detail;
59+
60+
public:
61+
using llvm::ErrorInfo<DetailedExpressionError, CloneableError>::ErrorInfo;
62+
DetailedExpressionError(DiagnosticDetail detail) : m_detail(detail) {}
63+
std::string message() const override;
64+
DiagnosticDetail GetDetail() const { return m_detail; }
65+
std::error_code convertToErrorCode() const override;
66+
void log(llvm::raw_ostream &OS) const override;
67+
std::unique_ptr<CloneableError> Clone() const override;
68+
69+
static char ID;
70+
};
71+
2372
enum DiagnosticOrigin {
2473
eDiagnosticOriginUnknown = 0,
2574
eDiagnosticOriginLLDB,
@@ -49,37 +98,28 @@ class Diagnostic {
4998
}
5099
}
51100

52-
Diagnostic(llvm::StringRef message, lldb::Severity severity,
53-
DiagnosticOrigin origin, uint32_t compiler_id)
54-
: m_message(message), m_severity(severity), m_origin(origin),
55-
m_compiler_id(compiler_id) {}
56-
57-
Diagnostic(const Diagnostic &rhs)
58-
: m_message(rhs.m_message), m_severity(rhs.m_severity),
59-
m_origin(rhs.m_origin), m_compiler_id(rhs.m_compiler_id) {}
101+
Diagnostic(DiagnosticOrigin origin, uint32_t compiler_id,
102+
DiagnosticDetail detail)
103+
: m_origin(origin), m_compiler_id(compiler_id), m_detail(detail) {}
60104

61105
virtual ~Diagnostic() = default;
62106

63107
virtual bool HasFixIts() const { return false; }
64108

65-
lldb::Severity GetSeverity() const { return m_severity; }
109+
lldb::Severity GetSeverity() const { return m_detail.severity; }
66110

67111
uint32_t GetCompilerID() const { return m_compiler_id; }
68112

69-
llvm::StringRef GetMessage() const { return m_message; }
113+
llvm::StringRef GetMessage() const { return m_detail.message; }
114+
llvm::Error GetAsError() const;
70115

71-
void AppendMessage(llvm::StringRef message,
72-
bool precede_with_newline = true) {
73-
if (precede_with_newline)
74-
m_message.push_back('\n');
75-
m_message += message;
76-
}
116+
void AppendMessage(llvm::StringRef message, bool precede_with_newline = true);
77117

78118
protected:
79-
std::string m_message;
80-
lldb::Severity m_severity;
81119
DiagnosticOrigin m_origin;
82-
uint32_t m_compiler_id; // Compiler-specific diagnostic ID
120+
/// Compiler-specific diagnostic ID.
121+
uint32_t m_compiler_id;
122+
DiagnosticDetail m_detail;
83123
};
84124

85125
typedef std::vector<std::unique_ptr<Diagnostic>> DiagnosticList;
@@ -102,10 +142,7 @@ class DiagnosticManager {
102142

103143
void AddDiagnostic(llvm::StringRef message, lldb::Severity severity,
104144
DiagnosticOrigin origin,
105-
uint32_t compiler_id = LLDB_INVALID_COMPILER_ID) {
106-
m_diagnostics.emplace_back(
107-
std::make_unique<Diagnostic>(message, severity, origin, compiler_id));
108-
}
145+
uint32_t compiler_id = LLDB_INVALID_COMPILER_ID);
109146

110147
void AddDiagnostic(std::unique_ptr<Diagnostic> diagnostic) {
111148
if (diagnostic)
@@ -130,6 +167,14 @@ class DiagnosticManager {
130167
m_diagnostics.back()->AppendMessage(str);
131168
}
132169

170+
/// Copies the diagnostics into an llvm::Error{List}.
171+
/// The first diagnostic wraps \c result.
172+
llvm::Error GetAsError(lldb::ExpressionResults result) const;
173+
174+
/// Copies the diagnostics into an llvm::Error, the first diagnostic
175+
/// being an llvm::StringError.
176+
llvm::Error GetAsError(llvm::Twine msg) const;
177+
133178
// Returns a string containing errors in this format:
134179
//
135180
// "error: error text\n

lldb/include/lldb/Utility/Status.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,8 +175,11 @@ class Status {
175175
/// Avoid using this in new code. Migrate APIs to llvm::Expected instead.
176176
static Status FromError(llvm::Error error);
177177

178-
/// FIXME: Replace this with a takeError() method.
178+
/// FIXME: Replace all uses with takeError() instead.
179179
llvm::Error ToError() const;
180+
181+
llvm::Error takeError() { return std::move(m_error); }
182+
180183
/// Don't call this function in new code. Instead, redesign the API
181184
/// to use llvm::Expected instead of Status.
182185
Status Clone() const { return Status(ToError()); }

lldb/source/Breakpoint/BreakpointLocation.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -264,9 +264,9 @@ bool BreakpointLocation::ConditionSaysStop(ExecutionContext &exe_ctx,
264264
if (!m_user_expression_sp->Parse(diagnostics, exe_ctx,
265265
eExecutionPolicyOnlyWhenNeeded, true,
266266
false)) {
267-
error = Status::FromErrorStringWithFormat(
268-
"Couldn't parse conditional expression:\n%s",
269-
diagnostics.GetString().c_str());
267+
error = Status::FromError(
268+
diagnostics.GetAsError("Couldn't parse conditional expression:"));
269+
270270
m_user_expression_sp.reset();
271271
return true;
272272
}
@@ -324,8 +324,8 @@ bool BreakpointLocation::ConditionSaysStop(ExecutionContext &exe_ctx,
324324
}
325325
} else {
326326
ret = false;
327-
error = Status::FromErrorStringWithFormat(
328-
"Couldn't execute expression:\n%s", diagnostics.GetString().c_str());
327+
error = Status::FromError(
328+
diagnostics.GetAsError("Couldn't execute expression:"));
329329
}
330330

331331
return ret;

lldb/source/Expression/DiagnosticManager.cpp

Lines changed: 75 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -14,22 +14,7 @@
1414
#include "lldb/Utility/StreamString.h"
1515

1616
using namespace lldb_private;
17-
18-
void DiagnosticManager::Dump(Log *log) {
19-
if (!log)
20-
return;
21-
22-
std::string str = GetString();
23-
24-
// GetString() puts a separator after each diagnostic. We want to remove the
25-
// last '\n' because log->PutCString will add one for us.
26-
27-
if (str.size() && str.back() == '\n') {
28-
str.pop_back();
29-
}
30-
31-
log->PutCString(str.c_str());
32-
}
17+
char DetailedExpressionError::ID;
3318

3419
static const char *StringForSeverity(lldb::Severity severity) {
3520
switch (severity) {
@@ -44,9 +29,28 @@ static const char *StringForSeverity(lldb::Severity severity) {
4429
llvm_unreachable("switch needs another case for lldb::Severity enum");
4530
}
4631

32+
std::string DetailedExpressionError::message() const {
33+
std::string str;
34+
llvm::raw_string_ostream(str)
35+
<< StringForSeverity(m_detail.severity) << m_detail.rendered;
36+
return str;
37+
}
38+
39+
std::error_code DetailedExpressionError::convertToErrorCode() const {
40+
return llvm::inconvertibleErrorCode();
41+
}
42+
43+
void DetailedExpressionError::log(llvm::raw_ostream &OS) const {
44+
OS << message();
45+
}
46+
47+
std::unique_ptr<CloneableError> DetailedExpressionError::Clone() const {
48+
return std::make_unique<DetailedExpressionError>(m_detail);
49+
}
50+
4751
std::string DiagnosticManager::GetString(char separator) {
48-
std::string ret;
49-
llvm::raw_string_ostream stream(ret);
52+
std::string str;
53+
llvm::raw_string_ostream stream(str);
5054

5155
for (const auto &diagnostic : Diagnostics()) {
5256
llvm::StringRef severity = StringForSeverity(diagnostic->GetSeverity());
@@ -61,8 +65,50 @@ std::string DiagnosticManager::GetString(char separator) {
6165
stream << message.drop_front(severity_pos + severity.size());
6266
stream << separator;
6367
}
68+
return str;
69+
}
6470

65-
return ret;
71+
void DiagnosticManager::Dump(Log *log) {
72+
if (!log)
73+
return;
74+
75+
std::string str = GetString();
76+
77+
// We want to remove the last '\n' because log->PutCString will add
78+
// one for us.
79+
80+
if (str.size() && str.back() == '\n')
81+
str.pop_back();
82+
83+
log->PutString(str);
84+
}
85+
86+
llvm::Error Diagnostic::GetAsError() const {
87+
return llvm::make_error<DetailedExpressionError>(m_detail);
88+
}
89+
90+
llvm::Error
91+
DiagnosticManager::GetAsError(lldb::ExpressionResults result) const {
92+
llvm::Error diags = Status::FromExpressionError(result, "").takeError();
93+
for (const auto &diagnostic : m_diagnostics)
94+
diags = llvm::joinErrors(std::move(diags), diagnostic->GetAsError());
95+
return diags;
96+
}
97+
98+
llvm::Error DiagnosticManager::GetAsError(llvm::Twine msg) const {
99+
llvm::Error diags = llvm::createStringError(msg);
100+
for (const auto &diagnostic : m_diagnostics)
101+
diags = llvm::joinErrors(std::move(diags), diagnostic->GetAsError());
102+
return diags;
103+
}
104+
105+
void DiagnosticManager::AddDiagnostic(llvm::StringRef message,
106+
lldb::Severity severity,
107+
DiagnosticOrigin origin,
108+
uint32_t compiler_id) {
109+
m_diagnostics.emplace_back(std::make_unique<Diagnostic>(
110+
origin, compiler_id,
111+
DiagnosticDetail{{}, severity, message.str(), message.str()}));
66112
}
67113

68114
size_t DiagnosticManager::Printf(lldb::Severity severity, const char *format,
@@ -85,3 +131,13 @@ void DiagnosticManager::PutString(lldb::Severity severity,
85131
return;
86132
AddDiagnostic(str, severity, eDiagnosticOriginLLDB);
87133
}
134+
135+
void Diagnostic::AppendMessage(llvm::StringRef message,
136+
bool precede_with_newline) {
137+
if (precede_with_newline) {
138+
m_detail.message.push_back('\n');
139+
m_detail.rendered.push_back('\n');
140+
}
141+
m_detail.message += message;
142+
m_detail.rendered += message;
143+
}

lldb/source/Expression/ExpressionParser.cpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,8 @@ ExpressionParser::RunStaticInitializers(IRExecutionUnitSP &execution_unit_sp,
6363
exe_ctx, call_static_initializer, options, execution_errors);
6464

6565
if (results != eExpressionCompleted) {
66-
err = Status::FromErrorStringWithFormat(
67-
"couldn't run static initializer: %s",
68-
execution_errors.GetString().c_str());
66+
err = Status::FromError(
67+
execution_errors.GetAsError("couldn't run static initializer:"));
6968
return err;
7069
}
7170
}

lldb/source/Expression/UserExpression.cpp

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -328,18 +328,20 @@ UserExpression::Evaluate(ExecutionContext &exe_ctx,
328328
}
329329

330330
if (!parse_success) {
331-
std::string msg;
332-
{
333-
llvm::raw_string_ostream os(msg);
334-
if (!diagnostic_manager.Diagnostics().empty())
335-
os << diagnostic_manager.GetString();
336-
else
337-
os << "expression failed to parse (no further compiler diagnostics)";
338-
if (target->GetEnableNotifyAboutFixIts() && fixed_expression &&
339-
!fixed_expression->empty())
340-
os << "\nfixed expression suggested:\n " << *fixed_expression;
331+
if (target->GetEnableNotifyAboutFixIts() && fixed_expression &&
332+
!fixed_expression->empty()) {
333+
std::string fixit =
334+
"fixed expression suggested:\n " + *fixed_expression;
335+
diagnostic_manager.AddDiagnostic(fixit, lldb::eSeverityInfo,
336+
eDiagnosticOriginLLDB);
341337
}
342-
error = Status::FromExpressionError(execution_results, msg);
338+
if (diagnostic_manager.Diagnostics().empty())
339+
error = Status::FromExpressionError(
340+
execution_results,
341+
"expression failed to parse (no further compiler diagnostics)");
342+
else
343+
error =
344+
Status::FromError(diagnostic_manager.GetAsError(execution_results));
343345
}
344346
}
345347

@@ -351,7 +353,7 @@ UserExpression::Evaluate(ExecutionContext &exe_ctx,
351353
LLDB_LOG(log, "== [UserExpression::Evaluate] Expression may not run, but "
352354
"is not constant ==");
353355

354-
if (!diagnostic_manager.Diagnostics().size())
356+
if (diagnostic_manager.Diagnostics().empty())
355357
error = Status::FromExpressionError(
356358
lldb::eExpressionSetupError,
357359
"expression needed to run but couldn't");
@@ -380,12 +382,12 @@ UserExpression::Evaluate(ExecutionContext &exe_ctx,
380382
LLDB_LOG(log, "== [UserExpression::Evaluate] Execution completed "
381383
"abnormally ==");
382384

383-
if (!diagnostic_manager.Diagnostics().size())
385+
if (diagnostic_manager.Diagnostics().empty())
384386
error = Status::FromExpressionError(
385387
execution_results, "expression failed to execute, unknown error");
386388
else
387-
error = Status::FromExpressionError(execution_results,
388-
diagnostic_manager.GetString());
389+
error = Status::FromError(
390+
diagnostic_manager.GetAsError(execution_results));
389391
} else {
390392
if (expr_result) {
391393
result_valobj_sp = expr_result->GetValueObject();

0 commit comments

Comments
 (0)