Skip to content

Commit 07a1d47

Browse files
authored
[lldb-dap] Creating a 'capabilities' event helper. (#142831)
This adds a new 'CapabilitiesEventBody' type for having a well structured type for the event and updates the 'restart' request to dynamically set their capabilities.
1 parent c309525 commit 07a1d47

File tree

13 files changed

+144
-30
lines changed

13 files changed

+144
-30
lines changed

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

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,10 @@ def as_dict(self):
135135
return source_dict
136136

137137

138+
class NotSupportedError(KeyError):
139+
"""Raised if a feature is not supported due to its capabilities."""
140+
141+
138142
class DebugCommunication(object):
139143
def __init__(
140144
self,
@@ -153,7 +157,7 @@ def __init__(
153157
self.recv_thread = threading.Thread(target=self._read_packet_thread)
154158
self.process_event_body = None
155159
self.exit_status: Optional[int] = None
156-
self.initialize_body = None
160+
self.capabilities: dict[str, Any] = {}
157161
self.progress_events: list[Event] = []
158162
self.reverse_requests = []
159163
self.sequence = 1
@@ -300,6 +304,9 @@ def _handle_recv_packet(self, packet: Optional[ProtocolMessage]) -> bool:
300304
elif event == "breakpoint":
301305
# Breakpoint events are sent when a breakpoint is resolved
302306
self._update_verified_breakpoints([body["breakpoint"]])
307+
elif event == "capabilities":
308+
# Update the capabilities with new ones from the event.
309+
self.capabilities.update(body["capabilities"])
303310

304311
elif packet_type == "response":
305312
if packet["command"] == "disconnect":
@@ -487,13 +494,13 @@ def wait_for_terminated(self, timeout: Optional[float] = None):
487494
raise ValueError("didn't get terminated event")
488495
return event_dict
489496

490-
def get_initialize_value(self, key):
497+
def get_capability(self, key):
491498
"""Get a value for the given key if it there is a key/value pair in
492-
the "initialize" request response body.
499+
the capabilities reported by the adapter.
493500
"""
494-
if self.initialize_body and key in self.initialize_body:
495-
return self.initialize_body[key]
496-
return None
501+
if key in self.capabilities:
502+
return self.capabilities[key]
503+
raise NotSupportedError(key)
497504

498505
def get_threads(self):
499506
if self.threads is None:
@@ -759,6 +766,9 @@ def request_continue(self, threadId=None, singleThread=False):
759766
return response
760767

761768
def request_restart(self, restartArguments=None):
769+
if self.exit_status is not None:
770+
raise ValueError("request_restart called after process exited")
771+
self.get_capability("supportsRestartRequest")
762772
command_dict = {
763773
"command": "restart",
764774
"type": "request",
@@ -866,7 +876,7 @@ def request_initialize(self, sourceInitFile=False):
866876
response = self.send_recv(command_dict)
867877
if response:
868878
if "body" in response:
869-
self.initialize_body = response["body"]
879+
self.capabilities = response["body"]
870880
return response
871881

872882
def request_launch(
@@ -971,6 +981,7 @@ def request_stepIn(self, threadId, targetId, granularity="statement"):
971981
def request_stepInTargets(self, frameId):
972982
if self.exit_status is not None:
973983
raise ValueError("request_stepInTargets called after process exited")
984+
self.get_capability("supportsStepInTargetsRequest")
974985
args_dict = {"frameId": frameId}
975986
command_dict = {
976987
"command": "stepInTargets",

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,16 @@ def waitUntil(self, condition_callback):
128128
time.sleep(0.5)
129129
return False
130130

131+
def assertCapabilityIsSet(self, key: str, msg: Optional[str] = None) -> None:
132+
"""Assert that given capability is set in the client."""
133+
self.assertIn(key, self.dap_server.capabilities, msg)
134+
self.assertEqual(self.dap_server.capabilities[key], True, msg)
135+
136+
def assertCapabilityIsNotSet(self, key: str, msg: Optional[str] = None) -> None:
137+
"""Assert that given capability is not set in the client."""
138+
if key in self.dap_server.capabilities:
139+
self.assertEqual(self.dap_server.capabilities[key], False, msg)
140+
131141
def verify_breakpoint_hit(self, breakpoint_ids, timeout=DEFAULT_TIMEOUT):
132142
"""Wait for the process we are debugging to stop, and verify we hit
133143
any breakpoint location in the "breakpoint_ids" array.

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -566,7 +566,7 @@ def test_version(self):
566566
)
567567
version_eval_output = version_eval_response["body"]["result"]
568568

569-
version_string = self.dap_server.get_initialize_value("$__lldb_version")
569+
version_string = self.dap_server.get_capability("$__lldb_version")
570570
self.assertEqual(
571571
version_eval_output.splitlines(),
572572
version_string.splitlines(),

lldb/tools/lldb-dap/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ add_lldb_library(lldbDAP
6767
Handler/VariablesRequestHandler.cpp
6868

6969
Protocol/ProtocolBase.cpp
70+
Protocol/ProtocolEvents.cpp
7071
Protocol/ProtocolTypes.cpp
7172
Protocol/ProtocolRequests.cpp
7273

lldb/tools/lldb-dap/EventHelper.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
#include "DAPError.h"
1212
#include "JSONUtils.h"
1313
#include "LLDBUtils.h"
14+
#include "Protocol/ProtocolEvents.h"
15+
#include "Protocol/ProtocolTypes.h"
1416
#include "lldb/API/SBFileSpec.h"
1517
#include "llvm/Support/Error.h"
1618

@@ -36,6 +38,22 @@ static void SendThreadExitedEvent(DAP &dap, lldb::tid_t tid) {
3638
dap.SendJSON(llvm::json::Value(std::move(event)));
3739
}
3840

41+
void SendTargetBasedCapabilities(DAP &dap) {
42+
if (!dap.target.IsValid())
43+
return;
44+
45+
protocol::CapabilitiesEventBody body;
46+
47+
// We only support restarting launch requests not attach requests.
48+
if (dap.last_launch_request)
49+
body.capabilities.supportedFeatures.insert(
50+
protocol::eAdapterFeatureRestartRequest);
51+
52+
// Only notify the client if supportedFeatures changed.
53+
if (!body.capabilities.supportedFeatures.empty())
54+
dap.Send(protocol::Event{"capabilities", body});
55+
}
56+
3957
// "ProcessEvent": {
4058
// "allOf": [
4159
// { "$ref": "#/definitions/Event" },

lldb/tools/lldb-dap/EventHelper.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ struct DAP;
1717

1818
enum LaunchMethod { Launch, Attach, AttachForSuspendedLaunch };
1919

20+
/// Update capabilities based on the configured target.
21+
void SendTargetBasedCapabilities(DAP &dap);
22+
2023
void SendProcessEvent(DAP &dap, LaunchMethod launch_method);
2124

2225
llvm::Error SendThreadStoppedEvent(DAP &dap, bool on_entry = false);

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ ConfigurationDoneRequestHandler::Run(const ConfigurationDoneArguments &) const {
4141
"any debugger command scripts are not resuming the process during the "
4242
"launch sequence.");
4343

44+
// Waiting until 'configurationDone' to send target based capabilities in case
45+
// the launch or attach scripts adjust the target. The initial dummy target
46+
// may have different capabilities than the final target.
47+
SendTargetBasedCapabilities(dap);
48+
4449
// Clients can request a baseline of currently existing threads after
4550
// we acknowledge the configurationDone request.
4651
// Client requests the baseline of currently existing threads after

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

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -334,9 +334,6 @@ class RestartRequestHandler : public LegacyRequestHandler {
334334
public:
335335
using LegacyRequestHandler::LegacyRequestHandler;
336336
static llvm::StringLiteral GetCommand() { return "restart"; }
337-
FeatureSet GetSupportedFeatures() const override {
338-
return {protocol::eAdapterFeatureRestartRequest};
339-
}
340337
void operator()(const llvm::json::Object &request) const override;
341338
};
342339

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

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -69,23 +69,6 @@ void RestartRequestHandler::operator()(
6969
dap.SendJSON(llvm::json::Value(std::move(response)));
7070
return;
7171
}
72-
// Check if we were in a "launch" session or an "attach" session.
73-
//
74-
// Restarting is not well defined when we started the session by attaching to
75-
// an existing process, because we don't know how the process was started, so
76-
// we don't support it.
77-
//
78-
// Note that when using runInTerminal we're technically attached, but it's an
79-
// implementation detail. The adapter *did* launch the process in response to
80-
// a "launch" command, so we can still stop it and re-run it. This is why we
81-
// don't just check `dap.is_attach`.
82-
if (!dap.last_launch_request) {
83-
response["success"] = llvm::json::Value(false);
84-
EmplaceSafeString(response, "message",
85-
"Restarting an \"attach\" session is not supported.");
86-
dap.SendJSON(llvm::json::Value(std::move(response)));
87-
return;
88-
}
8972

9073
const llvm::json::Object *arguments = request.getObject("arguments");
9174
if (arguments) {

lldb/tools/lldb-dap/Protocol/ProtocolBase.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@
1717
//
1818
//===----------------------------------------------------------------------===//
1919

20-
#ifndef LLDB_TOOLS_LLDB_DAP_PROTOCOL_H
21-
#define LLDB_TOOLS_LLDB_DAP_PROTOCOL_H
20+
#ifndef LLDB_TOOLS_LLDB_DAP_PROTOCOL_PROTOCOL_BASE_H
21+
#define LLDB_TOOLS_LLDB_DAP_PROTOCOL_PROTOCOL_BASE_H
2222

2323
#include "llvm/Support/JSON.h"
2424
#include <cstdint>
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
//===-- ProtocolEvents.cpp ------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "Protocol/ProtocolEvents.h"
10+
#include "llvm/Support/JSON.h"
11+
12+
using namespace llvm;
13+
14+
namespace lldb_dap::protocol {
15+
16+
json::Value toJSON(const CapabilitiesEventBody &CEB) {
17+
return json::Object{{"capabilities", CEB.capabilities}};
18+
}
19+
20+
} // namespace lldb_dap::protocol
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
//===-- ProtocolEvents.h --------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
//
9+
// This file contains POD structs based on the DAP specification at
10+
// https://microsoft.github.io/debug-adapter-protocol/specification
11+
//
12+
// This is not meant to be a complete implementation, new interfaces are added
13+
// when they're needed.
14+
//
15+
// Each struct has a toJSON and fromJSON function, that converts between
16+
// the struct and a JSON representation. (See JSON.h)
17+
//
18+
//===----------------------------------------------------------------------===//
19+
20+
#ifndef LLDB_TOOLS_LLDB_DAP_PROTOCOL_PROTOCOL_EVENTS_H
21+
#define LLDB_TOOLS_LLDB_DAP_PROTOCOL_PROTOCOL_EVENTS_H
22+
23+
#include "Protocol/ProtocolTypes.h"
24+
#include "llvm/Support/JSON.h"
25+
26+
namespace lldb_dap::protocol {
27+
28+
/// The event indicates that one or more capabilities have changed.
29+
///
30+
/// Since the capabilities are dependent on the client and its UI, it might not
31+
/// be possible to change that at random times (or too late).
32+
///
33+
/// Consequently this event has a hint characteristic: a client can only be
34+
/// expected to make a 'best effort' in honoring individual capabilities but
35+
/// there are no guarantees.
36+
///
37+
/// Only changed capabilities need to be included, all other capabilities keep
38+
/// their values.
39+
struct CapabilitiesEventBody {
40+
Capabilities capabilities;
41+
};
42+
llvm::json::Value toJSON(const CapabilitiesEventBody &);
43+
44+
} // end namespace lldb_dap::protocol
45+
46+
#endif

lldb/unittests/DAP/ProtocolTypesTest.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@
77
//===----------------------------------------------------------------------===//
88

99
#include "Protocol/ProtocolTypes.h"
10+
#include "Protocol/ProtocolEvents.h"
1011
#include "Protocol/ProtocolRequests.h"
1112
#include "llvm/ADT/StringRef.h"
1213
#include "llvm/Support/JSON.h"
1314
#include "llvm/Testing/Support/Error.h"
1415
#include "gmock/gmock.h"
1516
#include "gtest/gtest.h"
17+
#include <optional>
1618

1719
using namespace llvm;
1820
using namespace lldb;
@@ -666,3 +668,21 @@ TEST(ProtocolTypesTest, ThreadResponseBody) {
666668
// Validate toJSON
667669
EXPECT_EQ(json, pp(body));
668670
}
671+
672+
TEST(ProtocolTypesTest, CapabilitiesEventBody) {
673+
Capabilities capabilities;
674+
capabilities.supportedFeatures = {
675+
eAdapterFeatureANSIStyling,
676+
eAdapterFeatureBreakpointLocationsRequest,
677+
};
678+
CapabilitiesEventBody body;
679+
body.capabilities = capabilities;
680+
StringRef json = R"({
681+
"capabilities": {
682+
"supportsANSIStyling": true,
683+
"supportsBreakpointLocationsRequest": true
684+
}
685+
})";
686+
// Validate toJSON
687+
EXPECT_EQ(json, pp(body));
688+
}

0 commit comments

Comments
 (0)