Skip to content

Commit 9f014cf

Browse files
committed
[lldb] Add Status::Detail to store expression evaluator diagnostics [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 Status::Detail vector to Status that is used when the error type is eErrorTypeExpression. I tried my best to minimize the overhead this adds to Status in all other use-cases. Right now the extra information is not used by the CommandInterpreter, this will be added in a follow-up patch!
1 parent af3ee62 commit 9f014cf

File tree

9 files changed

+180
-64
lines changed

9 files changed

+180
-64
lines changed

lldb/include/lldb/Expression/DiagnosticManager.h

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

15+
#include "lldb/Utility/Status.h"
16+
1517
#include "llvm/ADT/STLExtras.h"
1618
#include "llvm/ADT/StringRef.h"
1719

@@ -49,37 +51,28 @@ class Diagnostic {
4951
}
5052
}
5153

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) {}
54+
Diagnostic(DiagnosticOrigin origin, uint32_t compiler_id,
55+
Status::Detail detail)
56+
: m_origin(origin), m_compiler_id(compiler_id), m_detail(detail) {}
6057

6158
virtual ~Diagnostic() = default;
6259

6360
virtual bool HasFixIts() const { return false; }
6461

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

6764
uint32_t GetCompilerID() const { return m_compiler_id; }
6865

69-
llvm::StringRef GetMessage() const { return m_message; }
66+
llvm::StringRef GetMessage() const { return m_detail.message; }
67+
Status::Detail GetDetail() const { return m_detail; }
7068

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

7871
protected:
79-
std::string m_message;
80-
lldb::Severity m_severity;
8172
DiagnosticOrigin m_origin;
82-
uint32_t m_compiler_id; // Compiler-specific diagnostic ID
73+
/// Compiler-specific diagnostic ID.
74+
uint32_t m_compiler_id;
75+
Status::Detail m_detail;
8376
};
8477

8578
typedef std::vector<std::unique_ptr<Diagnostic>> DiagnosticList;
@@ -102,10 +95,7 @@ class DiagnosticManager {
10295

10396
void AddDiagnostic(llvm::StringRef message, lldb::Severity severity,
10497
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-
}
98+
uint32_t compiler_id = LLDB_INVALID_COMPILER_ID);
10999

