[Lldb-commits] [lldb] [lldb-dap] Support cancel requests and add explicit DAP structures. (PR #128276)
John Harrison via lldb-commits
lldb-commits at lists.llvm.org
Fri Feb 21 19:40:46 PST 2025
https://github.com/ashgti updated https://github.com/llvm/llvm-project/pull/128276
>From 0e4cc277229e19fde768fe6faecbef1496d00b61 Mon Sep 17 00:00:00 2001
From: John Harrison <harjohn at google.com>
Date: Tue, 11 Feb 2025 16:11:55 -0800
Subject: [PATCH] [lldb-dap] Support cancel requests and add explicit DAP
structures.
This is a refactor of lldb-dap focused on supporting cancel requests and well defined Debug Adapter Protocol (DAP) structures.
Existing request handlers today tend to use unstructured `llvm::json::Object`'s to represent requests and replies. The lack of structure makes it hard for us to apply some unfirom handling around requests to better support cancellation. To address this, this change includes a new `Protocol.h` file with a set of well defined POD structures to represent the types in the DAP and includes `toJSON`/`fromJSON` serialization support.
Building on these types, this includes a new approach to registering request handlers with these new well defined types.
```
template <typename Args, typename Body>
void DAP::RegisterRequest(llvm::StringLiteral command,
RequestHandler<Args, Body> handler);
...
llvm::Expected<SourceResponseBody> request_source(DAP &dap, const SourceArguments &args) {
SourceResponseBody body;
...
if (/*some error*/)
return llvm::make_error<DAPError>("msg");
return std::move(body);
}
...
dap.RegisterRequest("source", request_source);
...
```
The main features of this refactor are:
* Created a queue of pending protocol messages to allow for preemptive cancellations and active cancellations.
* A cleaner separation between the Debug Adapter Protocol types and lldb types that include serialization support toJSON/fromJSON.
* Improved error handling using toJSON/fromJSON.
* Unified handling of responses and errors.
* Unified handling of cancel requests.
This change include minimal support to implement the Debug Adapter Protocol 'source' request, 'evaluate' request and the 'exited' event.
---
lldb/test/API/tools/lldb-dap/cancel/Makefile | 3 +
.../tools/lldb-dap/cancel/TestDAP_cancel.py | 84 ++
lldb/test/API/tools/lldb-dap/cancel/main.c | 6 +
.../lldb-dap/evaluate/TestDAP_evaluate.py | 27 +-
.../tools/lldb-dap/launch/TestDAP_launch.py | 9 +-
.../lldb-dap/optimized/TestDAP_optimized.py | 6 +-
lldb/tools/lldb-dap/CMakeLists.txt | 1 +
lldb/tools/lldb-dap/DAP.cpp | 218 +++--
lldb/tools/lldb-dap/DAP.h | 228 ++++-
lldb/tools/lldb-dap/IOStream.cpp | 8 +-
lldb/tools/lldb-dap/IOStream.h | 4 +-
lldb/tools/lldb-dap/JSONUtils.cpp | 280 +++---
lldb/tools/lldb-dap/JSONUtils.h | 16 +-
lldb/tools/lldb-dap/Protocol.cpp | 451 +++++++++
lldb/tools/lldb-dap/Protocol.h | 905 ++++++++++++++++++
lldb/tools/lldb-dap/lldb-dap.cpp | 141 +--
16 files changed, 2064 insertions(+), 323 deletions(-)
create mode 100644 lldb/test/API/tools/lldb-dap/cancel/Makefile
create mode 100644 lldb/test/API/tools/lldb-dap/cancel/TestDAP_cancel.py
create mode 100644 lldb/test/API/tools/lldb-dap/cancel/main.c
create mode 100644 lldb/tools/lldb-dap/Protocol.cpp
create mode 100644 lldb/tools/lldb-dap/Protocol.h
diff --git a/lldb/test/API/tools/lldb-dap/cancel/Makefile b/lldb/test/API/tools/lldb-dap/cancel/Makefile
new file mode 100644
index 0000000000000..10495940055b6
--- /dev/null
+++ b/lldb/test/API/tools/lldb-dap/cancel/Makefile
@@ -0,0 +1,3 @@
+C_SOURCES := main.c
+
+include Makefile.rules
diff --git a/lldb/test/API/tools/lldb-dap/cancel/TestDAP_cancel.py b/lldb/test/API/tools/lldb-dap/cancel/TestDAP_cancel.py
new file mode 100644
index 0000000000000..4410fc477bdb4
--- /dev/null
+++ b/lldb/test/API/tools/lldb-dap/cancel/TestDAP_cancel.py
@@ -0,0 +1,84 @@
+"""
+Test lldb-dap cancel request
+"""
+
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+import lldbdap_testcase
+
+
+class TestDAP_launch(lldbdap_testcase.DAPTestCaseBase):
+ def send_async_req(self, command: str, arguments={}) -> int:
+ seq = self.dap_server.sequence
+ self.dap_server.send_packet(
+ {
+ "type": "request",
+ "command": command,
+ "arguments": arguments,
+ }
+ )
+ return seq
+
+ def async_blocking_request(self, duration: float) -> int:
+ return self.send_async_req(
+ command="evaluate",
+ arguments={
+ "expression": "`script import time; time.sleep({})".format(duration),
+ "context": "repl",
+ },
+ )
+
+ def async_cancel(self, requestId: int) -> int:
+ return self.send_async_req(command="cancel", arguments={"requestId": requestId})
+
+ def test_pending_request(self):
+ """
+ Tests cancelling a pending request.
+ """
+ program = self.getBuildArtifact("a.out")
+ self.build_and_launch(program, stopOnEntry=True)
+ self.continue_to_next_stop()
+
+ # Use a relatively short timeout since this is only to ensure the
+ # following request is queued.
+ blocking_seq = self.async_blocking_request(duration=1.0)
+ # Use a longer timeout to ensure we catch if the request was interrupted
+ # properly.
+ pending_seq = self.async_blocking_request(duration=self.timeoutval * 2)
+ cancel_seq = self.async_cancel(requestId=pending_seq)
+
+ blocking_resp = self.dap_server.recv_packet(filter_type=["response"])
+ print("blocking_resp", blocking_resp)
+ self.assertEqual(blocking_resp["request_seq"], blocking_seq)
+ self.assertEqual(blocking_resp["success"], True)
+
+ pending_resp = self.dap_server.recv_packet(filter_type=["response"])
+ print("pending_resp", pending_resp)
+ self.assertEqual(pending_resp["request_seq"], pending_seq)
+ self.assertEqual(pending_resp["success"], False)
+ self.assertEqual(pending_resp["message"], "cancelled")
+
+ cancel_resp = self.dap_server.recv_packet(filter_type=["response"])
+ self.assertEqual(cancel_resp["request_seq"], cancel_seq)
+ self.continue_to_exit()
+
+ def test_inflight_request(self):
+ """
+ Tests cancelling an inflight request.
+ """
+ program = self.getBuildArtifact("a.out")
+ self.build_and_launch(program, stopOnEntry=True)
+ self.continue_to_next_stop()
+
+ blocking_seq = self.async_blocking_request(duration=self.timeoutval * 2)
+ cancel_seq = self.async_cancel(requestId=blocking_seq)
+
+ blocking_resp = self.dap_server.recv_packet(filter_type=["response"])
+ print("blocking_resp", blocking_resp)
+ self.assertEqual(blocking_resp["request_seq"], blocking_seq)
+ self.assertEqual(blocking_resp["success"], False)
+ self.assertEqual(blocking_resp["message"], "cancelled")
+
+ cancel_resp = self.dap_server.recv_packet(filter_type=["response"])
+ self.assertEqual(cancel_resp["request_seq"], cancel_seq)
+ self.continue_to_exit()
diff --git a/lldb/test/API/tools/lldb-dap/cancel/main.c b/lldb/test/API/tools/lldb-dap/cancel/main.c
new file mode 100644
index 0000000000000..ecc0d99ec8db7
--- /dev/null
+++ b/lldb/test/API/tools/lldb-dap/cancel/main.c
@@ -0,0 +1,6 @@
+#include <stdio.h>
+
+int main(int argc, char const *argv[]) {
+ printf("Hello world!\n");
+ return 0;
+}
diff --git a/lldb/test/API/tools/lldb-dap/evaluate/TestDAP_evaluate.py b/lldb/test/API/tools/lldb-dap/evaluate/TestDAP_evaluate.py
index 251d77d79d080..6bbca9f311cf9 100644
--- a/lldb/test/API/tools/lldb-dap/evaluate/TestDAP_evaluate.py
+++ b/lldb/test/API/tools/lldb-dap/evaluate/TestDAP_evaluate.py
@@ -3,6 +3,7 @@
"""
import re
+import json
import lldbdap_testcase
import dap_server
@@ -13,17 +14,21 @@
class TestDAP_evaluate(lldbdap_testcase.DAPTestCaseBase):
def assertEvaluate(self, expression, regex):
- self.assertRegex(
- self.dap_server.request_evaluate(expression, context=self.context)["body"][
- "result"
- ],
- regex,
+ resp = self.dap_server.request_evaluate(expression, context=self.context)
+ self.assertEqual(
+ resp["success"],
+ True,
+ "evaluate with expression = '{}' context = '{}' failed unexpectedly: {}".format(
+ expression, self.context, json.dumps(resp)
+ ),
)
+ self.assertRegex(resp["body"]["result"], regex)
def assertEvaluateFailure(self, expression):
- self.assertNotIn(
- "result",
- self.dap_server.request_evaluate(expression, context=self.context)["body"],
+ self.assertFalse(
+ self.dap_server.request_evaluate(expression, context=self.context)[
+ "success"
+ ],
)
def isResultExpandedDescription(self):
@@ -231,5 +236,7 @@ def test_hover_evaluate_expressions(self):
@skipIfWindows
def test_variable_evaluate_expressions(self):
- # Tests expression evaluations that are triggered in the variable explorer
- self.run_test_evaluate_expressions("variable", enableAutoVariableSummaries=True)
+ # Tests expression evaluations that are triggered in the variables explorer
+ self.run_test_evaluate_expressions(
+ "variables", enableAutoVariableSummaries=True
+ )
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 7898d01457afc..55e724c3f048b 100644
--- a/lldb/test/API/tools/lldb-dap/launch/TestDAP_launch.py
+++ b/lldb/test/API/tools/lldb-dap/launch/TestDAP_launch.py
@@ -1,13 +1,10 @@
"""
-Test lldb-dap setBreakpoints request
+Test lldb-dap launch request
"""
-import dap_server
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
-from lldbsuite.test import lldbutil
import lldbdap_testcase
-import time
import os
import re
@@ -41,7 +38,9 @@ def test_termination(self):
self.dap_server.request_disconnect()
# Wait until the underlying lldb-dap process dies.
- self.dap_server.process.wait(timeout=lldbdap_testcase.DAPTestCaseBase.timeoutval)
+ self.dap_server.process.wait(
+ timeout=lldbdap_testcase.DAPTestCaseBase.timeoutval
+ )
# Check the return code
self.assertEqual(self.dap_server.process.poll(), 0)
diff --git a/lldb/test/API/tools/lldb-dap/optimized/TestDAP_optimized.py b/lldb/test/API/tools/lldb-dap/optimized/TestDAP_optimized.py
index ae144ebdca46b..6cbe0b8858482 100644
--- a/lldb/test/API/tools/lldb-dap/optimized/TestDAP_optimized.py
+++ b/lldb/test/API/tools/lldb-dap/optimized/TestDAP_optimized.py
@@ -24,11 +24,11 @@ def test_stack_frame_name(self):
)
self.continue_to_breakpoints(breakpoint_ids)
leaf_frame = self.dap_server.get_stackFrame(frameIndex=0)
- self.assertTrue(leaf_frame["name"].endswith(" [opt]"))
+ self.assertRegex(leaf_frame["name"], r"\[.*opt.*\]")
parent_frame = self.dap_server.get_stackFrame(frameIndex=1)
- self.assertTrue(parent_frame["name"].endswith(" [opt]"))
+ self.assertRegex(parent_frame["name"], r"\[.*opt.*\]")
- @skipIfAsan # On ASAN builds this test intermittently fails https://github.com/llvm/llvm-project/issues/111061
+ @skipIfAsan # On ASAN builds this test intermittently fails https://github.com/llvm/llvm-project/issues/111061
@skipIfWindows
def test_optimized_variable(self):
"""Test optimized variable value contains error."""
diff --git a/lldb/tools/lldb-dap/CMakeLists.txt b/lldb/tools/lldb-dap/CMakeLists.txt
index 43fc18873feb3..2506fa36b1644 100644
--- a/lldb/tools/lldb-dap/CMakeLists.txt
+++ b/lldb/tools/lldb-dap/CMakeLists.txt
@@ -32,6 +32,7 @@ add_lldb_tool(lldb-dap
LLDBUtils.cpp
OutputRedirector.cpp
ProgressEvent.cpp
+ Protocol.cpp
RunInTerminal.cpp
SourceBreakpoint.cpp
Watchpoint.cpp
diff --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp
index f5a9b10ebe0f9..d996e184d2b8e 100644
--- a/lldb/tools/lldb-dap/DAP.cpp
+++ b/lldb/tools/lldb-dap/DAP.cpp
@@ -10,6 +10,7 @@
#include "JSONUtils.h"
#include "LLDBUtils.h"
#include "OutputRedirector.h"
+#include "Protocol.h"
#include "lldb/API/SBBreakpoint.h"
#include "lldb/API/SBCommandInterpreter.h"
#include "lldb/API/SBCommandReturnObject.h"
@@ -23,18 +24,23 @@
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/ScopeExit.h"
#include "llvm/ADT/StringExtras.h"
+#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/Twine.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/FormatVariadic.h"
+#include "llvm/Support/JSON.h"
#include "llvm/Support/raw_ostream.h"
#include <algorithm>
#include <cassert>
#include <chrono>
+#include <condition_variable>
#include <cstdarg>
#include <cstdio>
+#include <deque>
#include <fstream>
#include <mutex>
+#include <optional>
#include <utility>
#if defined(_WIN32)
@@ -47,17 +53,19 @@
#endif
using namespace lldb_dap;
+using namespace lldb_dap::protocol;
-namespace {
#ifdef _WIN32
-const char DEV_NULL[] = "nul";
+static const char DEV_NULL[] = "nul";
#else
-const char DEV_NULL[] = "/dev/null";
+static const char DEV_NULL[] = "/dev/null";
#endif
-} // namespace
namespace lldb_dap {
+char DAPError::ID;
+char CancelledError::ID;
+
DAP::DAP(std::string name, llvm::StringRef path, std::ofstream *log,
StreamDescriptor input, StreamDescriptor output, ReplMode repl_mode,
std::vector<std::string> pre_init_commands)
@@ -288,13 +296,6 @@ std::string DAP::ReadJSON() {
if (!input.read_full(log, length, json_str))
return json_str;
- if (log) {
- auto now = std::chrono::duration<double>(
- std::chrono::system_clock::now().time_since_epoch());
- *log << llvm::formatv("{0:f9} {1} --> ", now.count(), name).str()
- << std::endl
- << "Content-Length: " << length << "\r\n\r\n";
- }
return json_str;
}
@@ -532,6 +533,10 @@ lldb::SBThread DAP::GetLLDBThread(const llvm::json::Object &arguments) {
lldb::SBFrame DAP::GetLLDBFrame(const llvm::json::Object &arguments) {
const uint64_t frame_id = GetUnsigned(arguments, "frameId", UINT64_MAX);
+ return GetLLDBFrame(frame_id);
+}
+
+lldb::SBFrame DAP::GetLLDBFrame(const uint64_t frame_id) {
lldb::SBProcess process = target.GetProcess();
// Upper 32 bits is the thread index ID
lldb::SBThread thread =
@@ -720,64 +725,80 @@ void DAP::SetTarget(const lldb::SBTarget target) {
}
}
-PacketStatus DAP::GetNextObject(llvm::json::Object &object) {
+std::optional<protocol::ProtocolMessage> DAP::GetNextProtocolMessage() {
std::string json = ReadJSON();
if (json.empty())
- return PacketStatus::EndOfFile;
+ return std::nullopt;
- llvm::StringRef json_sref(json);
- llvm::Expected<llvm::json::Value> json_value = llvm::json::parse(json_sref);
- if (!json_value) {
- auto error = json_value.takeError();
+ llvm::Expected<llvm::json::Value> value = llvm::json::parse(json);
+ if (!value) {
if (log) {
- std::string error_str;
- llvm::raw_string_ostream strm(error_str);
- strm << error;
- *log << "error: failed to parse JSON: " << error_str << std::endl
- << json << std::endl;
+ auto now = std::chrono::duration<double>(
+ std::chrono::system_clock::now().time_since_epoch());
+ *log << llvm::formatv("{0:f9} -->", now.count()).str()
+ << "\nContent-Length: " << json.length() << "\r\n\r\n"
+ << json
+ << "\nfailed to parse json: " << llvm::toString(value.takeError())
+ << "\n";
+ } else {
+ llvm::consumeError(value.takeError());
}
- return PacketStatus::JSONMalformed;
+ return std::nullopt;
}
if (log) {
- *log << llvm::formatv("{0:2}", *json_value).str() << std::endl;
+ auto now = std::chrono::duration<double>(
+ std::chrono::system_clock::now().time_since_epoch());
+ *log << llvm::formatv("{0:f9} -->", now.count()).str()
+ << "\nContent-Length: " << json.length() << "\r\n\r\n"
+ << llvm::formatv("{0:2}", *value).str() << "\n";
}
- llvm::json::Object *object_ptr = json_value->getAsObject();
- if (!object_ptr) {
- if (log)
- *log << "error: json packet isn't a object" << std::endl;
- return PacketStatus::JSONNotObject;
+ protocol::ProtocolMessage message;
+ llvm::json::Path::Root root;
+ if (fromJSON(*value, message, root))
+ return std::move(message);
+
+ if (log) {
+ std::string error;
+ llvm::raw_string_ostream ss(error);
+ root.printErrorContext(*value, ss);
+ *log << "Failed to parse protocol message: " << error << "\n";
}
- object = *object_ptr;
- return PacketStatus::Success;
+
+ return std::nullopt;
}
-bool DAP::HandleObject(const llvm::json::Object &object) {
- const auto packet_type = GetString(object, "type");
- if (packet_type == "request") {
- const auto command = GetString(object, "command");
- auto handler_pos = request_handlers.find(command);
+bool DAP::HandleObject(const protocol::ProtocolMessage &message) {
+ if (const protocol::Request *req = std::get_if<protocol::Request>(&message)) {
+ auto handler_pos = request_handlers.find(req->command);
if (handler_pos == request_handlers.end()) {
if (log)
- *log << "error: unhandled command \"" << command.data() << "\""
- << std::endl;
+ *log << "error: unhandled command \"" << req->command << "\""
+ << "\n";
return false; // Fail
}
- handler_pos->second(*this, object);
+ active_request_seq = req->seq;
+ handler_pos->second(*this, *req);
+ active_request_seq = 0;
+
+ // Clear cancellation marker.
+ if (debugger.InterruptRequested())
+ debugger.CancelInterruptRequest();
+
return true; // Success
}
- if (packet_type == "response") {
- auto id = GetSigned(object, "request_seq", 0);
+ if (const protocol::Response *resp =
+ std::get_if<protocol::Response>(&message)) {
ResponseCallback response_handler = [](llvm::Expected<llvm::json::Value>) {
llvm::errs() << "Unhandled response\n";
};
{
- std::lock_guard<std::mutex> locker(call_mutex);
- auto inflight = inflight_reverse_requests.find(id);
+ std::lock_guard locker(call_mutex);
+ auto inflight = inflight_reverse_requests.find(resp->request_seq);
if (inflight != inflight_reverse_requests.end()) {
response_handler = std::move(inflight->second);
inflight_reverse_requests.erase(inflight);
@@ -785,17 +806,11 @@ bool DAP::HandleObject(const llvm::json::Object &object) {
}
// Result should be given, use null if not.
- if (GetBoolean(object, "success", false)) {
- llvm::json::Value Result = nullptr;
- if (auto *B = object.get("body")) {
- Result = std::move(*B);
- }
- response_handler(Result);
+ if (resp->success) {
+ response_handler(resp->rawBody);
} else {
- llvm::StringRef message = GetString(object, "message");
- if (message.empty()) {
- message = "Unknown error, response failed";
- }
+ std::string message =
+ resp->message.value_or("Unknown error, response failed");
response_handler(llvm::createStringError(
std::error_code(-1, std::generic_category()), message));
}
@@ -849,27 +864,91 @@ lldb::SBError DAP::Disconnect(bool terminateDebuggee) {
return error;
}
-llvm::Error DAP::Loop() {
- auto stop_io = llvm::make_scope_exit([this]() { StopIO(); });
- while (!disconnecting) {
- llvm::json::Object object;
- lldb_dap::PacketStatus status = GetNextObject(object);
+template <typename T>
+static std::optional<T>
+getArgumentsIfRequest(const protocol::ProtocolMessage &pm,
+ llvm::StringLiteral command) {
+ auto *const req = std::get_if<protocol::Request>(&pm);
+ if (!req || req->command != command)
+ return std::nullopt;
+
+ T args;
+ llvm::json::Path::Root root;
+ if (!fromJSON(req->rawArguments, args, root)) {
+ return std::nullopt;
+ }
- if (status == lldb_dap::PacketStatus::EndOfFile) {
- break;
+ return std::move(args);
+}
+
+llvm::Error DAP::Run() {
+ auto stop_io = llvm::make_scope_exit([this]() { StopIO(); });
+ std::thread reader([&]() {
+ while (!disconnecting) {
+ auto next = GetNextProtocolMessage();
+ if (!next)
+ break;
+
+ {
+ std::lock_guard lock(messages_mutex);
+ messages.push_back(std::move(*next));
+ if (const auto args = getArgumentsIfRequest<protocol::CancelArguments>(
+ *next, "cancel");
+ args && active_request_seq == args->requestId)
+ debugger.RequestInterrupt();
+ }
+ messages_cv.notify_one();
}
- if (status != lldb_dap::PacketStatus::Success) {
- return llvm::createStringError(llvm::inconvertibleErrorCode(),
- "failed to send packet");
+ std::unique_lock lock(messages_mutex);
+ std::notify_all_at_thread_exit(messages_cv, std::move(lock));
+ });
+
+ while (!disconnecting) {
+ protocol::ProtocolMessage next;
+ {
+ std::unique_lock lock(messages_mutex);
+ messages_cv.wait(lock,
+ [&] { return disconnecting || !messages.empty(); });
+
+ if (messages.empty())
+ break;
+
+ next = messages.front();
+ messages.pop_front();
+
+ // If we have a request check if we should preemptively cancel it.
+ if (protocol::Request *req = std::get_if<protocol::Request>(&next)) {
+ bool cancelled = false;
+ for (const auto &message : messages) {
+ if (const auto args =
+ getArgumentsIfRequest<protocol::CancelArguments>(message,
+ "cancel");
+ args && args->requestId == req->seq) {
+ cancelled = true;
+ break;
+ }
+ }
+
+ if (cancelled) {
+ ReplyOnce Reply(req->seq, req->command, this);
+ Reply(llvm::make_error<CancelledError>());
+ continue;
+ }
+ }
}
- if (!HandleObject(object)) {
- return llvm::createStringError(llvm::inconvertibleErrorCode(),
- "unhandled packet");
+ if (!HandleObject(next)) {
+ return llvm::make_error<llvm::StringError>(
+ llvm::formatv("Unsupported protocol message: {0:2}", toJSON(next))
+ .str(),
+ llvm::inconvertibleErrorCode());
}
}
+ if (reader.joinable())
+ reader.join();
+
return llvm::Error::success();
}
@@ -891,9 +970,12 @@ void DAP::SendReverseRequest(llvm::StringRef command,
});
}
-void DAP::RegisterRequestCallback(std::string request,
+void DAP::RegisterRequestCallback(llvm::StringLiteral command,
RequestCallback callback) {
- request_handlers[request] = callback;
+ request_handlers[command] = [callback](DAP &dap, const Request &request) {
+ llvm::json::Value rawRequest = toJSON(request);
+ callback(dap, *rawRequest.getAsObject());
+ };
}
lldb::SBError DAP::WaitForProcessToStop(uint32_t seconds) {
diff --git a/lldb/tools/lldb-dap/DAP.h b/lldb/tools/lldb-dap/DAP.h
index 655bec2639595..a23147537f3bc 100644
--- a/lldb/tools/lldb-dap/DAP.h
+++ b/lldb/tools/lldb-dap/DAP.h
@@ -16,6 +16,7 @@
#include "InstructionBreakpoint.h"
#include "OutputRedirector.h"
#include "ProgressEvent.h"
+#include "Protocol.h"
#include "SourceBreakpoint.h"
#include "lldb/API/SBBroadcaster.h"
#include "lldb/API/SBCommandInterpreter.h"
@@ -24,6 +25,7 @@
#include "lldb/API/SBFile.h"
#include "lldb/API/SBFormat.h"
#include "lldb/API/SBFrame.h"
+#include "lldb/API/SBSymbol.h"
#include "lldb/API/SBTarget.h"
#include "lldb/API/SBThread.h"
#include "lldb/API/SBValue.h"
@@ -34,11 +36,16 @@
#include "llvm/ADT/StringMap.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Error.h"
+#include "llvm/Support/FormatAdapters.h"
#include "llvm/Support/JSON.h"
#include "llvm/Support/Threading.h"
+#include <atomic>
+#include <cstdint>
+#include <functional>
#include <map>
#include <mutex>
#include <optional>
+#include <set>
#include <thread>
#include <vector>
@@ -66,18 +73,52 @@ enum DAPBroadcasterBits {
eBroadcastBitStopProgressThread = 1u << 1
};
-typedef void (*RequestCallback)(DAP &dap, const llvm::json::Object &command);
-typedef void (*ResponseCallback)(llvm::Expected<llvm::json::Value> value);
+using RequestCallback = std::function<void(DAP &, const llvm::json::Object &)>;
+using ResponseCallback = std::function<void(llvm::Expected<llvm::json::Value>)>;
-enum class PacketStatus {
- Success = 0,
- EndOfFile,
- JSONMalformed,
- JSONNotObject
-};
+template <typename Args, typename Body>
+using RequestHandler = std::function<llvm::Expected<Body>(DAP &, const Args &)>;
+template <typename Body>
+using ResponseHandler = std::function<void(llvm::Expected<Body>)>;
+
+/// A debug adapter initiated event.
+template <typename T> using OutgoingEvent = std::function<void(const T &)>;
+
+enum class PacketStatus { Success = 0, EndOfFile, JSONMalformed };
enum class ReplMode { Variable = 0, Command, Auto };
+class DAPError : public llvm::ErrorInfo<DAPError> {
+public:
+ std::string Message;
+ std::optional<protocol::Message> UserMessage;
+ static char ID;
+
+ explicit DAPError(std::string Message)
+ : Message(std::move(Message)), UserMessage(std::nullopt) {}
+ DAPError(std::string Message, protocol::Message UserMessage)
+ : Message(std::move(Message)), UserMessage(std::move(UserMessage)) {}
+
+ void log(llvm::raw_ostream &OS) const override {
+ OS << "DAPError: " << Message;
+ }
+
+ std::error_code convertToErrorCode() const override {
+ return llvm::inconvertibleErrorCode();
+ }
+};
+
+class CancelledError : public llvm::ErrorInfo<CancelledError> {
+public:
+ static char ID;
+
+ void log(llvm::raw_ostream &OS) const override { OS << "Cancalled"; }
+
+ std::error_code convertToErrorCode() const override {
+ return llvm::inconvertibleErrorCode();
+ }
+};
+
struct Variables {
/// Variable_reference start index of permanent expandable variable.
static constexpr int64_t PermanentVariableStartIndex = (1ll << 32);
@@ -172,7 +213,7 @@ struct DAP {
// arguments if we get a RestartRequest.
std::optional<llvm::json::Object> last_launch_or_attach_request;
lldb::tid_t focus_tid;
- bool disconnecting = false;
+ std::atomic<bool> disconnecting = false;
llvm::once_flag terminated_event_flag;
bool stop_at_entry;
bool is_attach;
@@ -184,7 +225,11 @@ struct DAP {
// the old process here so we can detect this case and keep running.
lldb::pid_t restarting_process_id;
bool configuration_done_sent;
- std::map<std::string, RequestCallback, std::less<>> request_handlers;
+
+ using RequestWrapper =
+ std::function<void(DAP &dap, const protocol::Request &)>;
+ llvm::StringMap<RequestWrapper> request_handlers;
+
bool waiting_for_run_in_terminal;
ProgressEventReporter progress_event_reporter;
// Keep track of the last stop thread index IDs as threads won't go away
@@ -204,6 +249,12 @@ struct DAP {
// empty; if the previous expression was a variable expression, this string
// will contain that expression.
std::string last_nonempty_var_expression;
+ std::set<lldb::SBSymbol> source_references;
+
+ /// MARK: Event Handlers
+
+ /// onExited indicates that the debuggee has exited and returns its exit code.
+ OutgoingEvent<protocol::ExitedEventBody> onExited;
DAP(std::string name, llvm::StringRef path, std::ofstream *log,
StreamDescriptor input, StreamDescriptor output, ReplMode repl_mode,
@@ -246,6 +297,8 @@ struct DAP {
lldb::SBFrame GetLLDBFrame(const llvm::json::Object &arguments);
+ lldb::SBFrame GetLLDBFrame(const uint64_t frame_id);
+
llvm::json::Value CreateTopLevelScopes();
void PopulateExceptionBreakpoints();
@@ -302,10 +355,14 @@ struct DAP {
/// listeing for its breakpoint events.
void SetTarget(const lldb::SBTarget target);
- const std::map<std::string, RequestCallback> &GetRequestHandlers();
+ /// Get the next protocol message from the client. The value may represent a
+ /// request, response or event.
+ ///
+ /// If the connection is closed then nullopt is returned.
+ std::optional<protocol::ProtocolMessage> GetNextProtocolMessage();
- PacketStatus GetNextObject(llvm::json::Object &object);
- bool HandleObject(const llvm::json::Object &object);
+ /// Handle the next protocol message.
+ bool HandleObject(const protocol::ProtocolMessage &);
/// Disconnect the DAP session.
lldb::SBError Disconnect();
@@ -316,7 +373,9 @@ struct DAP {
/// Send a "terminated" event to indicate the process is done being debugged.
void SendTerminatedEvent();
- llvm::Error Loop();
+ /// Runs the debug session until either disconnected or an unrecoverable error
+ /// is encountered.
+ llvm::Error Run();
/// Send a Debug Adapter Protocol reverse request to the IDE.
///
@@ -333,14 +392,32 @@ struct DAP {
/// Registers a callback handler for a Debug Adapter Protocol request
///
- /// \param[in] request
+ /// \param[in] command
+ /// The name of the request following the Debug Adapter Protocol
+ /// specification.
+ ///
+ /// \param[in] callback
+ /// The callback to execute when the given request is triggered by the
+ /// IDE.
+ void RegisterRequestCallback(llvm::StringLiteral command,
+ RequestCallback callback);
+
+ /// Register a request handler for a Debug Adapter Protocol request.
+ ///
+ /// \param[in] command
/// The name of the request following the Debug Adapter Protocol
/// specification.
///
/// \param[in] callback
/// The callback to execute when the given request is triggered by the
/// IDE.
- void RegisterRequestCallback(std::string request, RequestCallback callback);
+ template <typename Args, typename Body>
+ void RegisterRequest(llvm::StringLiteral command,
+ RequestHandler<Args, Body> handler);
+
+ /// Registeres an event handler for sending Debug Adapter Protocol events.
+ template <typename Body>
+ OutgoingEvent<Body> RegisterEvent(llvm::StringLiteral Event);
/// Debuggee will continue from stopped state.
void WillContinue() { variables.Clear(); }
@@ -375,12 +452,131 @@ struct DAP {
InstructionBreakpoint *GetInstructionBPFromStopReason(lldb::SBThread &thread);
private:
+ std::condition_variable messages_cv;
+ std::mutex messages_mutex;
+ std::atomic<int64_t> active_request_seq;
+ std::deque<protocol::ProtocolMessage> messages;
+
// Send the JSON in "json_str" to the "out" stream. Correctly send the
// "Content-Length:" field followed by the length, followed by the raw
// JSON bytes.
void SendJSON(const std::string &json_str);
+
+ template <typename T>
+ static llvm::Expected<T> parse(const llvm::json::Value &Raw,
+ llvm::StringRef PayloadName);
+
+ class ReplyOnce {
+ std::atomic<bool> replied = false;
+ int seq;
+ std::string command;
+ DAP *dap;
+
+ public:
+ ReplyOnce(int seq, std::string command, DAP *dap)
+ : seq(seq), command(command), dap(dap) {}
+ ReplyOnce(ReplyOnce &&other)
+ : replied(other.replied.load()), seq(other.seq), command(other.command),
+ dap(other.dap) {
+ other.dap = nullptr;
+ }
+ ReplyOnce &operator=(ReplyOnce &&) = delete;
+ ReplyOnce(const ReplyOnce &) = delete;
+ ReplyOnce &operator=(const ReplyOnce &) = delete;
+ ~ReplyOnce() {
+ if (dap && !dap->disconnecting && !replied) {
+ assert(false && "must reply to all requests");
+ (*this)(llvm::make_error<DAPError>("server failed to reply"));
+ }
+ }
+
+ void operator()(llvm::Expected<llvm::json::Value> maybeResponseBody) {
+ if (replied.exchange(true)) {
+ assert(false && "must reply to each call only once!");
+ return;
+ }
+
+ protocol::Response Resp;
+ Resp.request_seq = seq;
+ Resp.command = command;
+
+ if (auto Err = maybeResponseBody.takeError()) {
+ Resp.success = false;
+
+ auto newErr = handleErrors(
+ std::move(Err),
+ [&](const DAPError &dapErr) {
+ if (dapErr.UserMessage) {
+ protocol::ErrorResponseBody ERB;
+ ERB.error = *dapErr.UserMessage;
+ Resp.rawBody = std::move(ERB);
+ }
+ Resp.message = dapErr.Message;
+ },
+ [&](const CancelledError &cancelledErr) {
+ Resp.success = false;
+ Resp.message = "cancelled";
+ });
+ if (newErr)
+ Resp.message = llvm::toString(std::move(newErr));
+ } else {
+ // Check if the request was interrupted and mark the response as
+ // cancelled.
+ if (dap->debugger.InterruptRequested()) {
+ Resp.success = false;
+ Resp.message = "cancelled";
+ } else
+ Resp.success = true;
+ // Skip encoding a null body.
+ if (*maybeResponseBody != llvm::json::Value(nullptr))
+ Resp.rawBody = *maybeResponseBody;
+ }
+
+ dap->SendJSON(Resp);
+ }
+ };
};
+template <typename T>
+llvm::Expected<T> DAP::parse(const llvm::json::Value &Raw,
+ llvm::StringRef PayloadName) {
+ T Result;
+ llvm::json::Path::Root Root;
+ if (!fromJSON(Raw, Result, Root)) {
+ std::string Context;
+ llvm::raw_string_ostream OS(Context);
+ Root.printErrorContext(Raw, OS);
+ return llvm::make_error<DAPError>(
+ llvm::formatv("failed to decode {0}: {1}", PayloadName,
+ llvm::fmt_consume(Root.getError())));
+ }
+ return std::move(Result);
+}
+
+template <typename Args, typename Body>
+void DAP::RegisterRequest(llvm::StringLiteral command,
+ RequestHandler<Args, Body> handler) {
+ request_handlers[command] = [command, handler](DAP &dap,
+ const protocol::Request &req) {
+ ReplyOnce reply(req.seq, req.command, &dap);
+ auto parsedArgs = DAP::parse<Args>(req.rawArguments, command);
+ if (!parsedArgs)
+ return reply(parsedArgs.takeError());
+ llvm::Expected<Body> response = handler(dap, *parsedArgs);
+ reply(std::move(response));
+ };
+}
+
+template <typename Body>
+OutgoingEvent<Body> DAP::RegisterEvent(llvm::StringLiteral Event) {
+ return [&, Event](const Body &B) {
+ protocol::Event Evt;
+ Evt.event = Event;
+ Evt.rawBody = B;
+ SendJSON(std::move(Evt));
+ };
+}
+
} // namespace lldb_dap
#endif
diff --git a/lldb/tools/lldb-dap/IOStream.cpp b/lldb/tools/lldb-dap/IOStream.cpp
index 7d0f363440f53..bc05a41a5bd21 100644
--- a/lldb/tools/lldb-dap/IOStream.cpp
+++ b/lldb/tools/lldb-dap/IOStream.cpp
@@ -26,10 +26,14 @@ StreamDescriptor::StreamDescriptor(StreamDescriptor &&other) {
*this = std::move(other);
}
-StreamDescriptor::~StreamDescriptor() {
+StreamDescriptor::~StreamDescriptor() { close(); }
+
+void StreamDescriptor::close() {
if (!m_close)
return;
+ m_close = false;
+
if (m_is_socket)
#if defined(_WIN32)
::closesocket(m_socket);
@@ -41,7 +45,7 @@ StreamDescriptor::~StreamDescriptor() {
}
StreamDescriptor &StreamDescriptor::operator=(StreamDescriptor &&other) {
- m_close = other.m_close;
+ m_close.store(other.m_close.load());
other.m_close = false;
m_is_socket = other.m_is_socket;
if (m_is_socket)
diff --git a/lldb/tools/lldb-dap/IOStream.h b/lldb/tools/lldb-dap/IOStream.h
index c91b2f717893c..bdba9a9e4c4de 100644
--- a/lldb/tools/lldb-dap/IOStream.h
+++ b/lldb/tools/lldb-dap/IOStream.h
@@ -36,8 +36,10 @@ struct StreamDescriptor {
static StreamDescriptor from_socket(SOCKET s, bool close);
static StreamDescriptor from_file(int fd, bool close);
+ void close();
+
bool m_is_socket = false;
- bool m_close = false;
+ std::atomic<bool> m_close = false;
union {
int m_fd;
SOCKET m_socket;
diff --git a/lldb/tools/lldb-dap/JSONUtils.cpp b/lldb/tools/lldb-dap/JSONUtils.cpp
index 6ca4dfb4711a1..fded2c35fdf33 100644
--- a/lldb/tools/lldb-dap/JSONUtils.cpp
+++ b/lldb/tools/lldb-dap/JSONUtils.cpp
@@ -12,14 +12,18 @@
#include "DAP.h"
#include "ExceptionBreakpoint.h"
#include "LLDBUtils.h"
+#include "Protocol.h"
#include "lldb/API/SBAddress.h"
#include "lldb/API/SBCompileUnit.h"
#include "lldb/API/SBDeclaration.h"
#include "lldb/API/SBEnvironment.h"
#include "lldb/API/SBError.h"
#include "lldb/API/SBFileSpec.h"
+#include "lldb/API/SBFormat.h"
#include "lldb/API/SBFrame.h"
#include "lldb/API/SBFunction.h"
+#include "lldb/API/SBInstruction.h"
+#include "lldb/API/SBInstructionList.h"
#include "lldb/API/SBLineEntry.h"
#include "lldb/API/SBModule.h"
#include "lldb/API/SBQueue.h"
@@ -45,7 +49,6 @@
#include "llvm/Support/ScopedPrinter.h"
#include "llvm/Support/raw_ostream.h"
#include <chrono>
-#include <climits>
#include <cstddef>
#include <iomanip>
#include <optional>
@@ -698,134 +701,114 @@ llvm::json::Value CreateSource(llvm::StringRef source_path) {
return llvm::json::Value(std::move(source));
}
-static std::optional<llvm::json::Value> CreateSource(lldb::SBFrame &frame) {
- auto line_entry = frame.GetLineEntry();
- // A line entry of 0 indicates the line is compiler generated i.e. no source
- // file is associated with the frame.
- if (line_entry.GetFileSpec().IsValid() && line_entry.GetLine() != 0)
- return CreateSource(line_entry);
-
- return {};
-}
-
-// "StackFrame": {
-// "type": "object",
-// "description": "A Stackframe contains the source location.",
-// "properties": {
-// "id": {
-// "type": "integer",
-// "description": "An identifier for the stack frame. It must be unique
-// across all threads. This id can be used to retrieve
-// the scopes of the frame with the 'scopesRequest' or
-// to restart the execution of a stackframe."
-// },
-// "name": {
-// "type": "string",
-// "description": "The name of the stack frame, typically a method name."
-// },
-// "source": {
-// "$ref": "#/definitions/Source",
-// "description": "The optional source of the frame."
-// },
-// "line": {
-// "type": "integer",
-// "description": "The line within the file of the frame. If source is
-// null or doesn't exist, line is 0 and must be ignored."
-// },
-// "column": {
-// "type": "integer",
-// "description": "The column within the line. If source is null or
-// doesn't exist, column is 0 and must be ignored."
-// },
-// "endLine": {
-// "type": "integer",
-// "description": "An optional end line of the range covered by the
-// stack frame."
-// },
-// "endColumn": {
-// "type": "integer",
-// "description": "An optional end column of the range covered by the
-// stack frame."
-// },
-// "instructionPointerReference": {
-// "type": "string",
-// "description": "A memory reference for the current instruction
-// pointer in this frame."
-// },
-// "moduleId": {
-// "type": ["integer", "string"],
-// "description": "The module associated with this frame, if any."
-// },
-// "presentationHint": {
-// "type": "string",
-// "enum": [ "normal", "label", "subtle" ],
-// "description": "An optional hint for how to present this frame in
-// the UI. A value of 'label' can be used to indicate
-// that the frame is an artificial frame that is used
-// as a visual label or separator. A value of 'subtle'
-// can be used to change the appearance of a frame in
-// a 'subtle' way."
-// }
-// },
-// "required": [ "id", "name", "line", "column" ]
-// }
-llvm::json::Value CreateStackFrame(lldb::SBFrame &frame,
- lldb::SBFormat &format) {
- llvm::json::Object object;
- int64_t frame_id = MakeDAPFrameID(frame);
- object.try_emplace("id", frame_id);
-
- std::string frame_name;
+static std::string GetFormattedFrameName(lldb::SBFrame &frame,
+ lldb::SBFormat &format) {
lldb::SBStream stream;
if (format && frame.GetDescriptionWithFormat(format, stream).Success()) {
- frame_name = stream.GetData();
-
- // `function_name` can be a nullptr, which throws an error when assigned to
- // an `std::string`.
- } else if (const char *name = frame.GetDisplayFunctionName()) {
- frame_name = name;
+ return stream.GetData();
}
- if (frame_name.empty()) {
- // If the function name is unavailable, display the pc address as a 16-digit
- // hex string, e.g. "0x0000000000012345"
- llvm::raw_string_ostream os(frame_name);
- os << llvm::format_hex(frame.GetPC(), 18);
- }
+ std::string name;
+ // `function_name` can be a nullptr, which throws an error when assigned to
+ // an `std::string`.
+ const char *display_name = frame.GetDisplayFunctionName();
+ if (display_name)
+ name = display_name;
+ const char *function_name = frame.GetFunctionName();
+ if (name.empty() && function_name)
+ name = function_name;
+ if (name.empty() && frame.GetLineEntry().IsValid() &&
+ frame.GetLineEntry().GetLine() == 0)
+ name = "<compiler-generated>";
+ if (name.empty())
+ // If the function name is unavailable, display the pc address as a 16 -
+ // digit hex string, e.g. "0x0000000000012345"
+ name = "0x" + llvm::utohexstr(frame.GetPC(), false, 15);
+
+ std::vector<std::string> tags;
+ // Inlined functions may have their local variables merged with their
+ // caller.
+ if (frame.IsInlined())
+ tags.push_back("inlined");
+ // Artififical frames may be synthesized by the compiler and may not have
+ // local variables.
+ if (frame.IsArtificial())
+ tags.push_back("artificial");
+ // Optimized frames may not have full debug information for all local
+ // variables since some may have been removed by the optimizer.
+ if (frame.GetFunction().GetIsOptimized())
+ tags.push_back("opt");
+
+ // Add any tags to help the user understand more information about the
+ // frame.
+ if (!tags.empty())
+ name += " [" + llvm::join(tags, ",") + "]";
+
+ return name;
+}
- // We only include `[opt]` if a custom frame format is not specified.
- if (!format && frame.GetFunction().GetIsOptimized())
- frame_name += " [opt]";
+static lldb_dap::protocol::Source toSource(lldb::SBFrame &frame,
+ lldb::SBFormat &format) {
+ lldb_dap::protocol::Source source;
+ auto line_entry = frame.GetLineEntry();
+ auto file = line_entry.GetFileSpec();
+ // A line entry of 0 indicates the line is compiler generated i.e. no source
+ // file is associated with the frame.
+ if (file.IsValid() && line_entry.GetLine() != 0) {
+ char path[PATH_MAX];
+ file.GetPath(path, sizeof(path));
+ source.path = path;
+ source.name = llvm::sys::path::filename(path);
+ } else {
+ source.name = GetFormattedFrameName(frame, format);
- EmplaceSafeString(object, "name", frame_name);
+ // If we don't have a source file then this frame is probably from a
+ // system library. Lets deemphasize the stack frame in the UI so the user
+ // can more easily find the stack frames from their code.
+ source.presentationHint = protocol::Source::PresentationHint::deemphasize;
+ source.sourceReference = MakeDAPFrameID(frame);
+ }
+
+ return source;
+}
- auto source = CreateSource(frame);
+protocol::StackFrame toStackFrame(lldb::SBFrame &frame,
+ lldb::SBFormat &format) {
+ lldb_dap::protocol::StackFrame sf;
+ sf.id = MakeDAPFrameID(frame);
+ sf.name = GetFormattedFrameName(frame, format);
+ sf.source = toSource(frame, format);
- if (source) {
- object.try_emplace("source", *source);
- auto line_entry = frame.GetLineEntry();
+ auto line_entry = frame.GetLineEntry();
+ if (line_entry.IsValid() && line_entry.GetLine() != 0) {
auto line = line_entry.GetLine();
if (line && line != LLDB_INVALID_LINE_NUMBER)
- object.try_emplace("line", line);
- else
- object.try_emplace("line", 0);
+ sf.line = line;
auto column = line_entry.GetColumn();
- object.try_emplace("column", column);
+ if (column != LLDB_INVALID_COLUMN_NUMBER)
+ sf.column = column;
} else {
- object.try_emplace("line", 0);
- object.try_emplace("column", 0);
+ lldb::SBInstructionList insts = frame.GetSymbol().GetInstructions(
+ frame.GetThread().GetProcess().GetTarget());
+ for (size_t idx = 0; idx <= insts.GetSize(); idx++) {
+ lldb::SBInstruction inst = insts.GetInstructionAtIndex(idx);
+ if (inst.GetAddress() == frame.GetPCAddress()) {
+ // lines are base-1 indexed and the first line of the disassembly is
+ // the symbol name.
+ sf.line = idx + 2;
+ break;
+ }
+ }
}
const auto pc = frame.GetPC();
- if (pc != LLDB_INVALID_ADDRESS) {
- std::string formatted_addr = "0x" + llvm::utohexstr(pc);
- object.try_emplace("instructionPointerReference", formatted_addr);
- }
+ if (pc != LLDB_INVALID_ADDRESS)
+ sf.instructionPointerReference = EncodeMemoryReference(pc);
if (frame.IsArtificial() || frame.IsHidden())
- object.try_emplace("presentationHint", "subtle");
+ sf.presentationHint = protocol::StackFrame::PresentationHint::subtle;
- return llvm::json::Value(std::move(object));
+ return sf;
}
llvm::json::Value CreateExtendedStackFrameLabel(lldb::SBThread &thread,
@@ -905,10 +888,10 @@ llvm::json::Value CreateThread(lldb::SBThread &thread, lldb::SBFormat &format) {
// "allOf": [ { "$ref": "#/definitions/Event" }, {
// "type": "object",
// "description": "Event message for 'stopped' event type. The event
-// indicates that the execution of the debuggee has stopped
-// due to some condition. This can be caused by a break
-// point previously set, a stepping action has completed,
-// by executing a debugger statement etc.",
+// indicates that the execution of the debuggee has
+// stopped due to some condition. This can be caused by a
+// break point previously set, a stepping action has
+// completed, by executing a debugger statement etc.",
// "properties": {
// "event": {
// "type": "string",
@@ -923,7 +906,8 @@ llvm::json::Value CreateThread(lldb::SBThread &thread, lldb::SBFormat &format) {
// compatibility this string is shown in the UI if
// the 'description' attribute is missing (but it
// must not be translated).",
-// "_enum": [ "step", "breakpoint", "exception", "pause", "entry" ]
+// "_enum": [ "step", "breakpoint", "exception", "pause", "entry"
+// ]
// },
// "description": {
// "type": "string",
@@ -1025,8 +1009,8 @@ llvm::json::Value CreateThreadStopped(DAP &dap, lldb::SBThread &thread,
const lldb::tid_t tid = thread.GetThreadID();
body.try_emplace("threadId", (int64_t)tid);
// If no description has been set, then set it to the default thread stopped
- // description. If we have breakpoints that get hit and shouldn't be reported
- // as breakpoints, then they will set the description above.
+ // description. If we have breakpoints that get hit and shouldn't be
+ // reported as breakpoints, then they will set the description above.
if (!ObjectContainsKey(body, "description")) {
char description[1024];
if (thread.GetStopDescription(description, sizeof(description))) {
@@ -1159,10 +1143,10 @@ llvm::json::Object VariableDescription::GetVariableExtensionsJSON() {
return extensions;
}
-std::string VariableDescription::GetResult(llvm::StringRef context) {
+std::string VariableDescription::GetResult(bool multiline) {
// In repl context, the results can be displayed as multiple lines so more
// detailed descriptions can be returned.
- if (context != "repl")
+ if (!multiline)
return display_value;
if (!v.IsValid())
@@ -1227,7 +1211,8 @@ std::pair<int64_t, bool> UnpackLocation(int64_t location_id) {
// },
// "presentationHint": {
// "$ref": "#/definitions/VariablePresentationHint",
-// "description": "Properties of a variable that can be used to determine
+// "description": "Properties of a variable that can be used to
+// determine
// how to render the variable in the UI."
// },
// "evaluateName": {
@@ -1259,20 +1244,21 @@ std::pair<int64_t, bool> UnpackLocation(int64_t location_id) {
// "description": "A memory reference associated with this variable.
// For pointer type variables, this is generally a
// reference to the memory address contained in the
-// pointer. For executable data, this reference may later
-// be used in a `disassemble` request. This attribute may
-// be returned by a debug adapter if corresponding
-// capability `supportsMemoryReferences` is true."
+// pointer. For executable data, this reference may
+// later be used in a `disassemble` request. This
+// attribute may be returned by a debug adapter if
+// corresponding capability `supportsMemoryReferences`
+// is true."
// },
// "declarationLocationReference": {
// "type": "integer",
// "description": "A reference that allows the client to request the
-// location where the variable is declared. This should be
-// present only if the adapter is likely to be able to
-// resolve the location.\n\nThis reference shares the same
-// lifetime as the `variablesReference`. See 'Lifetime of
-// Object References' in the Overview section for
-// details."
+// location where the variable is declared. This should
+// be present only if the adapter is likely to be able
+// to resolve the location.\n\nThis reference shares the
+// same lifetime as the `variablesReference`. See
+// 'Lifetime of Object References' in the Overview
+// section for details."
// },
// "valueLocationReference": {
// "type": "integer",
@@ -1309,7 +1295,8 @@ std::pair<int64_t, bool> UnpackLocation(int64_t location_id) {
// },
// "column": {
// "type": "number",
-// "description": "The 1-indexed source column where the variable
+// "description": "The 1-indexed source column where the
+// variable
// was declared."
// }
// }
@@ -1317,10 +1304,10 @@ std::pair<int64_t, bool> UnpackLocation(int64_t location_id) {
// "value": {
// "type": "string",
// "description": "The internal value of the variable as returned by
-// This is effectively SBValue.GetValue(). The other
-// `value` entry in the top-level variable response
-// is, on the other hand, just a display string for
-// the variable."
+// This is effectively SBValue.GetValue(). The
+// other `value` entry in the top-level variable
+// response is, on the other hand, just a display
+// string for the variable."
// },
// "summary": {
// "type": "string",
@@ -1334,7 +1321,8 @@ std::pair<int64_t, bool> UnpackLocation(int64_t location_id) {
// },
// "error": {
// "type": "string",
-// "description": "An error message generated if LLDB couldn't inspect
+// "description": "An error message generated if LLDB couldn't
+// inspect
// the variable."
// }
// }
@@ -1361,10 +1349,10 @@ llvm::json::Value CreateVariable(lldb::SBValue v, int64_t var_ref,
// request can be broken up in grabbing only a few children at a time. We
// want to be careful and only call "v.GetNumChildren()" if we have an array
// type or if we have a synthetic child provider producing indexed children.
- // We don't want to call "v.GetNumChildren()" on all objects as class, struct
- // and union types don't need to be completed if they are never expanded. So
- // we want to avoid calling this to only cases where we it makes sense to keep
- // performance high during normal debugging.
+ // We don't want to call "v.GetNumChildren()" on all objects as class,
+ // struct and union types don't need to be completed if they are never
+ // expanded. So we want to avoid calling this to only cases where we it
+ // makes sense to keep performance high during normal debugging.
// If we have an array type, say that it is indexed and provide the number
// of children in case we have a huge array. If we don't do this, then we
@@ -1373,9 +1361,9 @@ llvm::json::Value CreateVariable(lldb::SBValue v, int64_t var_ref,
if (desc.type_obj.IsArrayType()) {
object.try_emplace("indexedVariables", v.GetNumChildren());
} else if (v.IsSynthetic()) {
- // For a type with a synthetic child provider, the SBType of "v" won't tell
- // us anything about what might be displayed. Instead, we check if the first
- // child's name is "[0]" and then say it is indexed. We call
+ // For a type with a synthetic child provider, the SBType of "v" won't
+ // tell us anything about what might be displayed. Instead, we check if
+ // the first child's name is "[0]" and then say it is indexed. We call
// GetNumChildren() only if the child name matches to avoid a potentially
// expensive operation.
if (lldb::SBValue first_child = v.GetChildAtIndex(0)) {
@@ -1392,8 +1380,8 @@ llvm::json::Value CreateVariable(lldb::SBValue v, int64_t var_ref,
}
EmplaceSafeString(object, "type", desc.display_type_name);
- // A unique variable identifier to help in properly identifying variables with
- // the same name. This is an extension to the VS protocol.
+ // A unique variable identifier to help in properly identifying variables
+ // with the same name. This is an extension to the VS protocol.
object.try_emplace("id", var_ref);
if (v.MightHaveChildren())
diff --git a/lldb/tools/lldb-dap/JSONUtils.h b/lldb/tools/lldb-dap/JSONUtils.h
index db56d98777347..9136754a6a78d 100644
--- a/lldb/tools/lldb-dap/JSONUtils.h
+++ b/lldb/tools/lldb-dap/JSONUtils.h
@@ -10,6 +10,7 @@
#define LLDB_TOOLS_LLDB_DAP_JSONUTILS_H
#include "DAPForward.h"
+#include "Protocol.h"
#include "lldb/API/SBCompileUnit.h"
#include "lldb/API/SBFileSpec.h"
#include "lldb/API/SBFormat.h"
@@ -353,15 +354,7 @@ llvm::json::Value CreateSource(const lldb::SBLineEntry &line_entry);
/// definition outlined by Microsoft.
llvm::json::Value CreateSource(llvm::StringRef source_path);
-/// Create a "StackFrame" object for a LLDB frame object.
-///
-/// This function will fill in the following keys in the returned
-/// object:
-/// "id" - the stack frame ID as an integer
-/// "name" - the function name as a string
-/// "source" - source file information as a "Source" DAP object
-/// "line" - the source file line number as an integer
-/// "column" - the source file column number as an integer
+/// Returns a DAP protocol StackFrame from the given lldb frame and format.
///
/// \param[in] frame
/// The LLDB stack frame to use when populating out the "StackFrame"
@@ -374,8 +367,7 @@ llvm::json::Value CreateSource(llvm::StringRef source_path);
/// \return
/// A "StackFrame" JSON object with that follows the formal JSON
/// definition outlined by Microsoft.
-llvm::json::Value CreateStackFrame(lldb::SBFrame &frame,
- lldb::SBFormat &format);
+protocol::StackFrame toStackFrame(lldb::SBFrame &frame, lldb::SBFormat &format);
/// Create a "StackFrame" label object for a LLDB thread.
///
@@ -497,7 +489,7 @@ struct VariableDescription {
llvm::json::Object GetVariableExtensionsJSON();
/// Returns a description of the value appropriate for the specified context.
- std::string GetResult(llvm::StringRef context);
+ std::string GetResult(bool multiline);
};
/// Does the given variable have an associated value location?
diff --git a/lldb/tools/lldb-dap/Protocol.cpp b/lldb/tools/lldb-dap/Protocol.cpp
new file mode 100644
index 0000000000000..0cc36eab577bd
--- /dev/null
+++ b/lldb/tools/lldb-dap/Protocol.cpp
@@ -0,0 +1,451 @@
+//===-- Protocol.cpp --------------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "Protocol.h"
+#include "llvm/ADT/StringSwitch.h"
+#include "llvm/Support/JSON.h"
+#include <optional>
+#include <utility>
+
+namespace llvm {
+namespace json {
+bool fromJSON(const llvm::json::Value &Params, llvm::json::Value &V,
+ llvm::json::Path P) {
+ V = std::move(Params);
+ return true;
+}
+} // namespace json
+} // namespace llvm
+
+namespace lldb_dap {
+namespace protocol {
+
+enum class MessageType { request, response, event };
+
+bool fromJSON(const llvm::json::Value &Params, MessageType &M,
+ llvm::json::Path P) {
+ auto rawType = Params.getAsString();
+ if (!rawType) {
+ P.report("expected a string");
+ return false;
+ }
+ std::optional<MessageType> type =
+ llvm::StringSwitch<std::optional<MessageType>>(*rawType)
+ .Case("request", MessageType::request)
+ .Case("response", MessageType::response)
+ .Case("event", MessageType::event)
+ .Default(std::nullopt);
+ if (!type) {
+ P.report("unexpected value");
+ return false;
+ }
+ M = *type;
+ return true;
+}
+
+bool fromJSON(const llvm::json::Value &Params, ProtocolMessage &PM,
+ llvm::json::Path P) {
+ llvm::json::ObjectMapper O(Params, P);
+ if (!O)
+ return false;
+
+ MessageType type;
+ if (!O.map("type", type))
+ return false;
+
+ switch (type) {
+ case MessageType::request: {
+ Request req;
+ if (!fromJSON(Params, req, P)) {
+ return false;
+ }
+ PM = std::move(req);
+ return true;
+ }
+ case MessageType::response: {
+ Response resp;
+ if (!fromJSON(Params, resp, P)) {
+ return false;
+ }
+ PM = std::move(resp);
+ return true;
+ }
+ case MessageType::event:
+ Event evt;
+ if (!fromJSON(Params, evt, P)) {
+ return false;
+ }
+ PM = std::move(evt);
+ return true;
+ }
+ llvm_unreachable("Unsupported protocol message");
+}
+
+llvm::json::Value toJSON(const ProtocolMessage &PM) {
+ if (auto const *Req = std::get_if<Request>(&PM)) {
+ return toJSON(*Req);
+ }
+ if (auto const *Resp = std::get_if<Response>(&PM)) {
+ return toJSON(*Resp);
+ }
+ if (auto const *Evt = std::get_if<Event>(&PM)) {
+ return toJSON(*Evt);
+ }
+ llvm_unreachable("Unsupported protocol message");
+}
+
+llvm::json::Value toJSON(const Request &R) {
+ llvm::json::Object Result{
+ {"type", "request"},
+ {"seq", R.seq},
+ {"command", R.command},
+ };
+ if (R.rawArguments)
+ Result.insert({"arguments", R.rawArguments});
+ return std::move(Result);
+}
+
+bool fromJSON(llvm::json::Value const &Params, Request &R, llvm::json::Path P) {
+ llvm::json::ObjectMapper O(Params, P);
+ MessageType type;
+ if (!O.map("type", type)) {
+ return false;
+ }
+ if (type != MessageType::request) {
+ P.field("type").report("expected to be 'request'");
+ return false;
+ }
+
+ return O && O.map("command", R.command) && O.map("seq", R.seq) &&
+ O.map("arguments", R.rawArguments);
+}
+
+bool fromJSON(llvm::json::Value const &Params, Response &R,
+ llvm::json::Path P) {
+ llvm::json::ObjectMapper O(Params, P);
+ MessageType type;
+ if (!O.map("type", type)) {
+ return false;
+ }
+ if (type != MessageType::response) {
+ P.field("type").report("expected to be 'response'");
+ return false;
+ }
+ return O && O.map("command", R.command) &&
+ O.map("request_seq", R.request_seq) && O.map("success", R.success) &&
+ O.mapOptional("message", R.message) &&
+ O.mapOptional("body", R.rawBody);
+}
+
+bool fromJSON(llvm::json::Value const &Params, Event &E, llvm::json::Path P) {
+ llvm::json::ObjectMapper O(Params, P);
+ MessageType type;
+ if (!O.map("type", type)) {
+ return false;
+ }
+ if (type != MessageType::event) {
+ P.field("type").report("expected to be 'event'");
+ return false;
+ }
+
+ return O && O.map("event", E.event) && O.mapOptional("body", E.rawBody);
+}
+
+llvm::json::Value toJSON(const Response &R) {
+ llvm::json::Object Result{{"type", "response"},
+ {"req", 0},
+ {"command", R.command},
+ {"request_seq", R.request_seq},
+ {"success", R.success}};
+
+ if (R.message)
+ Result.insert({"message", R.message});
+ if (R.rawBody)
+ Result.insert({"body", R.rawBody});
+
+ return std::move(Result);
+}
+
+llvm::json::Value toJSON(lldb_dap::protocol::ErrorResponseBody const &ERB) {
+ return llvm::json::Object{{"error", ERB.error}};
+}
+
+llvm::json::Value toJSON(std::map<std::string, std::string> const &KV) {
+ llvm::json::Object Result;
+ for (const auto &[K, V] : KV)
+ Result.insert({K, V});
+ return std::move(Result);
+}
+
+llvm::json::Value toJSON(lldb_dap::protocol::Message const &M) {
+ return llvm::json::Object{{"id", M.id},
+ {"format", M.format},
+ {"showUser", M.showUser},
+ {"sendTelemetry", M.sendTelemetry},
+ {"variables", toJSON(*M.variables)},
+ {"url", M.url},
+ {"urlLabel", M.urlLabel}};
+}
+
+bool fromJSON(const llvm::json::Value &Params, Source::PresentationHint &PH,
+ llvm::json::Path P) {
+ auto rawHint = Params.getAsString();
+ if (!rawHint) {
+ P.report("expected a string");
+ return false;
+ }
+ std::optional<Source::PresentationHint> hint =
+ llvm::StringSwitch<std::optional<Source::PresentationHint>>(*rawHint)
+ .Case("normal", Source::PresentationHint::normal)
+ .Case("emphasize", Source::PresentationHint::emphasize)
+ .Case("deemphasize", Source::PresentationHint::deemphasize)
+ .Default(std::nullopt);
+ if (!hint) {
+ P.report("unexpected value");
+ return false;
+ }
+ PH = *hint;
+ return true;
+}
+
+bool fromJSON(const llvm::json::Value &Params, Source &S, llvm::json::Path P) {
+ llvm::json::ObjectMapper O(Params, P);
+ return O && O.mapOptional("name", S.name) && O.mapOptional("path", S.path) &&
+ O.mapOptional("presentationHint", S.presentationHint) &&
+ O.mapOptional("sourceReference", S.sourceReference) &&
+ O.mapOptional("origin", S.origin);
+}
+
+bool fromJSON(const llvm::json::Value &Params, CancelArguments &CA,
+ llvm::json::Path P) {
+ llvm::json::ObjectMapper O(Params, P);
+ return O && O.mapOptional("requestId", CA.requestId) &&
+ O.mapOptional("progressId", CA.progressId);
+}
+
+llvm::json::Value toJSON(const Source &S) {
+ llvm::json::Object Result;
+
+ if (S.name)
+ Result.insert({"name", S.name});
+ if (S.path)
+ Result.insert({"path", S.path});
+ if (S.presentationHint)
+ switch (*S.presentationHint) {
+ case Source::PresentationHint::normal:
+ Result.insert({"presentationHint", "normal"});
+ break;
+ case Source::PresentationHint::emphasize:
+ Result.insert({"presentationHint", "emphasize"});
+ break;
+ case Source::PresentationHint::deemphasize:
+ Result.insert({"presentationHint", "deemphasize"});
+ break;
+ }
+ if (S.sourceReference)
+ Result.insert({"sourceReference", S.sourceReference});
+ if (S.origin)
+ Result.insert({"origin", S.origin});
+
+ return std::move(Result);
+}
+
+llvm::json::Value toJSON(const StackFrame &SF) {
+ llvm::json::Object Result{{"id", SF.id},
+ {"name", SF.name},
+ {"line", SF.line},
+ {"column", SF.column}};
+
+ if (SF.source)
+ Result.insert({"source", SF.source});
+ if (SF.endLine)
+ Result.insert({"endLine", SF.endLine});
+ if (SF.endColumn)
+ Result.insert({"endColumn", SF.endColumn});
+ if (SF.canRestart)
+ Result.insert({"canRestart", SF.canRestart});
+ if (SF.instructionPointerReference)
+ Result.insert(
+ {"instructionPointerReference", SF.instructionPointerReference});
+ if (SF.presentationHint)
+ switch (*SF.presentationHint) {
+ case StackFrame::PresentationHint::normal:
+ Result.insert({"presentationHint", "normal"});
+ break;
+ case StackFrame::PresentationHint::label:
+ Result.insert({"presentationHint", "label"});
+ break;
+ case StackFrame::PresentationHint::subtle:
+ Result.insert({"presentationHint", "subtle"});
+ break;
+ }
+ return std::move(Result);
+}
+
+bool fromJSON(const llvm::json::Value &Params, SourceArguments &SA,
+ llvm::json::Path P) {
+ llvm::json::ObjectMapper O(Params, P);
+ return O && O.mapOptional("source", SA.source) &&
+ O.map("sourceReference", SA.sourceReference);
+}
+
+llvm::json::Value toJSON(const SourceResponseBody &SA) {
+ llvm::json::Object Result{{"content", SA.content}};
+
+ if (SA.mimeType)
+ Result.insert({"mimeType", SA.mimeType});
+
+ return std::move(Result);
+}
+
+bool fromJSON(const llvm::json::Value &Params, EvaluateArguments::Context &C,
+ llvm::json::Path P) {
+ auto rawContext = Params.getAsString();
+ if (!rawContext) {
+ P.report("expected a string");
+ return false;
+ }
+ std::optional<EvaluateArguments::Context> context =
+ llvm::StringSwitch<std::optional<EvaluateArguments::Context>>(*rawContext)
+ .Case("repl", EvaluateArguments::Context::repl)
+ .Case("watch", EvaluateArguments::Context::watch)
+ .Case("clipboard", EvaluateArguments::Context::clipboard)
+ .Case("hover", EvaluateArguments::Context::hover)
+ .Case("variables", EvaluateArguments::Context::variables)
+ .Default(std::nullopt);
+ if (!context) {
+ P.report("unexpected value");
+ return false;
+ }
+ C = *context;
+ return true;
+}
+
+bool fromJSON(const llvm::json::Value &Params, EvaluateArguments &EA,
+ llvm::json::Path P) {
+ llvm::json::ObjectMapper O(Params, P);
+ return O && O.map("expression", EA.expression) &&
+ O.mapOptional("frameId", EA.frameId) &&
+ O.mapOptional("line", EA.line) && O.mapOptional("column", EA.column) &&
+ O.mapOptional("source", EA.source) &&
+ O.mapOptional("context", EA.context);
+}
+
+llvm::json::Value toJSON(const EvaluateResponseBody &ERB) {
+ llvm::json::Object Result{{"result", ERB.result},
+ {"variablesReference", ERB.variablesReference}};
+
+ if (ERB.type)
+ Result.insert({"type", ERB.type});
+ if (ERB.presentationHint)
+ Result.insert({"presentationHint", ERB.presentationHint});
+ if (ERB.namedVariables)
+ Result.insert({"namedVariables", ERB.namedVariables});
+ if (ERB.indexedVariables)
+ Result.insert({"indexedVariables", ERB.indexedVariables});
+ if (ERB.memoryReference)
+ Result.insert({"memoryReference", ERB.memoryReference});
+ if (ERB.valueLocationReference)
+ Result.insert({"valueLocationReference", ERB.valueLocationReference});
+
+ return std::move(Result);
+}
+
+llvm::json::Value toJSON(const VariablePresentationHint::Kind &K) {
+ switch (K) {
+ case VariablePresentationHint::Kind::property_:
+ return "property";
+ case VariablePresentationHint::Kind::method_:
+ return "method";
+ case VariablePresentationHint::Kind::class_:
+ return "class";
+ case VariablePresentationHint::Kind::data_:
+ return "data";
+ case VariablePresentationHint::Kind::event_:
+ return "event";
+ case VariablePresentationHint::Kind::baseClass_:
+ return "baseClass";
+ case VariablePresentationHint::Kind::innerClass_:
+ return "innerClass";
+ case VariablePresentationHint::Kind::interface_:
+ return "interface";
+ case VariablePresentationHint::Kind::mostDerivedClass_:
+ return "mostDerivedClass";
+ case VariablePresentationHint::Kind::virtual_:
+ return "virtual";
+ case VariablePresentationHint::Kind::dataBreakpoint_:
+ return "dataBreakpoint";
+ }
+}
+
+llvm::json::Value toJSON(const VariablePresentationHint::Attributes &A) {
+ switch (A) {
+ case VariablePresentationHint::Attributes::static_:
+ return "static";
+ case VariablePresentationHint::Attributes::constant:
+ return "constant";
+ case VariablePresentationHint::Attributes::readOnly:
+ return "readOnly";
+ case VariablePresentationHint::Attributes::rawString:
+ return "rawString";
+ case VariablePresentationHint::Attributes::hasObjectId:
+ return "hasObjectId";
+ case VariablePresentationHint::Attributes::canHaveObjectId:
+ return "canHaveObjectId";
+ case VariablePresentationHint::Attributes::hasSideEffects:
+ return "hasSideEffects";
+ case VariablePresentationHint::Attributes::hasDataBreakpoint:
+ return "hasDataBreakpoint";
+ }
+}
+
+llvm::json::Value toJSON(const VariablePresentationHint::Visibility &V) {
+ switch (V) {
+ case VariablePresentationHint::Visibility::public_:
+ return "public";
+ case VariablePresentationHint::Visibility::private_:
+ return "private";
+ case VariablePresentationHint::Visibility::protected_:
+ return "protected";
+ case VariablePresentationHint::Visibility::internal_:
+ return "internal";
+ case VariablePresentationHint::Visibility::final_:
+ return "final";
+ }
+}
+
+llvm::json::Value toJSON(const VariablePresentationHint &VPH) {
+ llvm::json::Object Result;
+ if (VPH.kind)
+ Result.insert({"kind", VPH.kind});
+ if (VPH.attributes)
+ Result.insert({"attributes", VPH.attributes});
+ if (VPH.visibility)
+ Result.insert({"visibility", VPH.visibility});
+ if (VPH.lazy)
+ Result.insert({"lazy", VPH.lazy});
+ return std::move(Result);
+}
+
+llvm::json::Value toJSON(const Event &E) {
+ llvm::json::Object Result{
+ {"type", "event"},
+ {"seq", 0},
+ {"event", E.event},
+ };
+ if (E.rawBody)
+ Result.insert({"body", E.rawBody});
+ return std::move(Result);
+}
+
+llvm::json::Value toJSON(const ExitedEventBody &EEB) {
+ return llvm::json::Object{{"exitCode", EEB.exitCode}};
+}
+
+} // namespace protocol
+} // namespace lldb_dap
diff --git a/lldb/tools/lldb-dap/Protocol.h b/lldb/tools/lldb-dap/Protocol.h
new file mode 100644
index 0000000000000..a1125607219e9
--- /dev/null
+++ b/lldb/tools/lldb-dap/Protocol.h
@@ -0,0 +1,905 @@
+//===-- Protocol.h ----------------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file contains POD structs based on the DAP specification at
+// https://microsoft.github.io/debug-adapter-protocol/specification
+//
+// This is not meant to be a complete implementation, new interfaces are added
+// when they're needed.
+//
+// Each struct has a toJSON and fromJSON function, that converts between
+// the struct and a JSON representation. (See JSON.h)
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_TOOLS_LLDB_DAP_PROTOCOL_H
+#define LLDB_TOOLS_LLDB_DAP_PROTOCOL_H
+
+#include "llvm/Support/JSON.h"
+#include <cstddef>
+#include <cstdint>
+#include <optional>
+#include <string>
+
+namespace lldb_dap {
+namespace protocol {
+
+// MARK: Base Protocol
+
+// "Request": {
+// "allOf": [ { "$ref": "#/definitions/ProtocolMessage" }, {
+// "type": "object",
+// "description": "A client or debug adapter initiated request.",
+// "properties": {
+// "type": {
+// "type": "string",
+// "enum": [ "request" ]
+// },
+// "command": {
+// "type": "string",
+// "description": "The command to execute."
+// },
+// "arguments": {
+// "type": [ "array", "boolean", "integer", "null", "number" , "object",
+// "string" ], "description": "Object containing arguments for the
+// command."
+// }
+// },
+// "required": [ "type", "command" ]
+// }]
+// },
+struct Request {
+ int64_t seq;
+ std::string command;
+ std::optional<llvm::json::Value> rawArguments;
+};
+llvm::json::Value toJSON(const Request &);
+bool fromJSON(const llvm::json::Value &, Request &, llvm::json::Path);
+
+// "Event": {
+// "allOf": [ { "$ref": "#/definitions/ProtocolMessage" }, {
+// "type": "object",
+// "description": "A debug adapter initiated event.",
+// "properties": {
+// "type": {
+// "type": "string",
+// "enum": [ "event" ]
+// },
+// "event": {
+// "type": "string",
+// "description": "Type of event."
+// },
+// "body": {
+// "type": [ "array", "boolean", "integer", "null", "number" , "object",
+// "string" ], "description": "Event-specific information."
+// }
+// },
+// "required": [ "type", "event" ]
+// }]
+// },
+struct Event {
+ std::string event;
+ std::optional<llvm::json::Value> rawBody;
+};
+llvm::json::Value toJSON(const Event &);
+bool fromJSON(const llvm::json::Value &, Event &, llvm::json::Path);
+
+// "Response" : {
+// "allOf" : [
+// {"$ref" : "#/definitions/ProtocolMessage"}, {
+// "type" : "object",
+// "description" : "Response for a request.",
+// "properties" : {
+// "type" : {"type" : "string", "enum" : ["response"]},
+// "request_seq" : {
+// "type" : "integer",
+// "description" : "Sequence number of the corresponding request."
+// },
+// "success" : {
+// "type" : "boolean",
+// "description" :
+// "Outcome of the request.\nIf true, the request was successful "
+// "and the `body` attribute may contain the result of the "
+// "request.\nIf the value is false, the attribute `message` "
+// "contains the error in short form and the `body` may contain "
+// "additional information (see `ErrorResponse.body.error`)."
+// },
+// "command" :
+// {"type" : "string", "description" : "The command requested."},
+// "message" : {
+// "type" : "string",
+// "description" :
+// "Contains the raw error in short form if `success` is "
+// "false.\nThis raw error might be interpreted by the client and
+// " "is not shown in the UI.\nSome predefined values exist.",
+// "_enum" : [ "cancelled", "notStopped" ],
+// "enumDescriptions" : [
+// "the request was cancelled.", "the request may be retried once
+// the "
+// "adapter is in a 'stopped' state."
+// ]
+// },
+// "body" : {
+// "type" : [
+// "array", "boolean", "integer", "null", "number", "object",
+// "string"
+// ],
+// "description" : "Contains request result if success is true and "
+// "error details if success is false."
+// }
+// },
+// "required" : [ "type", "request_seq", "success", "command" ]
+// }
+// ]
+// }
+struct Response {
+ int64_t request_seq;
+ bool success;
+ std::string command;
+ std::optional<std::string> message;
+ std::optional<llvm::json::Value> rawBody;
+};
+bool fromJSON(const llvm::json::Value &, Response &, llvm::json::Path);
+llvm::json::Value toJSON(const Response &);
+
+// A void response body for any response without a specific value.
+using VoidResponseBody = std::nullptr_t;
+
+// "ProtocolMessage": {
+// "type": "object",
+// "title": "Base Protocol",
+// "description": "Base class of requests, responses, and events.",
+// "properties": {
+// "seq": {
+// "type": "integer",
+// "description": "Sequence number of the message (also known as
+// message ID). The `seq` for the first message sent by a client or
+// debug adapter is 1, and for each subsequent message is 1 greater
+// than the previous message sent by that actor. `seq` can be used to
+// order requests, responses, and events, and to associate requests
+// with their corresponding responses. For protocol messages of type
+// `request` the sequence number can be used to cancel the request."
+// },
+// "type": {
+// "type": "string",
+// "description": "Message type.",
+// "_enum": [ "request", "response", "event" ]
+// }
+// },
+// "required": [ "seq", "type" ]
+// },
+// struct ProtocolMessage {
+// MessageType type;
+// int64_t seq;
+// };
+// enum class MessageType { request, event, response };
+using ProtocolMessage = std::variant<Request, Response, Event>;
+bool fromJSON(const llvm::json::Value &, ProtocolMessage &, llvm::json::Path);
+llvm::json::Value toJSON(const ProtocolMessage &);
+
+// "CancelRequest": {
+// "allOf": [ { "$ref": "#/definitions/Request" }, {
+// "type": "object",
+// "description": "The `cancel` request is used by the client in two
+// situations:\n- to indicate that it is no longer interested in the result
+// produced by a specific request issued earlier\n- to cancel a progress
+// sequence.\nClients should only call this request if the corresponding
+// capability `supportsCancelRequest` is true.\nThis request has a hint
+// characteristic: a debug adapter can only be expected to make a 'best
+// effort' in honoring this request but there are no guarantees.\nThe
+// `cancel` request may return an error if it could not cancel an operation
+// but a client should refrain from presenting this error to end users.\nThe
+// request that got cancelled still needs to send a response back. This can
+// either be a normal result (`success` attribute true) or an error response
+// (`success` attribute false and the `message` set to
+// `cancelled`).\nReturning partial results from a cancelled request is
+// possible but please note that a client has no generic way for detecting
+// that a response is partial or not.\nThe progress that got cancelled still
+// needs to send a `progressEnd` event back.\n A client should not assume
+// that progress just got cancelled after sending the `cancel` request.",
+// "properties": {
+// "command": {
+// "type": "string",
+// "enum": [ "cancel" ]
+// },
+// "arguments": {
+// "$ref": "#/definitions/CancelArguments"
+// }
+// },
+// "required": [ "command" ]
+// }]
+// },
+// "CancelArguments": {
+// "type": "object",
+// "description": "Arguments for `cancel` request.",
+// "properties": {
+// "requestId": {
+// "type": "integer",
+// "description": "The ID (attribute `seq`) of the request to cancel. If
+// missing no request is cancelled.\nBoth a `requestId` and a `progressId`
+// can be specified in one request."
+// },
+// "progressId": {
+// "type": "string",
+// "description": "The ID (attribute `progressId`) of the progress to
+// cancel. If missing no progress is cancelled.\nBoth a `requestId` and a
+// `progressId` can be specified in one request."
+// }
+// }
+// },
+struct CancelArguments {
+ std::optional<int64_t> requestId;
+ std::optional<int64_t> progressId;
+};
+bool fromJSON(const llvm::json::Value &, CancelArguments &, llvm::json::Path);
+
+// "CancelResponse": {
+// "allOf": [ { "$ref": "#/definitions/Response" }, {
+// "type": "object",
+// "description": "Response to `cancel` request. This is just an
+// acknowledgement, so no body field is required."
+// }]
+// },
+using CancelResponseBody = VoidResponseBody;
+
+// "Message": {
+// "type": "object",
+// "description": "A structured message object. Used to return errors from
+// requests.", "properties": {
+// "id": {
+// "type": "integer",
+// "description": "Unique (within a debug adapter implementation)
+// identifier for the message. The purpose of these error IDs is to
+// help extension authors that have the requirement that every user
+// visible error message needs a corresponding error number, so that
+// users or customer support can find information about the specific
+// error more easily."
+// },
+// "format": {
+// "type": "string",
+// "description": "A format string for the message. Embedded variables
+// have the form `{name}`.\nIf variable name starts with an underscore
+// character, the variable does not contain user data (PII) and can be
+// safely used for telemetry purposes."
+// },
+// "variables": {
+// "type": "object",
+// "description": "An object used as a dictionary for looking up the
+// variables in the format string.", "additionalProperties": {
+// "type": "string",
+// "description": "All dictionary values must be strings."
+// }
+// },
+// "sendTelemetry": {
+// "type": "boolean",
+// "description": "If true send to telemetry."
+// },
+// "showUser": {
+// "type": "boolean",
+// "description": "If true show user."
+// },
+// "url": {
+// "type": "string",
+// "description": "A url where additional information about this
+// message can be found."
+// },
+// "urlLabel": {
+// "type": "string",
+// "description": "A label that is presented to the user as the UI for
+// opening the url."
+// }
+// },
+// "required": [ "id", "format" ]
+// },
+struct Message {
+ int id;
+ std::string format;
+ std::optional<bool> showUser;
+ std::optional<std::map<std::string, std::string>> variables;
+ std::optional<bool> sendTelemetry;
+ std::optional<std::string> url;
+ std::optional<std::string> urlLabel;
+};
+llvm::json::Value toJSON(const Message &);
+
+// "ErrorResponse": {
+// "allOf": [ { "$ref": "#/definitions/Response" }, {
+// "type": "object",
+// "description": "On error (whenever `success` is false), the body can
+// provide more details.", "properties": {
+// "body": {
+// "type": "object",
+// "properties": {
+// "error": {
+// "$ref": "#/definitions/Message",
+// "description": "A structured error message."
+// }
+// }
+// }
+// },
+// "required": [ "body" ]
+// }]
+// },
+struct ErrorResponseBody {
+ std::optional<Message> error;
+};
+llvm::json::Value toJSON(const ErrorResponseBody &);
+
+// MARK: Types
+
+// "Source" : {
+// "type" : "object",
+// "description" : "A `Source` is a descriptor for source
+// code.\nIt is returned "
+// "from the debug adapter as part of a
+// `StackFrame` and it is " "used by clients when
+// specifying breakpoints.",
+// "properties" : {
+// "name" : {
+// "type" : "string",
+// "description" : "The short name of the source. Every
+// source returned "
+// "from the debug adapter has a name.\nWhen
+// sending a " "source to the debug adapter
+// this name is optional."
+// },
+// "path" : {
+// "type" : "string",
+// "description" :
+// "The path of the source to be shown in the UI.\nIt is
+// only used to " "locate and load the content of the
+// source if no `sourceReference` " "is specified (or its
+// value is 0)."
+// },
+// "sourceReference" : {
+// "type" : "integer",
+// "description" :
+// "If the value > 0 the contents of the source must be
+// retrieved " "through the `source` request (even if a
+// path is specified).\nSince " "a `sourceReference` is
+// only valid for a session, it can not be used " "to
+// persist a source.\nThe value should be less than or
+// equal to " "2147483647 (2^31-1)."
+// },
+// "presentationHint" : {
+// "type" : "string",
+// "description" :
+// "A hint for how to present the source in the UI.\nA
+// value of "
+// "`deemphasize` can be used to indicate that the source
+// is not " "available or that it is skipped on
+// stepping.",
+// "enum" : [ "normal", "emphasize", "deemphasize" ]
+// },
+// "origin" : {
+// "type" : "string",
+// "description" : "The origin of this source. For example,
+// 'internal "
+// "module', 'inlined content from source
+// map', etc."
+// },
+// "sources" : {
+// "type" : "array",
+// "items" : {"$ref" : "#/definitions/Source"},
+// "description" : "A list of sources that are related to
+// this source. "
+// "These may be the source that generated
+// this source."
+// },
+// "adapterData" : {
+// "type" : [
+// "array", "boolean", "integer", "null", "number",
+// "object", "string"
+// ],
+// "description" :
+// "Additional data that a debug adapter might want to
+// loop through the " "client.\nThe client should leave
+// the data intact and persist it " "across sessions. The
+// client should not interpret the data."
+// },
+// "checksums" : {
+// "type" : "array",
+// "items" : {"$ref" : "#/definitions/Checksum"},
+// "description" : "The checksums associated with this file."
+// }
+// }
+struct Source {
+ enum class PresentationHint { normal, emphasize, deemphasize };
+
+ std::optional<std::string> name;
+ std::optional<std::string> path;
+ std::optional<int64_t> sourceReference;
+ std::optional<PresentationHint> presentationHint;
+ std::optional<std::string> origin;
+
+ // Unsupported fields: adapterData, checksums
+};
+bool fromJSON(const llvm::json::Value &, Source &, llvm::json::Path);
+llvm::json::Value toJSON(const Source &);
+
+// "StackFrame" : {
+// "type" : "object",
+// "description" : "A Stackframe contains the source location.",
+// "properties" : {
+// "id" : {
+// "type" : "integer",
+// "description" : "An identifier for the stack frame. It must be unique "
+// "across all threads.\nThis id can be used to retrieve "
+// "the scopes of the frame with the `scopes` request or
+// to " "restart the execution of a stack frame."
+// },
+// "name" : {
+// "type" : "string",
+// "description" : "The name of the stack frame, typically a method name."
+// },
+// "source" : {
+// "$ref" : "#/definitions/Source",
+// "description" : "The source of the frame."
+// },
+// "line" : {
+// "type" : "integer",
+// "description" : "The line within the source of the frame. If the source
+// "
+// "attribute is missing or doesn't exist, `line` is 0 and
+// " "should be ignored by the client."
+// },
+// "column" : {
+// "type" : "integer",
+// "description" :
+// "Start position of the range covered by the stack frame. It is "
+// "measured in UTF-16 code units and the client capability "
+// "`columnsStartAt1` determines whether it is 0- or 1-based. If "
+// "attribute `source` is missing or doesn't exist, `column` is 0 and
+// " "should be ignored by the client."
+// },
+// "endLine" : {
+// "type" : "integer",
+// "description" : "The end line of the range covered by the stack frame."
+// },
+// "endColumn" : {
+// "type" : "integer",
+// "description" :
+// "End position of the range covered by the stack frame. It is "
+// "measured in UTF-16 code units and the client capability "
+// "`columnsStartAt1` determines whether it is 0- or 1-based."
+// },
+// "canRestart" : {
+// "type" : "boolean",
+// "description" :
+// "Indicates whether this frame can be restarted with the "
+// "`restartFrame` request. Clients should only use this if the debug
+// " "adapter supports the `restart` request and the corresponding "
+// "capability `supportsRestartFrame` is true. If a debug adapter has
+// " "this capability, then `canRestart` defaults to `true` if the "
+// "property is absent."
+// },
+// "instructionPointerReference" : {
+// "type" : "string",
+// "description" : "A memory reference for the current instruction pointer
+// "
+// "in this frame."
+// },
+// "moduleId" : {
+// "type" : [ "integer", "string" ],
+// "description" : "The module associated with this frame, if any."
+// },
+// "presentationHint" : {
+// "type" : "string",
+// "enum" : [ "normal", "label", "subtle" ],
+// "description" :
+// "A hint for how to present this frame in the UI.\nA value of
+// `label` " "can be used to indicate that the frame is an artificial
+// frame that " "is used as a visual label or separator. A value of
+// `subtle` can be " "used to change the appearance of a frame in a
+// 'subtle' way."
+// }
+// },
+// "required" : [ "id", "name", "line", "column" ]
+// }
+struct StackFrame {
+ enum class PresentationHint { normal, label, subtle };
+ int64_t id = 0;
+ std::string name = "";
+ uint32_t line = 0;
+ uint32_t column = 0;
+ std::optional<Source> source;
+ std::optional<uint32_t> endLine;
+ std::optional<uint32_t> endColumn;
+ std::optional<bool> canRestart;
+ std::optional<std::string> instructionPointerReference;
+ std::optional<PresentationHint> presentationHint;
+ // Unsupported fields: "moduleId"
+};
+llvm::json::Value toJSON(const StackFrame &);
+
+// "VariablePresentationHint": {
+// "type": "object",
+// "description": "Properties of a variable that can be used to determine how
+// to render the variable in the UI.", "properties": {
+// "kind": {
+// "description": "The kind of variable. Before introducing additional
+// values, try to use the listed values.", "type": "string",
+// "_enum": [ "property", "method", "class", "data", "event", "baseClass",
+// "innerClass", "interface", "mostDerivedClass", "virtual",
+// "dataBreakpoint" ], "enumDescriptions": [
+// "Indicates that the object is a property.",
+// "Indicates that the object is a method.",
+// "Indicates that the object is a class.",
+// "Indicates that the object is data.",
+// "Indicates that the object is an event.",
+// "Indicates that the object is a base class.",
+// "Indicates that the object is an inner class.",
+// "Indicates that the object is an interface.",
+// "Indicates that the object is the most derived class.",
+// "Indicates that the object is virtual, that means it is a synthetic
+// object introduced by the adapter for rendering purposes, e.g. an
+// index range for large arrays.", "Deprecated: Indicates that a data
+// breakpoint is registered for the object. The `hasDataBreakpoint`
+// attribute should generally be used instead."
+// ]
+// },
+// "attributes": {
+// "description": "Set of attributes represented as an array of strings.
+// Before introducing additional values, try to use the listed values.",
+// "type": "array",
+// "items": {
+// "type": "string",
+// "_enum": [ "static", "constant", "readOnly", "rawString",
+// "hasObjectId", "canHaveObjectId", "hasSideEffects",
+// "hasDataBreakpoint" ], "enumDescriptions": [
+// "Indicates that the object is static.",
+// "Indicates that the object is a constant.",
+// "Indicates that the object is read only.",
+// "Indicates that the object is a raw string.",
+// "Indicates that the object can have an Object ID created for it.
+// This is a vestigial attribute that is used by some clients; 'Object
+// ID's are not specified in the protocol.", "Indicates that the
+// object has an Object ID associated with it. This is a vestigial
+// attribute that is used by some clients; 'Object ID's are not
+// specified in the protocol.", "Indicates that the evaluation had
+// side effects.", "Indicates that the object has its value tracked by
+// a data breakpoint."
+// ]
+// }
+// },
+// "visibility": {
+// "description": "Visibility of variable. Before introducing additional
+// values, try to use the listed values.", "type": "string",
+// "_enum": [ "public", "private", "protected", "internal", "final" ]
+// },
+// "lazy": {
+// "description": "If true, clients can present the variable with a UI
+// that supports a specific gesture to trigger its evaluation.\nThis
+// mechanism can be used for properties that require executing code when
+// retrieving their value and where the code execution can be expensive
+// and/or produce side-effects. A typical example are properties based on
+// a getter function.\nPlease note that in addition to the `lazy` flag,
+// the variable's `variablesReference` is expected to refer to a variable
+// that will provide the value through another `variable` request.",
+// "type": "boolean"
+// }
+// }
+// },
+struct VariablePresentationHint {
+ enum class Kind {
+ property_,
+ method_,
+ class_,
+ data_,
+ event_,
+ baseClass_,
+ innerClass_,
+ interface_,
+ mostDerivedClass_,
+ virtual_,
+ dataBreakpoint_,
+ };
+ enum class Attributes {
+ static_,
+ constant,
+ readOnly,
+ rawString,
+ hasObjectId,
+ canHaveObjectId,
+ hasSideEffects,
+ hasDataBreakpoint,
+ };
+ enum class Visibility {
+ public_,
+ private_,
+ protected_,
+ internal_,
+ final_,
+ };
+
+ std::optional<Kind> kind;
+ std::optional<std::vector<Attributes>> attributes;
+ std::optional<Visibility> visibility;
+ std::optional<bool> lazy;
+};
+llvm::json::Value toJSON(const VariablePresentationHint &);
+llvm::json::Value toJSON(const VariablePresentationHint::Kind &);
+llvm::json::Value toJSON(const VariablePresentationHint::Attributes &);
+llvm::json::Value toJSON(const VariablePresentationHint::Visibility &);
+
+// MARK: Events
+
+// "ExitedEvent": {
+// "allOf": [ { "$ref": "#/definitions/Event" }, {
+// "type": "object",
+// "description": "The event indicates that the debuggee has exited and
+// returns its exit code.", "properties": {
+// "event": {
+// "type": "string",
+// "enum": [ "exited" ]
+// },
+// "body": {
+// "type": "object",
+// "properties": {
+// "exitCode": {
+// "type": "integer",
+// "description": "The exit code returned from the debuggee."
+// }
+// },
+// "required": [ "exitCode" ]
+// }
+// },
+// "required": [ "event", "body" ]
+// }]
+// }
+struct ExitedEventBody {
+ int exitCode;
+};
+llvm::json::Value toJSON(const ExitedEventBody &);
+
+// MARK: Requests
+
+// "EvaluateRequest": {
+// "allOf": [ { "$ref": "#/definitions/Request" }, {
+// "type": "object",
+// "description": "Evaluates the given expression in the context of a stack
+// frame.\nThe expression has access to any variables and arguments that are
+// in scope.", "properties": {
+// "command": {
+// "type": "string",
+// "enum": [ "evaluate" ]
+// },
+// "arguments": {
+// "$ref": "#/definitions/EvaluateArguments"
+// }
+// },
+// "required": [ "command", "arguments" ]
+// }]
+// },
+// "EvaluateArguments": {
+// "type": "object",
+// "description": "Arguments for `evaluate` request.",
+// "properties": {
+// "expression": {
+// "type": "string",
+// "description": "The expression to evaluate."
+// },
+// "frameId": {
+// "type": "integer",
+// "description": "Evaluate the expression in the scope of this stack
+// frame. If not specified, the expression is evaluated in the global
+// scope."
+// },
+// "line": {
+// "type": "integer",
+// "description": "The contextual line where the expression should be
+// evaluated. In the 'hover' context, this should be set to the start of
+// the expression being hovered."
+// },
+// "column": {
+// "type": "integer",
+// "description": "The contextual column where the expression should be
+// evaluated. This may be provided if `line` is also provided.\n\nIt is
+// measured in UTF-16 code units and the client capability
+// `columnsStartAt1` determines whether it is 0- or 1-based."
+// },
+// "source": {
+// "$ref": "#/definitions/Source",
+// "description": "The contextual source in which the `line` is found.
+// This must be provided if `line` is provided."
+// },
+// "context": {
+// "type": "string",
+// "_enum": [ "watch", "repl", "hover", "clipboard", "variables" ],
+// "enumDescriptions": [
+// "evaluate is called from a watch view context.",
+// "evaluate is called from a REPL context.",
+// "evaluate is called to generate the debug hover contents.\nThis value
+// should only be used if the corresponding capability
+// `supportsEvaluateForHovers` is true.", "evaluate is called to
+// generate clipboard contents.\nThis value should only be used if the
+// corresponding capability `supportsClipboardContext` is true.",
+// "evaluate is called from a variables view context."
+// ],
+// "description": "The context in which the evaluate request is used."
+// },
+// "format": {
+// "$ref": "#/definitions/ValueFormat",
+// "description": "Specifies details on how to format the result.\nThe
+// attribute is only honored by a debug adapter if the corresponding
+// capability `supportsValueFormattingOptions` is true."
+// }
+// },
+// "required": [ "expression" ]
+// },
+struct EvaluateArguments {
+ enum class Context { watch, repl, hover, clipboard, variables };
+
+ std::string expression;
+ std::optional<int64_t> frameId;
+ std::optional<int> line;
+ std::optional<int> column;
+ std::optional<Source> source;
+ std::optional<Context> context;
+ // std::optional<ValueFormat> format; // unsupported
+};
+bool fromJSON(const llvm::json::Value &, EvaluateArguments &, llvm::json::Path);
+
+// "EvaluateResponse": {
+// "allOf": [ { "$ref": "#/definitions/Response" }, {
+// "type": "object",
+// "description": "Response to `evaluate` request.",
+// "properties": {
+// "body": {
+// "type": "object",
+// "properties": {
+// "result": {
+// "type": "string",
+// "description": "The result of the evaluate request."
+// },
+// "type": {
+// "type": "string",
+// "description": "The type of the evaluate result.\nThis attribute
+// should only be returned by a debug adapter if the corresponding
+// capability `supportsVariableType` is true."
+// },
+// "presentationHint": {
+// "$ref": "#/definitions/VariablePresentationHint",
+// "description": "Properties of an evaluate result that can be used
+// to determine how to render the result in the UI."
+// },
+// "variablesReference": {
+// "type": "integer",
+// "description": "If `variablesReference` is > 0, the evaluate
+// result is structured and its children can be retrieved by passing
+// `variablesReference` to the `variables` request as long as
+// execution remains suspended. See 'Lifetime of Object References'
+// in the Overview section for details."
+// },
+// "namedVariables": {
+// "type": "integer",
+// "description": "The number of named child variables.\nThe client
+// can use this information to present the variables in a paged UI
+// and fetch them in chunks.\nThe value should be less than or equal
+// to 2147483647 (2^31-1)."
+// },
+// "indexedVariables": {
+// "type": "integer",
+// "description": "The number of indexed child variables.\nThe
+// client can use this information to present the variables in a
+// paged UI and fetch them in chunks.\nThe value should be less than
+// or equal to 2147483647 (2^31-1)."
+// },
+// "memoryReference": {
+// "type": "string",
+// "description": "A memory reference to a location appropriate for
+// this result.\nFor pointer type eval results, this is generally a
+// reference to the memory address contained in the pointer.\nThis
+// attribute may be returned by a debug adapter if corresponding
+// capability `supportsMemoryReferences` is true."
+// },
+// "valueLocationReference": {
+// "type": "integer",
+// "description": "A reference that allows the client to request the
+// location where the returned value is declared. For example, if a
+// function pointer is returned, the adapter may be able to look up
+// the function's location. This should be present only if the
+// adapter is likely to be able to resolve the location.\n\nThis
+// reference shares the same lifetime as the `variablesReference`.
+// See 'Lifetime of Object References' in the Overview section for
+// details."
+// }
+// },
+// "required": [ "result", "variablesReference" ]
+// }
+// },
+// "required": [ "body" ]
+// }]
+// },
+struct EvaluateResponseBody {
+ std::string result = "";
+ std::optional<std::string> type;
+ std::optional<VariablePresentationHint> presentationHint;
+ int64_t variablesReference = 0;
+ std::optional<int> namedVariables;
+ std::optional<int> indexedVariables;
+ std::optional<std::string> memoryReference;
+ std::optional<int> valueLocationReference;
+};
+llvm::json::Value toJSON(const EvaluateResponseBody &);
+
+// "SourceRequest": {
+// "allOf": [ { "$ref": "#/definitions/Request" }, {
+// "type": "object",
+// "description": "The request retrieves the source code for a given source
+// reference.", "properties": {
+// "command": {
+// "type": "string",
+// "enum": [ "source" ]
+// },
+// "arguments": {
+// "$ref": "#/definitions/SourceArguments"
+// }
+// },
+// "required": [ "command", "arguments" ]
+// }]
+// },
+// "SourceArguments": {
+// "type": "object",
+// "description": "Arguments for 'source' request.",
+// "properties": {
+// "source": {
+// "$ref": "#/definitions/Source",
+// "description": "Specifies the source content to load. Either
+// source.path or source.sourceReference must be specified."
+// },
+// "sourceReference": {
+// "type": "integer",
+// "description": "The reference to the source. This is the same as
+// source.sourceReference. This is provided for backward compatibility
+// since old backends do not understand the 'source' attribute."
+// }
+// },
+// "required": [ "sourceReference" ]
+// }
+struct SourceArguments {
+ std::optional<Source> source;
+ int64_t sourceReference;
+};
+bool fromJSON(const llvm::json::Value &, SourceArguments &, llvm::json::Path);
+
+// "SourceResponse": {
+// "allOf": [ { "$ref": "#/definitions/Response" }, {
+// "type": "object",
+// "description": "Response to 'source' request.",
+// "properties": {
+// "body": {
+// "type": "object",
+// "properties": {
+// "content": {
+// "type": "string",
+// "description": "Content of the source reference."
+// },
+// "mimeType": {
+// "type": "string",
+// "description": "Optional content type (mime type) of the source."
+// }
+// },
+// "required": [ "content" ]
+// }
+// },
+// "required": [ "body" ]
+// }]
+// }
+struct SourceResponseBody {
+ std::string content = "";
+ std::optional<std::string> mimeType;
+};
+llvm::json::Value toJSON(const SourceResponseBody &);
+
+// MARK: Reverse Requests
+
+} // namespace protocol
+} // namespace lldb_dap
+
+#endif // LLDB_TOOLS_LLDB_DAP_PROTOCOL_H
diff --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp
index 60465332a2605..a2dd52343dec2 100644
--- a/lldb/tools/lldb-dap/lldb-dap.cpp
+++ b/lldb/tools/lldb-dap/lldb-dap.cpp
@@ -10,6 +10,7 @@
#include "FifoFiles.h"
#include "JSONUtils.h"
#include "LLDBUtils.h"
+#include "Protocol.h"
#include "RunInTerminal.h"
#include "Watchpoint.h"
#include "lldb/API/SBDeclaration.h"
@@ -41,6 +42,7 @@
#include "llvm/Support/Error.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/InitLLVM.h"
+#include "llvm/Support/JSON.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/PrettyStackTrace.h"
#include "llvm/Support/Signals.h"
@@ -101,6 +103,7 @@ using namespace lldb_dap;
using lldb_private::NativeSocket;
using lldb_private::Socket;
using lldb_private::Status;
+using namespace lldb_dap::protocol;
namespace {
using namespace llvm::opt;
@@ -172,15 +175,6 @@ std::vector<const char *> MakeArgv(const llvm::ArrayRef<std::string> &strs) {
return argv;
}
-// Send a "exited" event to indicate the process has exited.
-void SendProcessExitedEvent(DAP &dap, lldb::SBProcess &process) {
- llvm::json::Object event(CreateEventObject("exited"));
- llvm::json::Object body;
- body.try_emplace("exitCode", (int64_t)process.GetExitStatus());
- event.try_emplace("body", std::move(body));
- dap.SendJSON(llvm::json::Value(std::move(event)));
-}
-
void SendThreadExitedEvent(DAP &dap, lldb::tid_t tid) {
llvm::json::Object event(CreateEventObject("thread"));
llvm::json::Object body;
@@ -484,7 +478,9 @@ void EventThreadFunction(DAP &dap) {
// Run any exit LLDB commands the user specified in the
// launch.json
dap.RunExitCommands();
- SendProcessExitedEvent(dap, process);
+ protocol::ExitedEventBody body;
+ body.exitCode = process.GetExitStatus();
+ dap.onExited(std::move(body));
dap.SendTerminatedEvent();
done = true;
}
@@ -675,7 +671,7 @@ bool FillStackFrames(DAP &dap, lldb::SBThread &thread,
break;
}
- stack_frames.emplace_back(CreateStackFrame(frame, dap.frame_format));
+ stack_frames.emplace_back(toStackFrame(frame, dap.frame_format));
}
if (dap.display_extended_backtrace && reached_end_of_stack) {
@@ -1727,18 +1723,17 @@ void request_completions(DAP &dap, const llvm::json::Object &request) {
// "required": [ "body" ]
// }]
// }
-void request_evaluate(DAP &dap, const llvm::json::Object &request) {
- llvm::json::Object response;
- FillResponse(request, response);
- llvm::json::Object body;
- const auto *arguments = request.getObject("arguments");
- lldb::SBFrame frame = dap.GetLLDBFrame(*arguments);
- std::string expression = GetString(arguments, "expression").str();
- llvm::StringRef context = GetString(arguments, "context");
+llvm::Expected<EvaluateResponseBody>
+request_evaluate(DAP &dap, const EvaluateArguments &arguments) {
+ EvaluateResponseBody body;
+ lldb::SBFrame frame;
+ if (arguments.frameId)
+ frame = dap.GetLLDBFrame(*arguments.frameId);
+ std::string expression = arguments.expression;
bool repeat_last_command =
expression.empty() && dap.last_nonempty_var_expression.empty();
- if (context == "repl" &&
+ if (arguments.context == EvaluateArguments::Context::repl &&
(repeat_last_command ||
(!expression.empty() &&
dap.DetectReplMode(frame, expression, false) == ReplMode::Command))) {
@@ -1752,10 +1747,9 @@ void request_evaluate(DAP &dap, const llvm::json::Object &request) {
}
auto result = RunLLDBCommandsVerbatim(dap.debugger, llvm::StringRef(),
{std::string(expression)});
- EmplaceSafeString(body, "result", result);
- body.try_emplace("variablesReference", (int64_t)0);
+ body.result = result;
} else {
- if (context == "repl") {
+ if (arguments.context == EvaluateArguments::Context::repl) {
// If the expression is empty and the last expression was for a
// variable, set the expression to the previous expression (repeat the
// evaluation); otherwise save the current non-empty expression for the
@@ -1775,43 +1769,45 @@ void request_evaluate(DAP &dap, const llvm::json::Object &request) {
expression.data(), lldb::eDynamicDontRunTarget);
// Freeze dry the value in case users expand it later in the debug console
- if (value.GetError().Success() && context == "repl")
+ if (value.GetError().Success() &&
+ arguments.context == EvaluateArguments::Context::repl)
value = value.Persist();
- if (value.GetError().Fail() && context != "hover")
+ if (value.GetError().Fail() &&
+ arguments.context != EvaluateArguments::Context::hover)
value = frame.EvaluateExpression(expression.data());
if (value.GetError().Fail()) {
- response["success"] = llvm::json::Value(false);
// This error object must live until we're done with the pointer returned
// by GetCString().
lldb::SBError error = value.GetError();
const char *error_cstr = error.GetCString();
if (error_cstr && error_cstr[0])
- EmplaceSafeString(response, "message", std::string(error_cstr));
- else
- EmplaceSafeString(response, "message", "evaluate failed");
- } else {
- VariableDescription desc(value, dap.enable_auto_variable_summaries);
- EmplaceSafeString(body, "result", desc.GetResult(context));
- EmplaceSafeString(body, "type", desc.display_type_name);
- int64_t var_ref = 0;
- if (value.MightHaveChildren() || ValuePointsToCode(value))
- var_ref = dap.variables.InsertVariable(
- value, /*is_permanent=*/context == "repl");
- if (value.MightHaveChildren())
- body.try_emplace("variablesReference", var_ref);
- else
- body.try_emplace("variablesReference", (int64_t)0);
- if (lldb::addr_t addr = value.GetLoadAddress();
- addr != LLDB_INVALID_ADDRESS)
- body.try_emplace("memoryReference", EncodeMemoryReference(addr));
- if (ValuePointsToCode(value))
- body.try_emplace("valueLocationReference", var_ref);
+ return llvm::make_error<DAPError>(std::string(error_cstr));
+ return llvm::make_error<DAPError>("evaluate failed");
}
+
+ VariableDescription desc(value, dap.enable_auto_variable_summaries);
+ body.result =
+ desc.GetResult(arguments.context == EvaluateArguments::Context::repl);
+ body.type = desc.display_type_name;
+ int64_t var_ref = 0;
+ if (value.MightHaveChildren() || ValuePointsToCode(value))
+ var_ref = dap.variables.InsertVariable(
+ value, /*is_permanent=*/arguments.context ==
+ EvaluateArguments::Context::repl);
+ if (value.MightHaveChildren())
+ body.variablesReference = var_ref;
+ else
+ body.variablesReference = 0;
+ if (lldb::addr_t addr = value.GetLoadAddress();
+ addr != LLDB_INVALID_ADDRESS)
+ body.memoryReference = EncodeMemoryReference(addr);
+ if (ValuePointsToCode(value))
+ body.valueLocationReference = var_ref;
}
- response.try_emplace("body", std::move(body));
- dap.SendJSON(llvm::json::Value(std::move(response)));
+
+ return std::move(body);
}
// "compileUnitsRequest": {
@@ -2152,6 +2148,8 @@ void request_initialize(DAP &dap, const llvm::json::Object &request) {
body.try_emplace("supportsDataBreakpoints", true);
// The debug adapter supports the `readMemory` request.
body.try_emplace("supportsReadMemoryRequest", true);
+ // The debug adapter supports the `cancel` request.
+ body.try_emplace("supportsCancelRequest", true);
// Put in non-DAP specification lldb specific information.
llvm::json::Object lldb_json;
@@ -3427,12 +3425,20 @@ void request_setDataBreakpoints(DAP &dap, const llvm::json::Object &request) {
// "required": [ "body" ]
// }]
// }
-void request_source(DAP &dap, const llvm::json::Object &request) {
- llvm::json::Object response;
- FillResponse(request, response);
- llvm::json::Object body{{"content", ""}};
- response.try_emplace("body", std::move(body));
- dap.SendJSON(llvm::json::Value(std::move(response)));
+llvm::Expected<SourceResponseBody> request_source(DAP &dap,
+ const SourceArguments &args) {
+ const int64_t srcRefId =
+ args.source ? *args.source->sourceReference : args.sourceReference;
+
+ lldb::SBFrame frame = dap.GetLLDBFrame(srcRefId);
+ if (!frame.IsValid()) {
+ return llvm::make_error<DAPError>("source not found");
+ }
+
+ SourceResponseBody body;
+ body.content = frame.Disassemble();
+ body.mimeType = "text/x-lldb.disassembly";
+ return std::move(body);
}
// "StackTraceRequest": {
@@ -4940,6 +4946,13 @@ void request_setInstructionBreakpoints(DAP &dap,
dap.SendJSON(llvm::json::Value(std::move(response)));
}
+llvm::Expected<CancelResponseBody> request_cancel(DAP &dap,
+ const CancelArguments &args) {
+ // Ack the cancel request, cancellation is handled within the DAP protocol
+ // message queue.
+ return nullptr;
+}
+
void RegisterRequestCallbacks(DAP &dap) {
dap.RegisterRequestCallback("attach", request_attach);
dap.RegisterRequestCallback("breakpointLocations",
@@ -4948,7 +4961,8 @@ void RegisterRequestCallbacks(DAP &dap) {
dap.RegisterRequestCallback("continue", request_continue);
dap.RegisterRequestCallback("configurationDone", request_configurationDone);
dap.RegisterRequestCallback("disconnect", request_disconnect);
- dap.RegisterRequestCallback("evaluate", request_evaluate);
+ dap.RegisterRequest<EvaluateArguments, EvaluateResponseBody>(
+ "evaluate", request_evaluate);
dap.RegisterRequestCallback("exceptionInfo", request_exceptionInfo);
dap.RegisterRequestCallback("initialize", request_initialize);
dap.RegisterRequestCallback("launch", request_launch);
@@ -4964,7 +4978,6 @@ void RegisterRequestCallbacks(DAP &dap) {
dap.RegisterRequestCallback("dataBreakpointInfo", request_dataBreakpointInfo);
dap.RegisterRequestCallback("setDataBreakpoints", request_setDataBreakpoints);
dap.RegisterRequestCallback("setVariable", request_setVariable);
- dap.RegisterRequestCallback("source", request_source);
dap.RegisterRequestCallback("stackTrace", request_stackTrace);
dap.RegisterRequestCallback("stepIn", request_stepIn);
dap.RegisterRequestCallback("stepInTargets", request_stepInTargets);
@@ -4976,12 +4989,20 @@ void RegisterRequestCallbacks(DAP &dap) {
dap.RegisterRequestCallback("readMemory", request_readMemory);
dap.RegisterRequestCallback("setInstructionBreakpoints",
request_setInstructionBreakpoints);
+ dap.RegisterRequest<SourceArguments, SourceResponseBody>("source",
+ request_source);
+ dap.RegisterRequest<CancelArguments, CancelResponseBody>("cancel",
+ request_cancel);
+
// Custom requests
dap.RegisterRequestCallback("compileUnits", request_compileUnits);
dap.RegisterRequestCallback("modules", request_modules);
// Testing requests
dap.RegisterRequestCallback("_testGetTargetBreakpoints",
request__testGetTargetBreakpoints);
+
+ // Event handlers
+ dap.onExited = dap.RegisterEvent<protocol::ExitedEventBody>("exited");
}
} // anonymous namespace
@@ -5165,7 +5186,7 @@ serveConnection(const Socket::SocketProtocol &protocol, const std::string &name,
&dap_sessions, sock = std::move(sock)]() {
llvm::set_thread_name(name + ".runloop");
StreamDescriptor input =
- StreamDescriptor::from_socket(sock->GetNativeSocket(), false);
+ StreamDescriptor::from_socket(sock->GetNativeSocket(), true);
// Close the output last for the best chance at error reporting.
StreamDescriptor output =
StreamDescriptor::from_socket(sock->GetNativeSocket(), false);
@@ -5185,7 +5206,7 @@ serveConnection(const Socket::SocketProtocol &protocol, const std::string &name,
dap_sessions[sock.get()] = &dap;
}
- if (auto Err = dap.Loop()) {
+ if (auto Err = dap.Run()) {
llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(),
"DAP session error: ");
}
@@ -5390,7 +5411,7 @@ int main(int argc, char *argv[]) {
return EXIT_FAILURE;
}
- StreamDescriptor input = StreamDescriptor::from_file(fileno(stdin), false);
+ StreamDescriptor input = StreamDescriptor::from_file(fileno(stdin), true);
StreamDescriptor output = StreamDescriptor::from_file(stdout_fd, false);
DAP dap = DAP("stdin/stdout", program_path, log.get(), std::move(input),
@@ -5409,7 +5430,7 @@ int main(int argc, char *argv[]) {
if (getenv("LLDB_DAP_TEST_STDOUT_STDERR_REDIRECTION") != nullptr)
redirection_test();
- if (auto Err = dap.Loop()) {
+ if (auto Err = dap.Run()) {
llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(),
"DAP session error: ");
return EXIT_FAILURE;
More information about the lldb-commits
mailing list