Skip to content

Commit 2225ef0

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 2225ef0

File tree

8 files changed

+163
-58
lines changed

8 files changed

+163
-58
lines changed

lldb/include/lldb/Expression/DiagnosticManager.h

Lines changed: 63 additions & 28 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,46 @@
2023

2124
namespace lldb_private {
2225

26+
/// A customizable "detail" for an error. For example, expression
27+
/// evaluation failures often have more than one diagnostic that the
28+
/// UI layer might want to render differently.
29+
///
30+
/// Running example:
31+
/// (lldb) expr 1+foo
32+
/// error: <user expression 0>:1:3: use of undeclared identifier 'foo'
33+
/// 1+foo
34+
/// ^
35+
struct DiagnosticDetail {
36+
struct SourceLocation {
37+
FileSpec file;
38+
unsigned line = 0;
39+
uint16_t column = 0;
40+
uint16_t length = 0;
41+
bool in_user_input = false;
42+
};
43+
/// Contains {{}, 1, 3, 3, true} in the example above.
44+
std::optional<SourceLocation> source_location;
45+
/// Contains eSeverityError in the example above.
46+
lldb::Severity severity = lldb::eSeverityInfo;
47+
/// Contains "use of undeclared identifier 'x'" in the example above.
48+
std::string message;
49+
/// Contains the fully rendered error message.
50+
std::string rendered;
51+
};
52+
53+
/// An llvm::Error used to communicate diagnostics in Status. Multiple
54+
/// diagnostics may be chained in an llvm::ErrorList.
55+
class DetailedExpressionError
56+
: public llvm::ErrorInfo<DetailedExpressionError, llvm::ECError> {
57+
DiagnosticDetail m_detail;
58+
59+
public:
60+
using llvm::ErrorInfo<DetailedExpressionError, llvm::ECError>::ErrorInfo;
61+
DetailedExpressionError(DiagnosticDetail detail) {}
62+
std::string message() const override;
63+
static char ID;
64+
};
65+
2366
enum DiagnosticOrigin {
2467
eDiagnosticOriginUnknown = 0,
2568
eDiagnosticOriginLLDB,
@@ -49,37 +92,28 @@ class Diagnostic {
4992
}
5093
}
5194

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

6199
virtual ~Diagnostic() = default;
62100

63101
virtual bool HasFixIts() const { return false; }
64102

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

67105
uint32_t GetCompilerID() const { return m_compiler_id; }
68106

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

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

78112
protected:
79-
std::string m_message;
80-
lldb::Severity m_severity;
81113
DiagnosticOrigin m_origin;
82-
uint32_t m_compiler_id; // Compiler-specific diagnostic ID
114+
/// Compiler-specific diagnostic ID.
115+
uint32_t m_compiler_id;
116+
DiagnosticDetail m_detail;
83117
};
84118

85119
typedef std::vector<std::unique_ptr<Diagnostic>> DiagnosticList;
@@ -102,10 +136,7 @@ class DiagnosticManager {
102136

103137
void AddDiagnostic(llvm::StringRef message, lldb::Severity severity,
104138
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-
}
139+
uint32_t compiler_id = LLDB_INVALID_COMPILER_ID);
109140

