Skip to content

Commit 3113275

Browse files
committed
[lldb] Expose structured errors in SBError
Building on top of previous work that exposed expression diagnostics via SBCommandReturnObject, this patch generalizes the support to expose any SBError as machine-readable structured data. One use-case of this is to allow IDEs to better visualize expression diagnostics. rdar://139997604
1 parent 8c00900 commit 3113275

File tree

10 files changed

+161
-85
lines changed

10 files changed

+161
-85
lines changed

lldb/include/lldb/API/SBError.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,13 @@ class LLDB_API SBError {
4444

4545
bool Success() const;
4646

47+
/// Get the error code.
4748
uint32_t GetError() const;
4849

50+
/// Get the error in machine-readable form. Particularly useful for
51+
/// compiler diagnostics.
52+
SBStructuredData GetErrorData() const;
53+
4954
lldb::ErrorType GetType() const;
5055

5156
void SetError(uint32_t err, lldb::ErrorType type);

lldb/include/lldb/API/SBStructuredData.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ class SBStructuredData {
115115
friend class SBLaunchInfo;
116116
friend class SBDebugger;
117117
friend class SBFrame;
118+
friend class SBError;
118119
friend class SBTarget;
119120
friend class SBProcess;
120121
friend class SBThread;

lldb/include/lldb/Utility/DiagnosticsRendering.h

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,19 +57,25 @@ struct DiagnosticDetail {
5757
std::string rendered;
5858
};
5959

60+
StructuredData::ObjectSP Serialize(llvm::ArrayRef<DiagnosticDetail> details);
61+
62+
void RenderDiagnosticDetails(Stream &stream,
63+
std::optional<uint16_t> offset_in_command,
64+
bool show_inline,
65+
llvm::ArrayRef<DiagnosticDetail> details);
66+
6067
class DiagnosticError
6168
: public llvm::ErrorInfo<DiagnosticError, CloneableECError> {
6269
public:
6370
using llvm::ErrorInfo<DiagnosticError, CloneableECError>::ErrorInfo;
6471
DiagnosticError(std::error_code ec) : ErrorInfo(ec) {}
6572
lldb::ErrorType GetErrorType() const override;
6673
virtual llvm::ArrayRef<DiagnosticDetail> GetDetails() const = 0;
74+
StructuredData::ObjectSP GetAsStructuredData() const override {
75+
return Serialize(GetDetails());
76+
}
6777
static char ID;
6878
};
6979

70-
void RenderDiagnosticDetails(Stream &stream,
71-
std::optional<uint16_t> offset_in_command,
72-
bool show_inline,
73-
llvm::ArrayRef<DiagnosticDetail> details);
7480
} // namespace lldb_private
7581
#endif

lldb/include/lldb/Utility/Status.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#define LLDB_UTILITY_STATUS_H
1111

1212
#include "lldb/Utility/FileSpec.h"
13+
#include "lldb/Utility/StructuredData.h"
1314
#include "lldb/lldb-defines.h"
1415
#include "lldb/lldb-enumerations.h"
1516
#include "llvm/ADT/StringRef.h"
@@ -38,6 +39,7 @@ class CloneableError
3839
CloneableError() : ErrorInfo() {}
3940
virtual std::unique_ptr<CloneableError> Clone() const = 0;
4041
virtual lldb::ErrorType GetErrorType() const = 0;
42+
virtual StructuredData::ObjectSP GetAsStructuredData() const = 0;
4143
static char ID;
4244
};
4345

@@ -49,6 +51,7 @@ class CloneableECError
4951
std::error_code convertToErrorCode() const override { return EC; }
5052
void log(llvm::raw_ostream &OS) const override { OS << EC.message(); }
5153
lldb::ErrorType GetErrorType() const override;
54+
virtual StructuredData::ObjectSP GetAsStructuredData() const override;
5255
static char ID;
5356

