llvmbot wrote:

<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-lldb

Author: John Harrison (ashgti)

<details>
<summary>Changes</summary>

Reverts llvm/llvm-project#<!-- -->148300

This is crashing in the aarch64 linux CI job. I'll revert it while I 
investigate why this is crashing.

---

Patch is 53.25 KiB, truncated to 20.00 KiB below, full version: 
https://github.com/llvm/llvm-project/pull/152155.diff


11 Files Affected:

- (modified) lldb/include/lldb/Host/JSONTransport.h (+29-86) 
- (modified) lldb/source/Host/common/JSONTransport.cpp (+114-53) 
- (modified) lldb/test/API/tools/lldb-dap/io/TestDAP_io.py (+10-17) 
- (modified) lldb/tools/lldb-dap/DAP.cpp (+63-65) 
- (modified) lldb/tools/lldb-dap/DAP.h (-7) 
- (modified) lldb/tools/lldb-dap/Transport.h (+1-1) 
- (modified) lldb/unittests/DAP/DAPTest.cpp (+6-5) 
- (modified) lldb/unittests/DAP/TestBase.cpp (+9-17) 
- (modified) lldb/unittests/DAP/TestBase.h (-20) 
- (modified) lldb/unittests/Host/JSONTransportTest.cpp (+58-186) 
- (modified) lldb/unittests/Protocol/ProtocolMCPServerTest.cpp (+57-78) 


``````````diff
diff --git a/lldb/include/lldb/Host/JSONTransport.h 
b/lldb/include/lldb/Host/JSONTransport.h
index 009f738b1a2e0..4087cdf2b42f7 100644
--- a/lldb/include/lldb/Host/JSONTransport.h
+++ b/lldb/include/lldb/Host/JSONTransport.h
@@ -13,16 +13,13 @@
 #ifndef LLDB_HOST_JSONTRANSPORT_H
 #define LLDB_HOST_JSONTRANSPORT_H
 
-#include "lldb/Host/MainLoopBase.h"
 #include "lldb/lldb-forward.h"
-#include "llvm/ADT/FunctionExtras.h"
 #include "llvm/ADT/StringRef.h"
 #include "llvm/Support/Error.h"
 #include "llvm/Support/FormatVariadic.h"
 #include "llvm/Support/JSON.h"
-#include <string>
+#include <chrono>
 #include <system_error>
