https://github.com/ashgti updated https://github.com/llvm/llvm-project/pull/130090
>From 228369443a418e23f82ddc47371f7531bd4a9cb7 Mon Sep 17 00:00:00 2001 From: John Harrison <harj...@google.com> Date: Thu, 6 Mar 2025 10:18:36 +0100 Subject: [PATCH 1/2] [lldb-dap] Refactoring IOStream into Transport handler. Instead of having two discrete InputStream and OutputStream helpers, this merges the two into a unifed 'Transport' handler. This handler is responsible for reading the DAP message headers, parsing the resulting JSON and converting the messages into `lldb_dap::protocol::Message`s for both input and output. --- .../test/tools/lldb-dap/dap_server.py | 4 +- .../TestDAP_terminatedEvent.py | 2 +- lldb/tools/lldb-dap/CMakeLists.txt | 4 +- lldb/tools/lldb-dap/DAP.cpp | 135 ++++------------ lldb/tools/lldb-dap/DAP.h | 18 +-- lldb/tools/lldb-dap/IOStream.cpp | 73 --------- lldb/tools/lldb-dap/IOStream.h | 42 ----- lldb/tools/lldb-dap/JSONUtils.cpp | 3 +- lldb/tools/lldb-dap/Transport.cpp | 152 ++++++++++++++++++ lldb/tools/lldb-dap/Transport.h | 56 +++++++ 10 files changed, 248 insertions(+), 241 deletions(-) delete mode 100644 lldb/tools/lldb-dap/IOStream.cpp delete mode 100644 lldb/tools/lldb-dap/IOStream.h create mode 100644 lldb/tools/lldb-dap/Transport.cpp create mode 100644 lldb/tools/lldb-dap/Transport.h diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py index 9471594b66012..0fea3419d9725 100644 --- a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py @@ -337,7 +337,7 @@ def send_recv(self, command): self.send_packet( { "type": "response", - "seq": -1, + "seq": 0, "request_seq": response_or_request["seq"], "success": True, "command": "runInTerminal", @@ -349,7 +349,7 @@ def send_recv(self, command): self.send_packet( { "type": "response", - "seq": -1, + "seq": 0, "request_seq": response_or_request["seq"], "success": True, "command": "startDebugging", diff --git a/lldb/test/API/tools/lldb-dap/terminated-event/TestDAP_terminatedEvent.py b/lldb/test/API/tools/lldb-dap/terminated-event/TestDAP_terminatedEvent.py index 6d1c25e8e4534..b0abe2a38dac4 100644 --- a/lldb/test/API/tools/lldb-dap/terminated-event/TestDAP_terminatedEvent.py +++ b/lldb/test/API/tools/lldb-dap/terminated-event/TestDAP_terminatedEvent.py @@ -43,7 +43,7 @@ def test_terminated_event(self): self.continue_to_breakpoints(breakpoint_ids) self.continue_to_exit() - statistics = self.dap_server.wait_for_terminated()["statistics"] + statistics = self.dap_server.wait_for_terminated()["body"]["$__lldb_statistics"] self.assertGreater(statistics["totalDebugInfoByteSize"], 0) self.assertGreater(statistics["totalDebugInfoEnabled"], 0) self.assertGreater(statistics["totalModuleCountHasDebugInfo"], 0) diff --git a/lldb/tools/lldb-dap/CMakeLists.txt b/lldb/tools/lldb-dap/CMakeLists.txt index 9a2d604f4d573..8a76cb58dbcab 100644 --- a/lldb/tools/lldb-dap/CMakeLists.txt +++ b/lldb/tools/lldb-dap/CMakeLists.txt @@ -28,14 +28,14 @@ add_lldb_tool(lldb-dap FifoFiles.cpp FunctionBreakpoint.cpp InstructionBreakpoint.cpp - IOStream.cpp JSONUtils.cpp LLDBUtils.cpp OutputRedirector.cpp ProgressEvent.cpp + Protocol.cpp RunInTerminal.cpp SourceBreakpoint.cpp - Protocol.cpp + Transport.cpp Watchpoint.cpp Handler/ResponseHandler.cpp diff --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp index b21b83a79aec7..4acf3ba7fafec 100644 --- a/lldb/tools/lldb-dap/DAP.cpp +++ b/lldb/tools/lldb-dap/DAP.cpp @@ -11,6 +11,7 @@ #include "JSONUtils.h" #include "LLDBUtils.h" #include "OutputRedirector.h" +#include "Transport.h" #include "lldb/API/SBBreakpoint.h" #include "lldb/API/SBCommandInterpreter.h" #include "lldb/API/SBCommandReturnObject.h" @@ -65,7 +66,7 @@ DAP::DAP(std::string name, llvm::StringRef path, std::ofstream *log, lldb::IOObjectSP input, lldb::IOObjectSP output, ReplMode repl_mode, std::vector<std::string> pre_init_commands) : name(std::move(name)), debug_adapter_path(path), log(log), - input(std::move(input)), output(std::move(output)), + transport(this->name, std::move(input), std::move(output)), broadcaster("lldb-dap"), exception_breakpoints(), pre_init_commands(std::move(pre_init_commands)), focus_tid(LLDB_INVALID_THREAD_ID), stop_at_entry(false), is_attach(false), @@ -237,65 +238,25 @@ void DAP::StopEventHandlers() { } } -// 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 DAP::SendJSON(const std::string &json_str) { - output.write_full("Content-Length: "); - output.write_full(llvm::utostr(json_str.size())); - output.write_full("\r\n\r\n"); - output.write_full(json_str); -} - // Serialize the JSON value into a string and send the JSON packet to // the "out" stream. void DAP::SendJSON(const llvm::json::Value &json) { - std::string json_str; - llvm::raw_string_ostream strm(json_str); - strm << json; - static std::mutex mutex; - std::lock_guard<std::mutex> locker(mutex); - SendJSON(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: " << json_str.size() << "\r\n\r\n" - << llvm::formatv("{0:2}", json).str() << std::endl; - } -} - -// Read a JSON packet from the "in" stream. -std::string DAP::ReadJSON() { - std::string length_str; - std::string json_str; - int length; - - if (!input.read_expected(log, "Content-Length: ")) - return json_str; - - if (!input.read_line(log, length_str)) - return json_str; - - if (!llvm::to_integer(length_str, length)) - return json_str; - - if (!input.read_expected(log, "\r\n")) - return json_str; - - 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"; + protocol::Message M; + llvm::json::Path::Root root; + if (!protocol::fromJSON(json, M, root)) { + if (log) { + std::string error; + llvm::raw_string_ostream OS(error); + root.printErrorContext(json, OS); + *log << "encoding failure: " << error << "\n"; + } + return; } - return json_str; + auto status = transport.Write(log, M); + if (status.Fail() && log) + *log << llvm::formatv("failed to send {0}: {1}\n", llvm::json::Value(M), + status.AsCString()) + .str(); } // "OutputEvent": { @@ -722,40 +683,9 @@ void DAP::SetTarget(const lldb::SBTarget target) { } } -PacketStatus DAP::GetNextObject(llvm::json::Object &object) { - std::string json = ReadJSON(); - if (json.empty()) - return PacketStatus::EndOfFile; - - 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(); - 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; - } - return PacketStatus::JSONMalformed; - } - - if (log) { - *log << llvm::formatv("{0:2}", *json_value).str() << std::endl; - } - - 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; - } - object = *object_ptr; - return PacketStatus::Success; -} - -bool DAP::HandleObject(const llvm::json::Object &object) { +bool DAP::HandleObject(const protocol::Message &M) { + 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"); @@ -858,25 +788,18 @@ lldb::SBError DAP::Disconnect(bool terminateDebuggee) { } llvm::Error DAP::Loop() { - auto cleanup = llvm::make_scope_exit([this]() { - if (output.descriptor) - output.descriptor->Close(); - StopEventHandlers(); - }); + auto cleanup = llvm::make_scope_exit([this]() { StopEventHandlers(); }); while (!disconnecting) { - llvm::json::Object object; - lldb_dap::PacketStatus status = GetNextObject(object); - - if (status == lldb_dap::PacketStatus::EndOfFile) { - break; - } - - if (status != lldb_dap::PacketStatus::Success) { - return llvm::createStringError(llvm::inconvertibleErrorCode(), - "failed to send packet"); + protocol::Message next; + auto status = transport.Read(log, next); + if (status.Fail()) { + // On EOF, simply break out of the loop. + if (status.GetError() == Transport::kEOF) + break; + return status.takeError(); } - if (!HandleObject(object)) { + if (!HandleObject(next)) { return llvm::createStringError(llvm::inconvertibleErrorCode(), "unhandled packet"); } diff --git a/lldb/tools/lldb-dap/DAP.h b/lldb/tools/lldb-dap/DAP.h index 8b2e498a28c95..ed730ee5d1961 100644 --- a/lldb/tools/lldb-dap/DAP.h +++ b/lldb/tools/lldb-dap/DAP.h @@ -14,11 +14,12 @@ #include "FunctionBreakpoint.h" #include "Handler/RequestHandler.h" #include "Handler/ResponseHandler.h" -#include "IOStream.h" #include "InstructionBreakpoint.h" #include "OutputRedirector.h" #include "ProgressEvent.h" +#include "Protocol.h" #include "SourceBreakpoint.h" +#include "Transport.h" #include "lldb/API/SBBroadcaster.h" #include "lldb/API/SBCommandInterpreter.h" #include "lldb/API/SBDebugger.h" @@ -39,7 +40,6 @@ #include "llvm/Support/Error.h" #include "llvm/Support/JSON.h" #include "llvm/Support/Threading.h" -#include <map> #include <memory> #include <mutex> #include <optional> @@ -148,8 +148,7 @@ struct DAP { std::string name; llvm::StringRef debug_adapter_path; std::ofstream *log; - InputStream input; - OutputStream output; + Transport transport; lldb::SBFile in; OutputRedirector out; OutputRedirector err; @@ -233,8 +232,6 @@ struct DAP { // the "out" stream. void SendJSON(const llvm::json::Value &json); - std::string ReadJSON(); - void SendOutput(OutputType o, const llvm::StringRef output); void SendProgressEvent(uint64_t progress_id, const char *message, @@ -307,8 +304,7 @@ struct DAP { /// listeing for its breakpoint events. void SetTarget(const lldb::SBTarget target); - PacketStatus GetNextObject(llvm::json::Object &object); - bool HandleObject(const llvm::json::Object &object); + bool HandleObject(const protocol::Message &M); /// Disconnect the DAP session. lldb::SBError Disconnect(); @@ -382,12 +378,6 @@ struct DAP { InstructionBreakpoint *GetInstructionBreakpoint(const lldb::break_id_t bp_id); InstructionBreakpoint *GetInstructionBPFromStopReason(lldb::SBThread &thread); - -private: - // 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); }; } // namespace lldb_dap diff --git a/lldb/tools/lldb-dap/IOStream.cpp b/lldb/tools/lldb-dap/IOStream.cpp deleted file mode 100644 index ee22a297ec248..0000000000000 --- a/lldb/tools/lldb-dap/IOStream.cpp +++ /dev/null @@ -1,73 +0,0 @@ -//===-- IOStream.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 "IOStream.h" -#include "lldb/Utility/IOObject.h" -#include "lldb/Utility/Status.h" -#include <fstream> -#include <string> - -using namespace lldb_dap; - -bool OutputStream::write_full(llvm::StringRef str) { - if (!descriptor) - return false; - - size_t num_bytes = str.size(); - auto status = descriptor->Write(str.data(), num_bytes); - return status.Success(); -} - -bool InputStream::read_full(std::ofstream *log, size_t length, - std::string &text) { - if (!descriptor) - return false; - - std::string data; - data.resize(length); - - auto status = descriptor->Read(data.data(), length); - if (status.Fail()) - return false; - - text += data.substr(0, length); - return true; -} - -bool InputStream::read_line(std::ofstream *log, std::string &line) { - line.clear(); - while (true) { - std::string next; - if (!read_full(log, 1, next)) - return false; - - // If EOF is encoutnered, '' is returned, break out of this loop. - if (next.empty()) - return false; - - line += next; - - if (llvm::StringRef(line).ends_with("\r\n")) - break; - } - line.erase(line.size() - 2); - return true; -} - -bool InputStream::read_expected(std::ofstream *log, llvm::StringRef expected) { - std::string result; - if (!read_full(log, expected.size(), result)) - return false; - if (expected != result) { - if (log) - *log << "Warning: Expected '" << expected.str() << "', got '" << result - << "\n"; - return false; - } - return true; -} diff --git a/lldb/tools/lldb-dap/IOStream.h b/lldb/tools/lldb-dap/IOStream.h deleted file mode 100644 index e9fb8e11c92da..0000000000000 --- a/lldb/tools/lldb-dap/IOStream.h +++ /dev/null @@ -1,42 +0,0 @@ -//===-- IOStream.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 -// -//===----------------------------------------------------------------------===// - -#ifndef LLDB_TOOLS_LLDB_DAP_IOSTREAM_H -#define LLDB_TOOLS_LLDB_DAP_IOSTREAM_H - -#include "lldb/lldb-forward.h" -#include "llvm/ADT/StringRef.h" -#include <fstream> -#include <string> - -namespace lldb_dap { - -struct InputStream { - lldb::IOObjectSP descriptor; - - explicit InputStream(lldb::IOObjectSP descriptor) - : descriptor(std::move(descriptor)) {} - - bool read_full(std::ofstream *log, size_t length, std::string &text); - - bool read_line(std::ofstream *log, std::string &line); - - bool read_expected(std::ofstream *log, llvm::StringRef expected); -}; - -struct OutputStream { - lldb::IOObjectSP descriptor; - - explicit OutputStream(lldb::IOObjectSP descriptor) - : descriptor(std::move(descriptor)) {} - - bool write_full(llvm::StringRef str); -}; -} // namespace lldb_dap - -#endif diff --git a/lldb/tools/lldb-dap/JSONUtils.cpp b/lldb/tools/lldb-dap/JSONUtils.cpp index 7094bf60bfbc2..932145b1799bd 100644 --- a/lldb/tools/lldb-dap/JSONUtils.cpp +++ b/lldb/tools/lldb-dap/JSONUtils.cpp @@ -1526,7 +1526,8 @@ static void addStatistic(lldb::SBTarget &target, llvm::json::Object &event) { const char *key = keys.GetStringAtIndex(i); FilterAndGetValueForKey(statistics, key, stats_body); } - event.try_emplace("statistics", std::move(stats_body)); + llvm::json::Object body{{"$__lldb_statistics", std::move(stats_body)}}; + event.try_emplace("body", std::move(body)); } llvm::json::Object CreateTerminatedEventObject(lldb::SBTarget &target) { diff --git a/lldb/tools/lldb-dap/Transport.cpp b/lldb/tools/lldb-dap/Transport.cpp new file mode 100644 index 0000000000000..f46bd78192332 --- /dev/null +++ b/lldb/tools/lldb-dap/Transport.cpp @@ -0,0 +1,152 @@ +//===-- Transport.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 "Transport.h" +#include "Protocol.h" +#include "lldb/Utility/IOObject.h" +#include "lldb/Utility/Status.h" +#include "lldb/lldb-enumerations.h" +#include "lldb/lldb-forward.h" +#include "llvm/ADT/StringExtras.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/raw_ostream.h" +#include <utility> + +using namespace llvm; +using namespace lldb; +using namespace lldb_private; +using namespace lldb_dap; +using namespace lldb_dap::protocol; + +static Status ReadFull(IOObjectSP &descriptor, size_t length, + std::string &text) { + if (!descriptor || !descriptor->IsValid()) + return Status("transport input is closed"); + + std::string data; + data.resize(length); + + auto status = descriptor->Read(data.data(), length); + if (status.Fail()) + return status; + + // If we got back zero then we have reached EOF. + if (length == 0) + return Status(Transport::kEOF, lldb::eErrorTypeGeneric, "end-of-file"); + + text += data.substr(0, length); + return Status(); +} + +static Status ReadUpTo(IOObjectSP &descriptor, std::string &line, + const std::string &delimiter) { + line.clear(); + while (true) { + std::string next; + auto status = ReadFull(descriptor, 1, next); + if (status.Fail()) + return status; + + line += next; + + if (llvm::StringRef(line).ends_with(delimiter)) + break; + } + line.erase(line.size() - delimiter.size()); + return Status(); +} + +static Status ReadExpected(IOObjectSP &descriptor, llvm::StringRef expected) { + std::string result; + auto status = ReadFull(descriptor, expected.size(), result); + if (status.Fail()) + return status; + if (expected != result) { + return Status::FromErrorStringWithFormatv("expected %s, got %s", expected, + result); + } + return Status(); +} + +namespace lldb_dap { + +Transport::Transport(StringRef client_name, IOObjectSP input, IOObjectSP output) + : m_client_name(client_name), m_input(std::move(input)), + m_output(std::move(output)) {} + +Status Transport::Read(std::ofstream *log, Message &M) { + // If we don't find the expected header we have reached EOF. + auto status = ReadExpected(m_input, "Content-Length: "); + if (status.Fail()) + return status; + + std::string rawLength; + status = ReadUpTo(m_input, rawLength, "\r\n\r\n"); + if (status.Fail()) + return status; + + size_t length; + if (!to_integer(rawLength, length)) + return Status::FromErrorStringWithFormatv("invalid content length {0}", + rawLength); + + std::string rawJSON; + status = ReadFull(m_input, length, rawJSON); + if (status.Fail()) + return status; + if (rawJSON.length() != length) + return Status::FromErrorStringWithFormatv( + "malformed request, expected {0} bytes, got {1} bytes", length, + rawJSON.length()); + + if (log) { + auto now = std::chrono::duration<double>( + std::chrono::system_clock::now().time_since_epoch()); + *log << formatv("{0:f9} <-- ({1}) {2}\n", now.count(), m_client_name, + rawJSON) + .str(); + } + + auto JSON = json::parse(rawJSON); + if (auto Err = JSON.takeError()) { + return Status::FromErrorStringWithFormatv("malformed JSON {0}\n{1}", + rawJSON, Err); + } + + llvm::json::Path::Root Root; + if (!fromJSON(*JSON, M, Root)) { + std::string error; + raw_string_ostream OS(error); + Root.printErrorContext(*JSON, OS); + return Status::FromErrorStringWithFormatv("malformed request: {0}", error); + } + return Status(); +} + +lldb_private::Status Transport::Write(std::ofstream *log, + const protocol::Message &M) { + if (!m_output || !m_output->IsValid()) + return Status("transport output is closed"); + + std::string JSON = formatv("{0}", toJSON(M)).str(); + + if (log) { + auto now = std::chrono::duration<double>( + std::chrono::system_clock::now().time_since_epoch()); + *log << formatv("{0:f9} --> ({1}) {2}\n", now.count(), m_client_name, JSON) + .str(); + } + + std::string Output; + raw_string_ostream OS(Output); + OS << "Content-Length: " << JSON.length() << "\r\n\r\n" << JSON; + size_t num_bytes = Output.size(); + return m_output->Write(Output.data(), num_bytes); +} + +} // end namespace lldb_dap diff --git a/lldb/tools/lldb-dap/Transport.h b/lldb/tools/lldb-dap/Transport.h new file mode 100644 index 0000000000000..041ceca7446c6 --- /dev/null +++ b/lldb/tools/lldb-dap/Transport.h @@ -0,0 +1,56 @@ +//===-- Transport.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 +// +//===----------------------------------------------------------------------===// +// +// Debug Adapter Protocol transport layer for encoding and decoding protocol +// messages. +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_TOOLS_LLDB_DAP_TRANSPORT_H +#define LLDB_TOOLS_LLDB_DAP_TRANSPORT_H + +#include "Protocol.h" +#include "lldb/Utility/Status.h" +#include "lldb/lldb-forward.h" +#include "llvm/ADT/StringRef.h" +#include <fstream> + +namespace lldb_dap { + +/// A transport class that performs the Debug Adapter Protocol communication +/// with the client. +class Transport { +public: + Transport(llvm::StringRef client_name, lldb::IOObjectSP input, + lldb::IOObjectSP output); + ~Transport() = default; + + Transport(const Transport &rhs) = delete; + void operator=(const Transport &rhs) = delete; + + static const lldb_private::Status::ValueType kEOF = + 0x1001; ///< ValueObject::GetError() returns this if EOF is encountered. + + /// Writes a Debug Adater Protocol message to the output stream. + lldb_private::Status Write(std::ofstream *log, const protocol::Message &M); + + /// Reads the next Debug Adater Protocol message from the input stream. + lldb_private::Status Read(std::ofstream *log, protocol::Message &M); + + /// Closes the transport. + void CloseOutput(); + +private: + llvm::StringRef m_client_name; + lldb::IOObjectSP m_input; + lldb::IOObjectSP m_output; +}; + +} // namespace lldb_dap + +#endif >From 36d0fec9569b4313055f5557b7d824c8a8188700 Mon Sep 17 00:00:00 2001 From: John Harrison <harj...@google.com> Date: Thu, 6 Mar 2025 13:21:10 +0100 Subject: [PATCH 2/2] [lldb-dap] Updating RequestHandler to encode/decode arguments and response. This is a work in progress refactor to add explicit types instead of generic 'llvm::json::Value' types to the DAP protocol. This updates RequestHandler to have take the type of the arguments and response body for serialization for requests. The 'source' request is updated to show how the new flow works. --- lldb/tools/lldb-dap/DAP.cpp | 57 +++-- lldb/tools/lldb-dap/DAP.h | 4 +- lldb/tools/lldb-dap/DAPForward.h | 2 + .../tools/lldb-dap/Handler/RequestHandler.cpp | 11 +- lldb/tools/lldb-dap/Handler/RequestHandler.h | 214 ++++++++++++------ .../lldb-dap/Handler/SourceRequestHandler.cpp | 104 ++------- lldb/tools/lldb-dap/Protocol.cpp | 44 ++++ lldb/tools/lldb-dap/Protocol.h | 131 +++++++++++ 8 files changed, 381 insertions(+), 186 deletions(-) diff --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp index 4acf3ba7fafec..ce118f36808dc 100644 --- a/lldb/tools/lldb-dap/DAP.cpp +++ b/lldb/tools/lldb-dap/DAP.cpp @@ -7,10 +7,12 @@ //===----------------------------------------------------------------------===// #include "DAP.h" +#include "Handler/RequestHandler.h" #include "Handler/ResponseHandler.h" #include "JSONUtils.h" #include "LLDBUtils.h" #include "OutputRedirector.h" +#include "Protocol.h" #include "Transport.h" #include "lldb/API/SBBreakpoint.h" #include "lldb/API/SBCommandInterpreter.h" @@ -24,6 +26,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/Twine.h" @@ -39,6 +42,7 @@ #include <fstream> #include <memory> #include <mutex> +#include <string> #include <utility> #if defined(_WIN32) @@ -684,31 +688,24 @@ void DAP::SetTarget(const lldb::SBTarget target) { } bool DAP::HandleObject(const protocol::Message &M) { - 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"); - - auto new_handler_pos = request_handlers.find(command); + if (const auto *req = std::get_if<protocol::Request>(&M)) { + auto new_handler_pos = request_handlers.find(req->command); if (new_handler_pos != request_handlers.end()) { - (*new_handler_pos->second)(object); + (*new_handler_pos->second)(*req); return true; // Success } if (log) - *log << "error: unhandled command \"" << command.data() << "\"" + *log << "error: unhandled command \"" << req->command << "\"" << std::endl; return false; // Fail } - if (packet_type == "response") { - auto id = GetInteger<int64_t>(object, "request_seq").value_or(0); - + if (const auto *resp = std::get_if<protocol::Response>(&M)) { std::unique_ptr<ResponseHandler> response_handler; { std::lock_guard<std::mutex> 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); @@ -716,20 +713,31 @@ bool DAP::HandleObject(const protocol::Message &M) { } if (!response_handler) - response_handler = std::make_unique<UnknownResponseHandler>("", id); + response_handler = + std::make_unique<UnknownResponseHandler>("", 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->rawBody); } else { - llvm::StringRef message = GetString(object, "message"); - if (message.empty()) { - message = "Unknown error, response failed"; + std::string message = "Unknown error, response failed"; + if (resp->message) { + message = std::visit( + llvm::makeVisitor( + [](const std::string &message) -> std::string { + return message; + }, + [](const protocol::Response::Message &message) -> std::string { + 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)); } @@ -737,6 +745,9 @@ bool DAP::HandleObject(const protocol::Message &M) { return true; } + if (log) + *log << "Unsupported protocol message" << std::endl; + return false; } diff --git a/lldb/tools/lldb-dap/DAP.h b/lldb/tools/lldb-dap/DAP.h index ed730ee5d1961..fbabe763b8171 100644 --- a/lldb/tools/lldb-dap/DAP.h +++ b/lldb/tools/lldb-dap/DAP.h @@ -12,8 +12,6 @@ #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" @@ -188,7 +186,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<std::unique_ptr<RequestHandler>> request_handlers; + llvm::StringMap<std::unique_ptr<BaseRequestHandler>> 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 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/RequestHandler.cpp b/lldb/tools/lldb-dap/Handler/RequestHandler.cpp index e43fa36d25e3f..13cf166e200e8 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<llvm::StringRef> 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..da65b2de9ab99 100644 --- a/lldb/tools/lldb-dap/Handler/RequestHandler.h +++ b/lldb/tools/lldb-dap/Handler/RequestHandler.h @@ -9,25 +9,36 @@ #ifndef LLDB_TOOLS_LLDB_DAP_HANDLER_HANDLER_H #define LLDB_TOOLS_LLDB_DAP_HANDLER_HANDLER_H +#include "DAP.h" +#include "Protocol.h" #include "lldb/API/SBError.h" +#include "llvm/ADT/STLExtras.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/JSON.h" 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 protocol::Request &request) const { + auto req = toJSON(request); + (*this)(*req.getAsObject()); + } + + /// FIXME: Migrate callers to typed RequestHandler for improved type handling. virtual void operator()(const llvm::json::Object &request) const = 0; protected: @@ -57,235 +68,288 @@ class RequestHandler { DAP &dap; }; -class AttachRequestHandler : public RequestHandler { -public: - using RequestHandler::RequestHandler; +/// Base class for handling DAP requests. Handlers should declare their +/// arguments and response body types like: +/// +/// class MyRequestHandler : public RequestHandler<Arguments, ResponseBody> { +/// .... +/// }; +template <typename Args, typename Body> +class RequestHandler : public BaseRequestHandler { + using BaseRequestHandler::BaseRequestHandler; + + void operator()(const llvm::json::Object &request) const override { + /* no-op, the other overload handles json coding. */ + } + + void operator()(const protocol::Request &request) const override { + protocol::Response response; + response.request_seq = request.seq; + response.command = request.command; + Args arguments; + llvm::json::Path::Root root; + if (request.rawArguments && + !fromJSON(request.rawArguments, arguments, root)) { + std::string parseFailure; + llvm::raw_string_ostream OS(parseFailure); + root.printErrorContext(request.rawArguments, OS); + response.success = false; + response.message = parseFailure; + dap.SendJSON(std::move(response)); + return; + } + + auto ResponseBody = Run(arguments); + // FIXME: Add a dedicated DAPError for enhanced errors that are user + // visibile. + if (auto Err = ResponseBody.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; + response.rawBody = std::move(*ResponseBody); + } + + dap.SendJSON(std::move(response)); + }; + + virtual llvm::Expected<Body> Run(const Args &) const = 0; +}; + +class AttachRequestHandler : public BaseRequestHandler { +public: + using BaseRequestHandler::BaseRequestHandler; static llvm::StringLiteral getCommand() { return "attach"; } void operator()(const llvm::json::Object &request) const override; }; -class BreakpointLocationsRequestHandler : public RequestHandler { +class BreakpointLocationsRequestHandler : public BaseRequestHandler { public: - using RequestHandler::RequestHandler; + using BaseRequestHandler::BaseRequestHandler; static llvm::StringLiteral getCommand() { return "breakpointLocations"; } void operator()(const llvm::json::Object &request) const override; }; -class CompletionsRequestHandler : public RequestHandler { +class CompletionsRequestHandler : public BaseRequestHandler { public: - using RequestHandler::RequestHandler; + using BaseRequestHandler::BaseRequestHandler; static llvm::StringLiteral getCommand() { return "completions"; } void operator()(const llvm::json::Object &request) const override; }; -class ContinueRequestHandler : public RequestHandler { +class ContinueRequestHandler : public BaseRequestHandler { public: - using RequestHandler::RequestHandler; + using BaseRequestHandler::BaseRequestHandler; static llvm::StringLiteral getCommand() { return "continue"; } void operator()(const llvm::json::Object &request) const override; }; -class ConfigurationDoneRequestHandler : public RequestHandler { +class ConfigurationDoneRequestHandler : public BaseRequestHandler { public: - using RequestHandler::RequestHandler; + using BaseRequestHandler::BaseRequestHandler; static llvm::StringLiteral getCommand() { return "configurationDone"; } void operator()(const llvm::json::Object &request) const override; }; -class DisconnectRequestHandler : public RequestHandler { +class DisconnectRequestHandler : public BaseRequestHandler { public: - using RequestHandler::RequestHandler; + using BaseRequestHandler::BaseRequestHandler; static llvm::StringLiteral getCommand() { return "disconnect"; } void operator()(const llvm::json::Object &request) const override; }; -class EvaluateRequestHandler : public RequestHandler { +class EvaluateRequestHandler : public BaseRequestHandler { public: - using RequestHandler::RequestHandler; + using BaseRequestHandler::BaseRequestHandler; static llvm::StringLiteral getCommand() { return "evaluate"; } void operator()(const llvm::json::Object &request) const override; }; -class ExceptionInfoRequestHandler : public RequestHandler { +class ExceptionInfoRequestHandler : public BaseRequestHandler { public: - using RequestHandler::RequestHandler; + using BaseRequestHandler::BaseRequestHandler; static llvm::StringLiteral getCommand() { return "exceptionInfo"; } void operator()(const llvm::json::Object &request) const override; }; -class InitializeRequestHandler : public RequestHandler { +class InitializeRequestHandler : public BaseRequestHandler { public: - using RequestHandler::RequestHandler; + using BaseRequestHandler::BaseRequestHandler; static llvm::StringLiteral getCommand() { return "initialize"; } void operator()(const llvm::json::Object &request) const override; }; -class LaunchRequestHandler : public RequestHandler { +class LaunchRequestHandler : public BaseRequestHandler { public: - using RequestHandler::RequestHandler; + using BaseRequestHandler::BaseRequestHandler; static llvm::StringLiteral getCommand() { return "launch"; } void operator()(const llvm::json::Object &request) const override; }; -class RestartRequestHandler : public RequestHandler { +class RestartRequestHandler : public BaseRequestHandler { public: - using RequestHandler::RequestHandler; + using BaseRequestHandler::BaseRequestHandler; static llvm::StringLiteral getCommand() { return "restart"; } void operator()(const llvm::json::Object &request) const override; }; -class NextRequestHandler : public RequestHandler { +class NextRequestHandler : public BaseRequestHandler { public: - using RequestHandler::RequestHandler; + using BaseRequestHandler::BaseRequestHandler; static llvm::StringLiteral getCommand() { return "next"; } void operator()(const llvm::json::Object &request) const override; }; -class StepInRequestHandler : public RequestHandler { +class StepInRequestHandler : public BaseRequestHandler { public: - using RequestHandler::RequestHandler; + using BaseRequestHandler::BaseRequestHandler; static llvm::StringLiteral getCommand() { return "stepIn"; } void operator()(const llvm::json::Object &request) const override; }; -class StepInTargetsRequestHandler : public RequestHandler { +class StepInTargetsRequestHandler : public BaseRequestHandler { public: - using RequestHandler::RequestHandler; + using BaseRequestHandler::BaseRequestHandler; static llvm::StringLiteral getCommand() { return "stepInTargets"; } void operator()(const llvm::json::Object &request) const override; }; -class StepOutRequestHandler : public RequestHandler { +class StepOutRequestHandler : public BaseRequestHandler { public: - using RequestHandler::RequestHandler; + using BaseRequestHandler::BaseRequestHandler; static llvm::StringLiteral getCommand() { return "stepOut"; } void operator()(const llvm::json::Object &request) const override; }; -class SetBreakpointsRequestHandler : public RequestHandler { +class SetBreakpointsRequestHandler : public BaseRequestHandler { public: - using RequestHandler::RequestHandler; + using BaseRequestHandler::BaseRequestHandler; static llvm::StringLiteral getCommand() { return "setBreakpoints"; } void operator()(const llvm::json::Object &request) const override; }; -class SetExceptionBreakpointsRequestHandler : public RequestHandler { +class SetExceptionBreakpointsRequestHandler : public BaseRequestHandler { public: - using RequestHandler::RequestHandler; + using BaseRequestHandler::BaseRequestHandler; static llvm::StringLiteral getCommand() { return "setExceptionBreakpoints"; } void operator()(const llvm::json::Object &request) const override; }; -class SetFunctionBreakpointsRequestHandler : public RequestHandler { +class SetFunctionBreakpointsRequestHandler : public BaseRequestHandler { public: - using RequestHandler::RequestHandler; + using BaseRequestHandler::BaseRequestHandler; static llvm::StringLiteral getCommand() { return "setFunctionBreakpoints"; } void operator()(const llvm::json::Object &request) const override; }; -class DataBreakpointInfoRequestHandler : public RequestHandler { +class DataBreakpointInfoRequestHandler : public BaseRequestHandler { public: - using RequestHandler::RequestHandler; + using BaseRequestHandler::BaseRequestHandler; static llvm::StringLiteral getCommand() { return "dataBreakpointInfo"; } void operator()(const llvm::json::Object &request) const override; }; -class SetDataBreakpointsRequestHandler : public RequestHandler { +class SetDataBreakpointsRequestHandler : public BaseRequestHandler { public: - using RequestHandler::RequestHandler; + using BaseRequestHandler::BaseRequestHandler; static llvm::StringLiteral getCommand() { return "setDataBreakpoints"; } void operator()(const llvm::json::Object &request) const override; }; -class SetInstructionBreakpointsRequestHandler : public RequestHandler { +class SetInstructionBreakpointsRequestHandler : public BaseRequestHandler { public: - using RequestHandler::RequestHandler; + using BaseRequestHandler::BaseRequestHandler; static llvm::StringLiteral getCommand() { return "setInstructionBreakpoints"; } void operator()(const llvm::json::Object &request) const override; }; -class CompileUnitsRequestHandler : public RequestHandler { +class CompileUnitsRequestHandler : public BaseRequestHandler { public: - using RequestHandler::RequestHandler; + using BaseRequestHandler::BaseRequestHandler; static llvm::StringLiteral getCommand() { return "compileUnits"; } void operator()(const llvm::json::Object &request) const override; }; -class ModulesRequestHandler : public RequestHandler { +class ModulesRequestHandler : public BaseRequestHandler { public: - using RequestHandler::RequestHandler; + using BaseRequestHandler::BaseRequestHandler; static llvm::StringLiteral getCommand() { return "modules"; } void operator()(const llvm::json::Object &request) const override; }; -class PauseRequestHandler : public RequestHandler { +class PauseRequestHandler : public BaseRequestHandler { public: - using RequestHandler::RequestHandler; + using BaseRequestHandler::BaseRequestHandler; static llvm::StringLiteral getCommand() { return "pause"; } void operator()(const llvm::json::Object &request) const override; }; -class ScopesRequestHandler : public RequestHandler { +class ScopesRequestHandler : public BaseRequestHandler { public: - using RequestHandler::RequestHandler; + using BaseRequestHandler::BaseRequestHandler; static llvm::StringLiteral getCommand() { return "scopes"; } void operator()(const llvm::json::Object &request) const override; }; -class SetVariableRequestHandler : public RequestHandler { +class SetVariableRequestHandler : public BaseRequestHandler { public: - using RequestHandler::RequestHandler; + using BaseRequestHandler::BaseRequestHandler; static llvm::StringLiteral getCommand() { return "setVariable"; } void operator()(const llvm::json::Object &request) const override; }; -class SourceRequestHandler : public RequestHandler { +class SourceRequestHandler + : public RequestHandler<protocol::SourceArguments, + protocol::SourceResponseBody> { public: using RequestHandler::RequestHandler; static llvm::StringLiteral getCommand() { return "source"; } - void operator()(const llvm::json::Object &request) const override; + llvm::Expected<protocol::SourceResponseBody> + Run(const protocol::SourceArguments &args) const override; }; -class StackTraceRequestHandler : public RequestHandler { +class StackTraceRequestHandler : public BaseRequestHandler { public: - using RequestHandler::RequestHandler; + using BaseRequestHandler::BaseRequestHandler; static llvm::StringLiteral getCommand() { return "stackTrace"; } void operator()(const llvm::json::Object &request) const override; }; -class ThreadsRequestHandler : public RequestHandler { +class ThreadsRequestHandler : public BaseRequestHandler { public: - using RequestHandler::RequestHandler; + using BaseRequestHandler::BaseRequestHandler; static llvm::StringLiteral getCommand() { return "threads"; } void operator()(const llvm::json::Object &request) const override; }; -class VariablesRequestHandler : public RequestHandler { +class VariablesRequestHandler : public BaseRequestHandler { public: - using RequestHandler::RequestHandler; + using BaseRequestHandler::BaseRequestHandler; static llvm::StringLiteral getCommand() { return "variables"; } void operator()(const llvm::json::Object &request) const override; }; -class LocationsRequestHandler : public RequestHandler { +class LocationsRequestHandler : public BaseRequestHandler { public: - using RequestHandler::RequestHandler; + using BaseRequestHandler::BaseRequestHandler; static llvm::StringLiteral getCommand() { return "locations"; } void operator()(const llvm::json::Object &request) const override; }; -class DisassembleRequestHandler : public RequestHandler { +class DisassembleRequestHandler : public BaseRequestHandler { public: - using RequestHandler::RequestHandler; + using BaseRequestHandler::BaseRequestHandler; static llvm::StringLiteral getCommand() { return "disassemble"; } void operator()(const llvm::json::Object &request) const override; }; -class ReadMemoryRequestHandler : public RequestHandler { +class ReadMemoryRequestHandler : public BaseRequestHandler { public: - using RequestHandler::RequestHandler; + using BaseRequestHandler::BaseRequestHandler; static llvm::StringLiteral getCommand() { return "readMemory"; } void operator()(const llvm::json::Object &request) const override; }; @@ -294,9 +358,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 BaseRequestHandler { public: - using RequestHandler::RequestHandler; + using BaseRequestHandler::BaseRequestHandler; 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..101f18ef5220a 100644 --- a/lldb/tools/lldb-dap/Handler/SourceRequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/SourceRequestHandler.cpp @@ -7,17 +7,15 @@ //===----------------------------------------------------------------------===// #include "DAP.h" -#include "EventHelper.h" -#include "JSONUtils.h" +#include "Handler/RequestHandler.h" #include "LLDBUtils.h" -#include "RequestHandler.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 { @@ -38,84 +36,30 @@ namespace lldb_dap { // "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<uint64_t>(source, "sourceReference") - .value_or( - GetInteger<uint64_t>(arguments, "sourceReference").value_or(0)); +llvm::Expected<protocol::SourceResponseBody> +SourceRequestHandler::Run(const protocol::SourceArguments &args) const { + const auto source = + args.source->sourceReference.value_or(args.sourceReference); + + if (!source) + return llvm::createStringError( + "invalid arguments, expected source.sourceReference to be set"); + + 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"); - 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"; - } + lldb::SBInstructionList insts = frame.GetSymbol().GetInstructions(dap.target); + lldb::SBStream stream; + insts.GetDescription(stream); - dap.SendJSON(llvm::json::Value(std::move(response))); + return protocol::SourceResponseBody{/*content=*/stream.GetData(), + /*mimeType=*/"text/x-lldb.disassembly"}; } } // namespace lldb_dap diff --git a/lldb/tools/lldb-dap/Protocol.cpp b/lldb/tools/lldb-dap/Protocol.cpp index b516c0cb19ebf..68cfa40580b31 100644 --- a/lldb/tools/lldb-dap/Protocol.cpp +++ b/lldb/tools/lldb-dap/Protocol.cpp @@ -287,5 +287,49 @@ json::Value toJSON(const Message &M) { return std::visit([](auto &M) { return toJSON(M); }, M); } +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); +} + +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); +} + } // namespace protocol } // namespace lldb_dap diff --git a/lldb/tools/lldb-dap/Protocol.h b/lldb/tools/lldb-dap/Protocol.h index a9a5532fa6bfa..a1219400fdea5 100644 --- a/lldb/tools/lldb-dap/Protocol.h +++ b/lldb/tools/lldb-dap/Protocol.h @@ -240,6 +240,137 @@ using Message = std::variant<Request, Response, Event>; bool fromJSON(const llvm::json::Value &, Message &, llvm::json::Path); llvm::json::Value toJSON(const Message &); +// 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; + + // unsupproted keys origin, sources, adapterData, checksums +}; +bool fromJSON(const llvm::json::Value &, Source &, llvm::json::Path); +llvm::json::Value toJSON(const Source &); + +// MARK: Requests + +// "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`.\nThis is provided for backward compatibility +// since old clients 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": "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 &); + } // namespace lldb_dap::protocol #endif _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits