Skip to content

Commit aa27c53

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 01e0a8e commit aa27c53

File tree

14 files changed

+211
-116
lines changed

14 files changed

+211
-116
lines changed

lldb/include/lldb/Expression/DiagnosticManager.h

Lines changed: 62 additions & 29 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,47 @@
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, llvm::ECError> {
58+
DiagnosticDetail m_detail;
59+
60+
public:
61+
using llvm::ErrorInfo<DetailedExpressionError, llvm::ECError>::ErrorInfo;
62+
DetailedExpressionError(DiagnosticDetail detail) {}
63+
std::string message() const override;
64+
static char ID;
65+
};
66+
2367
enum DiagnosticOrigin {
2468
eDiagnosticOriginUnknown = 0,
2569
eDiagnosticOriginLLDB,
@@ -49,37 +93,28 @@ class Diagnostic {
4993
}
5094
}
5195

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) {}
96+
Diagnostic(DiagnosticOrigin origin, uint32_t compiler_id,
97+
DiagnosticDetail detail)
98+
: m_origin(origin), m_compiler_id(compiler_id), m_detail(detail) {}
6099

61100
virtual ~Diagnostic() = default;
62101

63102
virtual bool HasFixIts() const { return false; }
64103

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

67106
uint32_t GetCompilerID() const { return m_compiler_id; }
68107

69-
llvm::StringRef GetMessage() const { return m_message; }
108+
llvm::StringRef GetMessage() const { return m_detail.message; }
109+
llvm::Error GetAsError() const;
70110

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-
}
111+
void AppendMessage(llvm::StringRef message, bool precede_with_newline = true);
77112

78113
protected:
79-
std::string m_message;
80-
lldb::Severity m_severity;
81114
DiagnosticOrigin m_origin;
82-
uint32_t m_compiler_id; // Compiler-specific diagnostic ID
115+
/// Compiler-specific diagnostic ID.
116+
uint32_t m_compiler_id;
117+
DiagnosticDetail m_detail;
83118
};
84119

85120
typedef std::vector<std::unique_ptr<Diagnostic>> DiagnosticList;
@@ -102,10 +137,7 @@ class DiagnosticManager {
102137

103138
void AddDiagnostic(llvm::StringRef message, lldb::Severity severity,
104139
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-
}
140+
uint32_t compiler_id = LLDB_INVALID_COMPILER_ID);
109141

