llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT--> @llvm/pr-subscribers-lldb Author: John Harrison (ashgti) <details> <summary>Changes</summary> This removes the previous 'OutputStream' and 'InputStream' objects and the new 'Transport' class takes on the responsibility of serializing and encoding messages. The new Protocol.h file has a dedicated namespace for POD style structures to represent the Debug Adapter Protocol messages. This should help unblock supporting 'cancel' requests by allowing us to have more detailed type information. --- Patch is 34.88 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/128972.diff 12 Files Affected: - (modified) lldb/tools/lldb-dap/CMakeLists.txt (+3-1) - (modified) lldb/tools/lldb-dap/DAP.cpp (+32-103) - (modified) lldb/tools/lldb-dap/DAP.h (+8-9) - (added) lldb/tools/lldb-dap/DAPLog.cpp (+20) - (added) lldb/tools/lldb-dap/DAPLog.h (+33) - (removed) lldb/tools/lldb-dap/IOStream.cpp (-65) - (removed) lldb/tools/lldb-dap/IOStream.h (-42) - (added) lldb/tools/lldb-dap/Protocol.cpp (+191) - (added) lldb/tools/lldb-dap/Protocol.h (+192) - (added) lldb/tools/lldb-dap/Transport.cpp (+110) - (added) lldb/tools/lldb-dap/Transport.h (+48) - (modified) lldb/tools/lldb-dap/lldb-dap.cpp (+30-8) ``````````diff diff --git a/lldb/tools/lldb-dap/CMakeLists.txt b/lldb/tools/lldb-dap/CMakeLists.txt index 8b3c520ec4360..cba20a53edcb3 100644 --- a/lldb/tools/lldb-dap/CMakeLists.txt +++ b/lldb/tools/lldb-dap/CMakeLists.txt @@ -23,18 +23,20 @@ add_lldb_tool(lldb-dap Breakpoint.cpp BreakpointBase.cpp DAP.cpp + DAPLog.cpp EventHelper.cpp ExceptionBreakpoint.cpp FifoFiles.cpp FunctionBreakpoint.cpp InstructionBreakpoint.cpp - IOStream.cpp JSONUtils.cpp LLDBUtils.cpp OutputRedirector.cpp ProgressEvent.cpp RunInTerminal.cpp SourceBreakpoint.cpp + Transport.cpp + Protocol.cpp Watchpoint.cpp Handler/ResponseHandler.cpp diff --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp index cd53e2aca3fb6..e23d9f4e910cc 100644 --- a/lldb/tools/lldb-dap/DAP.cpp +++ b/lldb/tools/lldb-dap/DAP.cpp @@ -7,10 +7,12 @@ //===----------------------------------------------------------------------===// #include "DAP.h" +#include "DAPLog.h" #include "Handler/ResponseHandler.h" #include "JSONUtils.h" #include "LLDBUtils.h" #include "OutputRedirector.h" +#include "Protocol.h" #include "lldb/API/SBBreakpoint.h" #include "lldb/API/SBCommandInterpreter.h" #include "lldb/API/SBCommandReturnObject.h" @@ -29,6 +31,7 @@ #include "llvm/Support/Error.h" #include "llvm/Support/ErrorHandling.h" #include "llvm/Support/FormatVariadic.h" +#include "llvm/Support/JSON.h" #include "llvm/Support/raw_ostream.h" #include <algorithm> #include <cassert> @@ -50,6 +53,7 @@ #endif using namespace lldb_dap; +using namespace lldb_private; namespace { #ifdef _WIN32 @@ -61,11 +65,11 @@ const char DEV_NULL[] = "/dev/null"; namespace lldb_dap { -DAP::DAP(std::string name, llvm::StringRef path, std::ofstream *log, +DAP::DAP(llvm::StringRef 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_adaptor_path(path), log(log), - input(std::move(input)), output(std::move(output)), + : name(name), debug_adaptor_path(path), log(log), + transport(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 +241,22 @@ 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::ProtocolMessage 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 json_str; + auto status = transport.Write(M); + if (status.Fail() && log) + *log << "transport failure: " << status.AsCString() << "\n"; } // "OutputEvent": { @@ -720,40 +681,13 @@ 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; +llvm::Expected<protocol::ProtocolMessage> DAP::GetNextObject() { + return transport.Read(); } -bool DAP::HandleObject(const llvm::json::Object &object) { +bool DAP::HandleObject(const protocol::ProtocolMessage &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"); @@ -764,9 +698,8 @@ bool DAP::HandleObject(const llvm::json::Object &object) { return true; // Success } - if (log) - *log << "error: unhandled command \"" << command.data() << "\"" - << std::endl; + LLDB_LOG(GetLog(DAPLog::Protocol), "Unhandled command {0}", command); + return false; // Fail } @@ -805,6 +738,8 @@ bool DAP::HandleObject(const llvm::json::Object &object) { return true; } + LLDB_LOG(GetLog(DAPLog::Protocol), "Unsupported protocol message"); + return false; } @@ -858,19 +793,13 @@ lldb::SBError DAP::Disconnect(bool terminateDebuggee) { llvm::Error DAP::Loop() { 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; - } + auto next = GetNextObject(); - if (status != lldb_dap::PacketStatus::Success) { - return llvm::createStringError(llvm::inconvertibleErrorCode(), - "failed to send packet"); + if (!next) { + return next.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 a7c7e5d9bbc19..aa86064e188a9 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,6 +40,7 @@ #include "llvm/Support/Error.h" #include "llvm/Support/JSON.h" #include "llvm/Support/Threading.h" +#include <fstream> #include <map> #include <memory> #include <mutex> @@ -145,11 +147,10 @@ struct SendEventRequestHandler : public lldb::SBCommandPluginInterface { }; struct DAP { - std::string name; + llvm::StringRef name; llvm::StringRef debug_adaptor_path; std::ofstream *log; - InputStream input; - OutputStream output; + Transport transport; lldb::SBFile in; OutputRedirector out; OutputRedirector err; @@ -210,7 +211,7 @@ struct DAP { // will contain that expression. std::string last_nonempty_var_expression; - DAP(std::string name, llvm::StringRef path, std::ofstream *log, + DAP(llvm::StringRef name, llvm::StringRef path, std::ofstream *log, lldb::IOObjectSP input, lldb::IOObjectSP output, ReplMode repl_mode, std::vector<std::string> pre_init_commands); ~DAP(); @@ -233,8 +234,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 +306,8 @@ 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); + llvm::Expected<protocol::ProtocolMessage> GetNextObject(); + bool HandleObject(const protocol::ProtocolMessage &); /// Disconnect the DAP session. lldb::SBError Disconnect(); diff --git a/lldb/tools/lldb-dap/DAPLog.cpp b/lldb/tools/lldb-dap/DAPLog.cpp new file mode 100644 index 0000000000000..73d256098d69d --- /dev/null +++ b/lldb/tools/lldb-dap/DAPLog.cpp @@ -0,0 +1,20 @@ +#include "DAPLog.h" + +using namespace lldb_private; +using namespace lldb_dap; + +static constexpr Log::Category g_categories[] = { + {{"transport"}, {"log DAP transport"}, DAPLog::Transport}, + {{"protocol"}, {"log protocol handling"}, DAPLog::Protocol}, +}; + +static Log::Channel g_log_channel(g_categories, + DAPLog::Transport | DAPLog::Protocol); + +template <> Log::Channel &lldb_private::LogChannelFor<DAPLog>() { + return g_log_channel; +} + +void lldb_dap::InitializeDAPChannel() { + Log::Register("lldb-dap", g_log_channel); +} diff --git a/lldb/tools/lldb-dap/DAPLog.h b/lldb/tools/lldb-dap/DAPLog.h new file mode 100644 index 0000000000000..c22cb53655dbf --- /dev/null +++ b/lldb/tools/lldb-dap/DAPLog.h @@ -0,0 +1,33 @@ +//===-- DAPLog.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_UTILITY_LLDBLOG_H +#define LLDB_UTILITY_LLDBLOG_H + +#include "lldb/Utility/Log.h" +#include "llvm/ADT/BitmaskEnum.h" + +namespace lldb_dap { + +enum class DAPLog : lldb_private::Log::MaskType { + Transport = lldb_private::Log::ChannelFlag<0>, + Protocol = lldb_private::Log::ChannelFlag<1>, + LLVM_MARK_AS_BITMASK_ENUM(Protocol), +}; + +LLVM_ENABLE_BITMASK_ENUMS_IN_NAMESPACE(); + +void InitializeDAPChannel(); + +} // end namespace lldb_dap + +namespace lldb_private { +template <> lldb_private::Log::Channel &LogChannelFor<lldb_dap::DAPLog>(); +} // namespace lldb_private + +#endif diff --git a/lldb/tools/lldb-dap/IOStream.cpp b/lldb/tools/lldb-dap/IOStream.cpp deleted file mode 100644 index c6f1bfaf3b799..0000000000000 --- a/lldb/tools/lldb-dap/IOStream.cpp +++ /dev/null @@ -1,65 +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; - return true; -} - -bool InputStream::read_line(std::ofstream *log, std::string &line) { - line.clear(); - while (true) { - if (!read_full(log, 1, line)) - return false; - - 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 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/Protocol.cpp b/lldb/tools/lldb-dap/Protocol.cpp new file mode 100644 index 0000000000000..6f8687ddb5a59 --- /dev/null +++ b/lldb/tools/lldb-dap/Protocol.cpp @@ -0,0 +1,191 @@ +//===-- Protocol.cpp --------------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "Protocol.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/ADT/StringSwitch.h" +#include "llvm/Support/ErrorHandling.h" +#include "llvm/Support/JSON.h" +#include <optional> +#include <utility> + +namespace llvm { +namespace json { +bool fromJSON(const llvm::json::Value &Params, llvm::json::Value &V, + llvm::json::Path P) { + V = std::move(Params); + return true; +} +} // namespace json +} // namespace llvm + +namespace lldb_dap { +namespace protocol { + +enum class MessageType { request, response, event }; + +bool fromJSON(const llvm::json::Value &Params, MessageType &M, + llvm::json::Path P) { + auto rawType = Params.getAsString(); + if (!rawType) { + P.report("expected a string"); + return false; + } + std::optional<MessageType> type = + llvm::StringSwitch<std::optional<MessageType>>(*rawType) + .Case("request", MessageType::request) + .Case("response", MessageType::response) + .Case("event", MessageType::event) + .Default(std::nullopt); + if (!type) { + P.report("unexpected value"); + return false; + } + M = *type; + return true; +} + +llvm::json::Value toJSON(const Request &R) { + llvm::json::Object Result{ + {"type", "request"}, + {"seq", R.seq}, + {"command", R.command}, + }; + if (R.rawArguments) + Result.insert({"arguments", R.rawArguments}); + return std::move(Result); +} + +bool fromJSON(llvm::json::Value const &Params, Request &R, llvm::json::Path P) { + llvm::json::ObjectMapper O(Params, P); + MessageType type; + if (!O.map("type", type)) { + return false; + } + if (type != MessageType::request) { + P.field("type").report("expected to be 'request'"); + return false; + } + + return O && O.map("command", R.command) && O.map("seq", R.seq) && + O.map("arguments", R.rawArguments); +} + +llvm::json::Value toJSON(const Response &R) { + llvm::json::Object Result{{"type", "response"}, + {"req", 0}, + {"command", R.command}, + {"request_seq", R.request_seq}, + {"success", R.success}}; + + if (R.message) + Result.insert({"message", R.message}); + if (R.rawBody) + Result.insert({"body", R.rawBody}); + + return std::move(Result); +} + +bool fromJSON(llvm::json::Value const &Params, Response &R, + llvm::json::Path P) { + llvm::json::ObjectMapper O(Params, P); + MessageType type; + if (!O.map("type", type)) { + return false; + } + if (type != MessageType::response) { + P.field("type").report("expected to be 'response'"); + return false; + } + return O && O.map("command", R.command) && + O.map("request_seq", R.request_seq) && O.map("success", R.success) && + O.mapOptional("message", R.message) && + O.mapOptional("body", R.rawBody); +} + +llvm::json::Value toJSON(const Event &E) { + llvm::json::Object Result{ + {"type", "event"}, + {"seq", 0}, + {"event", E.event}, + }; + if (E.rawBody) + Result.insert({"body", E.rawBody}); + if (E.statistics) + Result.insert({"statistics", E.statistics}); + return std::move(Result); +} + +bool fromJSON(llvm::json::Value const &Params, Event &E, llvm::json::Path P) { + llvm::json::ObjectMapper O(Params, P); + MessageType type; + if (!O.map("type", type)) { + return false; + } + if (type != MessageType::event) { + P.field("type").report("expected to be 'event'"); + return false; + } + + return O && O.map("event", E.event) && O.mapOptional("body", E.rawBody) && + O.mapOptional("statistics", E.statistics); +} + +bool fromJSON(const llvm::json::Value &Params, ProtocolMessage &PM, + llvm::json::Path P) { + llvm::json::ObjectMapper O(Params, P); + if (!O) + return false; + + MessageType type; + if (!O.map("type", type)) + return false; + + switch (type) { + case MessageType::request: { + Request req; + if (!fromJSON(Params, req, P)) { + return false; + } + PM = std::move(req); + return true; + } + case MessageType::response: { + Response resp; + if (!fromJSON(Params, resp, P)) { + return false; + } + PM = std::move(resp); + return true; + } + case MessageType::event: + Event evt; + if (!fromJSON(Params, evt, P)) { + return false; + } + PM = std::move(evt); + return true; + } + llvm_unreachable("Unsupported protocol message"); +} + +llvm::json::Value toJSON(const ProtocolMessage &PM) { + if (auto const *Req = std::get_if<Request>(&PM)) { + return toJSON(*Req); + } + if (auto ... [truncated] `````````` </details> https://github.com/llvm/llvm-project/pull/128972 _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits