diff --git a/lldb/tools/lldb-dap/CMakeLists.txt b/lldb/tools/lldb-dap/CMakeLists.txt index 8a76cb58dbcab..adad75a79fa7a 100644 --- a/lldb/tools/lldb-dap/CMakeLists.txt +++ b/lldb/tools/lldb-dap/CMakeLists.txt @@ -32,7 +32,6 @@ add_lldb_tool(lldb-dap LLDBUtils.cpp OutputRedirector.cpp ProgressEvent.cpp - Protocol.cpp RunInTerminal.cpp SourceBreakpoint.cpp Transport.cpp @@ -74,6 +73,10 @@ add_lldb_tool(lldb-dap Handler/TestGetTargetBreakpointsRequestHandler.cpp Handler/ThreadsRequestHandler.cpp Handler/VariablesRequestHandler.cpp + + Protocol/ProtocolBase.cpp + Protocol/ProtocolTypes.cpp + Protocol/ProtocolRequests.cpp LINK_LIBS liblldb diff --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp index 2a8fe8e24c242..a1e2187288768 100644 --- a/lldb/tools/lldb-dap/DAP.cpp +++ b/lldb/tools/lldb-dap/DAP.cpp @@ -8,10 +8,12 @@ #include "DAP.h" #include "DAPLog.h" +#include "Handler/RequestHandler.h" #include "Handler/ResponseHandler.h" #include "JSONUtils.h" #include "LLDBUtils.h" #include "OutputRedirector.h" +#include "Protocol/ProtocolBase.h" #include "Transport.h" #include "lldb/API/SBBreakpoint.h" #include "lldb/API/SBCommandInterpreter.h" @@ -25,6 +27,7 @@ #include "lldb/lldb-defines.h" #include "lldb/lldb-enumerations.h" #include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/STLExtras.h" #include "llvm/ADT/ScopeExit.h" #include "llvm/ADT/StringExtras.h" #include "llvm/ADT/StringRef.h" @@ -41,6 +44,7 @@ #include #include #include +#include #include #if defined(_WIN32) @@ -233,6 +237,10 @@ void DAP::SendJSON(const llvm::json::Value &json) { transport.GetClientName()); return; } + Send(message); +} + +void DAP::Send(const protocol::Message &message) { if (llvm::Error err = transport.Write(message)) DAP_LOG_ERROR(log, std::move(err), "({1}) write failed: {0}", transport.GetClientName()); @@ -665,31 +673,23 @@ void DAP::SetTarget(const lldb::SBTarget target) { } bool DAP::HandleObject(const protocol::Message &M) { - // FIXME: Directly handle `Message` instead of serializing to JSON. - llvm::json::Value v = toJSON(M); - llvm::json::Object object = *v.getAsObject(); - const auto packet_type = GetString(object, "type"); - if (packet_type == "request") { - const auto command = GetString(object, "command").value_or(""); - - auto new_handler_pos = request_handlers.find(command); - if (new_handler_pos != request_handlers.end()) { - (*new_handler_pos->second)(object); + if (const auto *req = std::get_if(&M)) { + auto handler_pos = request_handlers.find(req->command); + if (handler_pos != request_handlers.end()) { + (*handler_pos->second)(*req); return true; // Success } DAP_LOG(log, "({0}) error: unhandled command '{1}'", - transport.GetClientName(), command); + transport.GetClientName(), req->command); return false; // Fail } - if (packet_type == "response") { - auto id = GetInteger(object, "request_seq").value_or(0); - + if (const auto *resp = std::get_if(&M)) { std::unique_ptr response_handler; { std::lock_guard locker(call_mutex); - auto inflight = inflight_reverse_requests.find(id); + 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); @@ -697,19 +697,32 @@ bool DAP::HandleObject(const protocol::Message &M) { } if (!response_handler) - response_handler = std::make_unique("", id); + response_handler = + std::make_unique("", resp->request_seq); // Result should be given, use null if not. - if (GetBoolean(object, "success").value_or(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->body); } else { - llvm::StringRef message = GetString(object, "message").value_or(""); - if (message.empty()) { - message = "Unknown error, response failed"; + llvm::StringRef message = "Unknown error, response failed"; + if (resp->message) { + message = + std::visit(llvm::makeVisitor( + [](const std::string &message) -> llvm::StringRef { + return message; + }, + [](const protocol::Response::Message &message) + -> llvm::StringRef { + switch (message) { + case protocol::Response::Message::cancelled: + return "cancelled"; + case protocol::Response::Message::notStopped: + return "notStopped"; + } + }), + *resp->message); } + (*response_handler)(llvm::createStringError( std::error_code(-1, std::generic_category()), message)); } @@ -717,6 +730,8 @@ bool DAP::HandleObject(const protocol::Message &M) { return true; } + DAP_LOG(log, "Unsupported protocol message"); + return false; } @@ -730,9 +745,9 @@ void DAP::SendTerminatedEvent() { }); } -lldb::SBError DAP::Disconnect() { return Disconnect(is_attach); } +llvm::Error DAP::Disconnect() { return Disconnect(is_attach); } -lldb::SBError DAP::Disconnect(bool terminateDebuggee) { +llvm::Error DAP::Disconnect(bool terminateDebuggee) { lldb::SBError error; lldb::SBProcess process = target.GetProcess(); auto state = process.GetState(); @@ -760,7 +775,7 @@ lldb::SBError DAP::Disconnect(bool terminateDebuggee) { disconnecting = true; - return error; + return ToError(error); } llvm::Error DAP::Loop() { diff --git a/lldb/tools/lldb-dap/DAP.h b/lldb/tools/lldb-dap/DAP.h index db3473b7c7027..4c57f9fef3d89 100644 --- a/lldb/tools/lldb-dap/DAP.h +++ b/lldb/tools/lldb-dap/DAP.h @@ -12,12 +12,10 @@ #include "DAPForward.h" #include "ExceptionBreakpoint.h" #include "FunctionBreakpoint.h" -#include "Handler/RequestHandler.h" -#include "Handler/ResponseHandler.h" #include "InstructionBreakpoint.h" #include "OutputRedirector.h" #include "ProgressEvent.h" -#include "Protocol.h" +#include "Protocol/ProtocolBase.h" #include "SourceBreakpoint.h" #include "Transport.h" #include "lldb/API/SBBroadcaster.h" @@ -187,7 +185,7 @@ 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; - llvm::StringMap> request_handlers; + llvm::StringMap> 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 @@ -245,9 +243,11 @@ struct DAP { /// Stop event handler threads. void StopEventHandlers(); - // Serialize the JSON value into a string and send the JSON packet to - // the "out" stream. + /// Serialize the JSON value into a string and send the JSON packet to the + /// "out" stream. void SendJSON(const llvm::json::Value &json); + /// Send the given message to the client + void Send(const protocol::Message &message); void SendOutput(OutputType o, const llvm::StringRef output); @@ -324,10 +324,10 @@ struct DAP { bool HandleObject(const protocol::Message &M); /// Disconnect the DAP session. - lldb::SBError Disconnect(); + llvm::Error Disconnect(); /// Disconnect the DAP session and optionally terminate the debuggee. - lldb::SBError Disconnect(bool terminateDebuggee); + llvm::Error Disconnect(bool terminateDebuggee); /// Send a "terminated" event to indicate the process is done being debugged. void SendTerminatedEvent(); diff --git a/lldb/tools/lldb-dap/DAPForward.h b/lldb/tools/lldb-dap/DAPForward.h index 0196d83dcd6a9..667aef23abd0f 100644 --- a/lldb/tools/lldb-dap/DAPForward.h +++ b/lldb/tools/lldb-dap/DAPForward.h @@ -19,6 +19,8 @@ struct SourceBreakpoint; struct Watchpoint; struct InstructionBreakpoint; struct DAP; +class BaseRequestHandler; +class ResponseHandler; } // namespace lldb_dap namespace lldb { diff --git a/lldb/tools/lldb-dap/Handler/DisconnectRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/DisconnectRequestHandler.cpp index b8f3404874e91..f12fecfb2ff65 100644 --- a/lldb/tools/lldb-dap/Handler/DisconnectRequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/DisconnectRequestHandler.cpp @@ -7,70 +7,27 @@ //===----------------------------------------------------------------------===// #include "DAP.h" -#include "EventHelper.h" -#include "JSONUtils.h" +#include "Protocol/ProtocolRequests.h" #include "RequestHandler.h" +#include "llvm/Support/Error.h" +#include + +using namespace llvm; +using namespace lldb_dap::protocol; namespace lldb_dap { -// "DisconnectRequest": { -// "allOf": [ { "$ref": "#/definitions/Request" }, { -// "type": "object", -// "description": "Disconnect request; value of command field is -// 'disconnect'.", -// "properties": { -// "command": { -// "type": "string", -// "enum": [ "disconnect" ] -// }, -// "arguments": { -// "$ref": "#/definitions/DisconnectArguments" -// } -// }, -// "required": [ "command" ] -// }] -// }, -// "DisconnectArguments": { -// "type": "object", -// "description": "Arguments for 'disconnect' request.", -// "properties": { -// "terminateDebuggee": { -// "type": "boolean", -// "description": "Indicates whether the debuggee should be terminated -// when the debugger is disconnected. If unspecified, -// the debug adapter is free to do whatever it thinks -// is best. A client can only rely on this attribute -// being properly honored if a debug adapter returns -// true for the 'supportTerminateDebuggee' capability." -// }, -// "restart": { -// "type": "boolean", -// "description": "Indicates whether the debuggee should be restart -// the process." -// } -// } -// }, -// "DisconnectResponse": { -// "allOf": [ { "$ref": "#/definitions/Response" }, { -// "type": "object", -// "description": "Response to 'disconnect' request. This is just an -// acknowledgement, so no body field is required." -// }] -// } -void DisconnectRequestHandler::operator()( - const llvm::json::Object &request) const { - llvm::json::Object response; - FillResponse(request, response); - const auto *arguments = request.getObject("arguments"); +/// Disconnect request; value of command field is 'disconnect'. +Expected DisconnectRequestHandler::Run( + const std::optional &arguments) const { + bool terminateDebuggee = dap.is_attach ? false : true; - bool defaultTerminateDebuggee = dap.is_attach ? false : true; - bool terminateDebuggee = GetBoolean(arguments, "terminateDebuggee") - .value_or(defaultTerminateDebuggee); + if (arguments && arguments->terminateDebuggee) + terminateDebuggee = *arguments->terminateDebuggee; - lldb::SBError error = dap.Disconnect(terminateDebuggee); - if (error.Fail()) - EmplaceSafeString(response, "error", error.GetCString()); + if (Error error = dap.Disconnect(terminateDebuggee)) + return error; - dap.SendJSON(llvm::json::Value(std::move(response))); + return DisconnectResponse(); } } // namespace lldb_dap diff --git a/lldb/tools/lldb-dap/Handler/RequestHandler.cpp b/lldb/tools/lldb-dap/Handler/RequestHandler.cpp index 2e661329edea1..60c82649938d6 100644 --- a/lldb/tools/lldb-dap/Handler/RequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/RequestHandler.cpp @@ -6,8 +6,9 @@ // //===----------------------------------------------------------------------===// -#include "RequestHandler.h" +#include "Handler/RequestHandler.h" #include "DAP.h" +#include "Handler/ResponseHandler.h" #include "JSONUtils.h" #include "LLDBUtils.h" #include "RunInTerminal.h" @@ -45,7 +46,7 @@ static uint32_t SetLaunchFlag(uint32_t flags, const llvm::json::Object *obj, // Both attach and launch take either a sourcePath or a sourceMap // argument (or neither), from which we need to set the target.source-map. -void RequestHandler::SetSourceMapFromArguments( +void BaseRequestHandler::SetSourceMapFromArguments( const llvm::json::Object &arguments) const { const char *sourceMapHelp = "source must be be an array of two-element arrays, " @@ -159,7 +160,7 @@ static llvm::Error RunInTerminal(DAP &dap, } lldb::SBError -RequestHandler::LaunchProcess(const llvm::json::Object &request) const { +BaseRequestHandler::LaunchProcess(const llvm::json::Object &request) const { lldb::SBError error; const auto *arguments = request.getObject("arguments"); auto launchCommands = GetStrings(arguments, "launchCommands"); @@ -228,13 +229,13 @@ RequestHandler::LaunchProcess(const llvm::json::Object &request) const { return error; } -void RequestHandler::PrintWelcomeMessage() const { +void BaseRequestHandler::PrintWelcomeMessage() const { #ifdef LLDB_DAP_WELCOME_MESSAGE dap.SendOutput(OutputType::Console, LLDB_DAP_WELCOME_MESSAGE); #endif } -bool RequestHandler::HasInstructionGranularity( +bool BaseRequestHandler::HasInstructionGranularity( const llvm::json::Object &arguments) const { if (std::optional value = arguments.getString("granularity")) return value == "instruction"; diff --git a/lldb/tools/lldb-dap/Handler/RequestHandler.h b/lldb/tools/lldb-dap/Handler/RequestHandler.h index b44367518bcb9..c9bcf15933c33 100644 --- a/lldb/tools/lldb-dap/Handler/RequestHandler.h +++ b/lldb/tools/lldb-dap/Handler/RequestHandler.h @@ -9,26 +9,41 @@ #ifndef LLDB_TOOLS_LLDB_DAP_HANDLER_HANDLER_H #define LLDB_TOOLS_LLDB_DAP_HANDLER_HANDLER_H +#include "DAP.h" +#include "DAPLog.h" +#include "Protocol/ProtocolBase.h" +#include "Protocol/ProtocolRequests.h" #include "lldb/API/SBError.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/JSON.h" +#include +#include + +template struct is_optional : std::false_type {}; + +template struct is_optional> : std::true_type {}; + +template +inline constexpr bool is_optional_v = is_optional::value; namespace lldb_dap { struct DAP; -class RequestHandler { +/// Base class for request handlers. Do not extend this directly: Extend +/// the RequestHandler template subclass instead. +class BaseRequestHandler { public: - RequestHandler(DAP &dap) : dap(dap) {} + BaseRequestHandler(DAP &dap) : dap(dap) {} - /// RequestHandler are not copyable. + /// BaseRequestHandler are not copyable. /// @{ - RequestHandler(const RequestHandler &) = delete; - RequestHandler &operator=(const RequestHandler &) = delete; + BaseRequestHandler(const BaseRequestHandler &) = delete; + BaseRequestHandler &operator=(const BaseRequestHandler &) = delete; /// @} - virtual ~RequestHandler() = default; + virtual ~BaseRequestHandler() = default; - virtual void operator()(const llvm::json::Object &request) const = 0; + virtual void operator()(const protocol::Request &request) const = 0; protected: /// Helpers used by multiple request handlers. @@ -57,235 +72,311 @@ class RequestHandler { DAP &dap; }; -class AttachRequestHandler : public RequestHandler { -public: - using RequestHandler::RequestHandler; +/// FIXME: Migrate callers to typed RequestHandler for improved type handling. +class LegacyRequestHandler : public BaseRequestHandler { + using BaseRequestHandler::BaseRequestHandler; + virtual void operator()(const llvm::json::Object &request) const = 0; + void operator()(const protocol::Request &request) const override { + auto req = toJSON(request); + (*this)(*req.getAsObject()); + } +}; + +/// Base class for handling DAP requests. Handlers should declare their +/// arguments and response body types like: +/// +/// class MyRequestHandler : public RequestHandler { +/// .... +/// }; +template +class RequestHandler : public BaseRequestHandler { + using BaseRequestHandler::BaseRequestHandler; + + void operator()(const protocol::Request &request) const override { + protocol::Response response; + response.request_seq = request.seq; + response.command = request.command; + + if (!is_optional_v && !request.arguments) { + DAP_LOG(dap.log, + "({0}) malformed request {1}, expected arguments but got none", + dap.transport.GetClientName(), request.command); + response.success = false; + response.message = llvm::formatv("arguments required for command '{0}' " + "but none received", + request.command) + .str(); + dap.Send(response); + return; + } + + Args arguments; + llvm::json::Path::Root root; + if (request.arguments && !fromJSON(request.arguments, arguments, root)) { + std::string parse_failure; + llvm::raw_string_ostream OS(parse_failure); + root.printErrorContext(request.arguments, OS); + response.success = false; + response.message = parse_failure; + dap.Send(response); + return; + } + + auto body = Run(arguments); + // FIXME: Add a dedicated DAPError for enhanced errors that are + // user-visibile. + if (auto Err = body.takeError()) { + response.success = false; + // FIXME: Build ErrorMessage based on error details instead of using the + // 'message' field. + response.message = llvm::toString(std::move(Err)); + } else { + response.success = true; + if constexpr (!std::is_same_v) + response.body = std::move(*body); + } + + dap.Send(response); + }; + + virtual llvm::Expected Run(const Args &) const = 0; +}; + +class AttachRequestHandler : public LegacyRequestHandler { +public: + using LegacyRequestHandler::LegacyRequestHandler; static llvm::StringLiteral getCommand() { return "attach"; } void operator()(const llvm::json::Object &request) const override; }; -class BreakpointLocationsRequestHandler : public RequestHandler { +class BreakpointLocationsRequestHandler : public LegacyRequestHandler { public: - using RequestHandler::RequestHandler; + using LegacyRequestHandler::LegacyRequestHandler; static llvm::StringLiteral getCommand() { return "breakpointLocations"; } void operator()(const llvm::json::Object &request) const override; }; -class CompletionsRequestHandler : public RequestHandler { +class CompletionsRequestHandler : public LegacyRequestHandler { public: - using RequestHandler::RequestHandler; + using LegacyRequestHandler::LegacyRequestHandler; static llvm::StringLiteral getCommand() { return "completions"; } void operator()(const llvm::json::Object &request) const override; }; -class ContinueRequestHandler : public RequestHandler { +class ContinueRequestHandler : public LegacyRequestHandler { public: - using RequestHandler::RequestHandler; + using LegacyRequestHandler::LegacyRequestHandler; static llvm::StringLiteral getCommand() { return "continue"; } void operator()(const llvm::json::Object &request) const override; }; -class ConfigurationDoneRequestHandler : public RequestHandler { +class ConfigurationDoneRequestHandler : public LegacyRequestHandler { public: - using RequestHandler::RequestHandler; + using LegacyRequestHandler::LegacyRequestHandler; static llvm::StringLiteral getCommand() { return "configurationDone"; } void operator()(const llvm::json::Object &request) const override; }; -class DisconnectRequestHandler : public RequestHandler { +class DisconnectRequestHandler + : public RequestHandler, + protocol::DisconnectResponse> { public: using RequestHandler::RequestHandler; static llvm::StringLiteral getCommand() { return "disconnect"; } - void operator()(const llvm::json::Object &request) const override; + llvm::Expected + Run(const std::optional &args) const override; }; -class EvaluateRequestHandler : public RequestHandler { +class EvaluateRequestHandler : public LegacyRequestHandler { public: - using RequestHandler::RequestHandler; + using LegacyRequestHandler::LegacyRequestHandler; static llvm::StringLiteral getCommand() { return "evaluate"; } void operator()(const llvm::json::Object &request) const override; }; -class ExceptionInfoRequestHandler : public RequestHandler { +class ExceptionInfoRequestHandler : public LegacyRequestHandler { public: - using RequestHandler::RequestHandler; + using LegacyRequestHandler::LegacyRequestHandler; static llvm::StringLiteral getCommand() { return "exceptionInfo"; } void operator()(const llvm::json::Object &request) const override; }; -class InitializeRequestHandler : public RequestHandler { +class InitializeRequestHandler : public LegacyRequestHandler { public: - using RequestHandler::RequestHandler; + using LegacyRequestHandler::LegacyRequestHandler; static llvm::StringLiteral getCommand() { return "initialize"; } void operator()(const llvm::json::Object &request) const override; }; -class LaunchRequestHandler : public RequestHandler { +class LaunchRequestHandler : public LegacyRequestHandler { public: - using RequestHandler::RequestHandler; + using LegacyRequestHandler::LegacyRequestHandler; static llvm::StringLiteral getCommand() { return "launch"; } void operator()(const llvm::json::Object &request) const override; }; -class RestartRequestHandler : public RequestHandler { +class RestartRequestHandler : public LegacyRequestHandler { public: - using RequestHandler::RequestHandler; + using LegacyRequestHandler::LegacyRequestHandler; static llvm::StringLiteral getCommand() { return "restart"; } void operator()(const llvm::json::Object &request) const override; }; -class NextRequestHandler : public RequestHandler { +class NextRequestHandler : public LegacyRequestHandler { public: - using RequestHandler::RequestHandler; + using LegacyRequestHandler::LegacyRequestHandler; static llvm::StringLiteral getCommand() { return "next"; } void operator()(const llvm::json::Object &request) const override; }; -class StepInRequestHandler : public RequestHandler { +class StepInRequestHandler : public LegacyRequestHandler { public: - using RequestHandler::RequestHandler; + using LegacyRequestHandler::LegacyRequestHandler; static llvm::StringLiteral getCommand() { return "stepIn"; } void operator()(const llvm::json::Object &request) const override; }; -class StepInTargetsRequestHandler : public RequestHandler { +class StepInTargetsRequestHandler : public LegacyRequestHandler { public: - using RequestHandler::RequestHandler; + using LegacyRequestHandler::LegacyRequestHandler; static llvm::StringLiteral getCommand() { return "stepInTargets"; } void operator()(const llvm::json::Object &request) const override; }; -class StepOutRequestHandler : public RequestHandler { +class StepOutRequestHandler : public LegacyRequestHandler { public: - using RequestHandler::RequestHandler; + using LegacyRequestHandler::LegacyRequestHandler; static llvm::StringLiteral getCommand() { return "stepOut"; } void operator()(const llvm::json::Object &request) const override; }; -class SetBreakpointsRequestHandler : public RequestHandler { +class SetBreakpointsRequestHandler : public LegacyRequestHandler { public: - using RequestHandler::RequestHandler; + using LegacyRequestHandler::LegacyRequestHandler; static llvm::StringLiteral getCommand() { return "setBreakpoints"; } void operator()(const llvm::json::Object &request) const override; }; -class SetExceptionBreakpointsRequestHandler : public RequestHandler { +class SetExceptionBreakpointsRequestHandler : public LegacyRequestHandler { public: - using RequestHandler::RequestHandler; + using LegacyRequestHandler::LegacyRequestHandler; static llvm::StringLiteral getCommand() { return "setExceptionBreakpoints"; } void operator()(const llvm::json::Object &request) const override; }; -class SetFunctionBreakpointsRequestHandler : public RequestHandler { +class SetFunctionBreakpointsRequestHandler : public LegacyRequestHandler { public: - using RequestHandler::RequestHandler; + using LegacyRequestHandler::LegacyRequestHandler; static llvm::StringLiteral getCommand() { return "setFunctionBreakpoints"; } void operator()(const llvm::json::Object &request) const override; }; -class DataBreakpointInfoRequestHandler : public RequestHandler { +class DataBreakpointInfoRequestHandler : public LegacyRequestHandler { public: - using RequestHandler::RequestHandler; + using LegacyRequestHandler::LegacyRequestHandler; static llvm::StringLiteral getCommand() { return "dataBreakpointInfo"; } void operator()(const llvm::json::Object &request) const override; }; -class SetDataBreakpointsRequestHandler : public RequestHandler { +class SetDataBreakpointsRequestHandler : public LegacyRequestHandler { public: - using RequestHandler::RequestHandler; + using LegacyRequestHandler::LegacyRequestHandler; static llvm::StringLiteral getCommand() { return "setDataBreakpoints"; } void operator()(const llvm::json::Object &request) const override; }; -class SetInstructionBreakpointsRequestHandler : public RequestHandler { +class SetInstructionBreakpointsRequestHandler : public LegacyRequestHandler { public: - using RequestHandler::RequestHandler; + using LegacyRequestHandler::LegacyRequestHandler; static llvm::StringLiteral getCommand() { return "setInstructionBreakpoints"; } void operator()(const llvm::json::Object &request) const override; }; -class CompileUnitsRequestHandler : public RequestHandler { +class CompileUnitsRequestHandler : public LegacyRequestHandler { public: - using RequestHandler::RequestHandler; + using LegacyRequestHandler::LegacyRequestHandler; static llvm::StringLiteral getCommand() { return "compileUnits"; } void operator()(const llvm::json::Object &request) const override; }; -class ModulesRequestHandler : public RequestHandler { +class ModulesRequestHandler : public LegacyRequestHandler { public: - using RequestHandler::RequestHandler; + using LegacyRequestHandler::LegacyRequestHandler; static llvm::StringLiteral getCommand() { return "modules"; } void operator()(const llvm::json::Object &request) const override; }; -class PauseRequestHandler : public RequestHandler { +class PauseRequestHandler : public LegacyRequestHandler { public: - using RequestHandler::RequestHandler; + using LegacyRequestHandler::LegacyRequestHandler; static llvm::StringLiteral getCommand() { return "pause"; } void operator()(const llvm::json::Object &request) const override; }; -class ScopesRequestHandler : public RequestHandler { +class ScopesRequestHandler : public LegacyRequestHandler { public: - using RequestHandler::RequestHandler; + using LegacyRequestHandler::LegacyRequestHandler; static llvm::StringLiteral getCommand() { return "scopes"; } void operator()(const llvm::json::Object &request) const override; }; -class SetVariableRequestHandler : public RequestHandler { +class SetVariableRequestHandler : public LegacyRequestHandler { public: - using RequestHandler::RequestHandler; + using LegacyRequestHandler::LegacyRequestHandler; static llvm::StringLiteral getCommand() { return "setVariable"; } void operator()(const llvm::json::Object &request) const override; }; -class SourceRequestHandler : public RequestHandler { +class SourceRequestHandler + : public RequestHandler { public: using RequestHandler::RequestHandler; static llvm::StringLiteral getCommand() { return "source"; } - void operator()(const llvm::json::Object &request) const override; + llvm::Expected + Run(const protocol::SourceArguments &args) const override; }; -class StackTraceRequestHandler : public RequestHandler { +class StackTraceRequestHandler : public LegacyRequestHandler { public: - using RequestHandler::RequestHandler; + using LegacyRequestHandler::LegacyRequestHandler; static llvm::StringLiteral getCommand() { return "stackTrace"; } void operator()(const llvm::json::Object &request) const override; }; -class ThreadsRequestHandler : public RequestHandler { +class ThreadsRequestHandler : public LegacyRequestHandler { public: - using RequestHandler::RequestHandler; + using LegacyRequestHandler::LegacyRequestHandler; static llvm::StringLiteral getCommand() { return "threads"; } void operator()(const llvm::json::Object &request) const override; }; -class VariablesRequestHandler : public RequestHandler { +class VariablesRequestHandler : public LegacyRequestHandler { public: - using RequestHandler::RequestHandler; + using LegacyRequestHandler::LegacyRequestHandler; static llvm::StringLiteral getCommand() { return "variables"; } void operator()(const llvm::json::Object &request) const override; }; -class LocationsRequestHandler : public RequestHandler { +class LocationsRequestHandler : public LegacyRequestHandler { public: - using RequestHandler::RequestHandler; + using LegacyRequestHandler::LegacyRequestHandler; static llvm::StringLiteral getCommand() { return "locations"; } void operator()(const llvm::json::Object &request) const override; }; -class DisassembleRequestHandler : public RequestHandler { +class DisassembleRequestHandler : public LegacyRequestHandler { public: - using RequestHandler::RequestHandler; + using LegacyRequestHandler::LegacyRequestHandler; static llvm::StringLiteral getCommand() { return "disassemble"; } void operator()(const llvm::json::Object &request) const override; }; -class ReadMemoryRequestHandler : public RequestHandler { +class ReadMemoryRequestHandler : public LegacyRequestHandler { public: - using RequestHandler::RequestHandler; + using LegacyRequestHandler::LegacyRequestHandler; static llvm::StringLiteral getCommand() { return "readMemory"; } void operator()(const llvm::json::Object &request) const override; }; @@ -294,9 +385,9 @@ class ReadMemoryRequestHandler : public RequestHandler { /// currently set in the target. This helps us to test "setBreakpoints" and /// "setFunctionBreakpoints" requests to verify we have the correct set of /// breakpoints currently set in LLDB. -class TestGetTargetBreakpointsRequestHandler : public RequestHandler { +class TestGetTargetBreakpointsRequestHandler : public LegacyRequestHandler { public: - using RequestHandler::RequestHandler; + using LegacyRequestHandler::LegacyRequestHandler; static llvm::StringLiteral getCommand() { return "_testGetTargetBreakpoints"; } diff --git a/lldb/tools/lldb-dap/Handler/SourceRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/SourceRequestHandler.cpp index 493543b395fd1..57cfb09692990 100644 --- a/lldb/tools/lldb-dap/Handler/SourceRequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/SourceRequestHandler.cpp @@ -7,115 +7,46 @@ //===----------------------------------------------------------------------===// #include "DAP.h" -#include "EventHelper.h" -#include "JSONUtils.h" +#include "Handler/RequestHandler.h" #include "LLDBUtils.h" -#include "RequestHandler.h" +#include "Protocol/ProtocolRequests.h" +#include "Protocol/ProtocolTypes.h" #include "lldb/API/SBFrame.h" #include "lldb/API/SBInstructionList.h" #include "lldb/API/SBProcess.h" #include "lldb/API/SBStream.h" #include "lldb/API/SBTarget.h" #include "lldb/API/SBThread.h" -#include "llvm/Support/JSON.h" +#include "llvm/Support/Error.h" namespace lldb_dap { -// "SourceRequest": { -// "allOf": [ { "$ref": "#/definitions/Request" }, { -// "type": "object", -// "description": "Source request; value of command field is 'source'. 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" ] -// }, -// "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" ] -// }] -// } -void SourceRequestHandler::operator()(const llvm::json::Object &request) const { - llvm::json::Object response; - FillResponse(request, response); - const auto *arguments = request.getObject("arguments"); - const auto *source = arguments->getObject("source"); - llvm::json::Object body; - const auto source_ref = - GetInteger(source, "sourceReference") - .value_or( - GetInteger(arguments, "sourceReference").value_or(0)); +/// Source request; value of command field is 'source'. The request retrieves +/// the source code for a given source reference. +llvm::Expected +SourceRequestHandler::Run(const protocol::SourceArguments &args) const { + const auto source = + args.source->sourceReference.value_or(args.sourceReference); - if (source_ref) { - lldb::SBProcess process = dap.target.GetProcess(); - // Upper 32 bits is the thread index ID - lldb::SBThread thread = - process.GetThreadByIndexID(GetLLDBThreadIndexID(source_ref)); - // Lower 32 bits is the frame index - lldb::SBFrame frame = thread.GetFrameAtIndex(GetLLDBFrameID(source_ref)); - if (!frame.IsValid()) { - response["success"] = false; - response["message"] = "source not found"; - } else { - lldb::SBInstructionList insts = - frame.GetSymbol().GetInstructions(dap.target); - lldb::SBStream stream; - insts.GetDescription(stream); - body["content"] = stream.GetData(); - body["mimeType"] = "text/x-lldb.disassembly"; - response.try_emplace("body", std::move(body)); - } - } else { - response["success"] = false; - response["message"] = - "invalid arguments, expected source.sourceReference to be set"; - } + if (!source) + return llvm::createStringError( + "invalid arguments, expected source.sourceReference to be set"); - dap.SendJSON(llvm::json::Value(std::move(response))); + lldb::SBProcess process = dap.target.GetProcess(); + // Upper 32 bits is the thread index ID + lldb::SBThread thread = + process.GetThreadByIndexID(GetLLDBThreadIndexID(source)); + // Lower 32 bits is the frame index + lldb::SBFrame frame = thread.GetFrameAtIndex(GetLLDBFrameID(source)); + if (!frame.IsValid()) + return llvm::createStringError("source not found"); + + lldb::SBInstructionList insts = frame.GetSymbol().GetInstructions(dap.target); + lldb::SBStream stream; + insts.GetDescription(stream); + + return protocol::SourceResponseBody{/*content=*/stream.GetData(), + /*mimeType=*/"text/x-lldb.disassembly"}; } } // namespace lldb_dap diff --git a/lldb/tools/lldb-dap/LLDBUtils.cpp b/lldb/tools/lldb-dap/LLDBUtils.cpp index 16ca3d779dfea..a27beff0b030d 100644 --- a/lldb/tools/lldb-dap/LLDBUtils.cpp +++ b/lldb/tools/lldb-dap/LLDBUtils.cpp @@ -163,4 +163,13 @@ GetEnvironmentFromArguments(const llvm::json::Object &arguments) { return envs; } +llvm::Error ToError(const lldb::SBError &error) { + if (error.Success()) + return llvm::Error::success(); + + return llvm::createStringError( + std::error_code(error.GetError(), std::generic_category()), + error.GetCString()); +} + } // namespace lldb_dap diff --git a/lldb/tools/lldb-dap/LLDBUtils.h b/lldb/tools/lldb-dap/LLDBUtils.h index a9e13bb3678da..2c57847303cb3 100644 --- a/lldb/tools/lldb-dap/LLDBUtils.h +++ b/lldb/tools/lldb-dap/LLDBUtils.h @@ -12,8 +12,10 @@ #include "DAPForward.h" #include "lldb/API/SBDebugger.h" #include "lldb/API/SBEnvironment.h" +#include "lldb/API/SBError.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/StringRef.h" +#include "llvm/Support/Error.h" #include "llvm/Support/JSON.h" #include "llvm/Support/raw_ostream.h" #include @@ -154,6 +156,9 @@ uint32_t GetLLDBFrameID(uint64_t dap_frame_id); lldb::SBEnvironment GetEnvironmentFromArguments(const llvm::json::Object &arguments); +/// Take ownership of the stored error. +llvm::Error ToError(const lldb::SBError &error); + } // namespace lldb_dap #endif diff --git a/lldb/tools/lldb-dap/Protocol.h b/lldb/tools/lldb-dap/Protocol.h deleted file mode 100644 index a9a5532fa6bfa..0000000000000 --- a/lldb/tools/lldb-dap/Protocol.h +++ /dev/null @@ -1,245 +0,0 @@ -//===-- 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 -#include -#include -#include - -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 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 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> message; - std::optional 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> variables; - bool sendTelemetry; - bool showUser; - std::optional url; - std::optional 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; -bool fromJSON(const llvm::json::Value &, Message &, llvm::json::Path); -llvm::json::Value toJSON(const Message &); - -} // namespace lldb_dap::protocol - -#endif diff --git a/lldb/tools/lldb-dap/Protocol.cpp b/lldb/tools/lldb-dap/Protocol/ProtocolBase.cpp similarity index 93% rename from lldb/tools/lldb-dap/Protocol.cpp rename to lldb/tools/lldb-dap/Protocol/ProtocolBase.cpp index b516c0cb19ebf..f30517715194f 100644 --- a/lldb/tools/lldb-dap/Protocol.cpp +++ b/lldb/tools/lldb-dap/Protocol/ProtocolBase.cpp @@ -1,4 +1,4 @@ -//===-- Protocol.cpp ------------------------------------------------------===// +//===-- ProtocolBase.cpp --------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -6,7 +6,7 @@ // //===----------------------------------------------------------------------===// -#include "Protocol.h" +#include "Protocol/ProtocolBase.h" #include "llvm/ADT/StringRef.h" #include "llvm/ADT/StringSwitch.h" #include "llvm/Support/ErrorHandling.h" @@ -29,8 +29,7 @@ static bool mapRaw(const json::Value &Params, StringLiteral Prop, return true; } -namespace lldb_dap { -namespace protocol { +namespace lldb_dap::protocol { enum class MessageType { request, response, event }; @@ -61,8 +60,8 @@ json::Value toJSON(const Request &R) { {"command", R.command}, }; - if (R.rawArguments) - Result.insert({"arguments", R.rawArguments}); + if (R.arguments) + Result.insert({"arguments", R.arguments}); return std::move(Result); } @@ -92,7 +91,7 @@ bool fromJSON(json::Value const &Params, Request &R, json::Path P) { return false; } - return mapRaw(Params, "arguments", R.rawArguments, P); + return mapRaw(Params, "arguments", R.arguments, P); } json::Value toJSON(const Response &R) { @@ -119,8 +118,8 @@ json::Value toJSON(const Response &R) { } } - if (R.rawBody) - Result.insert({"body", R.rawBody}); + if (R.body) + Result.insert({"body", R.body}); return std::move(Result); } @@ -176,7 +175,7 @@ bool fromJSON(json::Value const &Params, Response &R, json::Path P) { } return O.map("success", R.success) && O.mapOptional("message", R.message) && - mapRaw(Params, "body", R.rawBody, P); + mapRaw(Params, "body", R.body, P); } json::Value toJSON(const ErrorMessage &EM) { @@ -216,8 +215,8 @@ json::Value toJSON(const Event &E) { {"event", E.event}, }; - if (E.rawBody) - Result.insert({"body", E.rawBody}); + if (E.body) + Result.insert({"body", E.body}); return std::move(Result); } @@ -247,7 +246,7 @@ bool fromJSON(json::Value const &Params, Event &E, json::Path P) { return false; } - return mapRaw(Params, "body", E.rawBody, P); + return mapRaw(Params, "body", E.body, P); } bool fromJSON(const json::Value &Params, Message &PM, json::Path P) { @@ -287,5 +286,4 @@ json::Value toJSON(const Message &M) { return std::visit([](auto &M) { return toJSON(M); }, M); } -} // namespace protocol -} // namespace lldb_dap +} // namespace lldb_dap::protocol diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolBase.h b/lldb/tools/lldb-dap/Protocol/ProtocolBase.h new file mode 100644 index 0000000000000..e10a903b80aaa --- /dev/null +++ b/lldb/tools/lldb-dap/Protocol/ProtocolBase.h @@ -0,0 +1,149 @@ +//===-- ProtocolBase.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 +#include +#include +#include + +namespace lldb_dap::protocol { + +// MARK: Base Protocol + +/// A client or debug adapter initiated request. +struct Request { + /// 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. + int64_t seq; + + /// The command to execute. + std::string command; + + /// Object containing arguments for the command. + /// + /// Request handlers are expected to validate the arguments, which is handled + /// by `RequestHandler`. + std::optional arguments; +}; +llvm::json::Value toJSON(const Request &); +bool fromJSON(const llvm::json::Value &, Request &, llvm::json::Path); + +/// A debug adapter initiated event. +struct Event { + /// Type of event. + std::string event; + + /// Event-specific information. + std::optional body; +}; +llvm::json::Value toJSON(const Event &); +bool fromJSON(const llvm::json::Value &, Event &, llvm::json::Path); + +/// Response for a request. +struct Response { + enum class Message { + /// The request was cancelled + cancelled, + /// The request may be retried once the adapter is in a 'stopped' state + notStopped, + }; + + /// Sequence number of the corresponding request. + int64_t request_seq; + + /// The command requested. + std::string command; + + /// Outcome of the request. If true, the request was successful and the `body` + /// attribute may contain the result of the request. If the value is false, + /// the attribute `message` contains the error in short form and the `body` + /// may contain additional information (see `ErrorMessage`). + bool success; + + // FIXME: Migrate usage of fallback string to ErrorMessage + + /// Contains the raw error in short form if `success` is false. This raw error + /// might be interpreted by the client and is not shown in the UI. Some + /// predefined values exist. + std::optional> message; + + /// Contains request result if success is true and error details if success is + /// false. + /// + /// Request handlers are expected to build an appropriate body, see + /// `RequestHandler`. + std::optional body; +}; +bool fromJSON(const llvm::json::Value &, Response &, llvm::json::Path); +llvm::json::Value toJSON(const Response &); + +/// A structured message object. Used to return errors from requests. +struct ErrorMessage { + /// 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. + uint64_t id; + + /// A format string for the message. Embedded variables have the form + /// `{name}`. If variable name starts with an underscore character, the + /// variable does not contain user data (PII) and can be safely used for + /// telemetry purposes. + std::string format; + + /// An object used as a dictionary for looking up the variables in the format + /// string. + std::optional> variables; + + /// If true send to telemetry. + bool sendTelemetry; + + /// If true show user. + bool showUser; + + /// A url where additional information about this message can be found. + std::optional url; + + /// A label that is presented to the user as the UI for opening the url. + std::optional urlLabel; +}; +bool fromJSON(const llvm::json::Value &, ErrorMessage &, llvm::json::Path); +llvm::json::Value toJSON(const ErrorMessage &); + +/// An individual protocol message of requests, responses, and events. +using Message = std::variant; +bool fromJSON(const llvm::json::Value &, Message &, llvm::json::Path); +llvm::json::Value toJSON(const Message &); + +/// This is just an acknowledgement, so no body field is required. +using VoidResponse = std::monostate; + +} // namespace lldb_dap::protocol + +#endif diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp new file mode 100644 index 0000000000000..5cc5429227439 --- /dev/null +++ b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp @@ -0,0 +1,41 @@ +//===-- ProtocolRequests.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/ProtocolRequests.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/JSON.h" +#include + +using namespace llvm; + +namespace lldb_dap::protocol { + +bool fromJSON(const json::Value &Params, DisconnectArguments &DA, + json::Path P) { + json::ObjectMapper O(Params, P); + return O && O.mapOptional("restart", DA.restart) && + O.mapOptional("terminateDebuggee", DA.terminateDebuggee) && + O.mapOptional("suspendDebuggee", DA.suspendDebuggee); +} + +bool fromJSON(const json::Value &Params, SourceArguments &SA, json::Path P) { + json::ObjectMapper O(Params, P); + return O && O.mapOptional("source", SA.source) && + O.map("sourceReference", SA.sourceReference); +} + +json::Value toJSON(const SourceResponseBody &SA) { + json::Object Result{{"content", SA.content}}; + + if (SA.mimeType) + Result.insert({"mimeType", SA.mimeType}); + + return std::move(Result); +} + +} // namespace lldb_dap::protocol diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h new file mode 100644 index 0000000000000..5dc4a589178d2 --- /dev/null +++ b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h @@ -0,0 +1,82 @@ +//===-- ProtocolTypes.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_PROTOCOL_REQUESTS_H +#define LLDB_TOOLS_LLDB_DAP_PROTOCOL_PROTOCOL_REQUESTS_H + +#include "Protocol/ProtocolBase.h" +#include "Protocol/ProtocolTypes.h" +#include "llvm/Support/JSON.h" +#include +#include +#include + +namespace lldb_dap::protocol { + +/// Arguments for `disconnect` request. +struct DisconnectArguments { + /// A value of true indicates that this `disconnect` request is part of a + /// restart sequence. + std::optional restart; + + /// Indicates whether the debuggee should be terminated when the debugger is + /// disconnected. If unspecified, the debug adapter is free to do whatever it + /// thinks is best. The attribute is only honored by a debug adapter if the + /// corresponding capability `supportTerminateDebuggee` is true. + std::optional terminateDebuggee; + + /// Indicates whether the debuggee should stay suspended when the debugger is + /// disconnected. If unspecified, the debuggee should resume execution. The + /// attribute is only honored by a debug adapter if the corresponding + /// capability `supportSuspendDebuggee` is true. + std::optional suspendDebuggee; +}; +bool fromJSON(const llvm::json::Value &, DisconnectArguments &, + llvm::json::Path); + +/// Response to `disconnect` request. This is just an acknowledgement, so no +/// body field is required. +using DisconnectResponse = VoidResponse; + +/// Arguments for `source` request. +struct SourceArguments { + /// Specifies the source content to load. Either `source.path` or + /// `source.sourceReference` must be specified. + std::optional source; + + /// The reference to the source. This is the same as `source.sourceReference`. + /// This is provided for backward compatibility since old clients do not + /// understand the `source` attribute. + int64_t sourceReference; +}; +bool fromJSON(const llvm::json::Value &, SourceArguments &, llvm::json::Path); + +/// Response to `source` request. +struct SourceResponseBody { + /// Content of the source reference. + std::string content; + + /// Content type (MIME type) of the source. + std::optional mimeType; +}; +llvm::json::Value toJSON(const SourceResponseBody &); + +} // namespace lldb_dap::protocol + +#endif diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp new file mode 100644 index 0000000000000..efb5c3abe32bf --- /dev/null +++ b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp @@ -0,0 +1,47 @@ +//===-- ProtocolTypes.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/ProtocolTypes.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/ErrorHandling.h" +#include "llvm/Support/JSON.h" +#include + +using namespace llvm; + +namespace lldb_dap::protocol { + +bool fromJSON(const json::Value &Params, Source::PresentationHint &PH, + json::Path P) { + auto rawHint = Params.getAsString(); + if (!rawHint) { + P.report("expected a string"); + return false; + } + std::optional hint = + StringSwitch>(*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 json::Value &Params, Source &S, json::Path P) { + 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); +} + +} // namespace lldb_dap::protocol diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h new file mode 100644 index 0000000000000..b54d76cb29a77 --- /dev/null +++ b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h @@ -0,0 +1,63 @@ +//===-- ProtocolTypes.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_PROTOCOL_TYPES_H +#define LLDB_TOOLS_LLDB_DAP_PROTOCOL_PROTOCOL_TYPES_H + +#include "llvm/Support/JSON.h" +#include +#include +#include + +namespace lldb_dap::protocol { + +/// A `Source` is a descriptor for source code. It is returned from the debug +/// adapter as part of a `StackFrame` and it is used by clients when specifying +/// breakpoints. +struct Source { + enum class PresentationHint { normal, emphasize, deemphasize }; + + /// The short name of the source. Every source returned from the debug adapter + /// has a name. When sending a source to the debug adapter this name is + /// optional. + std::optional name; + + /// The path of the source to be shown in the UI. It is only used to locate + /// and load the content of the source if no `sourceReference` is specified + /// (or its value is 0). + std::optional path; + + /// If the value > 0 the contents of the source must be retrieved through the + /// `source` request (even if a path is specified). Since a `sourceReference` + /// is only valid for a session, it can not be used to persist a source. The + /// value should be less than or equal to 2147483647 (2^31-1). + std::optional sourceReference; + + /// A hint for how to present the source in the UI. A value of `deemphasize` + /// can be used to indicate that the source is not available or that it is + /// skipped on stepping. + std::optional presentationHint; + + // unsupported keys: origin, sources, adapterData, checksums +}; +bool fromJSON(const llvm::json::Value &, Source &, llvm::json::Path); + +} // namespace lldb_dap::protocol + +#endif diff --git a/lldb/tools/lldb-dap/Transport.cpp b/lldb/tools/lldb-dap/Transport.cpp index a721662a345eb..4500e7cf909ba 100644 --- a/lldb/tools/lldb-dap/Transport.cpp +++ b/lldb/tools/lldb-dap/Transport.cpp @@ -8,7 +8,7 @@ #include "Transport.h" #include "DAPLog.h" -#include "Protocol.h" +#include "Protocol/ProtocolBase.h" #include "lldb/Utility/IOObject.h" #include "lldb/Utility/Status.h" #include "lldb/lldb-forward.h" diff --git a/lldb/tools/lldb-dap/Transport.h b/lldb/tools/lldb-dap/Transport.h index 013a6c98af1ce..e77bb5dd05e65 100644 --- a/lldb/tools/lldb-dap/Transport.h +++ b/lldb/tools/lldb-dap/Transport.h @@ -14,7 +14,7 @@ #ifndef LLDB_TOOLS_LLDB_DAP_TRANSPORT_H #define LLDB_TOOLS_LLDB_DAP_TRANSPORT_H -#include "Protocol.h" +#include "Protocol/ProtocolBase.h" #include "lldb/lldb-forward.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/Error.h" diff --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp index ca8b548632ff7..59e31cf8e2cc8 100644 --- a/lldb/tools/lldb-dap/lldb-dap.cpp +++ b/lldb/tools/lldb-dap/lldb-dap.cpp @@ -374,11 +374,11 @@ serveConnection(const Socket::SocketProtocol &protocol, const std::string &name, { std::scoped_lock lock(dap_sessions_mutex); for (auto [sock, dap] : dap_sessions) { - auto error = dap->Disconnect(); - if (error.Fail()) { + if (llvm::Error error = dap->Disconnect()) { client_failed = true; llvm::errs() << "DAP client " << dap->transport.GetClientName() - << " disconnected failed: " << error.GetCString() << "\n"; + << " disconnected failed: " + << llvm::toString(std::move(error)) << "\n"; } // Close the socket to ensure the DAP::Loop read finishes. sock->Close();