Skip to content

[lldb] Move Transport class into lldb_private (NFC) #143806

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 12, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 131 additions & 0 deletions lldb/include/lldb/Host/JSONTransport.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
//===-- JSONTransport.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
//
//===----------------------------------------------------------------------===//
//
// Transport layer for encoding and decoding JSON protocol messages.
//
//===----------------------------------------------------------------------===//

#ifndef LLDB_HOST_JSONTRANSPORT_H
#define LLDB_HOST_JSONTRANSPORT_H

#include "lldb/lldb-forward.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/FormatVariadic.h"
#include "llvm/Support/JSON.h"
#include <chrono>
#include <system_error>

namespace lldb_private {

class TransportEOFError : public llvm::ErrorInfo<TransportEOFError> {
public:
static char ID;

TransportEOFError() = default;

void log(llvm::raw_ostream &OS) const override {
OS << "transport end of file reached";
}
std::error_code convertToErrorCode() const override {
return llvm::inconvertibleErrorCode();
}
};

class TransportTimeoutError : public llvm::ErrorInfo<TransportTimeoutError> {
public:
static char ID;

TransportTimeoutError() = default;

void log(llvm::raw_ostream &OS) const override {
OS << "transport operation timed out";
}
std::error_code convertToErrorCode() const override {
return std::make_error_code(std::errc::timed_out);
}
};

class TransportClosedError : public llvm::ErrorInfo<TransportClosedError> {
public:
static char ID;

TransportClosedError() = default;

void log(llvm::raw_ostream &OS) const override {
OS << "transport is closed";
}
std::error_code convertToErrorCode() const override {
return llvm::inconvertibleErrorCode();
}
};

/// A transport class that uses JSON for communication.
class JSONTransport {
public:
JSONTransport(llvm::StringRef client_name, lldb::IOObjectSP input,
lldb::IOObjectSP output);
virtual ~JSONTransport() = default;

/// Transport is not copyable.
/// @{
JSONTransport(const JSONTransport &rhs) = delete;
void operator=(const JSONTransport &rhs) = delete;
/// @}

/// Writes a message to the output stream.
template <typename T> llvm::Error Write(const T &t) {
const std::string message = llvm::formatv("{0}", toJSON(t)).str();
return WriteImpl(message);
}

/// Reads the next message from the input stream.
template <typename T>
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,
/*RootName=*/"transport_message");
}

/// Returns the name of this transport client, for example `stdin/stdout` or
/// `client_1`.
llvm::StringRef GetClientName() { return m_client_name; }

protected:
virtual void Log(llvm::StringRef message);
virtual llvm::Error WriteImpl(const std::string &message) = 0;
virtual llvm::Expected<std::string>
ReadImpl(const std::chrono::microseconds &timeout) = 0;

llvm::StringRef m_client_name;
lldb::IOObjectSP m_input;
lldb::IOObjectSP m_output;
};

/// A transport class that uses JSON with a header for communication.
class JSONWithHeaderTransport : public JSONTransport {
public:
JSONWithHeaderTransport(llvm::StringRef client_name, lldb::IOObjectSP input,
lldb::IOObjectSP output)
: JSONTransport(client_name, input, output) {}
virtual ~JSONWithHeaderTransport() = default;

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 kHeaderContentLength =
"Content-Length: ";
static constexpr llvm::StringLiteral kHeaderSeparator = "\r\n\r\n";
};

} // namespace lldb_private

#endif
3 changes: 2 additions & 1 deletion lldb/source/Host/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ add_host_subdirectory(common
common/HostNativeThreadBase.cpp
common/HostProcess.cpp
common/HostThread.cpp
common/LockFileBase.cpp
common/JSONTransport.cpp
common/LZMA.cpp
common/LockFileBase.cpp
common/MainLoopBase.cpp
common/MemoryMonitor.cpp
common/MonitoringProcessLauncher.cpp
Expand Down
149 changes: 149 additions & 0 deletions lldb/source/Host/common/JSONTransport.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
//===-- JSONTransport.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 "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>

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<TransportClosedError>();

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(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(StringRef client_name, IOObjectSP input,
IOObjectSP output)
: m_client_name(client_name), m_input(std::move(input)),
m_output(std::move(output)) {}

void JSONTransport::Log(llvm::StringRef message) {
LLDB_LOG(GetLog(LLDBLog::Host), "{0}", message);
}

Expected<std::string>
JSONWithHeaderTransport::ReadImpl(const std::chrono::microseconds &timeout) {
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(), 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}) {1}", m_client_name, *raw_json).str());

return raw_json;
}

Error JSONWithHeaderTransport::WriteImpl(const std::string &message) {
if (!m_output || !m_output->IsValid())
return llvm::make_error<TransportClosedError>();

Log(llvm::formatv("<-- ({0}) {1}", m_client_name, message).str());

std::string Output;
raw_string_ostream OS(Output);
OS << kHeaderContentLength << message.length() << kHeaderSeparator << message;
size_t num_bytes = Output.size();
return m_output->Write(Output.data(), num_bytes).takeError();
}

char TransportEOFError::ID;
char TransportTimeoutError::ID;
char TransportClosedError::ID;
7 changes: 4 additions & 3 deletions lldb/tools/lldb-dap/DAP.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@

using namespace lldb_dap;
using namespace lldb_dap::protocol;
using namespace lldb_private;

namespace {
#ifdef _WIN32
Expand Down Expand Up @@ -893,14 +894,14 @@ llvm::Error DAP::Loop() {

while (!disconnecting) {
llvm::Expected<Message> next =
transport.Read(std::chrono::seconds(1));
if (next.errorIsA<EndOfFileError>()) {
transport.Read<protocol::Message>(std::chrono::seconds(1));
if (next.errorIsA<TransportEOFError>()) {
consumeError(next.takeError());
break;
}

// If the read timed out, continue to check if we should disconnect.
if (next.errorIsA<TimeoutError>()) {
if (next.errorIsA<TransportTimeoutError>()) {
consumeError(next.takeError());
continue;
}
Expand Down
Loading
Loading