Skip to content

Commit

Permalink
[lldb] Expose structured errors in SBError (#120784)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
adrian-prantl authored Dec 20, 2024
1 parent d8e10d1 commit d9cc37f
Show file tree
Hide file tree
Showing 10 changed files with 161 additions and 85 deletions.
5 changes: 5 additions & 0 deletions lldb/include/lldb/API/SBError.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,13 @@ class LLDB_API SBError {

bool Success() const;

/// Get the error code.
uint32_t GetError() const;

/// Get the error in machine-readable form. Particularly useful for
/// compiler diagnostics.
SBStructuredData GetErrorData() const;

lldb::ErrorType GetType() const;

void SetError(uint32_t err, lldb::ErrorType type);
Expand Down
1 change: 1 addition & 0 deletions lldb/include/lldb/API/SBStructuredData.h
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ class SBStructuredData {
friend class SBLaunchInfo;
friend class SBDebugger;
friend class SBFrame;
friend class SBError;
friend class SBTarget;
friend class SBProcess;
friend class SBThread;
Expand Down
14 changes: 10 additions & 4 deletions lldb/include/lldb/Utility/DiagnosticsRendering.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,19 +57,25 @@ struct DiagnosticDetail {
std::string rendered;
};

StructuredData::ObjectSP Serialize(llvm::ArrayRef<DiagnosticDetail> details);

void RenderDiagnosticDetails(Stream &stream,
std::optional<uint16_t> offset_in_command,
bool show_inline,
llvm::ArrayRef<DiagnosticDetail> details);

class DiagnosticError
: public llvm::ErrorInfo<DiagnosticError, CloneableECError> {
public:
using llvm::ErrorInfo<DiagnosticError, CloneableECError>::ErrorInfo;
DiagnosticError(std::error_code ec) : ErrorInfo(ec) {}
lldb::ErrorType GetErrorType() const override;
virtual llvm::ArrayRef<DiagnosticDetail> GetDetails() const = 0;
StructuredData::ObjectSP GetAsStructuredData() const override {
return Serialize(GetDetails());
}
static char ID;
};

void RenderDiagnosticDetails(Stream &stream,
std::optional<uint16_t> offset_in_command,
bool show_inline,
llvm::ArrayRef<DiagnosticDetail> details);
} // namespace lldb_private
#endif
6 changes: 6 additions & 0 deletions lldb/include/lldb/Utility/Status.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#define LLDB_UTILITY_STATUS_H

#include "lldb/Utility/FileSpec.h"
#include "lldb/Utility/StructuredData.h"
#include "lldb/lldb-defines.h"
#include "lldb/lldb-enumerations.h"
#include "llvm/ADT/StringRef.h"
Expand Down Expand Up @@ -38,6 +39,7 @@ class CloneableError
CloneableError() : ErrorInfo() {}
virtual std::unique_ptr<CloneableError> Clone() const = 0;
virtual lldb::ErrorType GetErrorType() const = 0;
virtual StructuredData::ObjectSP GetAsStructuredData() const = 0;
static char ID;
};

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

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

/// Get the error in machine-readable form.
StructuredData::ObjectSP GetAsStructuredData() const;

/// Clear the object state.
///
/// Reverts the state of this object to contain a generic success value and
Expand Down
14 changes: 14 additions & 0 deletions lldb/source/API/SBError.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
#include "lldb/API/SBError.h"
#include "Utils.h"
#include "lldb/API/SBStream.h"
#include "lldb/API/SBStructuredData.h"
#include "lldb/Core/StructuredDataImpl.h"
#include "lldb/Utility/Instrumentation.h"
#include "lldb/Utility/Status.h"
#include "lldb/Utility/VASPrintf.h"
Expand Down Expand Up @@ -97,6 +99,18 @@ uint32_t SBError::GetError() const {
return err;
}

SBStructuredData SBError::GetErrorData() const {
LLDB_INSTRUMENT_VA(this);

SBStructuredData sb_data;
if (!m_opaque_up)
return sb_data;

StructuredData::ObjectSP data(m_opaque_up->GetAsStructuredData());
sb_data.m_impl_up->SetObjectSP(data);
return sb_data;
}

ErrorType SBError::GetType() const {
LLDB_INSTRUMENT_VA(this);

Expand Down
52 changes: 1 addition & 51 deletions lldb/source/Interpreter/CommandReturnObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -169,57 +169,7 @@ std::string CommandReturnObject::GetErrorString(bool with_diagnostics) {
}

StructuredData::ObjectSP CommandReturnObject::GetErrorData() {
auto make_array = []() { return std::make_unique<StructuredData::Array>(); };
auto make_bool = [](bool b) {
return std::make_unique<StructuredData::Boolean>(b);
};
auto make_dict = []() {
return std::make_unique<StructuredData::Dictionary>();
};
auto make_int = [](unsigned i) {
return std::make_unique<StructuredData::UnsignedInteger>(i);
};
auto make_string = [](llvm::StringRef s) {
return std::make_unique<StructuredData::String>(s);
};
auto dict_up = make_dict();
dict_up->AddItem("version", make_int(1));
auto array_up = make_array();
for (const DiagnosticDetail &diag : m_diagnostics) {
auto detail_up = make_dict();
if (auto &sloc = diag.source_location) {
auto sloc_up = make_dict();
sloc_up->AddItem("file", make_string(sloc->file.GetPath()));
sloc_up->AddItem("line", make_int(sloc->line));
sloc_up->AddItem("length", make_int(sloc->length));
sloc_up->AddItem("hidden", make_bool(sloc->hidden));
sloc_up->AddItem("in_user_input", make_bool(sloc->in_user_input));
detail_up->AddItem("source_location", std::move(sloc_up));
}
llvm::StringRef severity = "unknown";
switch (diag.severity) {
case lldb::eSeverityError:
severity = "error";
break;
case lldb::eSeverityWarning:
severity = "warning";
break;
case lldb::eSeverityInfo:
severity = "note";
break;
}
detail_up->AddItem("severity", make_string(severity));
detail_up->AddItem("message", make_string(diag.message));
detail_up->AddItem("rendered", make_string(diag.rendered));
array_up->AddItem(std::move(detail_up));
}
dict_up->AddItem("details", std::move(array_up));
if (auto stream_sp = m_err_stream.GetStreamAtIndex(eStreamStringIndex)) {
auto text = std::static_pointer_cast<StreamString>(stream_sp)->GetString();
if (!text.empty())
dict_up->AddItem("text", make_string(text));
}
return dict_up;
return Serialize(m_diagnostics);
}

// Similar to AppendError, but do not prepend 'Status: ' to message, and don't
Expand Down
40 changes: 40 additions & 0 deletions lldb/source/Utility/DiagnosticsRendering.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,46 @@ lldb::ErrorType DiagnosticError::GetErrorType() const {
return lldb::eErrorTypeExpression;
}

StructuredData::ObjectSP Serialize(llvm::ArrayRef<DiagnosticDetail> details) {
auto make_array = []() { return std::make_unique<StructuredData::Array>(); };
auto make_dict = []() {
return std::make_unique<StructuredData::Dictionary>();
};
auto dict_up = make_dict();
dict_up->AddIntegerItem("version", 1u);
auto array_up = make_array();
for (const DiagnosticDetail &diag : details) {
auto detail_up = make_dict();
if (auto &sloc = diag.source_location) {
auto sloc_up = make_dict();
sloc_up->AddStringItem("file", sloc->file.GetPath());
sloc_up->AddIntegerItem("line", sloc->line);
sloc_up->AddIntegerItem("length", sloc->length);
sloc_up->AddBooleanItem("hidden", sloc->hidden);
sloc_up->AddBooleanItem("in_user_input", sloc->in_user_input);
detail_up->AddItem("source_location", std::move(sloc_up));
}
llvm::StringRef severity = "unknown";
switch (diag.severity) {
case lldb::eSeverityError:
severity = "error";
break;
case lldb::eSeverityWarning:
severity = "warning";
break;
case lldb::eSeverityInfo:
severity = "note";
break;
}
detail_up->AddStringItem("severity", severity);
detail_up->AddStringItem("message", diag.message);
detail_up->AddStringItem("rendered", diag.rendered);
array_up->AddItem(std::move(detail_up));
}
dict_up->AddItem("details", std::move(array_up));
return dict_up;
}

static llvm::raw_ostream &PrintSeverity(Stream &stream,
lldb::Severity severity) {
llvm::HighlightColor color;
Expand Down
24 changes: 24 additions & 0 deletions lldb/source/Utility/Status.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,30 @@ lldb::ErrorType Win32Error::GetErrorType() const {
return lldb::eErrorTypeWin32;
}

StructuredData::ObjectSP Status::GetAsStructuredData() const {
auto dict_up = std::make_unique<StructuredData::Dictionary>();
auto array_up = std::make_unique<StructuredData::Array>();
llvm::visitErrors(m_error, [&](const llvm::ErrorInfoBase &error) {
if (error.isA<CloneableError>())
array_up->AddItem(
static_cast<const CloneableError &>(error).GetAsStructuredData());
else
array_up->AddStringItem(error.message());
});
dict_up->AddIntegerItem("version", 1u);
dict_up->AddIntegerItem("type", (unsigned)GetType());
dict_up->AddItem("errors", std::move(array_up));
return dict_up;
}

StructuredData::ObjectSP CloneableECError::GetAsStructuredData() const {
auto dict_up = std::make_unique<StructuredData::Dictionary>();
dict_up->AddIntegerItem("version", 1u);
dict_up->AddIntegerItem("error_code", EC.value());
dict_up->AddStringItem("message", message());
return dict_up;
}

ErrorType Status::GetType() const {
ErrorType result = eErrorTypeInvalid;
llvm::visitErrors(m_error, [&](const llvm::ErrorInfoBase &error) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,37 +207,56 @@ def test_command_expr_sbdata(self):
(target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(
self, "// Break here", self.main_source_spec
)

def check_error(diags):
# Version.
version = diags.GetValueForKey("version")
self.assertEqual(version.GetIntegerValue(), 1)

details = diags.GetValueForKey("details")

# Detail 1/2: undeclared 'a'
diag = details.GetItemAtIndex(0)

severity = diag.GetValueForKey("severity")
message = diag.GetValueForKey("message")
rendered = diag.GetValueForKey("rendered")
sloc = diag.GetValueForKey("source_location")
filename = sloc.GetValueForKey("file")
hidden = sloc.GetValueForKey("hidden")
in_user_input = sloc.GetValueForKey("in_user_input")

self.assertEqual(str(severity), "error")
self.assertIn("undeclared identifier 'a'", str(message))
# The rendered string should contain the source file.
self.assertIn("user expression", str(rendered))
self.assertIn("user expression", str(filename))
self.assertFalse(hidden.GetBooleanValue())
self.assertTrue(in_user_input.GetBooleanValue())

# Detail 1/2: undeclared 'b'
diag = details.GetItemAtIndex(1)
message = diag.GetValueForKey("message")
self.assertIn("undeclared identifier 'b'", str(message))

# Test diagnostics in CommandReturnObject
interp = self.dbg.GetCommandInterpreter()
cro = lldb.SBCommandReturnObject()
interp.HandleCommand("expression -- a+b", cro)

diags = cro.GetErrorData()
# Version.
version = diags.GetValueForKey("version")
self.assertEqual(version.GetIntegerValue(), 1)
check_error(diags)

details = diags.GetValueForKey("details")

# Detail 1/2: undeclared 'a'
diag = details.GetItemAtIndex(0)

severity = diag.GetValueForKey("severity")
message = diag.GetValueForKey("message")
rendered = diag.GetValueForKey("rendered")
sloc = diag.GetValueForKey("source_location")
filename = sloc.GetValueForKey("file")
hidden = sloc.GetValueForKey("hidden")
in_user_input = sloc.GetValueForKey("in_user_input")

self.assertEqual(str(severity), "error")
self.assertIn("undeclared identifier 'a'", str(message))
# The rendered string should contain the source file.
self.assertIn("user expression", str(rendered))
self.assertIn("user expression", str(filename))
self.assertFalse(hidden.GetBooleanValue())
self.assertTrue(in_user_input.GetBooleanValue())

# Detail 1/2: undeclared 'b'
diag = details.GetItemAtIndex(1)
message = diag.GetValueForKey("message")
self.assertIn("undeclared identifier 'b'", str(message))
# Test diagnostics in SBError
frame = thread.GetSelectedFrame()
value = frame.EvaluateExpression("a+b")
error = value.GetError()
self.assertTrue(error.Fail())
self.assertEquals(error.GetType(), lldb.eErrorTypeExpression)
data = error.GetErrorData()
version = data.GetValueForKey("version")
self.assertEqual(version.GetIntegerValue(), 1)
err_ty = data.GetValueForKey("type")
self.assertEqual(err_ty.GetIntegerValue(), lldb.eErrorTypeExpression)
diags = data.GetValueForKey("errors").GetItemAtIndex(0)
check_error(diags)
15 changes: 13 additions & 2 deletions lldb/test/API/commands/frame/var/TestFrameVar.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,12 +113,23 @@ def check_frame_variable_errors(self, thread, error_strings):
frame = thread.GetFrameAtIndex(0)
var_list = frame.GetVariables(True, True, False, True)
self.assertEqual(var_list.GetSize(), 0)
api_error = var_list.GetError().GetCString()
api_error = var_list.GetError()
api_error_str = api_error.GetCString()

for s in error_strings:
self.assertIn(s, command_error)
for s in error_strings:
self.assertIn(s, api_error)
self.assertIn(s, api_error_str)

# Check the structured error data.
data = api_error.GetErrorData()
version = data.GetValueForKey("version")
self.assertEqual(version.GetIntegerValue(), 1)
err_ty = data.GetValueForKey("type")
self.assertEqual(err_ty.GetIntegerValue(), lldb.eErrorTypeGeneric)
message = str(data.GetValueForKey("errors").GetItemAtIndex(0))
for s in error_strings:
self.assertIn(s, message)

@skipIfRemote
@skipUnlessDarwin
Expand Down

0 comments on commit d9cc37f

Please sign in to comment.