Skip to content

Commit cf7ea7a

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 c275080 commit cf7ea7a

File tree

8 files changed

+128
-60
lines changed

8 files changed

+128
-60
lines changed

lldb/include/lldb/API/SBCommandReturnObject.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,14 @@ 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)",
5152
"PutOutput(SBFile)")
5253
size_t PutOutput(FILE *fh);
5354
#endif
54-
55+
\
5556
size_t PutOutput(SBFile file);
5657

5758
size_t PutOutput(FileSP BORROWED);

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 "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/CommandObjectExpression.cpp

Lines changed: 1 addition & 28 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 {

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 {1}",
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+
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: 66 additions & 16 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,80 @@ void CommandReturnObject::SetError(llvm::Error error) {
123123
}
124124
}
125125

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

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

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-
}
137+
std::string CommandReturnObject::GetErrorString(bool with_diagnostics) {
138+
StreamString stream(m_colors);
139+
if (with_diagnostics)
140+
RenderDiagnosticDetails(stream, std::nullopt, false, m_diagnostics);
145141

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

152202
// Similar to AppendError, but do not prepend 'Status: ' to message, and don't

lldb/test/API/commands/expression/diagnostics/TestExprDiagnostics.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,3 +234,28 @@ def check(input_ref):
234234
]
235235
)
236236
check(["expression --\na\n+\nb", "error: <user", "a", "error: <user", "b"])
237+
238+
def test_command_expr_sbdata(self):
239+
"""Test that the source and caret positions LLDB prints are correct"""
240+
self.build()
241+
242+
(target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(
243+
self, "// Break here", self.main_source_spec
244+
)
245+
interp = self.dbg.GetCommandInterpreter()
246+
cro = lldb.SBCommandReturnObject()
247+
interp.HandleCommand("expression -- a+b", cro)
248+
diags = cro.GetErrorData()
249+
self.assertEqual(diags.GetValueForKey("version").GetFloatValue(), 1.0)
250+
details = diags.GetValueForKey("details")
251+
diag = details.GetItemAtIndex(0)
252+
self.assertEqual(str(diag.GetValueForKey("severity")), "error")
253+
self.assertIn("undeclared identifier 'a'", str(diag.GetValueForKey("message")))
254+
self.assertIn("user expression", str(diag.GetValueForKey("rendered")))
255+
sloc = diag.GetValueForKey("source_location")
256+
self.assertIn("user expression", str(sloc.GetValueForKey("file")))
257+
self.assertFalse(sloc.GetValueForKey("hidden").GetBooleanValue())
258+
self.assertTrue(sloc.GetValueForKey("in_user_input").GetBooleanValue())
259+
diag = details.GetItemAtIndex(1)
260+
self.assertIn("undeclared identifier 'b'", str(diag.GetValueForKey("message")))
261+

0 commit comments

Comments
 (0)