110142
void AddDiagnostic(std::unique_ptr<Diagnostic> diagnostic) {
111143
if (diagnostic)
@@ -130,12 +162,13 @@ class DiagnosticManager {
130162
m_diagnostics.back()->AppendMessage(str);
131163
}
132164

133-
// Returns a string containing errors in this format:
134-
//
135-
// "error: error text\n
136-
// warning: warning text\n
137-
// remark text\n"
138-
std::string GetString(char separator = '\n');
165+
/// Copies the diagnostics into an llvm::Error{List}.
166+
/// The first diagnostic wraps \c result.
167+
llvm::Error GetAsError(lldb::ExpressionResults result) const;
168+
169+
/// Copies the diagnostics into an llvm::Error, the first diagnostic
170+
/// being an llvm::StringError.
171+
llvm::Error GetAsError(llvm::Twine msg) const;
139172

140173
void Dump(Log *log);
141174

lldb/include/lldb/Utility/Status.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ class Status {
144144
Status &operator=(Status &&);
145145
/// FIXME: Replace this with a takeError() method.
146146
llvm::Error ToError() const;
147+
llvm::Error takeError() const { return std::move(m_error); }
147148
/// Don't call this function in new code. Redesign the API instead.
148149
Status clone() const { return Status(ToError()); }
149150

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: 58 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -14,23 +14,12 @@
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());
17+
char DetailedExpressionError::ID;
18+
std::string DetailedExpressionError::message() const {
19+
return m_detail.rendered;
3220
}
3321

22+
3423
static const char *StringForSeverity(lldb::Severity severity) {
3524
switch (severity) {
3625
// this should be exhaustive
@@ -44,9 +33,13 @@ static const char *StringForSeverity(lldb::Severity severity) {
4433
llvm_unreachable("switch needs another case for lldb::Severity enum");
4534
}
4635

47-
std::string DiagnosticManager::GetString(char separator) {
48-
std::string ret;
49-
llvm::raw_string_ostream stream(ret);
36+
37+
void DiagnosticManager::Dump(Log *log) {
38+
if (!log)
39+
return;
40+
41+
std::string str;
42+
llvm::raw_string_ostream stream(str);
5043

5144
for (const auto &diagnostic : Diagnostics()) {
5245
llvm::StringRef severity = StringForSeverity(diagnostic->GetSeverity());
@@ -59,10 +52,45 @@ std::string DiagnosticManager::GetString(char separator) {
5952

6053
if (severity_pos != llvm::StringRef::npos)
6154
stream << message.drop_front(severity_pos + severity.size());
62-
stream << separator;
55+
stream << '\n';
6356
}
6457

65-
return ret;
58+
// We want to remove the last '\n' because log->PutCString will add
59+
// one for us.
60+
61+
if (str.size() && str.back() == '\n')
62+
str.pop_back();
63+
64+
log->PutString(str);
65+
}
66+
67+
llvm::Error Diagnostic::GetAsError() const {
68+
return llvm::make_error<DetailedExpressionError>(m_detail);
69+
}
70+
71+
llvm::Error
72+
DiagnosticManager::GetAsError(lldb::ExpressionResults result) const {
73+
llvm::Error diags = Status::FromExpressionError(result, "").takeError();
74+
for (const auto &diagnostic : m_diagnostics)
75+
diags = llvm::joinErrors(std::move(diags), diagnostic->GetAsError());
76+
return diags;
77+
}
78+
79+
llvm::Error
80+
DiagnosticManager::GetAsError(llvm::Twine msg) const {
81+
llvm::Error diags = llvm::createStringError(msg);
82+
for (const auto &diagnostic : m_diagnostics)
83+
diags = llvm::joinErrors(std::move(diags), diagnostic->GetAsError());
84+
return diags;
85+
}
86+
87+
void DiagnosticManager::AddDiagnostic(llvm::StringRef message,
88+
lldb::Severity severity,
89+
DiagnosticOrigin origin,
90+
uint32_t compiler_id) {
91+
m_diagnostics.emplace_back(std::make_unique<Diagnostic>(
92+
origin, compiler_id,
93+
DiagnosticDetail{{}, severity, message.str(), message.str()}));
6694
}
6795

6896
size_t DiagnosticManager::Printf(lldb::Severity severity, const char *format,
@@ -85,3 +113,13 @@ void DiagnosticManager::PutString(lldb::Severity severity,
85113
return;
86114
AddDiagnostic(str, severity, eDiagnosticOriginLLDB);
87115
}
116+
117+
void Diagnostic::AppendMessage(llvm::StringRef message,
118+
bool precede_with_newline) {
119+
if (precede_with_newline) {
120+
m_detail.message.push_back('\n');
121+
m_detail.rendered.push_back('\n');
122+
}
123+
m_detail.message += message;
124+
m_detail.rendered += message;
125+
}

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: 15 additions & 13 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().size())
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

@@ -384,8 +386,8 @@ UserExpression::Evaluate(ExecutionContext &exe_ctx,
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();

lldb/source/Expression/UtilityFunction.cpp

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -83,19 +83,18 @@ FunctionCaller *UtilityFunction::MakeFunctionCaller(
8383
m_caller_up.reset(process_sp->GetTarget().GetFunctionCallerForLanguage(
8484
Language().AsLanguageType(), return_type, impl_code_address,
8585
arg_value_list, name.c_str(), error));
86-
if (error.Fail()) {
87-
86+
if (error.Fail())
8887
return nullptr;
89-
}
88+
9089
if (m_caller_up) {
9190
DiagnosticManager diagnostics;
9291

9392
unsigned num_errors =
9493
m_caller_up->CompileFunction(thread_to_use_sp, diagnostics);
9594
if (num_errors) {
96-
error = Status::FromErrorStringWithFormat(
97-
"Error compiling %s caller function: \"%s\".",
98-
m_function_name.c_str(), diagnostics.GetString().c_str());
95+
error = Status::FromError(diagnostics.GetAsError(
96+
"Error compiling " + m_function_name + " caller function:"));
97+
9998
m_caller_up.reset();
10099
return nullptr;
101100
}
@@ -104,9 +103,8 @@ FunctionCaller *UtilityFunction::MakeFunctionCaller(
104103
ExecutionContext exe_ctx(process_sp);
105104

106105
if (!m_caller_up->WriteFunctionWrapper(exe_ctx, diagnostics)) {
107-
error = Status::FromErrorStringWithFormat(
108-
"Error inserting caller function for %s: \"%s\".",
109-
m_function_name.c_str(), diagnostics.GetString().c_str());
106+
error = Status::FromError(diagnostics.GetAsError(
107+
"Error inserting " + m_function_name + " caller function:"));
110108
m_caller_up.reset();
111109
return nullptr;
112110
}

lldb/source/Plugins/ExpressionParser/Clang/ClangDiagnostic.h

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,8 @@ class ClangDiagnostic : public Diagnostic {
2929
return diag->getKind() == eDiagnosticOriginClang;
3030
}
3131

32-
ClangDiagnostic(llvm::StringRef message, lldb::Severity severity,
33-
uint32_t compiler_id)
34-
: Diagnostic(message, severity, eDiagnosticOriginClang, compiler_id) {}
32+
ClangDiagnostic(DiagnosticDetail detail, uint32_t compiler_id)
33+
: Diagnostic(eDiagnosticOriginClang, compiler_id, detail) {}
3534

3635
~ClangDiagnostic() override = default;
3736

0 commit comments

Comments
 (0)