110100
void AddDiagnostic(std::unique_ptr<Diagnostic> diagnostic) {
111101
if (diagnostic)
@@ -130,13 +120,17 @@ class DiagnosticManager {
130120
m_diagnostics.back()->AppendMessage(str);
131121
}
132122

133-
// Returns a string containing errors in this format:
134-
//
135-
// "error: error text\n
136-
// warning: warning text\n
137-
// remark text\n"
123+
/// Returns a string containing errors in this format:
124+
///
125+
/// "error: error text\n
126+
/// warning: warning text\n
127+
/// remark text\n"
128+
LLVM_DEPRECATED("Use GetAsStatus instead", "GetAsStatus()")
138129
std::string GetString(char separator = '\n');
139130

131+
/// Copies the diagnostics into an lldb::Status.
132+
Status GetAsStatus(lldb::ExpressionResults result);
133+
140134
void Dump(Log *log);
141135

142136
const std::string &GetFixedExpression() { return m_fixed_expression; }

lldb/include/lldb/Utility/Status.h

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#ifndef LLDB_UTILITY_STATUS_H
1010
#define LLDB_UTILITY_STATUS_H
1111

12+
#include "lldb/Utility/FileSpec.h"
1213
#include "lldb/lldb-defines.h"
1314
#include "lldb/lldb-enumerations.h"
1415
#include "llvm/ADT/StringRef.h"
@@ -47,7 +48,34 @@ class Status {
4748
/// into ValueType.
4849
typedef uint32_t ValueType;
4950

50-
Status();
51+
/// A customizable "detail" for an error. For example, expression
52+
/// evaluation failures often have more than one diagnostic that the
53+
/// UI layer might want to render differently.
54+
///
55+
/// Running example:
56+
/// (lldb) expr 1+x
57+
/// error: <user expression 0>:1:3: use of undeclared identifier 'foo'
58+
/// 1+foo
59+
/// ^
60+
struct Detail {
61+
struct SourceLocation {
62+
FileSpec file;
63+
unsigned line = 0;
64+
uint16_t column = 0;
65+
uint16_t length = 0;
66+
bool in_user_input = false;
67+
};
68+
/// Contains {{}, 1, 3, 3, true} in the example above.
69+
std::optional<SourceLocation> source_location;
70+
/// Contains eSeverityError in the example above.
71+
lldb::Severity severity = lldb::eSeverityInfo;
72+
/// Contains "use of undeclared identifier 'x'" in the example above.
73+
std::string message;
74+
/// Contains the fully rendered error message.
75+
std::string rendered;
76+
};
77+
78+
Status() = default;
5179

5280
/// Initialize the error object with a generic success value.
5381
///
@@ -57,7 +85,8 @@ class Status {
5785
/// \param[in] type
5886
/// The type for \a err.
5987
explicit Status(ValueType err, lldb::ErrorType type = lldb::eErrorTypeGeneric,
60-
std::string msg = {});
88+
std::string msg = {})
89+
: m_code(err), m_type(type), m_string(std::move(msg)) {}
6190

6291
Status(std::error_code EC);
6392

@@ -83,6 +112,12 @@ class Status {
83112
return Status(result, lldb::eErrorTypeExpression, msg);
84113
}
85114

115+
static Status
116+
FromExpressionErrorDetails(lldb::ExpressionResults result,
117+
llvm::SmallVectorImpl<Detail> &&details) {
118+
return Status(result, lldb::eErrorTypeExpression, std::move(details));
119+
}
120+
86121
/// Set the current error to errno.
87122
///
88123
/// Update the error value to be \c errno and update the type to be \c
@@ -144,13 +179,24 @@ class Status {
144179
/// success (non-erro), \b false otherwise.
145180
bool Success() const;
146181

182+
/// Get the list of details for this error. If this is non-empty,
183+
/// clients can use this to render more appealing error messages
184+
/// from the details. If you just want a pre-rendered string, use
185+
/// AsCString() instead. Currently details are only used for
186+
/// eErrorTypeExpression.
187+
llvm::ArrayRef<Detail> GetDetails() const;
188+
147189
protected:
190+
/// Separate so the main constructor doesn't need to deal with the array.
191+
Status(ValueType err, lldb::ErrorType type,
192+
llvm::SmallVectorImpl<Detail> &&details);
148193
/// Status code as an integer value.
149194
ValueType m_code = 0;
150195
/// The type of the above error code.
151196
lldb::ErrorType m_type = lldb::eErrorTypeInvalid;
152197
/// A string representation of the error code.
153198
mutable std::string m_string;
199+
llvm::SmallVector<Detail, 0> m_details;
154200
};
155201

156202
} // namespace lldb_private

lldb/source/Expression/DiagnosticManager.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,23 @@ std::string DiagnosticManager::GetString(char separator) {
6565
return ret;
6666
}
6767

68+
Status DiagnosticManager::GetAsStatus(lldb::ExpressionResults result) {
69+
llvm::SmallVector<Status::Detail, 0> details;
70+
details.reserve(m_diagnostics.size());
71+
for (const auto &diagnostic : m_diagnostics)
72+
details.push_back(diagnostic->GetDetail());
73+
return Status::FromExpressionErrorDetails(result, std::move(details));
74+
}
75+
76+
void DiagnosticManager::AddDiagnostic(llvm::StringRef message,
77+
lldb::Severity severity,
78+
DiagnosticOrigin origin,
79+
uint32_t compiler_id) {
80+
m_diagnostics.emplace_back(std::make_unique<Diagnostic>(
81+
origin, compiler_id,
82+
Status::Detail{{}, severity, message.str(), message.str()}));
83+
}
84+
6885
size_t DiagnosticManager::Printf(lldb::Severity severity, const char *format,
6986
...) {
7087
StreamString ss;
@@ -85,3 +102,13 @@ void DiagnosticManager::PutString(lldb::Severity severity,
85102
return;
86103
AddDiagnostic(str, severity, eDiagnosticOriginLLDB);
87104
}
105+
106+
void Diagnostic::AppendMessage(llvm::StringRef message,
107+
bool precede_with_newline) {
108+
if (precede_with_newline) {
109+
m_detail.message.push_back('\n');
110+
m_detail.rendered.push_back('\n');
111+
}
112+
m_detail.message += message;
113+
m_detail.rendered += message;
114+
}

lldb/source/Expression/UserExpression.cpp

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -328,18 +328,19 @@ 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 = diagnostic_manager.GetAsStatus(execution_results);
343344
}
344345
}
345346