-#include <vector>
 
 namespace lldb_private {
 
@@ -31,33 +28,27 @@ class TransportEOFError : public 
llvm::ErrorInfo<TransportEOFError> {
   static char ID;
 
   TransportEOFError() = default;
-  void log(llvm::raw_ostream &OS) const override { OS << "transport EOF"; }
+
+  void log(llvm::raw_ostream &OS) const override {
+    OS << "transport end of file reached";
+  }
   std::error_code convertToErrorCode() const override {
-    return std::make_error_code(std::errc::io_error);
+    return llvm::inconvertibleErrorCode();
   }
 };
 
-class TransportUnhandledContentsError
-    : public llvm::ErrorInfo<TransportUnhandledContentsError> {
+class TransportTimeoutError : public llvm::ErrorInfo<TransportTimeoutError> {
 public:
   static char ID;
 
-  explicit TransportUnhandledContentsError(std::string unhandled_contents)
-      : m_unhandled_contents(unhandled_contents) {}
+  TransportTimeoutError() = default;
 
   void log(llvm::raw_ostream &OS) const override {
-    OS << "transport EOF with unhandled contents " << m_unhandled_contents;
+    OS << "transport operation timed out";
   }
   std::error_code convertToErrorCode() const override {
-    return std::make_error_code(std::errc::bad_message);
+    return std::make_error_code(std::errc::timed_out);
   }
-
-  const std::string &getUnhandledContents() const {
-    return m_unhandled_contents;
-  }
-
-private:
-  std::string m_unhandled_contents;
 };
 
 class TransportInvalidError : public llvm::ErrorInfo<TransportInvalidError> {
@@ -77,11 +68,6 @@ class TransportInvalidError : public 
llvm::ErrorInfo<TransportInvalidError> {
 /// A transport class that uses JSON for communication.
 class JSONTransport {
 public:
-  using ReadHandleUP = MainLoopBase::ReadHandleUP;
-  template <typename T>
-  using Callback =
-      llvm::unique_function<void(MainLoopBase &, const llvm::Expected<T>)>;
-
   JSONTransport(lldb::IOObjectSP input, lldb::IOObjectSP output);
   virtual ~JSONTransport() = default;
 
@@ -97,69 +83,24 @@ class JSONTransport {
     return WriteImpl(message);
   }
 
-  /// Registers the transport with the MainLoop.
+  /// Reads the next message from the input stream.
   template <typename T>
-  llvm::Expected<ReadHandleUP> RegisterReadObject(MainLoopBase &loop,
-                                                  Callback<T> callback) {
-    Status error;
-    ReadHandleUP handle = loop.RegisterReadObject(
-        m_input,
-        [&](MainLoopBase &loop) {
-          char buffer[kReadBufferSize];
-          size_t len = sizeof(buffer);
-          if (llvm::Error error = m_input->Read(buffer, len).takeError()) {
-            callback(loop, std::move(error));
-            return;
-          }
-
-          if (len)
-            m_buffer.append(std::string(buffer, len));
-
-          // If the buffer has contents, try parsing any pending messages.
-          if (!m_buffer.empty()) {
-            llvm::Expected<std::vector<std::string>> messages = Parse();
-            if (llvm::Error error = messages.takeError()) {
-              callback(loop, std::move(error));
-              return;
-            }
-
-            for (const auto &message : *messages)
-              if constexpr (std::is_same<T, std::string>::value)
-                callback(loop, message);
-              else
-                callback(loop, llvm::json::parse<T>(message));
-          }
-
-          // On EOF, notify the callback after the remaining messages were
-          // handled.
-          if (len == 0) {
-            if (m_buffer.empty())
-              callback(loop, llvm::make_error<TransportEOFError>());
-            else
-              callback(loop, llvm::make_error<TransportUnhandledContentsError>(
-                                 m_buffer));
-          }
-        },
-        error);
-    if (error.Fail())
-      return error.takeError();
-    return handle;
+  llvm::Expected<T> Read(const std::chrono::microseconds &timeout) {
+    llvm::Expected<std::string> message = ReadImpl(timeout);
+    if (!message)
+      return message.takeError();
+    return llvm::json::parse<T>(/*JSON=*/*message);
   }
 
 protected:
-  template <typename... Ts> inline auto Logv(const char *Fmt, Ts &&...Vals) {
-    Log(llvm::formatv(Fmt, std::forward<Ts>(Vals)...).str());
-  }
   virtual void Log(llvm::StringRef message);
 
   virtual llvm::Error WriteImpl(const std::string &message) = 0;
-  virtual llvm::Expected<std::vector<std::string>> Parse() = 0;
+  virtual llvm::Expected<std::string>
+  ReadImpl(const std::chrono::microseconds &timeout) = 0;
 
   lldb::IOObjectSP m_input;
   lldb::IOObjectSP m_output;
-  std::string m_buffer;
-
-  static constexpr size_t kReadBufferSize = 1024;
 };
 
 /// A transport class for JSON with a HTTP header.
@@ -170,13 +111,14 @@ class HTTPDelimitedJSONTransport : public JSONTransport {
   virtual ~HTTPDelimitedJSONTransport() = default;
 
 protected:
-  llvm::Error WriteImpl(const std::string &message) override;
-  llvm::Expected<std::vector<std::string>> Parse() override;
-
-  static constexpr llvm::StringLiteral kHeaderContentLength = "Content-Length";
-  static constexpr llvm::StringLiteral kHeaderFieldSeparator = ":";
-  static constexpr llvm::StringLiteral kHeaderSeparator = "\r\n";
-  static constexpr llvm::StringLiteral kEndOfHeader = "\r\n\r\n";
+  virtual llvm::Error WriteImpl(const std::string &message) override;
+  virtual llvm::Expected<std::string>
+  ReadImpl(const std::chrono::microseconds &timeout) override;
+
+  // FIXME: Support any header.
+  static constexpr llvm::StringLiteral kHeaderContentLength =
+      "Content-Length: ";
+  static constexpr llvm::StringLiteral kHeaderSeparator = "\r\n\r\n";
 };
 
 /// A transport class for JSON RPC.
@@ -187,8 +129,9 @@ class JSONRPCTransport : public JSONTransport {
   virtual ~JSONRPCTransport() = default;
 
 protected:
-  llvm::Error WriteImpl(const std::string &message) override;
-  llvm::Expected<std::vector<std::string>> Parse() override;
+  virtual llvm::Error WriteImpl(const std::string &message) override;
+  virtual llvm::Expected<std::string>
+  ReadImpl(const std::chrono::microseconds &timeout) override;
 
   static constexpr llvm::StringLiteral kMessageSeparator = "\n";
 };
diff --git a/lldb/source/Host/common/JSONTransport.cpp 
b/lldb/source/Host/common/JSONTransport.cpp
index c3a3b06ecbced..546c12c8f7114 100644
--- a/lldb/source/Host/common/JSONTransport.cpp
+++ b/lldb/source/Host/common/JSONTransport.cpp
@@ -7,14 +7,17 @@
 
//===----------------------------------------------------------------------===//
 
 #include "lldb/Host/JSONTransport.h"
+#include "lldb/Utility/IOObject.h"
 #include "lldb/Utility/LLDBLog.h"
 #include "lldb/Utility/Log.h"
+#include "lldb/Utility/SelectHelper.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 <optional>
 #include <string>
 #include <utility>
 
@@ -22,6 +25,64 @@ using namespace llvm;
 using namespace lldb;
 using namespace lldb_private;
 
+/// 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::optional<std::chrono::microseconds> timeout = std::nullopt) {
+  if (!descriptor.IsValid())
+    return llvm::make_error<TransportInvalidError>();
+
+  bool timeout_supported = true;
+  // FIXME: SelectHelper does not work with NativeFile on Win32.
+#if _WIN32
+  timeout_supported = descriptor.GetFdType() == IOObject::eFDTypeSocket;
+#endif
+
+  if (timeout && timeout_supported) {
+    SelectHelper sh;
+    sh.SetTimeout(*timeout);
+    sh.FDSetRead(
+        reinterpret_cast<lldb::socket_t>(descriptor.GetWaitableHandle()));
+    Status status = sh.Select();
+    if (status.Fail()) {
+      // Convert timeouts into a specific error.
+      if (status.GetType() == lldb::eErrorTypePOSIX &&
+          status.GetError() == ETIMEDOUT)
+        return make_error<TransportTimeoutError>();
+      return status.takeError();
+    }
+  }
+
+  std::string data;
+  data.resize(length);
+  Status status = descriptor.Read(data.data(), length);
+  if (status.Fail())
+    return status.takeError();
+
+  // Read returns '' on EOF.
+  if (length == 0)
+    return make_error<TransportEOFError>();
+
+  // Return the actual number of bytes read.
+  return data.substr(0, length);
+}
+
+static Expected<std::string>
+ReadUntil(IOObject &descriptor, StringRef delimiter,
+          std::optional<std::chrono::microseconds> timeout = std::nullopt) {
+  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, timeout);
+    if (auto Err = next.takeError())
+      return std::move(Err);
+    buffer += *next;
+  }
+  return buffer.substr(0, buffer.size() - delimiter.size());
+}
+
 JSONTransport::JSONTransport(IOObjectSP input, IOObjectSP output)
     : m_input(std::move(input)), m_output(std::move(output)) {}
 
@@ -29,80 +90,80 @@ void JSONTransport::Log(llvm::StringRef message) {
   LLDB_LOG(GetLog(LLDBLog::Host), "{0}", message);
 }
 
-// Parses messages based on
-// https://microsoft.github.io/debug-adapter-protocol/overview#base-protocol
-Expected<std::vector<std::string>> HTTPDelimitedJSONTransport::Parse() {
-  std::vector<std::string> messages;
-  StringRef buffer = m_buffer;
-  while (buffer.contains(kEndOfHeader)) {
-    auto [headers, rest] = buffer.split(kEndOfHeader);
-    size_t content_length = 0;
-    // HTTP Headers are formatted like `<field-name> ':' [<field-value>]`.
-    for (const auto &header : llvm::split(headers, kHeaderSeparator)) {
-      auto [key, value] = header.split(kHeaderFieldSeparator);
-      // 'Content-Length' is the only meaningful key at the moment. Others are
-      // ignored.
-      if (!key.equals_insensitive(kHeaderContentLength))
-        continue;
-
-      value = value.trim();
-      if (!llvm::to_integer(value, content_length, 10))
-        return createStringError(std::errc::invalid_argument,
-                                 "invalid content length: %s",
-                                 value.str().c_str());
-    }
-
-    // Check if we have enough data.
-    if (content_length > rest.size())
-      break;
-
-    StringRef body = rest.take_front(content_length);
-    buffer = rest.drop_front(content_length);
-    messages.emplace_back(body.str());
-    Logv("--> {0}", body);
-  }
-
-  // Store the remainder of the buffer for the next read callback.
-  m_buffer = buffer.str();
+Expected<std::string>
+HTTPDelimitedJSONTransport::ReadImpl(const std::chrono::microseconds &timeout) 
{
+  if (!m_input || !m_input->IsValid())
+    return llvm::make_error<TransportInvalidError>();
 
-  return std::move(messages);
+  IOObject *input = m_input.get();
+  Expected<std::string> message_header =
+      ReadFull(*input, kHeaderContentLength.size(), timeout);
+  if (!message_header)
+    return message_header.takeError();
+  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 handleErrors(raw_length.takeError(),
+                        [&](const TransportEOFError &E) -> llvm::Error {
+                          return createStringError(
+                              "unexpected EOF while reading header separator");
+                        });
+
+  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 handleErrors(
+        raw_json.takeError(), [&](const TransportEOFError &E) -> llvm::Error {
+          return createStringError("unexpected EOF while reading JSON");
+        });
+
+  Log(llvm::formatv("--> {0}", *raw_json).str());
+
+  return raw_json;
 }
 
 Error HTTPDelimitedJSONTransport::WriteImpl(const std::string &message) {
   if (!m_output || !m_output->IsValid())
     return llvm::make_error<TransportInvalidError>();
 
-  Logv("<-- {0}", message);
+  Log(llvm::formatv("<-- {0}", message).str());
 
   std::string Output;
   raw_string_ostream OS(Output);
-  OS << kHeaderContentLength << kHeaderFieldSeparator << ' ' << 
message.length()
-     << kHeaderSeparator << kHeaderSeparator << message;
+  OS << kHeaderContentLength << message.length() << kHeaderSeparator << 
message;
   size_t num_bytes = Output.size();
   return m_output->Write(Output.data(), num_bytes).takeError();
 }
 
-Expected<std::vector<std::string>> JSONRPCTransport::Parse() {
-  std::vector<std::string> messages;
-  StringRef buf = m_buffer;
-  while (buf.contains(kMessageSeparator)) {
-    auto [raw_json, rest] = buf.split(kMessageSeparator);
-    buf = rest;
-    messages.emplace_back(raw_json.str());
-    Logv("--> {0}", raw_json);
-  }
+Expected<std::string>
+JSONRPCTransport::ReadImpl(const std::chrono::microseconds &timeout) {
+  if (!m_input || !m_input->IsValid())
+    return make_error<TransportInvalidError>();
+
+  IOObject *input = m_input.get();
+  Expected<std::string> raw_json =
+      ReadUntil(*input, kMessageSeparator, timeout);
+  if (!raw_json)
+    return raw_json.takeError();
 
-  // Store the remainder of the buffer for the next read callback.
-  m_buffer = buf.str();
+  Log(llvm::formatv("--> {0}", *raw_json).str());
 
-  return messages;
+  return *raw_json;
 }
 
 Error JSONRPCTransport::WriteImpl(const std::string &message) {
   if (!m_output || !m_output->IsValid())
     return llvm::make_error<TransportInvalidError>();
 
-  Logv("<-- {0}", message);
+  Log(llvm::formatv("<-- {0}", message).str());
 
   std::string Output;
   llvm::raw_string_ostream OS(Output);
@@ -112,5 +173,5 @@ Error JSONRPCTransport::WriteImpl(const std::string 
&message) {
 }
 
 char TransportEOFError::ID;
-char TransportUnhandledContentsError::ID;
+char TransportTimeoutError::ID;
 char TransportInvalidError::ID;
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 af5c62a8c4eb5..b72b98de412b4 100644
--- a/lldb/test/API/tools/lldb-dap/io/TestDAP_io.py
+++ b/lldb/test/API/tools/lldb-dap/io/TestDAP_io.py
@@ -8,9 +8,6 @@
 import lldbdap_testcase
 import dap_server
 
-EXIT_FAILURE = 1
-EXIT_SUCCESS = 0
-
 
 class TestDAP_io(lldbdap_testcase.DAPTestCaseBase):
     def launch(self):
@@ -44,44 +41,40 @@ def test_eof_immediately(self):
         """
         process = self.launch()
         process.stdin.close()
-        self.assertEqual(process.wait(timeout=5.0), EXIT_SUCCESS)
+        self.assertEqual(process.wait(timeout=5.0), 0)
 
     def test_invalid_header(self):
         """
-        lldb-dap returns a failure exit code when the input stream is closed
-        with a malformed request header.
+        lldb-dap handles invalid message headers.
         """
         process = self.launch()
-        process.stdin.write(b"not the correct message header")
+        process.stdin.write(b"not the corret message header")
         process.stdin.close()
-        self.assertEqual(process.wait(timeout=5.0), EXIT_FAILURE)
+        self.assertEqual(process.wait(timeout=5.0), 1)
 
     def test_partial_header(self):
         """
-        lldb-dap returns a failure exit code when the input stream is closed
-        with an incomplete message header is in the message buffer.
+        lldb-dap handles parital message headers.
         """
         process = self.launch()
         process.stdin.write(b"Content-Length: ")
         process.stdin.close()
-        self.assertEqual(process.wait(timeout=5.0), EXIT_FAILURE)
+        self.assertEqual(process.wait(timeout=5.0), 1)
 
     def test_incorrect_content_length(self):
         """
-        lldb-dap returns a failure exit code when reading malformed content
-        length headers.
+        lldb-dap handles malformed content length headers.
         """
         process = self.launch()
         process.stdin.write(b"Content-Length: abc")
         process.stdin.close()
-        self.assertEqual(process.wait(timeout=5.0), EXIT_FAILURE)
+        self.assertEqual(process.wait(timeout=5.0), 1)
 
     def test_partial_content_length(self):
         """
-        lldb-dap returns a failure exit code when the input stream is closed
-        with a partial message in the message buffer.
+        lldb-dap handles partial messages.
         """
         process = self.launch()
         process.stdin.write(b"Content-Length: 10\r\n\r\n{")
         process.stdin.close()
-        self.assertEqual(process.wait(timeout=5.0), EXIT_FAILURE)
+        self.assertEqual(process.wait(timeout=5.0), 1)
diff --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp
index 55c5c9347bf6f..cbd3b14463e25 100644
--- a/lldb/tools/lldb-dap/DAP.cpp
+++ b/lldb/tools/lldb-dap/DAP.cpp
@@ -23,14 +23,13 @@
 #include "Transport.h"
 #include "lldb/API/SBBreakpoint.h"
 #include "lldb/API/SBCommandInterpreter.h"
+#include "lldb/API/SBCommandReturnObject.h"
 #include "lldb/API/SBEvent.h"
 #include "lldb/API/SBLanguageRuntime.h"
 #include "lldb/API/SBListener.h"
 #include "lldb/API/SBProcess.h"
 #include "lldb/API/SBStream.h"
-#include "lldb/Host/JSONTransport.h"
-#include "lldb/Host/MainLoop.h"
-#include "lldb/Host/MainLoopBase.h"
+#include "lldb/Utility/IOObject.h"
 #include "lldb/Utility/Status.h"
 #include "lldb/lldb-defines.h"
 #include "lldb/lldb-enumerations.h"
@@ -53,7 +52,7 @@
 #include <cstdarg>
 #include <cstdint>
 #include <cstdio>
-#include <functional>
+#include <fstream>
 #include <future>
 #include <memory>
 #include <mutex>
@@ -920,8 +919,6 @@ llvm::Error DAP::Disconnect(bool terminateDebuggee) {
   SendTerminatedEvent();
 
   disconnecting = true;
-  m_loop.AddPendingCallback(
-      [](MainLoopBase &loop) { loop.RequestTermination(); });
 
   return ToError(error);
 }
@@ -952,74 +949,75 @@ static std::optional<T> getArgumentsIfRequest(const 
Message &pm,
   return args;
 }
 
-Status DAP::TransportHandler() {
-  llvm::set_thread_name(transport.GetClientName() + ".transport_handler");
-
-  auto cleanup = llvm::make_scope_exit([&]() {
-    // Ensure we're marked as disconnecting when the reader exits.
-    disconnecting = true;
-    m_queue_cv.notify_all();
-  });
+llvm::Error DAP::Loop() {
+  // Can't use \a std::future<llvm::Error> because it doesn't compile on
+  // Windows.
+  std::future<lldb::SBError> queue_reader =
+      std::async(std::launch::async, [&]() -> lldb::SBError {
+        llvm::set_thread_name(transport.GetClientName() + 
".transport_handler");
+        auto cleanup = llvm::make_scope_exit([&]() {
+          // Ensure we're marked as disconnecting when the reader exits.
+          disconnecting = true;
+          m_queue_cv.notify_all();
+        });
+
+        while (!disconnecting) {
+          llvm::Expected<Message> next =
+              transport.Read<protocol::Message>(std::chrono::seconds(1));
+          if (next.errorIsA<TransportEOFError>()) {
+            consumeError(next.takeError());
+            break;
+          }
 
-  Status status;
-  auto handle = transport.RegisterReadObject<protocol::Message>(
-      m_loop,
-      [&](MainLoopBase &loop, llvm::Expected<protocol::Message> message) {
-        if (message.errorIsA<TransportEOFError>()) {
-          llvm::consumeError(message.takeError());
-          loop.RequestTermination();
-          return;
-        }
+          // If the read timed out, contin...
[truncated]

``````````

</details>


https://github.com/llvm/llvm-project/pull/152155
_______________________________________________
lldb-commits mailing list
lldb-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to