-
Notifications
You must be signed in to change notification settings - Fork 14.3k
[lldb-dap] Migrating DAP 'initialize' to new typed RequestHandler. #133007
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
@llvm/pr-subscribers-lldb Author: John Harrison (ashgti) ChangesThis 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 Patch is 56.73 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/133007.diff 13 Files Affected:
diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py
index 359ac718138b2..01ef4b68f2653 100644
--- a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py
+++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py
@@ -776,7 +776,8 @@ def request_initialize(self, sourceInitFile):
"supportsVariablePaging": True,
"supportsVariableType": True,
"supportsStartDebuggingRequest": True,
- "sourceInitFile": sourceInitFile,
+ "supportsProgressReporting": True,
+ "$__lldb_sourceInitFile": sourceInitFile,
},
}
response = self.send_recv(command_dict)
@@ -1261,7 +1262,7 @@ def launch(cls, /, executable, env=None, log_file=None, connection=None):
expected_prefix = "Listening for: "
out = process.stdout.readline().decode()
if not out.startswith(expected_prefix):
- self.process.kill()
+ process.kill()
raise ValueError(
"lldb-dap failed to print listening address, expected '{}', got '{}'".format(
expected_prefix, out
diff --git a/lldb/test/API/tools/lldb-dap/launch/TestDAP_launch.py b/lldb/test/API/tools/lldb-dap/launch/TestDAP_launch.py
index 0c92e5bff07c6..64c99019a1c9b 100644
--- a/lldb/test/API/tools/lldb-dap/launch/TestDAP_launch.py
+++ b/lldb/test/API/tools/lldb-dap/launch/TestDAP_launch.py
@@ -524,8 +524,7 @@ def test_version(self):
# The first line is the prompt line like "(lldb) version", so we skip it.
version_eval_output_without_prompt_line = version_eval_output.splitlines()[1:]
- lldb_json = self.dap_server.get_initialize_value("__lldb")
- version_string = lldb_json["version"]
+ version_string = self.dap_server.get_initialize_value("$__lldb_version")
self.assertEqual(
version_eval_output_without_prompt_line,
version_string.splitlines(),
diff --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp
index 65de0488729e5..0da8ce43f73c4 100644
--- a/lldb/tools/lldb-dap/DAP.cpp
+++ b/lldb/tools/lldb-dap/DAP.cpp
@@ -14,6 +14,7 @@
#include "LLDBUtils.h"
#include "OutputRedirector.h"
#include "Protocol/ProtocolBase.h"
+#include "Protocol/ProtocolTypes.h"
#include "Transport.h"
#include "lldb/API/SBBreakpoint.h"
#include "lldb/API/SBCommandInterpreter.h"
@@ -1144,31 +1145,137 @@ lldb::SBValue Variables::FindVariable(uint64_t variablesReference,
return variable;
}
-llvm::StringMap<bool> DAP::GetCapabilities() {
- llvm::StringMap<bool> capabilities;
+static void mergeCapabilities(protocol::Capabilities &into,
+ const protocol::Capabilities &from) {
+ if (from.supportsConfigurationDoneRequest)
+ into.supportsConfigurationDoneRequest =
+ *from.supportsConfigurationDoneRequest;
+ if (from.supportsFunctionBreakpoints)
+ into.supportsFunctionBreakpoints = *from.supportsFunctionBreakpoints;
+ if (from.supportsConditionalBreakpoints)
+ into.supportsConditionalBreakpoints = *from.supportsConditionalBreakpoints;
+ if (from.supportsHitConditionalBreakpoints)
+ into.supportsHitConditionalBreakpoints =
+ *from.supportsHitConditionalBreakpoints;
+ if (from.supportsEvaluateForHovers)
+ into.supportsEvaluateForHovers = *from.supportsEvaluateForHovers;
+ if (from.exceptionBreakpointFilters)
+ into.exceptionBreakpointFilters = *from.exceptionBreakpointFilters;
+ if (from.supportsStepBack)
+ into.supportsStepBack = *from.supportsStepBack;
+ if (from.supportsSetVariable)
+ into.supportsSetVariable = *from.supportsSetVariable;
+ if (from.supportsRestartFrame)
+ into.supportsRestartFrame = *from.supportsRestartFrame;
+ if (from.supportsGotoTargetsRequest)
+ into.supportsGotoTargetsRequest = *from.supportsGotoTargetsRequest;
+ if (from.supportsStepInTargetsRequest)
+ into.supportsStepInTargetsRequest = *from.supportsStepInTargetsRequest;
+ if (from.supportsCompletionsRequest)
+ into.supportsCompletionsRequest = *from.supportsCompletionsRequest;
+ if (from.completionTriggerCharacters)
+ into.completionTriggerCharacters = *from.completionTriggerCharacters;
+ if (from.supportsModulesRequest)
+ into.supportsModulesRequest = *from.supportsModulesRequest;
+ if (from.additionalModuleColumns)
+ into.additionalModuleColumns = *from.additionalModuleColumns;
+ if (from.supportedChecksumAlgorithms)
+ into.supportedChecksumAlgorithms = *from.supportedChecksumAlgorithms;
+ if (from.supportsRestartRequest)
+ into.supportsRestartRequest = *from.supportsRestartRequest;
+ if (from.supportsExceptionOptions)
+ into.supportsExceptionOptions = *from.supportsExceptionOptions;
+ if (from.supportsValueFormattingOptions)
+ into.supportsValueFormattingOptions = *from.supportsValueFormattingOptions;
+ if (from.supportsExceptionInfoRequest)
+ into.supportsExceptionInfoRequest = *from.supportsExceptionInfoRequest;
+ if (from.supportTerminateDebuggee)
+ into.supportTerminateDebuggee = *from.supportTerminateDebuggee;
+ if (from.supportSuspendDebuggee)
+ into.supportSuspendDebuggee = *from.supportSuspendDebuggee;
+ if (from.supportsDelayedStackTraceLoading)
+ into.supportsDelayedStackTraceLoading =
+ *from.supportsDelayedStackTraceLoading;
+ if (from.supportsLoadedSourcesRequest)
+ into.supportsLoadedSourcesRequest = *from.supportsLoadedSourcesRequest;
+ if (from.supportsLogPoints)
+ into.supportsLogPoints = *from.supportsLogPoints;
+ if (from.supportsTerminateThreadsRequest)
+ into.supportsTerminateThreadsRequest =
+ *from.supportsTerminateThreadsRequest;
+ if (from.supportsSetExpression)
+ into.supportsSetExpression = *from.supportsSetExpression;
+ if (from.supportsTerminateRequest)
+ into.supportsTerminateRequest = *from.supportsTerminateRequest;
+ if (from.supportsDataBreakpoints)
+ into.supportsDataBreakpoints = *from.supportsDataBreakpoints;
+ if (from.supportsReadMemoryRequest)
+ into.supportsReadMemoryRequest = *from.supportsReadMemoryRequest;
+ if (from.supportsWriteMemoryRequest)
+ into.supportsWriteMemoryRequest = *from.supportsWriteMemoryRequest;
+ if (from.supportsDisassembleRequest)
+ into.supportsDisassembleRequest = *from.supportsDisassembleRequest;
+ if (from.supportsCancelRequest)
+ into.supportsCancelRequest = *from.supportsCancelRequest;
+ if (from.supportsBreakpointLocationsRequest)
+ into.supportsBreakpointLocationsRequest =
+ *from.supportsBreakpointLocationsRequest;
+ if (from.supportsClipboardContext)
+ into.supportsClipboardContext = *from.supportsClipboardContext;
+ if (from.supportsSteppingGranularity)
+ into.supportsSteppingGranularity = *from.supportsSteppingGranularity;
+ if (from.supportsInstructionBreakpoints)
+ into.supportsInstructionBreakpoints = *from.supportsInstructionBreakpoints;
+ if (from.supportsExceptionFilterOptions)
+ into.supportsExceptionFilterOptions = *from.supportsExceptionFilterOptions;
+ if (from.supportsSingleThreadExecutionRequests)
+ into.supportsSingleThreadExecutionRequests =
+ *from.supportsSingleThreadExecutionRequests;
+ if (from.supportsDataBreakpointBytes)
+ into.supportsDataBreakpointBytes = *from.supportsDataBreakpointBytes;
+ if (from.breakpointModes)
+ into.breakpointModes = *from.breakpointModes;
+ if (from.supportsANSIStyling)
+ into.supportsANSIStyling = *from.supportsANSIStyling;
+}
+
+protocol::Capabilities DAP::GetCapabilities() {
+ protocol::Capabilities capabilities;
// Supported capabilities.
- capabilities["supportTerminateDebuggee"] = true;
- capabilities["supportsDataBreakpoints"] = true;
- capabilities["supportsDelayedStackTraceLoading"] = true;
- capabilities["supportsEvaluateForHovers"] = true;
- capabilities["supportsExceptionOptions"] = true;
- capabilities["supportsLogPoints"] = true;
- capabilities["supportsProgressReporting"] = true;
- capabilities["supportsSteppingGranularity"] = true;
- capabilities["supportsValueFormattingOptions"] = true;
+ capabilities.supportTerminateDebuggee = true;
+ capabilities.supportsDataBreakpoints = true;
+ capabilities.supportsDelayedStackTraceLoading = true;
+ capabilities.supportsEvaluateForHovers = true;
+ capabilities.supportsExceptionOptions = true;
+ capabilities.supportsLogPoints = true;
+ capabilities.supportsSteppingGranularity = true;
+ capabilities.supportsValueFormattingOptions = true;
// Unsupported capabilities.
- capabilities["supportsGotoTargetsRequest"] = false;
- capabilities["supportsLoadedSourcesRequest"] = false;
- capabilities["supportsRestartFrame"] = false;
- capabilities["supportsStepBack"] = false;
+ capabilities.supportsGotoTargetsRequest = false;
+ capabilities.supportsLoadedSourcesRequest = false;
+ capabilities.supportsRestartFrame = false;
+ capabilities.supportsStepBack = false;
// Capabilities associated with specific requests.
- for (auto &kv : request_handlers) {
- for (auto &request_kv : kv.second->GetCapabilities())
- capabilities[request_kv.getKey()] = request_kv.getValue();
- }
+ for (auto &kv : request_handlers)
+ mergeCapabilities(capabilities, kv.second->GetCapabilities());
+
+ // Available filters or options for the setExceptionBreakpoints request.
+ std::vector<protocol::ExceptionBreakpointsFilter> filters;
+ for (const auto &exc_bp : *exception_breakpoints)
+ filters.emplace_back(CreateExceptionBreakpointFilter(exc_bp));
+ capabilities.exceptionBreakpointFilters = std::move(filters);
+
+ std::vector<std::string> completion_characters;
+ completion_characters.emplace_back(".");
+ completion_characters.emplace_back(" ");
+ completion_characters.emplace_back("\t");
+ capabilities.completionTriggerCharacters = std::move(completion_characters);
+
+ // Put in non-DAP specification lldb specific information.
+ capabilities.lldbVersion = debugger.GetVersionString();
return capabilities;
}
diff --git a/lldb/tools/lldb-dap/DAP.h b/lldb/tools/lldb-dap/DAP.h
index 4b4d471161137..2f23e5a116bc3 100644
--- a/lldb/tools/lldb-dap/DAP.h
+++ b/lldb/tools/lldb-dap/DAP.h
@@ -16,6 +16,7 @@
#include "OutputRedirector.h"
#include "ProgressEvent.h"
#include "Protocol/ProtocolBase.h"
+#include "Protocol/ProtocolTypes.h"
#include "SourceBreakpoint.h"
#include "Transport.h"
#include "lldb/API/SBBroadcaster.h"
@@ -180,6 +181,7 @@ struct DAP {
bool enable_auto_variable_summaries;
bool enable_synthetic_child_debugging;
bool display_extended_backtrace;
+ bool supports_run_in_terminal_request;
// The process event thread normally responds to process exited events by
// shutting down the entire adapter. When we're restarting, we keep the id of
// the old process here so we can detect this case and keep running.
@@ -363,8 +365,8 @@ struct DAP {
request_handlers[Handler::GetCommand()] = std::make_unique<Handler>(*this);
}
- /// Return a key-value list of capabilities.
- llvm::StringMap<bool> GetCapabilities();
+ /// The set of capablities supported by this adapter.
+ protocol::Capabilities GetCapabilities();
/// Debuggee will continue from stopped state.
void WillContinue() { variables.Clear(); }
diff --git a/lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp
index a8fe0d6ffce8b..b85424975a0fc 100644
--- a/lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp
@@ -9,6 +9,7 @@
#include "DAP.h"
#include "EventHelper.h"
#include "JSONUtils.h"
+#include "Protocol/ProtocolRequests.h"
#include "RequestHandler.h"
#include "lldb/API/SBEvent.h"
#include "lldb/API/SBListener.h"
@@ -229,118 +230,29 @@ static void EventThreadFunction(DAP &dap) {
}
}
-// "InitializeRequest": {
-// "allOf": [ { "$ref": "#/definitions/Request" }, {
-// "type": "object",
-// "description": "Initialize request; value of command field is
-// 'initialize'.",
-// "properties": {
-// "command": {
-// "type": "string",
-// "enum": [ "initialize" ]
-// },
-// "arguments": {
-// "$ref": "#/definitions/InitializeRequestArguments"
-// }
-// },
-// "required": [ "command", "arguments" ]
-// }]
-// },
-// "InitializeRequestArguments": {
-// "type": "object",
-// "description": "Arguments for 'initialize' request.",
-// "properties": {
-// "clientID": {
-// "type": "string",
-// "description": "The ID of the (frontend) client using this adapter."
-// },
-// "adapterID": {
-// "type": "string",
-// "description": "The ID of the debug adapter."
-// },
-// "locale": {
-// "type": "string",
-// "description": "The ISO-639 locale of the (frontend) client using
-// this adapter, e.g. en-US or de-CH."
-// },
-// "linesStartAt1": {
-// "type": "boolean",
-// "description": "If true all line numbers are 1-based (default)."
-// },
-// "columnsStartAt1": {
-// "type": "boolean",
-// "description": "If true all column numbers are 1-based (default)."
-// },
-// "pathFormat": {
-// "type": "string",
-// "_enum": [ "path", "uri" ],
-// "description": "Determines in what format paths are specified. The
-// default is 'path', which is the native format."
-// },
-// "supportsVariableType": {
-// "type": "boolean",
-// "description": "Client supports the optional type attribute for
-// variables."
-// },
-// "supportsVariablePaging": {
-// "type": "boolean",
-// "description": "Client supports the paging of variables."
-// },
-// "supportsRunInTerminalRequest": {
-// "type": "boolean",
-// "description": "Client supports the runInTerminal request."
-// }
-// },
-// "required": [ "adapterID" ]
-// },
-// "InitializeResponse": {
-// "allOf": [ { "$ref": "#/definitions/Response" }, {
-// "type": "object",
-// "description": "Response to 'initialize' request.",
-// "properties": {
-// "body": {
-// "$ref": "#/definitions/Capabilities",
-// "description": "The capabilities of this debug adapter."
-// }
-// }
-// }]
-// }
-void InitializeRequestHandler::operator()(
- const llvm::json::Object &request) const {
- llvm::json::Object response;
- FillResponse(request, response);
- llvm::json::Object body;
-
- const auto *arguments = request.getObject("arguments");
- // sourceInitFile option is not from formal DAP specification. It is only
- // used by unit tests to prevent sourcing .lldbinit files from environment
- // which may affect the outcome of tests.
- bool source_init_file =
- GetBoolean(arguments, "sourceInitFile").value_or(true);
-
+/// Initialize request; value of command field is 'initialize'.
+llvm::Expected<protocol::InitializeResponseBody> InitializeRequestHandler::Run(
+ const protocol::InitializeRequestArguments &arguments) const {
// Do not source init files until in/out/err are configured.
dap.debugger = lldb::SBDebugger::Create(false);
dap.debugger.SetInputFile(dap.in);
- auto out_fd = dap.out.GetWriteFileDescriptor();
- if (llvm::Error err = out_fd.takeError()) {
- response["success"] = false;
- EmplaceSafeString(response, "message", llvm::toString(std::move(err)));
- dap.SendJSON(llvm::json::Value(std::move(response)));
- return;
- }
+
+ llvm::Expected<int> out_fd = dap.out.GetWriteFileDescriptor();
+ if (!out_fd)
+ return out_fd.takeError();
dap.debugger.SetOutputFile(lldb::SBFile(*out_fd, "w", false));
- auto err_fd = dap.err.GetWriteFileDescriptor();
- if (llvm::Error err = err_fd.takeError()) {
- response["success"] = false;
- EmplaceSafeString(response, "message", llvm::toString(std::move(err)));
- dap.SendJSON(llvm::json::Value(std::move(response)));
- return;
- }
+
+ llvm::Expected<int> err_fd = dap.err.GetWriteFileDescriptor();
+ if (!err_fd)
+ return err_fd.takeError();
dap.debugger.SetErrorFile(lldb::SBFile(*err_fd, "w", false));
auto interp = dap.debugger.GetCommandInterpreter();
- if (source_init_file) {
+ // sourceInitFile option is not from formal DAP specification. It is only
+ // used by unit tests to prevent sourcing .lldbinit files from environment
+ // which may affect the outcome of tests.
+ if (arguments.sourceInitFile.value_or(true)) {
dap.debugger.SkipLLDBInitFiles(false);
dap.debugger.SkipAppInitFiles(false);
lldb::SBCommandReturnObject init;
@@ -348,59 +260,35 @@ void InitializeRequestHandler::operator()(
interp.SourceInitFileInHomeDirectory(init);
}
- if (llvm::Error err = dap.RunPreInitCommands()) {
- response["success"] = false;
- EmplaceSafeString(response, "message", llvm::toString(std::move(err)));
- dap.SendJSON(llvm::json::Value(std::move(response)));
- return;
- }
+ if (llvm::Error err = dap.RunPreInitCommands())
+ return err;
dap.PopulateExceptionBreakpoints();
auto cmd = dap.debugger.GetCommandInterpreter().AddMultiwordCommand(
"lldb-dap", "Commands for managing lldb-dap.");
- if (GetBoolean(arguments, "supportsStartDebuggingRequest").value_or(false)) {
+ if (arguments.supportsStartDebuggingRequest.value_or(false)) {
cmd.AddCommand(
"start-debugging", new StartDebuggingRequestHandler(dap),
"Sends a startDebugging request from the debug adapter to the client "
"to start a child debug session of the same type as the caller.");
}
+ dap.supports_run_in_terminal_request =
+ arguments.supportsRunInTerminalRequest.value_or(false);
cmd.AddCommand(
"repl-mode", new ReplModeRequestHandler(dap),
"Get or set the repl behavior of lldb-dap evaluation requests.");
cmd.AddCommand("send-event", new SendEventRequestHandler(dap),
"Sends an DAP event to the client.");
- dap.progress_event_thread =
- std::thread(ProgressEventThreadFunction, std::ref(dap));
+ if (arguments.supportsProgressReporting)
+ dap.progress_event_thread =
+ std::thread(ProgressEventThreadFunction, std::ref(dap));
// Start our event thread so we can receive events from the debugger, target,
// process and more.
dap.event_thread = std::thread(EventThreadFunction, std::ref(dap));
- llvm::StringMap<bool> capabilities = dap.GetCapabilities();
- for (auto &kv : capabilities)
- body.try_emplace(kv.getKey(), kv.getValue());
-
- // Available filters or options for the setExceptionBreakpoints request.
- llvm::json::Array filters;
- for (const auto &exc_bp : *dap.exception_breakpoints)
- filters.emplace_back(CreateExceptionBreakpointFilter(exc_bp));
- body.try_emplace("exceptionBreakpointFilters", std::move(filters));
-
- llvm::json::Array completion_characters;
- completion_characters.emplace_back(".");
- completion_characters.emplace_back(" ");
- completion_characters.emplace_back("\t");
- body.try_emplace("completionTriggerCharacters",
- std::move(completion_characters));
-
- // Put in non-DAP specification lldb specific information.
- llvm::json::Object lldb_json;
- lldb_json.try_emplace("version", dap.debugger.GetVersionString());
- body.try_emplace("__lldb", std::move(lldb_json));
-
- response.try_emplace("body", std::move(body));
- dap.SendJSON(llvm::json::Value(std::move(response)));
+ return dap.GetCapabilities();
}
} // namespace lldb_dap
diff --git a/lldb/tools/lldb-dap/Handler/RequestHandler.cpp b/lldb/tools/lldb-dap/Handler/RequestHandler.cpp
index 60c82649938d6..d2a0da420739e 100644
--- a/lldb/tools/lldb-dap/Handler/RequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/RequestHandler.cpp
@@ -201,6 +201,7 @@ BaseRequestHandler::LaunchProcess(const llvm::json::Object &request) const {
const auto timeout_seconds =
GetInteger<uint64_t>(arguments, "timeout").value_or(30);
+ // FIXME: Check dap.supports_run_in_terminal_request.
if (GetBoolean(arguments, "runInTerminal").value_or(false)) {
if (llvm::Error err = RunInTerminal(dap, request, timeout_seconds))
error.SetErrorString(llvm::toString(std::move(err)).c_str());
diff --git a/lldb/tools/lldb-dap/Handler/...
[truncated]
|
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code seems quite repetitive. Would it be possible to have a Capabilities
enum and then representing the actual values by something like map<Capability, bool>
(with std::nullopt replaced by removing the capability from the map). Or even map<string, bool>
if you don't need to access the capabilities individually (very often)? And maybe the handlers don't even need to return the map, but just a list/vector of "new" capabilities that they enable?
…zeRequestHandler into a `std::set<Feature>` to simplify these flags.
Done. Converted these into |
Co-authored-by: Jonas Devlieghere <[email protected]>
✅ With the latest revision this PR passed the C/C++ code formatter. |
…nseSet/SmallDenseSet and using some lookup tables to map strings to enum values.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
case eAdapterFeatureSupportsBreakpointLocationsRequest: | ||
return "supportsBreakpointLocationsRequest"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel bad for not thinking about this earlier, but now that I see this switch, I think this would' bea good candidate for a .def
file. We could have a dap.def
file that defines and use that to implement methods for both serializing and deserializing. That doesn't need to be part of this PR though.
Co-authored-by: Jonas Devlieghere <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, I'm happy with this!
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
andlldb_dap::protocol::Capabilities
uncovered the inconsistencies.