110141
void AddDiagnostic(std::unique_ptr<Diagnostic> diagnostic) {
111142
if (diagnostic)
@@ -130,13 +161,17 @@ class DiagnosticManager {
130161
m_diagnostics.back()->AppendMessage(str);
131162
}
132163

133-
// Returns a string containing errors in this format:
134-
//
135-
// "error: error text\n
136-
// warning: warning text\n
137-
// remark text\n"
164+
/// Returns a string containing errors in this format:
165+
///
166+
/// "error: error text\n
167+
/// warning: warning text\n
168+
/// remark text\n"
169+
LLVM_DEPRECATED("Use GetAsStatus instead", "GetAsStatus()")
138170
std::string GetString(char separator = '\n');
139171

172+
/// Copies the diagnostics into an llvm::Error.
173+
llvm::Error GetAsError(lldb::ExpressionResults result) const;
174+
140175
void Dump(Log *log);
141176

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

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/Expression/DiagnosticManager.cpp

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@
1414
#include "lldb/Utility/StreamString.h"
1515

1616
using namespace lldb_private;
17+
char DetailedExpressionError::ID;
18+
std::string DetailedExpressionError::message() const {
19+
return m_detail.rendered;
20+
}
1721

1822
void DiagnosticManager::Dump(Log *log) {
1923
if (!log)
@@ -65,6 +69,27 @@ std::string DiagnosticManager::GetString(char separator) {
6569
return ret;
6670
}
6771

72+
llvm::Error Diagnostic::GetAsError() const {
73+
return llvm::make_error<DetailedExpressionError>(m_detail);
74+
}
75+
76+
llvm::Error
77+
DiagnosticManager::GetAsError(lldb::ExpressionResults result) const {
78+
llvm::Error diags = Status::FromExpressionError(result, "").takeError();
79+
for (const auto &diagnostic : m_diagnostics)
80+
diags = llvm::joinErrors(std::move(diags), diagnostic->GetAsError());
81+
return diags;
82+
}
83+
84+
void DiagnosticManager::AddDiagnostic(llvm::StringRef message,
85+
lldb::Severity severity,
86+
DiagnosticOrigin origin,
87+
uint32_t compiler_id) {
88+
m_diagnostics.emplace_back(std::make_unique<Diagnostic>(
89+
origin, compiler_id,
90+
DiagnosticDetail{{}, severity, message.str(), message.str()}));
91+
}
92+
6893
size_t DiagnosticManager::Printf(lldb::Severity severity, const char *format,
6994
...) {
7095
StreamString ss;
@@ -85,3 +110,13 @@ void DiagnosticManager::PutString(lldb::Severity severity,
85110
return;
86111
AddDiagnostic(str, severity, eDiagnosticOriginLLDB);
87112
}
113+
114+
void Diagnostic::AppendMessage(llvm::StringRef message,
115+
bool precede_with_newline) {
116+
if (precede_with_newline) {
117+
m_detail.message.push_back('\n');
118+
m_detail.rendered.push_back('\n');
119+
}
120+
m_detail.message += message;
121+
m_detail.rendered += message;
122+
}

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/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

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+
DiagnosticDetail 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+
DiagnosticDetail::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));

lldb/test/API/lang/objc/modules-compile-error/TestModulesCompileError.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ def test(self):
2121
"expr @import LLDBTestModule",
2222
error=True,
2323
substrs=[
24-
"module.h:4:1: use of undeclared identifier 'syntax_error_for_lldb_to_find'",
24+
"module.h:4:1: error: use of undeclared identifier 'syntax_error_for_lldb_to_find'",
2525
"syntax_error_for_lldb_to_find // comment that tests source printing",
2626
"could not build module 'LLDBTestModule'",
2727
],

lldb/unittests/Expression/DiagnosticManagerTest.cpp

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ class FixItDiag : public Diagnostic {
1919

2020
public:
2121
FixItDiag(llvm::StringRef msg, bool has_fixits)
22-
: Diagnostic(msg, lldb::eSeverityError,
23-
DiagnosticOrigin::eDiagnosticOriginLLDB, custom_diag_id),
22+
: Diagnostic(DiagnosticOrigin::eDiagnosticOriginLLDB, custom_diag_id,
23+
DiagnosticDetail{{}, lldb::eSeverityError, msg.str(), {}}),
2424
m_has_fixits(has_fixits) {}
2525
bool HasFixIts() const override { return m_has_fixits; }
2626
};
@@ -30,8 +30,8 @@ namespace {
3030
class TextDiag : public Diagnostic {
3131
public:
3232
TextDiag(llvm::StringRef msg, lldb::Severity severity)
33-
: Diagnostic(msg, severity, DiagnosticOrigin::eDiagnosticOriginLLDB,
34-
custom_diag_id) {}
33+
: Diagnostic(DiagnosticOrigin::eDiagnosticOriginLLDB, custom_diag_id,
34+
DiagnosticDetail{{}, severity, msg.str(), {}}) {}
3535
};
3636
} // namespace
3737

@@ -42,8 +42,8 @@ TEST(DiagnosticManagerTest, AddDiagnostic) {
4242
std::string msg = "foo bar has happened";
4343
lldb::Severity severity = lldb::eSeverityError;
4444
DiagnosticOrigin origin = DiagnosticOrigin::eDiagnosticOriginLLDB;
45-
auto diag =
46-
std::make_unique<Diagnostic>(msg, severity, origin, custom_diag_id);
45+
auto diag = std::make_unique<Diagnostic>(
46+
origin, custom_diag_id, DiagnosticDetail{{}, severity, msg, {}});
4747
mgr.AddDiagnostic(std::move(diag));
4848
EXPECT_EQ(1U, mgr.Diagnostics().size());
4949
const Diagnostic *got = mgr.Diagnostics().front().get();

0 commit comments

Comments
 (0)