Skip to content

Commit bd992b3

Browse files
committed
[lldb] Expose structured command diagnostics via the SBAPI. (llvm#112109)
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. (cherry picked from commit 9eddc8b)
1 parent de0e550 commit bd992b3

File tree

11 files changed

+190
-107
lines changed

11 files changed

+190
-107
lines changed

lldb/include/lldb/API/SBCommandReturnObject.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ class LLDB_API SBCommandReturnObject {
4545
const char *GetOutput();
4646

4747
const char *GetError();
48+
SBStructuredData GetErrorData();
4849

4950
#ifndef SWIG
5051
LLDB_DEPRECATED_FIXME("Use PutOutput(SBFile) or PutOutput(FileSP)",

lldb/include/lldb/API/SBStructuredData.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#ifndef LLDB_API_SBSTRUCTUREDDATA_H
1010
#define LLDB_API_SBSTRUCTUREDDATA_H
1111

12+
#include "lldb/API/SBCommandReturnObject.h"
1213
#include "lldb/API/SBDefines.h"
1314
#include "lldb/API/SBModule.h"
1415
#include "lldb/API/SBScriptObject.h"
@@ -110,6 +111,7 @@ class SBStructuredData {
110111

111112
protected:
112113
friend class SBAttachInfo;
114+
friend class SBCommandReturnObject;
113115
friend class SBLaunchInfo;
114116
friend class SBDebugger;
115117
friend class SBFrame;

lldb/include/lldb/Interpreter/CommandReturnObject.h

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include "lldb/Utility/DiagnosticsRendering.h"
1414
#include "lldb/Utility/StreamString.h"
1515
#include "lldb/Utility/StreamTee.h"
16+
#include "lldb/Utility/StructuredData.h"
1617
#include "lldb/lldb-private.h"
1718

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

3334
/// Format any inline diagnostics with an indentation of \c indent.
34-
llvm::StringRef GetInlineDiagnosticString(unsigned indent);
35+
std::string GetInlineDiagnosticString(unsigned indent);
3536

3637
llvm::StringRef GetOutputString() {
3738
lldb::StreamSP stream_sp(m_out_stream.GetStreamAtIndex(eStreamStringIndex));
@@ -40,7 +41,13 @@ class CommandReturnObject {
4041
return llvm::StringRef();
4142
}
4243

43-
llvm::StringRef GetErrorString();
44+
/// Return the errors as a string.
45+
///
46+
/// If \c with_diagnostics is true, all diagnostics are also
47+
/// rendered into the string. Otherwise the expectation is that they
48+
/// are fetched with \ref GetInlineDiagnosticString().
49+
std::string GetErrorString(bool with_diagnostics = true);
50+
StructuredData::ObjectSP GetErrorData();
4451

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

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

179185
/// If true, then the input handle from the debugger will be hooked up.
180186
bool m_interactive = true;
187+
bool m_colors;
181188
};
182189

183190
} // namespace lldb_private

lldb/source/API/SBCommandReturnObject.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
#include "lldb/API/SBError.h"
1212
#include "lldb/API/SBFile.h"
1313
#include "lldb/API/SBStream.h"
14+
#include "lldb/API/SBStructuredData.h"
15+
#include "lldb/Core/StructuredDataImpl.h"
1416
#include "lldb/Interpreter/CommandReturnObject.h"
1517
#include "lldb/Utility/ConstString.h"
1618
#include "lldb/Utility/Instrumentation.h"
@@ -96,6 +98,15 @@ const char *SBCommandReturnObject::GetError() {
9698
return output.AsCString(/*value_if_empty*/ "");
9799
}
98100

101+
SBStructuredData SBCommandReturnObject::GetErrorData() {
102+
LLDB_INSTRUMENT_VA(this);
103+
104+
StructuredData::ObjectSP data(ref().GetErrorData());
105+
SBStructuredData sb_data;
106+
sb_data.m_impl_up->SetObjectSP(data);
107+
return sb_data;
108+
}
109+
99110
size_t SBCommandReturnObject::GetOutputSize() {
100111
LLDB_INSTRUMENT_VA(this);
101112

lldb/source/Commands/CommandObjectDWIMPrint.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ void CommandObjectDWIMPrint::DoExecute(StringRef command,
233233
// Record the position of the expression in the command.
234234
std::optional<uint16_t> indent;
235235
if (fixed_expression.empty()) {
236-
size_t pos = m_original_command.find(expr);
236+
size_t pos = m_original_command.rfind(expr);
237237
if (pos != llvm::StringRef::npos)
238238
indent = pos;
239239
}

lldb/source/Commands/CommandObjectExpression.cpp

Lines changed: 13 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -509,35 +509,8 @@ bool CommandObjectExpression::EvaluateExpression(llvm::StringRef expr,
509509

510510
result.SetStatus(eReturnStatusSuccessFinishResult);
511511
} else {
512-
// Retrieve the diagnostics.
513-
std::vector<DiagnosticDetail> details;
514-
llvm::consumeError(llvm::handleErrors(
515-
result_valobj_sp->GetError().ToError(),
516-
[&](DiagnosticError &error) { details = error.GetDetails(); }));
517-
// Find the position of the expression in the command.
518-
std::optional<uint16_t> expr_pos;
519-
size_t nchar = m_original_command.find(expr);
520-
if (nchar != std::string::npos)
521-
expr_pos = nchar + GetDebugger().GetPrompt().size();
522-
523-
if (!details.empty()) {
524-
bool show_inline =
525-
GetDebugger().GetShowInlineDiagnostics() && !expr.contains('\n');
526-
RenderDiagnosticDetails(error_stream, expr_pos, show_inline, details);
527-
} else {
528-
const char *error_cstr = result_valobj_sp->GetError().AsCString();
529-
llvm::StringRef error(error_cstr);
530-
if (!error.empty()) {
531-
if (!error.starts_with("error:"))
532-
error_stream << "error: ";
533-
error_stream << error;
534-
if (!error.ends_with('\n'))
535-
error_stream.EOL();
536-
} else {
537-
error_stream << "error: unknown error\n";
538-
}
539-
}
540512
result.SetStatus(eReturnStatusFailed);
513+
result.SetError(result_valobj_sp->GetError().ToError());
541514
}
542515
}
543516
} else {
@@ -557,10 +530,13 @@ void CommandObjectExpression::IOHandlerInputComplete(IOHandler &io_handler,
557530
CommandReturnObject return_obj(
558531
GetCommandInterpreter().GetDebugger().GetUseColor());
559532
EvaluateExpression(line.c_str(), *output_sp, *error_sp, return_obj);
533+
560534
if (output_sp)
561535
output_sp->Flush();
562-
if (error_sp)
536+
if (error_sp) {
537+
*error_sp << return_obj.GetErrorString();
563538
error_sp->Flush();
539+
}
564540
}
565541

566542
bool CommandObjectExpression::IOHandlerIsInputComplete(IOHandler &io_handler,
@@ -729,6 +705,14 @@ void CommandObjectExpression::DoExecute(llvm::StringRef command,
729705
}
730706
}
731707

708+
// Previously the indent was set up for diagnosing command line
709+
// parsing errors. Now point it to the expression.
710+
std::optional<uint16_t> indent;
711+
size_t pos = m_original_command.rfind(expr);
712+
if (pos != llvm::StringRef::npos)
713+
indent = pos;
714+
result.SetDiagnosticIndent(indent);
715+
732716
Target &target = GetTarget();
733717
if (EvaluateExpression(expr, result.GetOutputStream(),
734718
result.GetErrorStream(), result)) {

lldb/source/Interpreter/CommandInterpreter.cpp

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2648,20 +2648,18 @@ void CommandInterpreter::HandleCommands(const StringList &commands,
26482648
}
26492649

26502650
if (!success || !tmp_result.Succeeded()) {
2651-
llvm::StringRef error_msg = tmp_result.GetErrorString();
2651+
std::string error_msg = tmp_result.GetErrorString();
26522652
if (error_msg.empty())
26532653
error_msg = "<unknown error>.\n";
26542654
if (options.GetStopOnError()) {
2655-
result.AppendErrorWithFormat(
2656-
"Aborting reading of commands after command #%" PRIu64
2657-
": '%s' failed with %s",
2658-
(uint64_t)idx, cmd, error_msg.str().c_str());
2655+
result.AppendErrorWithFormatv("Aborting reading of commands after "
2656+
"command #{0}: '{1}' failed with {2}",
2657+
(uint64_t)idx, cmd, error_msg);
26592658
m_debugger.SetAsyncExecution(old_async_execution);
26602659
return;
26612660
} else if (options.GetPrintResults()) {
2662-
result.AppendMessageWithFormat(
2663-
"Command #%" PRIu64 " '%s' failed with %s", (uint64_t)idx + 1, cmd,
2664-
error_msg.str().c_str());
2661+
result.AppendMessageWithFormatv("Command #{0} '{1}' failed with {2}",
2662+
(uint64_t)idx + 1, cmd, error_msg);
26652663
}
26662664
}
26672665

@@ -3199,11 +3197,12 @@ void CommandInterpreter::IOHandlerInputComplete(IOHandler &io_handler,
31993197
io_handler.GetFlags().Test(eHandleCommandFlagPrintResult)) ||
32003198
io_handler.GetFlags().Test(eHandleCommandFlagPrintErrors)) {
32013199
// Display any inline diagnostics first.
3202-
if (!result.GetImmediateErrorStream() &&
3203-
GetDebugger().GetShowInlineDiagnostics()) {
3200+
const bool inline_diagnostics = !result.GetImmediateErrorStream() &&
3201+
GetDebugger().GetShowInlineDiagnostics();
3202+
if (inline_diagnostics) {
32043203
unsigned prompt_len = m_debugger.GetPrompt().size();
32053204
if (auto indent = result.GetDiagnosticIndent()) {
3206-
llvm::StringRef diags =
3205+
std::string diags =
32073206
result.GetInlineDiagnosticString(prompt_len + *indent);
32083207
PrintCommandOutput(io_handler, diags, true);
32093208
}
@@ -3219,7 +3218,7 @@ void CommandInterpreter::IOHandlerInputComplete(IOHandler &io_handler,
32193218

32203219
// Now emit the command error text from the command we just executed.
32213220
if (!result.GetImmediateErrorStream()) {
3222-
llvm::StringRef error = result.GetErrorString();
3221+
std::string error = result.GetErrorString(!inline_diagnostics);
32233222
PrintCommandOutput(io_handler, error, false);
32243223
}
32253224
}

lldb/source/Interpreter/CommandReturnObject.cpp

Lines changed: 67 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ static void DumpStringToStreamWithNewline(Stream &strm, const std::string &s) {
4242
}
4343

4444
CommandReturnObject::CommandReturnObject(bool colors)
45-
: m_out_stream(colors), m_err_stream(colors), m_diag_stream(colors) {}
45+
: m_out_stream(colors), m_err_stream(colors), m_colors(colors) {}
4646

4747
void CommandReturnObject::AppendErrorWithFormat(const char *format, ...) {
4848
SetStatus(eReturnStatusFailed);
@@ -123,30 +123,79 @@ void CommandReturnObject::SetError(llvm::Error error) {
123123
}
124124
}
125125

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

133-
// Clear them so GetErrorData() doesn't render them again.
134-
m_diagnostics.clear();
135-
return m_diag_stream.GetString();
133+
return diag_stream.GetString().str();
136134
}
137135

138-
llvm::StringRef CommandReturnObject::GetErrorString() {
139-
// Diagnostics haven't been fetched; render them now (not inlined).
140-
if (!m_diagnostics.empty()) {
141-
RenderDiagnosticDetails(GetErrorStream(), std::nullopt, false,
142-
m_diagnostics);
143-
m_diagnostics.clear();
144-
}
136+
std::string CommandReturnObject::GetErrorString(bool with_diagnostics) {
137+
StreamString stream(m_colors);
138+
if (with_diagnostics)
139+
RenderDiagnosticDetails(stream, std::nullopt, false, m_diagnostics);
145140

146141
lldb::StreamSP stream_sp(m_err_stream.GetStreamAtIndex(eStreamStringIndex));
147142
if (stream_sp)
148-
return std::static_pointer_cast<StreamString>(stream_sp)->GetString();
149-
return llvm::StringRef();
143+
stream << std::static_pointer_cast<StreamString>(stream_sp)->GetString();
144+
return stream.GetString().str();
145+
}
146+
147+
StructuredData::ObjectSP CommandReturnObject::GetErrorData() {
148+
auto make_array = []() { return std::make_unique<StructuredData::Array>(); };
149+
auto make_bool = [](bool b) {
150+
return std::make_unique<StructuredData::Boolean>(b);
151+
};
152+
auto make_dict = []() {
153+
return std::make_unique<StructuredData::Dictionary>();
154+
};
155+
auto make_int = [](unsigned i) {
156+
return std::make_unique<StructuredData::UnsignedInteger>(i);
157+
};
158+
auto make_string = [](llvm::StringRef s) {
159+
return std::make_unique<StructuredData::String>(s);
160+
};
161+
auto dict_up = make_dict();
162+
dict_up->AddItem("version", make_int(1));
163+
auto array_up = make_array();
164+
for (const DiagnosticDetail &diag : m_diagnostics) {
165+
auto detail_up = make_dict();
166+
if (auto &sloc = diag.source_location) {
167+
auto sloc_up = make_dict();
168+
sloc_up->AddItem("file", make_string(sloc->file.GetPath()));
169+
sloc_up->AddItem("line", make_int(sloc->line));
170+
sloc_up->AddItem("length", make_int(sloc->length));
171+
sloc_up->AddItem("hidden", make_bool(sloc->hidden));
172+
sloc_up->AddItem("in_user_input", make_bool(sloc->in_user_input));
173+
detail_up->AddItem("source_location", std::move(sloc_up));
174+
}
175+
llvm::StringRef severity = "unknown";
176+
switch (diag.severity) {
177+
case lldb::eSeverityError:
178+
severity = "error";
179+
break;
180+
case lldb::eSeverityWarning:
181+
severity = "warning";
182+
break;
183+
case lldb::eSeverityInfo:
184+
severity = "note";
185+
break;
186+
}
187+
detail_up->AddItem("severity", make_string(severity));
188+
detail_up->AddItem("message", make_string(diag.message));
189+
detail_up->AddItem("rendered", make_string(diag.rendered));
190+
array_up->AddItem(std::move(detail_up));
191+
}
192+
dict_up->AddItem("details", std::move(array_up));
193+
if (auto stream_sp = m_err_stream.GetStreamAtIndex(eStreamStringIndex)) {
194+
auto text = std::static_pointer_cast<StreamString>(stream_sp)->GetString();
195+
if (!text.empty())
196+
dict_up->AddItem("text", make_string(text));
197+
}
198+
return dict_up;
150199
}
151200

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

0 commit comments

Comments
 (0)