@@ -384,8 +385,7 @@ UserExpression::Evaluate(ExecutionContext &exe_ctx,
384385
error = Status::FromExpressionError(
385386
execution_results, "expression failed to execute, unknown error");
386387
else
387-
error = Status::FromExpressionError(execution_results,
388-
diagnostic_manager.GetString());
388+
error = diagnostic_manager.GetAsStatus(execution_results);
389389
} else {
390390
if (expr_result) {
391391
result_valobj_sp = expr_result->GetValueObject();

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(Status::Detail detail, uint32_t compiler_id)
33+
: Diagnostic(eDiagnosticOriginClang, compiler_id, detail) {}
3534

3635
~ClangDiagnostic() override = default;
3736

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

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include "clang/Frontend/FrontendActions.h"
2727
#include "clang/Frontend/FrontendDiagnostic.h"
2828
#include "clang/Frontend/FrontendPluginRegistry.h"
29+
#include "clang/Frontend/TextDiagnostic.h"
2930
#include "clang/Frontend/TextDiagnosticBuffer.h"
3031
#include "clang/Frontend/TextDiagnosticPrinter.h"
3132
#include "clang/Lex/Preprocessor.h"
@@ -212,20 +213,20 @@ class ClangDiagnosticManagerAdapter : public clang::DiagnosticConsumer {
212213
m_passthrough->HandleDiagnostic(DiagLevel, Info);
213214
m_os->flush();
214215

215-
lldb::Severity severity;
216+
Status::Detail detail;
216217
bool make_new_diagnostic = true;
217218

218219
switch (DiagLevel) {
219220
case DiagnosticsEngine::Level::Fatal:
220221
case DiagnosticsEngine::Level::Error:
221-
severity = lldb::eSeverityError;
222+
detail.severity = lldb::eSeverityError;
222223
break;
223224
case DiagnosticsEngine::Level::Warning:
224-
severity = lldb::eSeverityWarning;
225+
detail.severity = lldb::eSeverityWarning;
225226
break;
226227
case DiagnosticsEngine::Level::Remark:
227228
case DiagnosticsEngine::Level::Ignored:
228-
severity = lldb::eSeverityInfo;
229+
detail.severity = lldb::eSeverityInfo;
229230
break;
230231
case DiagnosticsEngine::Level::Note:
231232
m_manager->AppendMessageToDiagnostic(m_output);
@@ -254,14 +255,46 @@ class ClangDiagnosticManagerAdapter : public clang::DiagnosticConsumer {
254255
std::string stripped_output =
255256
std::string(llvm::StringRef(m_output).trim());
256257

257-
auto new_diagnostic = std::make_unique<ClangDiagnostic>(
258-
stripped_output, severity, Info.getID());
258+
// Translate the source location.
259+
if (Info.hasSourceManager()) {
260+
Status::Detail::SourceLocation loc;
261+
clang::SourceManager &sm = Info.getSourceManager();
262+
const clang::SourceLocation sloc = Info.getLocation();
263+
if (sloc.isValid()) {
264+
const clang::FullSourceLoc fsloc(sloc, sm);
265+
clang::PresumedLoc PLoc = fsloc.getPresumedLoc(true);
266+
StringRef filename =
267+
PLoc.isValid() ? PLoc.getFilename() : StringRef{};
268+
loc.file = FileSpec(filename);
269+
loc.line = fsloc.getSpellingLineNumber();
270+
loc.column = fsloc.getSpellingColumnNumber();
271+
// A heuristic detecting the #line 1 "<user expression 1>".
272+
loc.in_user_input = filename.starts_with("<user expression ");
273+
// Find the range of the primary location.
274+
for (const auto &range : Info.getRanges()) {
275+
if (range.getBegin() == sloc) {
276+
// FIXME: This is probably not handling wide characters correctly.
277+
unsigned end_col = sm.getSpellingColumnNumber(range.getEnd());
278+
if (end_col > loc.column)
279+
loc.length = end_col - loc.column;
280+
break;
281+
}
282+
}
283+
detail.source_location = loc;
284+
}
285+
}
286+
llvm::SmallString<0> msg;
287+
Info.FormatDiagnostic(msg);
288+
detail.message = msg.str();
289+
detail.rendered = stripped_output;
290+
auto new_diagnostic =
291+
std::make_unique<ClangDiagnostic>(detail, Info.getID());
259292

260293
// Don't store away warning fixits, since the compiler doesn't have
261294
// enough context in an expression for the warning to be useful.
262295
// FIXME: Should we try to filter out FixIts that apply to our generated
263296
// code, and not the user's expression?
264-
if (severity == lldb::eSeverityError)
297+
if (detail.severity == lldb::eSeverityError)
265298
AddAllFixIts(new_diagnostic.get(), Info);
266299

267300
m_manager->AddDiagnostic(std::move(new_diagnostic));

0 commit comments

Comments
 (0)