Skip to content

Commit

Permalink
[lldb] Expose structured command diagnostics via the SBAPI. (#112109)
Browse files Browse the repository at this point in the history
This allows IDEs to render LLDB expression diagnostics to their liking
without relying on characterprecise ASCII art from LLDB. It is exposed
as a versioned SBStructuredData object, since it is expected that this
may need to be tweaked based on actual usage.
  • Loading branch information
adrian-prantl authored Oct 14, 2024
1 parent fc08ad6 commit 9eddc8b
Show file tree
Hide file tree
Showing 11 changed files with 190 additions and 107 deletions.
1 change: 1 addition & 0 deletions lldb/include/lldb/API/SBCommandReturnObject.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ class LLDB_API SBCommandReturnObject {
const char *GetOutput();

const char *GetError();
SBStructuredData GetErrorData();

#ifndef SWIG
LLDB_DEPRECATED_FIXME("Use PutOutput(SBFile) or PutOutput(FileSP)",
Expand Down
2 changes: 2 additions & 0 deletions lldb/include/lldb/API/SBStructuredData.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#ifndef LLDB_API_SBSTRUCTUREDDATA_H
#define LLDB_API_SBSTRUCTUREDDATA_H

#include "lldb/API/SBCommandReturnObject.h"
#include "lldb/API/SBDefines.h"
#include "lldb/API/SBModule.h"
#include "lldb/API/SBScriptObject.h"
Expand Down Expand Up @@ -110,6 +111,7 @@ class SBStructuredData {

protected:
friend class SBAttachInfo;
friend class SBCommandReturnObject;
friend class SBLaunchInfo;
friend class SBDebugger;
friend class SBTarget;
Expand Down
13 changes: 10 additions & 3 deletions lldb/include/lldb/Interpreter/CommandReturnObject.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include "lldb/Utility/DiagnosticsRendering.h"
#include "lldb/Utility/StreamString.h"
#include "lldb/Utility/StreamTee.h"
#include "lldb/Utility/StructuredData.h"
#include "lldb/lldb-private.h"

#include "llvm/ADT/StringRef.h"
Expand All @@ -31,7 +32,7 @@ class CommandReturnObject {
~CommandReturnObject() = default;

/// Format any inline diagnostics with an indentation of \c indent.
llvm::StringRef GetInlineDiagnosticString(unsigned indent);
std::string GetInlineDiagnosticString(unsigned indent);

llvm::StringRef GetOutputString() {
lldb::StreamSP stream_sp(m_out_stream.GetStreamAtIndex(eStreamStringIndex));
Expand All @@ -40,7 +41,13 @@ class CommandReturnObject {
return llvm::StringRef();
}

llvm::StringRef GetErrorString();
/// Return the errors as a string.
///
/// If \c with_diagnostics is true, all diagnostics are also
/// rendered into the string. Otherwise the expectation is that they
/// are fetched with \ref GetInlineDiagnosticString().
std::string GetErrorString(bool with_diagnostics = true);
StructuredData::ObjectSP GetErrorData();

Stream &GetOutputStream() {
// Make sure we at least have our normal string stream output stream
Expand Down Expand Up @@ -168,7 +175,6 @@ class CommandReturnObject {
StreamTee m_out_stream;
StreamTee m_err_stream;
std::vector<DiagnosticDetail> m_diagnostics;
StreamString m_diag_stream;
std::optional<uint16_t> m_diagnostic_indent;

lldb::ReturnStatus m_status = lldb::eReturnStatusStarted;
Expand All @@ -178,6 +184,7 @@ class CommandReturnObject {

/// If true, then the input handle from the debugger will be hooked up.
bool m_interactive = true;
bool m_colors;
};

} // namespace lldb_private
Expand Down
11 changes: 11 additions & 0 deletions lldb/source/API/SBCommandReturnObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
#include "lldb/API/SBError.h"
#include "lldb/API/SBFile.h"
#include "lldb/API/SBStream.h"
#include "lldb/API/SBStructuredData.h"
#include "lldb/Core/StructuredDataImpl.h"
#include "lldb/Interpreter/CommandReturnObject.h"
#include "lldb/Utility/ConstString.h"
#include "lldb/Utility/Instrumentation.h"
Expand Down Expand Up @@ -96,6 +98,15 @@ const char *SBCommandReturnObject::GetError() {
return output.AsCString(/*value_if_empty*/ "");
}

SBStructuredData SBCommandReturnObject::GetErrorData() {
LLDB_INSTRUMENT_VA(this);

StructuredData::ObjectSP data(ref().GetErrorData());
SBStructuredData sb_data;
sb_data.m_impl_up->SetObjectSP(data);
return sb_data;
}

size_t SBCommandReturnObject::GetOutputSize() {
LLDB_INSTRUMENT_VA(this);

Expand Down
2 changes: 1 addition & 1 deletion lldb/source/Commands/CommandObjectDWIMPrint.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ void CommandObjectDWIMPrint::DoExecute(StringRef command,
// Record the position of the expression in the command.
std::optional<uint16_t> indent;
if (fixed_expression.empty()) {
size_t pos = m_original_command.find(expr);
size_t pos = m_original_command.rfind(expr);
if (pos != llvm::StringRef::npos)
indent = pos;
}
Expand Down
42 changes: 13 additions & 29 deletions lldb/source/Commands/CommandObjectExpression.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -485,35 +485,8 @@ bool CommandObjectExpression::EvaluateExpression(llvm::StringRef expr,

result.SetStatus(eReturnStatusSuccessFinishResult);
} else {
// Retrieve the diagnostics.
std::vector<DiagnosticDetail> details;
llvm::consumeError(llvm::handleErrors(
result_valobj_sp->GetError().ToError(),
[&](DiagnosticError &error) { details = error.GetDetails(); }));
// Find the position of the expression in the command.
std::optional<uint16_t> expr_pos;
size_t nchar = m_original_command.find(expr);
if (nchar != std::string::npos)
expr_pos = nchar + GetDebugger().GetPrompt().size();

if (!details.empty()) {
bool show_inline =
GetDebugger().GetShowInlineDiagnostics() && !expr.contains('\n');
RenderDiagnosticDetails(error_stream, expr_pos, show_inline, details);
} else {
const char *error_cstr = result_valobj_sp->GetError().AsCString();
llvm::StringRef error(error_cstr);
if (!error.empty()) {
if (!error.starts_with("error:"))
error_stream << "error: ";
error_stream << error;
if (!error.ends_with('\n'))
error_stream.EOL();
} else {
error_stream << "error: unknown error\n";
}
}
result.SetStatus(eReturnStatusFailed);
result.SetError(result_valobj_sp->GetError().ToError());
}
}
} else {
Expand All @@ -533,10 +506,13 @@ void CommandObjectExpression::IOHandlerInputComplete(IOHandler &io_handler,
CommandReturnObject return_obj(
GetCommandInterpreter().GetDebugger().GetUseColor());
EvaluateExpression(line.c_str(), *output_sp, *error_sp, return_obj);

if (output_sp)
output_sp->Flush();
if (error_sp)
if (error_sp) {
*error_sp << return_obj.GetErrorString();
error_sp->Flush();
}
}

bool CommandObjectExpression::IOHandlerIsInputComplete(IOHandler &io_handler,
Expand Down Expand Up @@ -679,6 +655,14 @@ void CommandObjectExpression::DoExecute(llvm::StringRef command,
}
}

// Previously the indent was set up for diagnosing command line
// parsing errors. Now point it to the expression.
std::optional<uint16_t> indent;
size_t pos = m_original_command.rfind(expr);
if (pos != llvm::StringRef::npos)
indent = pos;
result.SetDiagnosticIndent(indent);

Target &target = GetTarget();
if (EvaluateExpression(expr, result.GetOutputStream(),
result.GetErrorStream(), result)) {
Expand Down
23 changes: 11 additions & 12 deletions lldb/source/Interpreter/CommandInterpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2636,20 +2636,18 @@ void CommandInterpreter::HandleCommands(const StringList &commands,
}

if (!success || !tmp_result.Succeeded()) {
llvm::StringRef error_msg = tmp_result.GetErrorString();
std::string error_msg = tmp_result.GetErrorString();
if (error_msg.empty())
error_msg = "<unknown error>.\n";
if (options.GetStopOnError()) {
result.AppendErrorWithFormat(
"Aborting reading of commands after command #%" PRIu64
": '%s' failed with %s",
(uint64_t)idx, cmd, error_msg.str().c_str());
result.AppendErrorWithFormatv("Aborting reading of commands after "
"command #{0}: '{1}' failed with {2}",
(uint64_t)idx, cmd, error_msg);
m_debugger.SetAsyncExecution(old_async_execution);
return;
} else if (options.GetPrintResults()) {
result.AppendMessageWithFormat(
"Command #%" PRIu64 " '%s' failed with %s", (uint64_t)idx + 1, cmd,
error_msg.str().c_str());
result.AppendMessageWithFormatv("Command #{0} '{1}' failed with {2}",
(uint64_t)idx + 1, cmd, error_msg);
}
}

Expand Down Expand Up @@ -3187,11 +3185,12 @@ void CommandInterpreter::IOHandlerInputComplete(IOHandler &io_handler,
io_handler.GetFlags().Test(eHandleCommandFlagPrintResult)) ||
io_handler.GetFlags().Test(eHandleCommandFlagPrintErrors)) {
// Display any inline diagnostics first.
if (!result.GetImmediateErrorStream() &&
GetDebugger().GetShowInlineDiagnostics()) {
const bool inline_diagnostics = !result.GetImmediateErrorStream() &&
GetDebugger().GetShowInlineDiagnostics();
if (inline_diagnostics) {
unsigned prompt_len = m_debugger.GetPrompt().size();
if (auto indent = result.GetDiagnosticIndent()) {
llvm::StringRef diags =
std::string diags =
result.GetInlineDiagnosticString(prompt_len + *indent);
PrintCommandOutput(io_handler, diags, true);
}
Expand All @@ -3207,7 +3206,7 @@ void CommandInterpreter::IOHandlerInputComplete(IOHandler &io_handler,

// Now emit the command error text from the command we just executed.
if (!result.GetImmediateErrorStream()) {
llvm::StringRef error = result.GetErrorString();
std::string error = result.GetErrorString(!inline_diagnostics);
PrintCommandOutput(io_handler, error, false);
}
}
Expand Down
84 changes: 67 additions & 17 deletions lldb/source/Interpreter/CommandReturnObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ static void DumpStringToStreamWithNewline(Stream &strm, const std::string &s) {
}

CommandReturnObject::CommandReturnObject(bool colors)
: m_out_stream(colors), m_err_stream(colors), m_diag_stream(colors) {}
: m_out_stream(colors), m_err_stream(colors), m_colors(colors) {}

void CommandReturnObject::AppendErrorWithFormat(const char *format, ...) {
SetStatus(eReturnStatusFailed);
Expand Down Expand Up @@ -123,30 +123,79 @@ void CommandReturnObject::SetError(llvm::Error error) {
}
}

llvm::StringRef
CommandReturnObject::GetInlineDiagnosticString(unsigned indent) {
RenderDiagnosticDetails(m_diag_stream, indent, true, m_diagnostics);
std::string CommandReturnObject::GetInlineDiagnosticString(unsigned indent) {
StreamString diag_stream(m_colors);
RenderDiagnosticDetails(diag_stream, indent, true, m_diagnostics);
// Duplex the diagnostics to the secondary stream (but not inlined).
if (auto stream_sp = m_err_stream.GetStreamAtIndex(eStreamStringIndex))
if (auto stream_sp = m_err_stream.GetStreamAtIndex(eImmediateStreamIndex))
RenderDiagnosticDetails(*stream_sp, std::nullopt, false, m_diagnostics);

// Clear them so GetErrorData() doesn't render them again.
m_diagnostics.clear();
return m_diag_stream.GetString();
return diag_stream.GetString().str();
}

llvm::StringRef CommandReturnObject::GetErrorString() {
// Diagnostics haven't been fetched; render them now (not inlined).
if (!m_diagnostics.empty()) {
RenderDiagnosticDetails(GetErrorStream(), std::nullopt, false,
m_diagnostics);
m_diagnostics.clear();
}
std::string CommandReturnObject::GetErrorString(bool with_diagnostics) {
StreamString stream(m_colors);
if (with_diagnostics)
RenderDiagnosticDetails(stream, std::nullopt, false, m_diagnostics);

lldb::StreamSP stream_sp(m_err_stream.GetStreamAtIndex(eStreamStringIndex));
if (stream_sp)
return std::static_pointer_cast<StreamString>(stream_sp)->GetString();
return llvm::StringRef();
stream << std::static_pointer_cast<StreamString>(stream_sp)->GetString();
return stream.GetString().str();
}

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

// Similar to AppendError, but do not prepend 'Status: ' to message, and don't
Expand Down Expand Up @@ -179,6 +228,7 @@ void CommandReturnObject::Clear() {
stream_sp = m_err_stream.GetStreamAtIndex(eStreamStringIndex);
if (stream_sp)
static_cast<StreamString *>(stream_sp.get())->Clear();
m_diagnostics.clear();
m_status = eReturnStatusStarted;
m_did_change_process_state = false;
m_suppress_immediate_output = false;
Expand Down
Loading

0 comments on commit 9eddc8b

Please sign in to comment.