5457
protected:
@@ -183,6 +186,9 @@ class Status {
183186
/// NULL otherwise.
184187
const char *AsCString(const char *default_error_str = "unknown error") const;
185188

189+
/// Get the error in machine-readable form.
190+
StructuredData::ObjectSP GetAsStructuredData() const;
191+
186192
/// Clear the object state.
187193
///
188194
/// Reverts the state of this object to contain a generic success value and

lldb/source/API/SBError.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
#include "lldb/API/SBError.h"
1010
#include "Utils.h"
1111
#include "lldb/API/SBStream.h"
12+
#include "lldb/API/SBStructuredData.h"
13+
#include "lldb/Core/StructuredDataImpl.h"
1214
#include "lldb/Utility/Instrumentation.h"
1315
#include "lldb/Utility/Status.h"
1416
#include "lldb/Utility/VASPrintf.h"
@@ -97,6 +99,18 @@ uint32_t SBError::GetError() const {
9799
return err;
98100
}
99101

102+
SBStructuredData SBError::GetErrorData() const {
103+
LLDB_INSTRUMENT_VA(this);
104+
105+
SBStructuredData sb_data;
106+
if (!m_opaque_up)
107+
return sb_data;
108+
109+
StructuredData::ObjectSP data(m_opaque_up->GetAsStructuredData());
110+
sb_data.m_impl_up->SetObjectSP(data);
111+
return sb_data;
112+
}
113+
100114
ErrorType SBError::GetType() const {
101115
LLDB_INSTRUMENT_VA(this);
102116

lldb/source/Interpreter/CommandReturnObject.cpp

Lines changed: 1 addition & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -169,57 +169,7 @@ std::string CommandReturnObject::GetErrorString(bool with_diagnostics) {
169169
}
170170

171171
StructuredData::ObjectSP CommandReturnObject::GetErrorData() {
172-
auto make_array = []() { return std::make_unique<StructuredData::Array>(); };
173-
auto make_bool = [](bool b) {
174-
return std::make_unique<StructuredData::Boolean>(b);
175-
};
176-
auto make_dict = []() {
177-
return std::make_unique<StructuredData::Dictionary>();
178-
};
179-
auto make_int = [](unsigned i) {
180-
return std::make_unique<StructuredData::UnsignedInteger>(i);
181-
};
182-
auto make_string = [](llvm::StringRef s) {
183-
return std::make_unique<StructuredData::String>(s);
184-
};
185-
auto dict_up = make_dict();
186-
dict_up->AddItem("version", make_int(1));
187-
auto array_up = make_array();
188-
for (const DiagnosticDetail &diag : m_diagnostics) {
189-
auto detail_up = make_dict();
190-
if (auto &sloc = diag.source_location) {
191-
auto sloc_up = make_dict();
192-
sloc_up->AddItem("file", make_string(sloc->file.GetPath()));
193-
sloc_up->AddItem("line", make_int(sloc->line));
194-
sloc_up->AddItem("length", make_int(sloc->length));
195-
sloc_up->AddItem("hidden", make_bool(sloc->hidden));
196-
sloc_up->AddItem("in_user_input", make_bool(sloc->in_user_input));
197-
detail_up->AddItem("source_location", std::move(sloc_up));
198-
}
199-
llvm::StringRef severity = "unknown";
200-
switch (diag.severity) {
201-
case lldb::eSeverityError:
202-
severity = "error";
203-
break;
204-
case lldb::eSeverityWarning:
205-
severity = "warning";
206-
break;
207-
case lldb::eSeverityInfo:
208-
severity = "note";
209-
break;
210-
}
211-
detail_up->AddItem("severity", make_string(severity));
212-
detail_up->AddItem("message", make_string(diag.message));
213-
detail_up->AddItem("rendered", make_string(diag.rendered));
214-
array_up->AddItem(std::move(detail_up));
215-
}
216-
dict_up->AddItem("details", std::move(array_up));
217-
if (auto stream_sp = m_err_stream.GetStreamAtIndex(eStreamStringIndex)) {
218-
auto text = std::static_pointer_cast<StreamString>(stream_sp)->GetString();
219-
if (!text.empty())
220-
dict_up->AddItem("text", make_string(text));
221-
}
222-
return dict_up;
172+
return Serialize(m_diagnostics);
223173
}
224174

225175
// Similar to AppendError, but do not prepend 'Status: ' to message, and don't

lldb/source/Utility/DiagnosticsRendering.cpp

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,46 @@ lldb::ErrorType DiagnosticError::GetErrorType() const {
2020
return lldb::eErrorTypeExpression;
2121
}
2222

23+
StructuredData::ObjectSP Serialize(llvm::ArrayRef<DiagnosticDetail> details) {
24+
auto make_array = []() { return std::make_unique<StructuredData::Array>(); };
25+
auto make_dict = []() {
26+
return std::make_unique<StructuredData::Dictionary>();
27+
};
28+
auto dict_up = make_dict();
29+
dict_up->AddIntegerItem("version", 1u);
30+
auto array_up = make_array();
31+
for (const DiagnosticDetail &diag : details) {
32+
auto detail_up = make_dict();
33+
if (auto &sloc = diag.source_location) {
34+
auto sloc_up = make_dict();
35+
sloc_up->AddStringItem("file", sloc->file.GetPath());
36+
sloc_up->AddIntegerItem("line", sloc->line);
37+
sloc_up->AddIntegerItem("length", sloc->length);
38+
sloc_up->AddBooleanItem("hidden", sloc->hidden);
39+
sloc_up->AddBooleanItem("in_user_input", sloc->in_user_input);
40+
detail_up->AddItem("source_location", std::move(sloc_up));
41+
}
42+
llvm::StringRef severity = "unknown";
43+
switch (diag.severity) {
44+
case lldb::eSeverityError:
45+
severity = "error";
46+
break;
47+
case lldb::eSeverityWarning:
48+
severity = "warning";
49+
break;
50+
case lldb::eSeverityInfo:
51+
severity = "note";
52+
break;
53+
}
54+
detail_up->AddStringItem("severity", severity);
55+
detail_up->AddStringItem("message", diag.message);
56+
detail_up->AddStringItem("rendered", diag.rendered);
57+
array_up->AddItem(std::move(detail_up));
58+
}
59+
dict_up->AddItem("details", std::move(array_up));
60+
return dict_up;
61+
}
62+
2363
static llvm::raw_ostream &PrintSeverity(Stream &stream,
2464
lldb::Severity severity) {
2565
llvm::HighlightColor color;

lldb/source/Utility/Status.cpp

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,30 @@ lldb::ErrorType Win32Error::GetErrorType() const {
252252
return lldb::eErrorTypeWin32;
253253
}
254254

255+
StructuredData::ObjectSP Status::GetAsStructuredData() const {
256+
auto dict_up = std::make_unique<StructuredData::Dictionary>();
257+
auto array_up = std::make_unique<StructuredData::Array>();
258+
llvm::visitErrors(m_error, [&](const llvm::ErrorInfoBase &error) {
259+
if (error.isA<CloneableError>())
260+
array_up->AddItem(
261+
static_cast<const CloneableError &>(error).GetAsStructuredData());
262+
else
263+
array_up->AddStringItem(error.message());
264+
});
265+
dict_up->AddIntegerItem("version", 1u);
266+
dict_up->AddIntegerItem("type", (unsigned)GetType());
267+
dict_up->AddItem("errors", std::move(array_up));
268+
return dict_up;
269+
}
270+
271+
StructuredData::ObjectSP CloneableECError::GetAsStructuredData() const {
272+
auto dict_up = std::make_unique<StructuredData::Dictionary>();
273+
dict_up->AddIntegerItem("version", 1u);
274+
dict_up->AddIntegerItem("error_code", EC.value());
275+
dict_up->AddStringItem("message", message());
276+
return dict_up;
277+
}
278+
255279
ErrorType Status::GetType() const {
256280
ErrorType result = eErrorTypeInvalid;
257281
llvm::visitErrors(m_error, [&](const llvm::ErrorInfoBase &error) {

lldb/test/API/commands/expression/diagnostics/TestExprDiagnostics.py

Lines changed: 47 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -207,37 +207,56 @@ def test_command_expr_sbdata(self):
207207
(target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(
208208
self, "// Break here", self.main_source_spec
209209
)
210+
211+
def check_error(diags):
212+
# Version.
213+
version = diags.GetValueForKey("version")
214+
self.assertEqual(version.GetIntegerValue(), 1)
215+
216+
details = diags.GetValueForKey("details")
217+
218+
# Detail 1/2: undeclared 'a'
219+
diag = details.GetItemAtIndex(0)
220+
221+
severity = diag.GetValueForKey("severity")
222+
message = diag.GetValueForKey("message")
223+
rendered = diag.GetValueForKey("rendered")
224+
sloc = diag.GetValueForKey("source_location")
225+
filename = sloc.GetValueForKey("file")
226+
hidden = sloc.GetValueForKey("hidden")
227+
in_user_input = sloc.GetValueForKey("in_user_input")
228+
229+
self.assertEqual(str(severity), "error")
230+
self.assertIn("undeclared identifier 'a'", str(message))
231+
# The rendered string should contain the source file.
232+
self.assertIn("user expression", str(rendered))
233+
self.assertIn("user expression", str(filename))
234+
self.assertFalse(hidden.GetBooleanValue())
235+
self.assertTrue(in_user_input.GetBooleanValue())
236+
237+
# Detail 1/2: undeclared 'b'
238+
diag = details.GetItemAtIndex(1)
239+
message = diag.GetValueForKey("message")
240+
self.assertIn("undeclared identifier 'b'", str(message))
241+
242+
# Test diagnostics in CommandReturnObject
210243
interp = self.dbg.GetCommandInterpreter()
211244
cro = lldb.SBCommandReturnObject()
212245
interp.HandleCommand("expression -- a+b", cro)
213246

214247
diags = cro.GetErrorData()
215-
# Version.
216-
version = diags.GetValueForKey("version")
217-
self.assertEqual(version.GetIntegerValue(), 1)
248+
check_error(diags)
218249

219-
details = diags.GetValueForKey("details")
220-
221-
# Detail 1/2: undeclared 'a'
222-
diag = details.GetItemAtIndex(0)
223-
224-
severity = diag.GetValueForKey("severity")
225-
message = diag.GetValueForKey("message")
226-
rendered = diag.GetValueForKey("rendered")
227-
sloc = diag.GetValueForKey("source_location")
228-
filename = sloc.GetValueForKey("file")
229-
hidden = sloc.GetValueForKey("hidden")
230-
in_user_input = sloc.GetValueForKey("in_user_input")
231-
232-
self.assertEqual(str(severity), "error")
233-
self.assertIn("undeclared identifier 'a'", str(message))
234-
# The rendered string should contain the source file.
235-
self.assertIn("user expression", str(rendered))
236-
self.assertIn("user expression", str(filename))
237-
self.assertFalse(hidden.GetBooleanValue())
238-
self.assertTrue(in_user_input.GetBooleanValue())
239-
240-
# Detail 1/2: undeclared 'b'
241-
diag = details.GetItemAtIndex(1)
242-
message = diag.GetValueForKey("message")
243-
self.assertIn("undeclared identifier 'b'", str(message))
250+
# Test diagnostics in SBError
251+
frame = thread.GetSelectedFrame()
252+
value = frame.EvaluateExpression("a+b")
253+
error = value.GetError()
254+
self.assertTrue(error.Fail())
255+
self.assertEquals(error.GetType(), lldb.eErrorTypeExpression)
256+
data = error.GetErrorData()
257+
version = data.GetValueForKey("version")
258+
self.assertEqual(version.GetIntegerValue(), 1)
259+
err_ty = data.GetValueForKey("type")
260+
self.assertEqual(err_ty.GetIntegerValue(), lldb.eErrorTypeExpression)
261+
diags = data.GetValueForKey("errors").GetItemAtIndex(0)
262+
check_error(diags)

lldb/test/API/commands/frame/var/TestFrameVar.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,12 +113,23 @@ def check_frame_variable_errors(self, thread, error_strings):
113113
frame = thread.GetFrameAtIndex(0)
114114
var_list = frame.GetVariables(True, True, False, True)
115115
self.assertEqual(var_list.GetSize(), 0)
116-
api_error = var_list.GetError().GetCString()
116+
api_error = var_list.GetError()
117+
api_error_str = api_error.GetCString()
117118

118119
for s in error_strings:
119120
self.assertIn(s, command_error)
120121
for s in error_strings:
121-
self.assertIn(s, api_error)
122+
self.assertIn(s, api_error_str)
123+
124+
# Check the structured error data.
125+
data = api_error.GetErrorData()
126+
version = data.GetValueForKey("version")
127+
self.assertEqual(version.GetIntegerValue(), 1)
128+
err_ty = data.GetValueForKey("type")
129+
self.assertEqual(err_ty.GetIntegerValue(), lldb.eErrorTypeGeneric)
130+
message = str(data.GetValueForKey("errors").GetItemAtIndex(0))
131+
for s in error_strings:
132+
self.assertIn(s, message)
122133

123134
@skipIfRemote
124135
@skipUnlessDarwin

0 commit comments

Comments
 (0)