[Lldb-commits] [lldb] 3f12155 - [lldb-dap] Creating well defined structures for DAP messages. (#129155)
via lldb-commits
lldb-commits at lists.llvm.org
Tue Mar 4 21:45:41 PST 2025
Author: John Harrison
Date: 2025-03-04T21:45:37-08:00
New Revision: 3f121558705ac7c9ae2398baca5a2d764ce022fd
URL: https://github.com/llvm/llvm-project/commit/3f121558705ac7c9ae2398baca5a2d764ce022fd
DIFF: https://github.com/llvm/llvm-project/commit/3f121558705ac7c9ae2398baca5a2d764ce022fd.diff
LOG: [lldb-dap] Creating well defined structures for DAP messages. (#129155)
This adds a new `Protocol.{h,cpp}` for defining structured types that
represent Debug Adapter Protocol messages.
This adds static types to define well structure messages for the
protocol. This iteration includes only the basic `Event`, `Request` and
`Response` types.
These types help simplify and improve the validation of messages and
give us additional static type checks on the overall structure of DAP
messages, compared to today where we tend to use `llvm::json::Value`
directly.
In a follow-up patch I plan on adding more types as need to allow for
incrementally migrating raw `llvm::json::Value` usage to well defined
types.
---------
Co-authored-by: Adrian Vogelsgesang <adrian.vogelsgesang at tum.de>
Added:
lldb/tools/lldb-dap/Protocol.cpp
lldb/tools/lldb-dap/Protocol.h
Modified:
lldb/tools/lldb-dap/CMakeLists.txt
Removed:
################################################################################
diff --git a/lldb/tools/lldb-dap/CMakeLists.txt b/lldb/tools/lldb-dap/CMakeLists.txt
index 8b3c520ec4360..9a2d604f4d573 100644
--- a/lldb/tools/lldb-dap/CMakeLists.txt
+++ b/lldb/tools/lldb-dap/CMakeLists.txt
@@ -35,6 +35,7 @@ add_lldb_tool(lldb-dap
ProgressEvent.cpp
RunInTerminal.cpp
SourceBreakpoint.cpp
+ Protocol.cpp
Watchpoint.cpp
Handler/ResponseHandler.cpp
diff --git a/lldb/tools/lldb-dap/Protocol.cpp b/lldb/tools/lldb-dap/Protocol.cpp
new file mode 100644
index 0000000000000..b516c0cb19ebf
--- /dev/null
+++ b/lldb/tools/lldb-dap/Protocol.cpp
@@ -0,0 +1,291 @@
+//===-- Protocol.cpp ------------------------------------------------------===//
+//
+// 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/StringRef.h"
+#include "llvm/ADT/StringSwitch.h"
+#include "llvm/Support/ErrorHandling.h"
+#include "llvm/Support/JSON.h"
+#include <optional>
+#include <utility>
+
+using namespace llvm;
+
+static bool mapRaw(const json::Value &Params, StringLiteral Prop,
+ std::optional<json::Value> &V, json::Path P) {
+ const auto *O = Params.getAsObject();
+ if (!O) {
+ P.report("expected object");
+ return false;
+ }
+ const json::Value *E = O->get(Prop);
+ if (E)
+ V = std::move(*E);
+ return true;
+}
+
+namespace lldb_dap {
+namespace protocol {
+
+enum class MessageType { request, response, event };
+
+bool fromJSON(const json::Value &Params, MessageType &M, json::Path P) {
+ auto rawType = Params.getAsString();
+ if (!rawType) {
+ P.report("expected a string");
+ return false;
+ }
+ std::optional<MessageType> type =
+ 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, expected 'request', 'response' or 'event'");
+ return false;
+ }
+ M = *type;
+ return true;
+}
+
+json::Value toJSON(const Request &R) {
+ 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(json::Value const &Params, Request &R, json::Path P) {
+ json::ObjectMapper O(Params, P);
+ if (!O)
+ return false;
+
+ MessageType type;
+ if (!O.map("type", type) || !O.map("command", R.command) ||
+ !O.map("seq", R.seq))
+ return false;
+
+ if (type != MessageType::request) {
+ P.field("type").report("expected to be 'request'");
+ return false;
+ }
+
+ if (R.command.empty()) {
+ P.field("command").report("expected to not be ''");
+ return false;
+ }
+
+ if (!R.seq) {
+ P.field("seq").report("expected to not be '0'");
+ return false;
+ }
+
+ return mapRaw(Params, "arguments", R.rawArguments, P);
+}
+
+json::Value toJSON(const Response &R) {
+ json::Object Result{{"type", "response"},
+ {"seq", 0},
+ {"command", R.command},
+ {"request_seq", R.request_seq},
+ {"success", R.success}};
+
+ if (R.message) {
+ assert(!R.success && "message can only be used if success is false");
+ if (const auto *messageEnum = std::get_if<Response::Message>(&*R.message)) {
+ switch (*messageEnum) {
+ case Response::Message::cancelled:
+ Result.insert({"message", "cancelled"});
+ break;
+ case Response::Message::notStopped:
+ Result.insert({"message", "notStopped"});
+ break;
+ }
+ } else if (const auto *messageString =
+ std::get_if<std::string>(&*R.message)) {
+ Result.insert({"message", *messageString});
+ }
+ }
+
+ if (R.rawBody)
+ Result.insert({"body", R.rawBody});
+
+ return std::move(Result);
+}
+
+bool fromJSON(json::Value const &Params,
+ std::variant<Response::Message, std::string> &M, json::Path P) {
+ auto rawMessage = Params.getAsString();
+ if (!rawMessage) {
+ P.report("expected a string");
+ return false;
+ }
+ std::optional<Response::Message> message =
+ StringSwitch<std::optional<Response::Message>>(*rawMessage)
+ .Case("cancelled", Response::Message::cancelled)
+ .Case("notStopped", Response::Message::notStopped)
+ .Default(std::nullopt);
+ if (message)
+ M = *message;
+ else if (!rawMessage->empty())
+ M = rawMessage->str();
+ return true;
+}
+
+bool fromJSON(json::Value const &Params, Response &R, json::Path P) {
+ json::ObjectMapper O(Params, P);
+ if (!O)
+ return false;
+
+ MessageType type;
+ int64_t seq;
+ if (!O.map("type", type) || !O.map("seq", seq) ||
+ !O.map("command", R.command) || !O.map("request_seq", R.request_seq))
+ return false;
+
+ if (type != MessageType::response) {
+ P.field("type").report("expected to be 'response'");
+ return false;
+ }
+
+ if (seq != 0) {
+ P.field("seq").report("expected to be '0'");
+ return false;
+ }
+
+ if (R.command.empty()) {
+ P.field("command").report("expected to not be ''");
+ return false;
+ }
+
+ if (R.request_seq == 0) {
+ P.field("request_seq").report("expected to not be '0'");
+ return false;
+ }
+
+ return O.map("success", R.success) && O.mapOptional("message", R.message) &&
+ mapRaw(Params, "body", R.rawBody, P);
+}
+
+json::Value toJSON(const ErrorMessage &EM) {
+ json::Object Result{{"id", EM.id}, {"format", EM.format}};
+
+ if (EM.variables) {
+ json::Object variables;
+ for (auto &var : *EM.variables)
+ variables[var.first] = var.second;
+ Result.insert({"variables", std::move(variables)});
+ }
+ if (EM.sendTelemetry)
+ Result.insert({"sendTelemetry", EM.sendTelemetry});
+ if (EM.showUser)
+ Result.insert({"showUser", EM.showUser});
+ if (EM.url)
+ Result.insert({"url", EM.url});
+ if (EM.urlLabel)
+ Result.insert({"urlLabel", EM.urlLabel});
+
+ return std::move(Result);
+}
+
+bool fromJSON(json::Value const &Params, ErrorMessage &EM, json::Path P) {
+ json::ObjectMapper O(Params, P);
+ return O && O.map("id", EM.id) && O.map("format", EM.format) &&
+ O.map("variables", EM.variables) &&
+ O.map("sendTelemetry", EM.sendTelemetry) &&
+ O.map("showUser", EM.showUser) && O.map("url", EM.url) &&
+ O.map("urlLabel", EM.urlLabel);
+}
+
+json::Value toJSON(const Event &E) {
+ json::Object Result{
+ {"type", "event"},
+ {"seq", 0},
+ {"event", E.event},
+ };
+
+ if (E.rawBody)
+ Result.insert({"body", E.rawBody});
+
+ return std::move(Result);
+}
+
+bool fromJSON(json::Value const &Params, Event &E, json::Path P) {
+ json::ObjectMapper O(Params, P);
+ if (!O)
+ return false;
+
+ MessageType type;
+ int64_t seq;
+ if (!O.map("type", type) || !O.map("seq", seq) || !O.map("event", E.event))
+ return false;
+
+ if (type != MessageType::event) {
+ P.field("type").report("expected to be 'event'");
+ return false;
+ }
+
+ if (seq != 0) {
+ P.field("seq").report("expected to be '0'");
+ return false;
+ }
+
+ if (E.event.empty()) {
+ P.field("event").report("expected to not be ''");
+ return false;
+ }
+
+ return mapRaw(Params, "body", E.rawBody, P);
+}
+
+bool fromJSON(const json::Value &Params, Message &PM, json::Path P) {
+ 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;
+ }
+}
+
+json::Value toJSON(const Message &M) {
+ return std::visit([](auto &M) { return toJSON(M); }, M);
+}
+
+} // 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..a9a5532fa6bfa
--- /dev/null
+++ b/lldb/tools/lldb-dap/Protocol.h
@@ -0,0 +1,245 @@
+//===-- Protocol.h --------------------------------------------------------===//
+//
+// 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 <cstdint>
+#include <optional>
+#include <string>
+#include <variant>
+
+namespace lldb_dap::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 {
+ enum class Message {
+ cancelled,
+ notStopped,
+ };
+
+ int64_t request_seq;
+ std::string command;
+ bool success;
+ // FIXME: Migrate usage of fallback string to ErrorMessage
+ std::optional<std::variant<Message, 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 &);
+
+// "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 ErrorMessage {
+ uint64_t id;
+ std::string format;
+ std::optional<std::map<std::string, std::string>> variables;
+ bool sendTelemetry;
+ bool showUser;
+ std::optional<std::string> url;
+ std::optional<std::string> urlLabel;
+};
+bool fromJSON(const llvm::json::Value &, ErrorMessage &, llvm::json::Path);
+llvm::json::Value toJSON(const ErrorMessage &);
+
+// "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" ]
+// },
+using Message = std::variant<Request, Response, Event>;
+bool fromJSON(const llvm::json::Value &, Message &, llvm::json::Path);
+llvm::json::Value toJSON(const Message &);
+
+} // namespace lldb_dap::protocol
+
+#endif
More information about the lldb-commits
mailing list