[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:
Adrian Prantl
2024-12-20 13:02:54 -08:00
committed by GitHub
parent d8e10d13d7
commit d9cc37fea7
10 changed files with 161 additions and 85 deletions

View File

@@ -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);

View File

@@ -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;

View File

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

View File

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

View File

@@ -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);

View File

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

View File

@@ -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;

View File

@@ -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) {

View File

@@ -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)

View File

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