Skip to content

Commit 6526cda

Browse files
ashgtiJDevlieghere
andauthored
[lldb-dap] Migrating DAP 'initialize' to new typed RequestHandler. (#133007)
This adds new types and helpers to support the 'initialize' request with the new typed RequestHandler. While working on this I found there were a few cases where we incorrectly treated initialize arguments as capabilities. The new `lldb_dap::protocol::InitializeRequestArguments` and `lldb_dap::protocol::Capabilities` uncovered the inconsistencies. --------- Co-authored-by: Jonas Devlieghere <[email protected]>
1 parent a33d789 commit 6526cda

File tree

14 files changed

+712
-243
lines changed

14 files changed

+712
-243
lines changed

lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -776,7 +776,8 @@ def request_initialize(self, sourceInitFile):
776776
"supportsVariablePaging": True,
777777
"supportsVariableType": True,
778778
"supportsStartDebuggingRequest": True,
779-
"sourceInitFile": sourceInitFile,
779+
"supportsProgressReporting": True,
780+
"$__lldb_sourceInitFile": sourceInitFile,
780781
},
781782
}
782783
response = self.send_recv(command_dict)
@@ -1261,7 +1262,7 @@ def launch(cls, /, executable, env=None, log_file=None, connection=None):
12611262
expected_prefix = "Listening for: "
12621263
out = process.stdout.readline().decode()
12631264
if not out.startswith(expected_prefix):
1264-
self.process.kill()
1265+
process.kill()
12651266
raise ValueError(
12661267
"lldb-dap failed to print listening address, expected '{}', got '{}'".format(
12671268
expected_prefix, out

lldb/test/API/tools/lldb-dap/launch/TestDAP_launch.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -524,8 +524,7 @@ def test_version(self):
524524

525525
# The first line is the prompt line like "(lldb) version", so we skip it.
526526
version_eval_output_without_prompt_line = version_eval_output.splitlines()[1:]
527-
lldb_json = self.dap_server.get_initialize_value("__lldb")
528-
version_string = lldb_json["version"]
527+
version_string = self.dap_server.get_initialize_value("$__lldb_version")
529528
self.assertEqual(
530529
version_eval_output_without_prompt_line,
531530
version_string.splitlines(),

lldb/tools/lldb-dap/DAP.cpp

Lines changed: 31 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "LLDBUtils.h"
1515
#include "OutputRedirector.h"
1616
#include "Protocol/ProtocolBase.h"
17+
#include "Protocol/ProtocolTypes.h"
1718
#include "Transport.h"
1819
#include "lldb/API/SBBreakpoint.h"
1920
#include "lldb/API/SBCommandInterpreter.h"
@@ -1144,32 +1145,41 @@ lldb::SBValue Variables::FindVariable(uint64_t variablesReference,
11441145
return variable;
11451146
}
11461147

1147-
llvm::StringMap<bool> DAP::GetCapabilities() {
1148-
llvm::StringMap<bool> capabilities;
1149-
1150-
// Supported capabilities.
1151-
capabilities["supportTerminateDebuggee"] = true;
1152-
capabilities["supportsDataBreakpoints"] = true;
1153-
capabilities["supportsDelayedStackTraceLoading"] = true;
1154-
capabilities["supportsEvaluateForHovers"] = true;
1155-
capabilities["supportsExceptionOptions"] = true;
1156-
capabilities["supportsLogPoints"] = true;
1157-
capabilities["supportsProgressReporting"] = true;
1158-
capabilities["supportsSteppingGranularity"] = true;
1159-
capabilities["supportsValueFormattingOptions"] = true;
1160-
1161-
// Unsupported capabilities.
1162-
capabilities["supportsGotoTargetsRequest"] = false;
1163-
capabilities["supportsLoadedSourcesRequest"] = false;
1164-
capabilities["supportsRestartFrame"] = false;
1165-
capabilities["supportsStepBack"] = false;
1148+
protocol::Capabilities DAP::GetCapabilities() {
1149+
protocol::Capabilities capabilities;
1150+
1151+
// Supported capabilities that are not specific to a single request.
1152+
capabilities.supportedFeatures = {
1153+
protocol::eAdapterFeatureLogPoints,
1154+
protocol::eAdapterFeatureSteppingGranularity,
1155+
protocol::eAdapterFeatureValueFormattingOptions,
1156+
};
11661157

11671158
// Capabilities associated with specific requests.
11681159
for (auto &kv : request_handlers) {
1169-
for (auto &request_kv : kv.second->GetCapabilities())
1170-
capabilities[request_kv.getKey()] = request_kv.getValue();
1160+
llvm::SmallDenseSet<AdapterFeature, 1> features =
1161+
kv.second->GetSupportedFeatures();
1162+
capabilities.supportedFeatures.insert(features.begin(), features.end());
11711163
}
11721164

1165+
// Available filters or options for the setExceptionBreakpoints request.
1166+
std::vector<protocol::ExceptionBreakpointsFilter> filters;
1167+
for (const auto &exc_bp : *exception_breakpoints)
1168+
filters.emplace_back(CreateExceptionBreakpointFilter(exc_bp));
1169+
capabilities.exceptionBreakpointFilters = std::move(filters);
1170+
1171+
// FIXME: This should be registered based on the supported languages?
1172+
std::vector<std::string> completion_characters;
1173+
completion_characters.emplace_back(".");
1174+
// FIXME: I wonder if we should remove this key... its very aggressive
1175+
// triggering and accepting completions.
1176+
completion_characters.emplace_back(" ");
1177+
completion_characters.emplace_back("\t");
1178+
capabilities.completionTriggerCharacters = std::move(completion_characters);
1179+
1180+
// Put in non-DAP specification lldb specific information.
1181+
capabilities.lldbExtVersion = debugger.GetVersionString();
1182+
11731183
return capabilities;
11741184
}
11751185

lldb/tools/lldb-dap/DAP.h

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
#include "OutputRedirector.h"
1717
#include "ProgressEvent.h"
1818
#include "Protocol/ProtocolBase.h"
19+
#include "Protocol/ProtocolRequests.h"
20+
#include "Protocol/ProtocolTypes.h"
1921
#include "SourceBreakpoint.h"
2022
#include "Transport.h"
2123
#include "lldb/API/SBBroadcaster.h"
@@ -58,6 +60,9 @@ typedef llvm::StringMap<FunctionBreakpoint> FunctionBreakpointMap;
5860
typedef llvm::DenseMap<lldb::addr_t, InstructionBreakpoint>
5961
InstructionBreakpointMap;
6062

63+
using AdapterFeature = protocol::AdapterFeature;
64+
using ClientFeature = protocol::ClientFeature;
65+
6166
enum class OutputType { Console, Stdout, Stderr, Telemetry };
6267

6368
/// Buffer size for handling output events.
@@ -205,6 +210,8 @@ struct DAP {
205210
// empty; if the previous expression was a variable expression, this string
206211
// will contain that expression.
207212
std::string last_nonempty_var_expression;
213+
/// The set of features supported by the connected client.
214+
llvm::DenseSet<ClientFeature> clientFeatures;
208215

209216
/// Creates a new DAP sessions.
210217
///
@@ -363,8 +370,8 @@ struct DAP {
363370
request_handlers[Handler::GetCommand()] = std::make_unique<Handler>(*this);
364371
}
365372

366-
/// Return a key-value list of capabilities.
367-
llvm::StringMap<bool> GetCapabilities();
373+
/// The set of capablities supported by this adapter.
374+
protocol::Capabilities GetCapabilities();
368375

369376
/// Debuggee will continue from stopped state.
370377
void WillContinue() { variables.Clear(); }

lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp

Lines changed: 26 additions & 136 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@
99
#include "DAP.h"
1010
#include "EventHelper.h"
1111
#include "JSONUtils.h"
12+
#include "Protocol/ProtocolRequests.h"
1213
#include "RequestHandler.h"
1314
#include "lldb/API/SBEvent.h"
1415
#include "lldb/API/SBListener.h"
1516
#include "lldb/API/SBStream.h"
1617

1718
using namespace lldb;
19+
using namespace lldb_dap::protocol;
1820

1921
namespace lldb_dap {
2022

@@ -229,136 +231,46 @@ static void EventThreadFunction(DAP &dap) {
229231
}
230232
}
231233

232-
// "InitializeRequest": {
233-
// "allOf": [ { "$ref": "#/definitions/Request" }, {
234-
// "type": "object",
235-
// "description": "Initialize request; value of command field is
236-
// 'initialize'.",
237-
// "properties": {
238-
// "command": {
239-
// "type": "string",
240-
// "enum": [ "initialize" ]
241-
// },
242-
// "arguments": {
243-
// "$ref": "#/definitions/InitializeRequestArguments"
244-
// }
245-
// },
246-
// "required": [ "command", "arguments" ]
247-
// }]
248-
// },
249-
// "InitializeRequestArguments": {
250-
// "type": "object",
251-
// "description": "Arguments for 'initialize' request.",
252-
// "properties": {
253-
// "clientID": {
254-
// "type": "string",
255-
// "description": "The ID of the (frontend) client using this adapter."
256-
// },
257-
// "adapterID": {
258-
// "type": "string",
259-
// "description": "The ID of the debug adapter."
260-
// },
261-
// "locale": {
262-
// "type": "string",
263-
// "description": "The ISO-639 locale of the (frontend) client using
264-
// this adapter, e.g. en-US or de-CH."
265-
// },
266-
// "linesStartAt1": {
267-
// "type": "boolean",
268-
// "description": "If true all line numbers are 1-based (default)."
269-
// },
270-
// "columnsStartAt1": {
271-
// "type": "boolean",
272-
// "description": "If true all column numbers are 1-based (default)."
273-
// },
274-
// "pathFormat": {
275-
// "type": "string",
276-
// "_enum": [ "path", "uri" ],
277-
// "description": "Determines in what format paths are specified. The
278-
// default is 'path', which is the native format."
279-
// },
280-
// "supportsVariableType": {
281-
// "type": "boolean",
282-
// "description": "Client supports the optional type attribute for
283-
// variables."
284-
// },
285-
// "supportsVariablePaging": {
286-
// "type": "boolean",
287-
// "description": "Client supports the paging of variables."
288-
// },
289-
// "supportsRunInTerminalRequest": {
290-
// "type": "boolean",
291-
// "description": "Client supports the runInTerminal request."
292-
// }
293-
// },
294-
// "required": [ "adapterID" ]
295-
// },
296-
// "InitializeResponse": {
297-
// "allOf": [ { "$ref": "#/definitions/Response" }, {
298-
// "type": "object",
299-
// "description": "Response to 'initialize' request.",
300-
// "properties": {
301-
// "body": {
302-
// "$ref": "#/definitions/Capabilities",
303-
// "description": "The capabilities of this debug adapter."
304-
// }
305-
// }
306-
// }]
307-
// }
308-
void InitializeRequestHandler::operator()(
309-
const llvm::json::Object &request) const {
310-
llvm::json::Object response;
311-
FillResponse(request, response);
312-
llvm::json::Object body;
313-
314-
const auto *arguments = request.getObject("arguments");
315-
// sourceInitFile option is not from formal DAP specification. It is only
316-
// used by unit tests to prevent sourcing .lldbinit files from environment
317-
// which may affect the outcome of tests.
318-
bool source_init_file =
319-
GetBoolean(arguments, "sourceInitFile").value_or(true);
234+
/// Initialize request; value of command field is 'initialize'.
235+
llvm::Expected<InitializeResponseBody> InitializeRequestHandler::Run(
236+
const InitializeRequestArguments &arguments) const {
237+
dap.clientFeatures = arguments.supportedFeatures;
320238

321239
// Do not source init files until in/out/err are configured.
322240
dap.debugger = lldb::SBDebugger::Create(false);
323241
dap.debugger.SetInputFile(dap.in);
324-
auto out_fd = dap.out.GetWriteFileDescriptor();
325-
if (llvm::Error err = out_fd.takeError()) {
326-
response["success"] = false;
327-
EmplaceSafeString(response, "message", llvm::toString(std::move(err)));
328-
dap.SendJSON(llvm::json::Value(std::move(response)));
329-
return;
330-
}
242+
243+
llvm::Expected<int> out_fd = dap.out.GetWriteFileDescriptor();
244+
if (!out_fd)
245+
return out_fd.takeError();
331246
dap.debugger.SetOutputFile(lldb::SBFile(*out_fd, "w", false));
332-
auto err_fd = dap.err.GetWriteFileDescriptor();
333-
if (llvm::Error err = err_fd.takeError()) {
334-
response["success"] = false;
335-
EmplaceSafeString(response, "message", llvm::toString(std::move(err)));
336-
dap.SendJSON(llvm::json::Value(std::move(response)));
337-
return;
338-
}
247+
248+
llvm::Expected<int> err_fd = dap.err.GetWriteFileDescriptor();
249+
if (!err_fd)
250+
return err_fd.takeError();
339251
dap.debugger.SetErrorFile(lldb::SBFile(*err_fd, "w", false));
340252

341253
auto interp = dap.debugger.GetCommandInterpreter();
342254

343-
if (source_init_file) {
255+
// The sourceInitFile option is not part of the DAP specification. It is an
256+
// extension used by the test suite to prevent sourcing `.lldbinit` and
257+
// changing its behavior.
258+
if (arguments.lldbExtSourceInitFile.value_or(true)) {
344259
dap.debugger.SkipLLDBInitFiles(false);
345260
dap.debugger.SkipAppInitFiles(false);
346261
lldb::SBCommandReturnObject init;
347262
interp.SourceInitFileInGlobalDirectory(init);
348263
interp.SourceInitFileInHomeDirectory(init);
349264
}
350265

351-
if (llvm::Error err = dap.RunPreInitCommands()) {
352-
response["success"] = false;
353-
EmplaceSafeString(response, "message", llvm::toString(std::move(err)));
354-
dap.SendJSON(llvm::json::Value(std::move(response)));
355-
return;
356-
}
266+
if (llvm::Error err = dap.RunPreInitCommands())
267+
return err;
357268

358269
dap.PopulateExceptionBreakpoints();
359270
auto cmd = dap.debugger.GetCommandInterpreter().AddMultiwordCommand(
360271
"lldb-dap", "Commands for managing lldb-dap.");
361-
if (GetBoolean(arguments, "supportsStartDebuggingRequest").value_or(false)) {
272+
if (arguments.supportedFeatures.contains(
273+
eClientFeatureStartDebuggingRequest)) {
362274
cmd.AddCommand(
363275
"start-debugging", new StartDebuggingRequestHandler(dap),
364276
"Sends a startDebugging request from the debug adapter to the client "
@@ -370,37 +282,15 @@ void InitializeRequestHandler::operator()(
370282
cmd.AddCommand("send-event", new SendEventRequestHandler(dap),
371283
"Sends an DAP event to the client.");
372284

373-
dap.progress_event_thread =
374-
std::thread(ProgressEventThreadFunction, std::ref(dap));
285+
if (arguments.supportedFeatures.contains(eClientFeatureProgressReporting))
286+
dap.progress_event_thread =
287+
std::thread(ProgressEventThreadFunction, std::ref(dap));
375288

376289
// Start our event thread so we can receive events from the debugger, target,
377290
// process and more.
378291
dap.event_thread = std::thread(EventThreadFunction, std::ref(dap));
379292

380-
llvm::StringMap<bool> capabilities = dap.GetCapabilities();
381-
for (auto &kv : capabilities)
382-
body.try_emplace(kv.getKey(), kv.getValue());
383-
384-
// Available filters or options for the setExceptionBreakpoints request.
385-
llvm::json::Array filters;
386-
for (const auto &exc_bp : *dap.exception_breakpoints)
387-
filters.emplace_back(CreateExceptionBreakpointFilter(exc_bp));
388-
body.try_emplace("exceptionBreakpointFilters", std::move(filters));
389-
390-
llvm::json::Array completion_characters;
391-
completion_characters.emplace_back(".");
392-
completion_characters.emplace_back(" ");
393-
completion_characters.emplace_back("\t");
394-
body.try_emplace("completionTriggerCharacters",
395-
std::move(completion_characters));
396-
397-
// Put in non-DAP specification lldb specific information.
398-
llvm::json::Object lldb_json;
399-
lldb_json.try_emplace("version", dap.debugger.GetVersionString());
400-
body.try_emplace("__lldb", std::move(lldb_json));
401-
402-
response.try_emplace("body", std::move(body));
403-
dap.SendJSON(llvm::json::Value(std::move(response)));
293+
return dap.GetCapabilities();
404294
}
405295

406296
} // namespace lldb_dap

lldb/tools/lldb-dap/Handler/RequestHandler.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include "JSONUtils.h"
1313
#include "LLDBUtils.h"
1414
#include "RunInTerminal.h"
15+
#include "llvm/Support/Error.h"
1516

1617
#if !defined(_WIN32)
1718
#include <unistd.h>
@@ -97,6 +98,11 @@ void BaseRequestHandler::SetSourceMapFromArguments(
9798
static llvm::Error RunInTerminal(DAP &dap,
9899
const llvm::json::Object &launch_request,
99100
const uint64_t timeout_seconds) {
101+
if (!dap.clientFeatures.contains(
102+
protocol::eClientFeatureRunInTerminalRequest))
103+
return llvm::make_error<DAPError>("Cannot use runInTerminal, feature is "
104+
"not supported by the connected client");
105+
100106
dap.is_attach = true;
101107
lldb::SBAttachInfo attach_info;
102108

0 commit comments

Comments
 (0)