[Lldb-commits] [lldb] 7790d69 - [lldb-dap] Refactoring IOStream into Transport handler. (#130026)

via lldb-commits lldb-commits at lists.llvm.org
Wed Mar 12 12:29:08 PDT 2025


Author: John Harrison
Date: 2025-03-12T12:29:05-07:00
New Revision: 7790d69cce048d7c81fceaf979fd2ec60e37476b

URL: https://github.com/llvm/llvm-project/commit/7790d69cce048d7c81fceaf979fd2ec60e37476b
DIFF: https://github.com/llvm/llvm-project/commit/7790d69cce048d7c81fceaf979fd2ec60e37476b.diff

LOG: [lldb-dap] Refactoring IOStream into Transport handler. (#130026)

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.

---------

Co-authored-by: Jonas Devlieghere <jonas at devlieghere.com>

Added: 
    lldb/tools/lldb-dap/Transport.cpp
    lldb/tools/lldb-dap/Transport.h

Modified: 
    lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py
    lldb/test/API/tools/lldb-dap/io/TestDAP_io.py
    lldb/tools/lldb-dap/CMakeLists.txt
    lldb/tools/lldb-dap/DAP.cpp
    lldb/tools/lldb-dap/DAP.h
    lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp
    lldb/tools/lldb-dap/lldb-dap.cpp

Removed: 
    lldb/tools/lldb-dap/IOStream.cpp
    lldb/tools/lldb-dap/IOStream.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/io/TestDAP_io.py b/lldb/test/API/tools/lldb-dap/io/TestDAP_io.py
index 04414cd7a3cdf..f05f876e57b49 100644
--- a/lldb/test/API/tools/lldb-dap/io/TestDAP_io.py
+++ b/lldb/test/API/tools/lldb-dap/io/TestDAP_io.py
@@ -2,6 +2,8 @@
 Test lldb-dap IO handling.
 """
 
+import sys
+
 from lldbsuite.test.decorators import *
 import lldbdap_testcase
 import dap_server
@@ -19,18 +21,18 @@ def cleanup():
             if process.poll() is None:
                 process.terminate()
                 process.wait()
-            stdout_data = process.stdout.read()
-            stderr_data = process.stderr.read()
-            print("========= STDOUT =========")
-            print(stdout_data)
-            print("========= END =========")
-            print("========= STDERR =========")
-            print(stderr_data)
-            print("========= END =========")
-            print("========= DEBUG ADAPTER PROTOCOL LOGS =========")
+            stdout_data = process.stdout.read().decode()
+            stderr_data = process.stderr.read().decode()
+            print("========= STDOUT =========", file=sys.stderr)
+            print(stdout_data, file=sys.stderr)
+            print("========= END =========", file=sys.stderr)
+            print("========= STDERR =========", file=sys.stderr)
+            print(stderr_data, file=sys.stderr)
+            print("========= END =========", file=sys.stderr)
+            print("========= DEBUG ADAPTER PROTOCOL LOGS =========", file=sys.stderr)
             with open(log_file_path, "r") as file:
-                print(file.read())
-            print("========= END =========")
+                print(file.read(), file=sys.stderr)
+            print("========= END =========", file=sys.stderr)
 
         # Execute the cleanup function during test case tear down.
         self.addTearDownHook(cleanup)
@@ -45,6 +47,15 @@ def test_eof_immediately(self):
         process.stdin.close()
         self.assertEqual(process.wait(timeout=5.0), 0)
 
+    def test_invalid_header(self):
+        """
+        lldb-dap handles invalid message headers.
+        """
+        process = self.launch()
+        process.stdin.write(b"not the corret message header")
+        process.stdin.close()
+        self.assertEqual(process.wait(timeout=5.0), 1)
+
     def test_partial_header(self):
         """
         lldb-dap handles parital message headers.
@@ -52,7 +63,7 @@ def test_partial_header(self):
         process = self.launch()
         process.stdin.write(b"Content-Length: ")
         process.stdin.close()
-        self.assertEqual(process.wait(timeout=5.0), 0)
+        self.assertEqual(process.wait(timeout=5.0), 1)
 
     def test_incorrect_content_length(self):
         """
@@ -61,13 +72,13 @@ def test_incorrect_content_length(self):
         process = self.launch()
         process.stdin.write(b"Content-Length: abc")
         process.stdin.close()
-        self.assertEqual(process.wait(timeout=5.0), 0)
+        self.assertEqual(process.wait(timeout=5.0), 1)
 
     def test_partial_content_length(self):
         """
         lldb-dap handles partial messages.
         """
         process = self.launch()
-        process.stdin.write(b"Content-Length: 10{")
+        process.stdin.write(b"Content-Length: 10\r\n\r\n{")
         process.stdin.close()
-        self.assertEqual(process.wait(timeout=5.0), 0)
+        self.assertEqual(process.wait(timeout=5.0), 1)

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 edd3b31be8ff7..4080e2c211035 100644
--- a/lldb/tools/lldb-dap/DAP.cpp
+++ b/lldb/tools/lldb-dap/DAP.cpp
@@ -12,6 +12,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"
@@ -63,11 +64,10 @@ const char DEV_NULL[] = "/dev/null";
 
 namespace lldb_dap {
 
-DAP::DAP(llvm::StringRef client_name, llvm::StringRef path, std::ofstream *log,
-         lldb::IOObjectSP input, lldb::IOObjectSP output, ReplMode repl_mode,
-         std::vector<std::string> pre_init_commands)
-    : client_name(client_name), debug_adapter_path(path), log(log),
-      input(std::move(input)), output(std::move(output)),
+DAP::DAP(llvm::StringRef path, std::ofstream *log,
+         const ReplMode default_repl_mode,
+         std::vector<std::string> pre_init_commands, Transport &transport)
+    : debug_adapter_path(path), log(log), transport(transport),
       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),
@@ -78,7 +78,7 @@ DAP::DAP(llvm::StringRef client_name, llvm::StringRef path, std::ofstream *log,
       configuration_done_sent(false), waiting_for_run_in_terminal(false),
       progress_event_reporter(
           [&](const ProgressEvent &event) { SendJSON(event.ToJSON()); }),
-      reverse_request_seq(0), repl_mode(repl_mode) {}
+      reverse_request_seq(0), repl_mode(default_repl_mode) {}
 
 DAP::~DAP() = default;
 
@@ -221,52 +221,21 @@ 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);
-
-  DAP_LOG(log, "({0}) <-- {1}", client_name, json_str);
-}
-
-// 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;
-
-  DAP_LOG(log, "({0}) --> {1}", client_name, json_str);
-  return json_str;
+  // FIXME: Instead of parsing the output message from JSON, pass the `Message`
+  // as parameter to `SendJSON`.
+  protocol::Message message;
+  llvm::json::Path::Root root;
+  if (!fromJSON(json, message, root)) {
+    DAP_LOG_ERROR(log, root.getError(), "({1}) encoding failed: {0}",
+                  transport.GetClientName());
+    return;
+  }
+  if (llvm::Error err = transport.Write(message))
+    DAP_LOG_ERROR(log, std::move(err), "({1}) write failed: {0}",
+                  transport.GetClientName());
 }
 
 // "OutputEvent": {
@@ -693,29 +662,10 @@ 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) {
-    DAP_LOG_ERROR(log, json_value.takeError(),
-                  "({1}) failed to parse JSON: {0}", client_name);
-    return PacketStatus::JSONMalformed;
-  }
-
-  llvm::json::Object *object_ptr = json_value->getAsObject();
-  if (!object_ptr) {
-    DAP_LOG(log, "({0}) error: json packet isn't a object", client_name);
-    return PacketStatus::JSONNotObject;
-  }
-  object = *object_ptr;
-  return PacketStatus::Success;
-}
-
-bool DAP::HandleObject(const llvm::json::Object &object) {
+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");
@@ -726,7 +676,8 @@ bool DAP::HandleObject(const llvm::json::Object &object) {
       return true; // Success
     }
 
-    DAP_LOG(log, "({0}) error: unhandled command '{1}'", client_name, command);
+    DAP_LOG(log, "({0}) error: unhandled command '{1}'",
+            transport.GetClientName(), command);
     return false; // Fail
   }
 
@@ -749,9 +700,8 @@ bool DAP::HandleObject(const llvm::json::Object &object) {
     // 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")) {
+      if (auto *B = object.get("body"))
         Result = std::move(*B);
-      }
       (*response_handler)(Result);
     } else {
       llvm::StringRef message = GetString(object, "message");
@@ -818,19 +768,15 @@ llvm::Error DAP::Loop() {
     StopEventHandlers();
   });
   while (!disconnecting) {
-    llvm::json::Object object;
-    lldb_dap::PacketStatus status = GetNextObject(object);
+    llvm::Expected<std::optional<protocol::Message>> next = transport.Read();
+    if (!next)
+      return next.takeError();
 
-    if (status == lldb_dap::PacketStatus::EndOfFile) {
+    // nullopt on EOF
+    if (!*next)
       break;
-    }
-
-    if (status != lldb_dap::PacketStatus::Success) {
-      return llvm::createStringError(llvm::inconvertibleErrorCode(),
-                                     "failed to send packet");
-    }
 
-    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 3ff1992b61f5b..db3473b7c7027 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>
@@ -145,11 +145,9 @@ struct SendEventRequestHandler : public lldb::SBCommandPluginInterface {
 };
 
 struct DAP {
-  llvm::StringRef client_name;
   llvm::StringRef debug_adapter_path;
   std::ofstream *log;
-  InputStream input;
-  OutputStream output;
+  Transport &transport;
   lldb::SBFile in;
   OutputRedirector out;
   OutputRedirector err;
@@ -210,12 +208,30 @@ struct DAP {
   // will contain that expression.
   std::string last_nonempty_var_expression;
 
-  DAP(llvm::StringRef client_name, llvm::StringRef path, std::ofstream *log,
-      lldb::IOObjectSP input, lldb::IOObjectSP output, ReplMode repl_mode,
-      std::vector<std::string> pre_init_commands);
+  /// Creates a new DAP sessions.
+  ///
+  /// \param[in] path
+  ///     Path to the lldb-dap binary.
+  /// \param[in] log
+  ///     Log file stream, if configured.
+  /// \param[in] default_repl_mode
+  ///     Default repl mode behavior, as configured by the binary.
+  /// \param[in] pre_init_commands
+  ///     LLDB commands to execute as soon as the debugger instance is allocaed.
+  /// \param[in] transport
+  ///     Transport for this debug session.
+  DAP(llvm::StringRef path, std::ofstream *log,
+      const ReplMode default_repl_mode,
+      std::vector<std::string> pre_init_commands, Transport &transport);
+
   ~DAP();
+
+  /// DAP is not copyable.
+  /// @{
   DAP(const DAP &rhs) = delete;
   void operator=(const DAP &rhs) = delete;
+  /// @}
+
   ExceptionBreakpoint *GetExceptionBreakpoint(const std::string &filter);
   ExceptionBreakpoint *GetExceptionBreakpoint(const lldb::break_id_t bp_id);
 
@@ -233,8 +249,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 +321,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 +395,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/Handler/InitializeRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp
index 7b7d8d5cedaa6..3262b70042a0e 100644
--- a/lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp
@@ -111,7 +111,7 @@ void ProgressEventThreadFunction(DAP &dap) {
 // them prevent multiple threads from writing simultaneously so no locking
 // is required.
 static void EventThreadFunction(DAP &dap) {
-  llvm::set_thread_name(dap.client_name + ".event_handler");
+  llvm::set_thread_name(dap.transport.GetClientName() + ".event_handler");
   lldb::SBEvent event;
   lldb::SBListener listener = dap.debugger.GetListener();
   dap.broadcaster.AddListener(listener, eBroadcastBitStopEventThread);

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/Transport.cpp b/lldb/tools/lldb-dap/Transport.cpp
new file mode 100644
index 0000000000000..db2d7228d3fb7
--- /dev/null
+++ b/lldb/tools/lldb-dap/Transport.cpp
@@ -0,0 +1,126 @@
+//===-- 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 "DAPLog.h"
+#include "Protocol.h"
+#include "lldb/Utility/IOObject.h"
+#include "lldb/Utility/Status.h"
+#include "lldb/lldb-forward.h"
+#include "llvm/ADT/StringExtras.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Support/raw_ostream.h"
+#include <string>
+#include <utility>
+
+using namespace llvm;
+using namespace lldb;
+using namespace lldb_private;
+using namespace lldb_dap;
+using namespace lldb_dap::protocol;
+
+/// ReadFull attempts to read the specified number of bytes. If EOF is
+/// encountered, an empty string is returned.
+static Expected<std::string> ReadFull(IOObject &descriptor, size_t length) {
+  std::string data;
+  data.resize(length);
+  auto status = descriptor.Read(data.data(), length);
+  if (status.Fail())
+    return status.takeError();
+  // Return the actual number of bytes read.
+  return data.substr(0, length);
+}
+
+static Expected<std::string> ReadUntil(IOObject &descriptor,
+                                       StringRef delimiter) {
+  std::string buffer;
+  buffer.reserve(delimiter.size() + 1);
+  while (!llvm::StringRef(buffer).ends_with(delimiter)) {
+    Expected<std::string> next =
+        ReadFull(descriptor, buffer.empty() ? delimiter.size() : 1);
+    if (auto Err = next.takeError())
+      return std::move(Err);
+    // Return "" if EOF is encountered.
+    if (next->empty())
+      return "";
+    buffer += *next;
+  }
+  return buffer.substr(0, buffer.size() - delimiter.size());
+}
+
+/// DAP message format
+/// ```
+/// Content-Length: (?<length>\d+)\r\n\r\n(?<content>.{\k<length>})
+/// ```
+static constexpr StringLiteral kHeaderContentLength = "Content-Length: ";
+static constexpr StringLiteral kHeaderSeparator = "\r\n\r\n";
+
+namespace lldb_dap {
+
+Transport::Transport(StringRef client_name, std::ofstream *log,
+                     IOObjectSP input, IOObjectSP output)
+    : m_client_name(client_name), m_log(log), m_input(std::move(input)),
+      m_output(std::move(output)) {}
+
+Expected<std::optional<Message>> Transport::Read() {
+  if (!m_input || !m_input->IsValid())
+    return createStringError("transport output is closed");
+
+  IOObject *input = m_input.get();
+  Expected<std::string> message_header =
+      ReadFull(*input, kHeaderContentLength.size());
+  if (!message_header)
+    return message_header.takeError();
+  // '' returned on EOF.
+  if (message_header->empty())
+    return std::nullopt;
+  if (*message_header != kHeaderContentLength)
+    return createStringError(formatv("expected '{0}' and got '{1}'",
+                                     kHeaderContentLength, *message_header)
+                                 .str());
+
+  Expected<std::string> raw_length = ReadUntil(*input, kHeaderSeparator);
+  if (!raw_length)
+    return raw_length.takeError();
+  if (raw_length->empty())
+    return createStringError("unexpected EOF parsing DAP header");
+
+  size_t length;
+  if (!to_integer(*raw_length, length))
+    return createStringError(
+        formatv("invalid content length {0}", *raw_length).str());
+
+  Expected<std::string> raw_json = ReadFull(*input, length);
+  if (!raw_json)
+    return raw_json.takeError();
+  // If we got less than the expected number of bytes then we hit EOF.
+  if (raw_json->length() != length)
+    return createStringError("unexpected EOF parse DAP message body");
+
+  DAP_LOG(m_log, "<-- ({0}) {1}", m_client_name, *raw_json);
+
+  return json::parse<Message>(*raw_json);
+}
+
+Error Transport::Write(const Message &message) {
+  if (!m_output || !m_output->IsValid())
+    return createStringError("transport output is closed");
+
+  std::string json = formatv("{0}", toJSON(message)).str();
+
+  DAP_LOG(m_log, "--> ({0}) {1}", m_client_name, json);
+
+  std::string Output;
+  raw_string_ostream OS(Output);
+  OS << kHeaderContentLength << json.length() << kHeaderSeparator << json;
+  size_t num_bytes = Output.size();
+  return m_output->Write(Output.data(), num_bytes).takeError();
+}
+
+} // 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..013a6c98af1ce
--- /dev/null
+++ b/lldb/tools/lldb-dap/Transport.h
@@ -0,0 +1,61 @@
+//===-- 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/lldb-forward.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+#include <fstream>
+#include <optional>
+
+namespace lldb_dap {
+
+/// A transport class that performs the Debug Adapter Protocol communication
+/// with the client.
+class Transport {
+public:
+  Transport(llvm::StringRef client_name, std::ofstream *log,
+            lldb::IOObjectSP input, lldb::IOObjectSP output);
+  ~Transport() = default;
+
+  /// Transport is not copyable.
+  /// @{
+  Transport(const Transport &rhs) = delete;
+  void operator=(const Transport &rhs) = delete;
+  /// @}
+
+  /// Writes a Debug Adater Protocol message to the output stream.
+  llvm::Error Write(const protocol::Message &M);
+
+  /// Reads the next Debug Adater Protocol message from the input stream.
+  ///
+  /// \returns Returns the next protocol message or nullopt if EOF is reached.
+  llvm::Expected<std::optional<protocol::Message>> Read();
+
+  /// Returns the name of this transport client, for example `stdin/stdout` or
+  /// `client_1`.
+  llvm::StringRef GetClientName() { return m_client_name; }
+
+private:
+  llvm::StringRef m_client_name;
+  std::ofstream *m_log;
+  lldb::IOObjectSP m_input;
+  lldb::IOObjectSP m_output;
+};
+
+} // namespace lldb_dap
+
+#endif

diff  --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp
index ab40b75e5425d..ca8b548632ff7 100644
--- a/lldb/tools/lldb-dap/lldb-dap.cpp
+++ b/lldb/tools/lldb-dap/lldb-dap.cpp
@@ -11,6 +11,7 @@
 #include "EventHelper.h"
 #include "Handler/RequestHandler.h"
 #include "RunInTerminal.h"
+#include "Transport.h"
 #include "lldb/API/SBDebugger.h"
 #include "lldb/API/SBStream.h"
 #include "lldb/Host/Config.h"
@@ -325,8 +326,9 @@ serveConnection(const Socket::SocketProtocol &protocol, const std::string &name,
     std::thread client([=, &dap_sessions_condition, &dap_sessions_mutex,
                         &dap_sessions]() {
       llvm::set_thread_name(client_name + ".runloop");
-      DAP dap = DAP(client_name, program_path, log, io, io, default_repl_mode,
-                    pre_init_commands);
+      Transport transport(client_name, log, io, io);
+      DAP dap(program_path, log, default_repl_mode, pre_init_commands,
+              transport);
 
       if (auto Err = dap.ConfigureIO()) {
         llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(),
@@ -343,7 +345,8 @@ serveConnection(const Socket::SocketProtocol &protocol, const std::string &name,
 
       if (auto Err = dap.Loop()) {
         llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(),
-                                    "DAP session error: ");
+                                    "DAP session (" + client_name +
+                                        ") error: ");
       }
 
       DAP_LOG(log, "({0}) client disconnected", client_name);
@@ -374,7 +377,7 @@ serveConnection(const Socket::SocketProtocol &protocol, const std::string &name,
       auto error = dap->Disconnect();
       if (error.Fail()) {
         client_failed = true;
-        llvm::errs() << "DAP client " << dap->client_name
+        llvm::errs() << "DAP client " << dap->transport.GetClientName()
                      << " disconnected failed: " << error.GetCString() << "\n";
       }
       // Close the socket to ensure the DAP::Loop read finishes.
@@ -498,9 +501,8 @@ int main(int argc, char *argv[]) {
   // Create a memory monitor. This can return nullptr if the host platform is
   // not supported.
   std::unique_ptr<lldb_private::MemoryMonitor> memory_monitor =
-      lldb_private::MemoryMonitor::Create([&]() {
-        if (log)
-          *log << "memory pressure detected\n";
+      lldb_private::MemoryMonitor::Create([log = log.get()]() {
+        DAP_LOG(log, "memory pressure detected");
         lldb::SBDebugger::MemoryPressureDetected();
       });
 
@@ -565,8 +567,10 @@ int main(int argc, char *argv[]) {
   lldb::IOObjectSP output = std::make_shared<NativeFile>(
       stdout_fd, File::eOpenOptionWriteOnly, false);
 
-  DAP dap = DAP("stdin/stdout", program_path, log.get(), std::move(input),
-                std::move(output), default_repl_mode, pre_init_commands);
+  constexpr llvm::StringLiteral client_name = "stdin/stdout";
+  Transport transport(client_name, log.get(), input, output);
+  DAP dap(program_path, log.get(), default_repl_mode, pre_init_commands,
+          transport);
 
   // stdout/stderr redirection to the IDE's console
   if (auto Err = dap.ConfigureIO(stdout, stderr)) {
@@ -583,7 +587,7 @@ int main(int argc, char *argv[]) {
 
   if (auto Err = dap.Loop()) {
     llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(),
-                                "DAP session error: ");
+                                "DAP session (" + client_name + ") error: ");
     return EXIT_FAILURE;
   }
   return EXIT_SUCCESS;


        


More information about the lldb-commits mailing list