[lldb] Expose structured errors in SBError (#120784)
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
This commit is contained in:
@@ -44,8 +44,13 @@ public:
|
||||
|
||||
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);
|
||||
|
||||
@@ -115,6 +115,7 @@ protected:
|
||||
friend class SBLaunchInfo;
|
||||
friend class SBDebugger;
|
||||
friend class SBFrame;
|
||||
friend class SBError;
|
||||
friend class SBTarget;
|
||||
friend class SBProcess;
|
||||
friend class SBThread;
|
||||
|
||||
@@ -57,6 +57,13 @@ 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:
|
||||
@@ -64,12 +71,11 @@ public:
|
||||
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
|
||||
|
||||
@@ -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"
|
||||
@@ -38,6 +39,7 @@ public:
|
||||
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;
|
||||
};
|
||||
|
||||
@@ -49,6 +51,7 @@ public:
|
||||
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:
|
||||
@@ -183,6 +186,9 @@ public:
|
||||
/// 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
|
||||
|
||||
@@ -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"
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -207,37 +207,56 @@ note: candidate function not viable: requires single argument 'x', but 2 argumen
|
||||
(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")
|
||||
check_error(diags)
|
||||
|
||||
# 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)
|
||||
|
||||
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))
|
||||
err_ty = data.GetValueForKey("type")
|
||||
self.assertEqual(err_ty.GetIntegerValue(), lldb.eErrorTypeExpression)
|
||||
diags = data.GetValueForKey("errors").GetItemAtIndex(0)
|
||||
check_error(diags)
|
||||
|
||||
@@ -113,12 +113,23 @@ class TestFrameVar(TestBase):
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user