Skip to content

Commit 23e1eea

Browse files
committed
[lldb] Expose structured command diagnostics via the SBAPI.
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.
1 parent fc08ad6 commit 23e1eea

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

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
@@ -194,7 +194,7 @@ void CommandObjectDWIMPrint::DoExecute(StringRef command,
194194
// Record the position of the expression in the command.
195195
std::optional<uint16_t> indent;
196196
if (fixed_expression.empty()) {
197-
size_t pos = m_original_command.find(expr);
197+
size_t pos = m_original_command.rfind(expr);
198198
if (pos != llvm::StringRef::npos)
199199
indent = pos;
200200
}

lldb/source/Commands/CommandObjectExpression.cpp

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

486486
result.SetStatus(eReturnStatusSuccessFinishResult);
487487
} else {
488-
// Retrieve the diagnostics.
489-
std::vector<DiagnosticDetail> details;
490-
llvm::consumeError(llvm::handleErrors(
491-
result_valobj_sp->GetError().ToError(),
492-
[&](DiagnosticError &error) { details = error.GetDetails(); }));
493-
// Find the position of the expression in the command.
494-
std::optional<uint16_t> expr_pos;
495-
size_t nchar = m_original_command.find(expr);
496-
if (nchar != std::string::npos)
497-
expr_pos = nchar + GetDebugger().GetPrompt().size();
498-
499-
if (!details.empty()) {
500-
bool show_inline =
501-
GetDebugger().GetShowInlineDiagnostics() && !expr.contains('\n');
502-
RenderDiagnosticDetails(error_stream, expr_pos, show_inline, details);
503-
} else {
504-
const char *error_cstr = result_valobj_sp->GetError().AsCString();
505-
llvm::StringRef error(error_cstr);
506-
if (!error.empty()) {
507-
if (!error.starts_with("error:"))
508-
error_stream << "error: ";
509-
error_stream << error;
510-
if (!error.ends_with('\n'))
511-
error_stream.EOL();
512-
} else {
513-
error_stream << "error: unknown error\n";
514-
}
515-
}
516488
result.SetStatus(eReturnStatusFailed);
489+
result.SetError(result_valobj_sp->GetError().ToError());
517490
}
518491
}
519492
} else {
@@ -533,10 +506,13 @@ void CommandObjectExpression::IOHandlerInputComplete(IOHandler &io_handler,
533506
CommandReturnObject return_obj(
534507
GetCommandInterpreter().GetDebugger().GetUseColor());
535508
EvaluateExpression(line.c_str(), *output_sp, *error_sp, return_obj);
509+
536510
if (output_sp)
537511
output_sp->Flush();
538-
if (error_sp)
512+
if (error_sp) {
513+
*error_sp << return_obj.GetErrorString();
539514
error_sp->Flush();
515+
}
540516
}
541517

542518
bool CommandObjectExpression::IOHandlerIsInputComplete(IOHandler &io_handler,
@@ -679,6 +655,14 @@ void CommandObjectExpression::DoExecute(llvm::StringRef command,
679655
}
680656
}
681657

658+
// Previously the indent was set up for diagnosing command line
659+
// parsing errors. Now point it to the expression.
660+
std::optional<uint16_t> indent;
661+
size_t pos = m_original_command.rfind(expr);
662+
if (pos != llvm::StringRef::npos)
663+
indent = pos;
664+
result.SetDiagnosticIndent(indent);
665+
682666
Target &target = GetTarget();
683667
if (EvaluateExpression(expr, result.GetOutputStream(),
684668
result.GetErrorStream(), result)) {

lldb/source/Interpreter/CommandInterpreter.cpp

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

26382638
if (!success || !tmp_result.Succeeded()) {
2639-
llvm::StringRef error_msg = tmp_result.GetErrorString();
2639+
std::string error_msg = tmp_result.GetErrorString();
26402640
if (error_msg.empty())
26412641
error_msg = "<unknown error>.\n";
26422642
if (options.GetStopOnError()) {
2643-
result.AppendErrorWithFormat(
2644-
"Aborting reading of commands after command #%" PRIu64
2645-
": '%s' failed with %s",
2646-
(uint64_t)idx, cmd, error_msg.str().c_str());
2643+
result.AppendErrorWithFormatv("Aborting reading of commands after "
2644+
"command #{0}: '{1}' failed with {2}",
2645+
(uint64_t)idx, cmd, error_msg);
26472646
m_debugger.SetAsyncExecution(old_async_execution);
26482647
return;
26492648
} else if (options.GetPrintResults()) {
2650-
result.AppendMessageWithFormat(
2651-
"Command #%" PRIu64 " '%s' failed with %s", (uint64_t)idx + 1, cmd,
2652-
error_msg.str().c_str());
2649+
result.AppendMessageWithFormatv("Command #{0} '{1}' failed with {2}",
2650+
(uint64_t)idx + 1, cmd, error_msg);
26532651
}
26542652
}
26552653

@@ -3187,11 +3185,12 @@ void CommandInterpreter::IOHandlerInputComplete(IOHandler &io_handler,
31873185
io_handler.GetFlags().Test(eHandleCommandFlagPrintResult)) ||
31883186
io_handler.GetFlags().Test(eHandleCommandFlagPrintErrors)) {
31893187
// Display any inline diagnostics first.
3190-
if (!result.GetImmediateErrorStream() &&
3191-
GetDebugger().GetShowInlineDiagnostics()) {
3188+
const bool inline_diagnostics = !result.GetImmediateErrorStream() &&
3189+
GetDebugger().GetShowInlineDiagnostics();
3190+
if (inline_diagnostics) {
31923191
unsigned prompt_len = m_debugger.GetPrompt().size();
31933192
if (auto indent = result.GetDiagnosticIndent()) {
3194-
llvm::StringRef diags =
3193+
std::string diags =
31953194
result.GetInlineDiagnosticString(prompt_len + *indent);
31963195
PrintCommandOutput(io_handler, diags, true);
31973196
}
@@ -3207,7 +3206,7 @@ void CommandInterpreter::IOHandlerInputComplete(IOHandler &io_handler,
32073206

32083207
// Now emit the command error text from the command we just executed.
32093208
if (!result.GetImmediateErrorStream()) {
3210-
llvm::StringRef error = result.GetErrorString();
3209+
std::string error = result.GetErrorString(!inline_diagnostics);
32113210
PrintCommandOutput(io_handler, error, false);
32123211
}
32133212
}

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)