Skip to content

Commit 7d9eeb1

Browse files
committed
Adjusting Transport.Read to return an llvm::Expected<std::optional<protocol::Message>> to try to refine the caller.
An error reading or validating a message should now result in an `llvm::Error`. EOF is now only acceptable at the start of a message, otherwise we return an error indiciating we encountered a partial message.
1 parent 40d149d commit 7d9eeb1

File tree

5 files changed

+70
-72
lines changed

5 files changed

+70
-72
lines changed

lldb/test/API/tools/lldb-dap/io/TestDAP_io.py

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
Test lldb-dap IO handling.
33
"""
44

5+
import sys
6+
57
from lldbsuite.test.decorators import *
68
import lldbdap_testcase
79
import dap_server
@@ -19,18 +21,18 @@ def cleanup():
1921
if process.poll() is None:
2022
process.terminate()
2123
process.wait()
22-
stdout_data = process.stdout.read()
23-
stderr_data = process.stderr.read()
24-
print("========= STDOUT =========")
25-
print(stdout_data)
26-
print("========= END =========")
27-
print("========= STDERR =========")
28-
print(stderr_data)
29-
print("========= END =========")
30-
print("========= DEBUG ADAPTER PROTOCOL LOGS =========")
24+
stdout_data = process.stdout.read().decode()
25+
stderr_data = process.stderr.read().decode()
26+
print("========= STDOUT =========", file=sys.stderr)
27+
print(stdout_data, file=sys.stderr)
28+
print("========= END =========", file=sys.stderr)
29+
print("========= STDERR =========", file=sys.stderr)
30+
print(stderr_data, file=sys.stderr)
31+
print("========= END =========", file=sys.stderr)
32+
print("========= DEBUG ADAPTER PROTOCOL LOGS =========", file=sys.stderr)
3133
with open(log_file_path, "r") as file:
32-
print(file.read())
33-
print("========= END =========")
34+
print(file.read(), file=sys.stderr)
35+
print("========= END =========", file=sys.stderr)
3436

3537
# Execute the cleanup function during test case tear down.
3638
self.addTearDownHook(cleanup)
@@ -45,14 +47,23 @@ def test_eof_immediately(self):
4547
process.stdin.close()
4648
self.assertEqual(process.wait(timeout=5.0), 0)
4749

50+
def test_invalid_header(self):
51+
"""
52+
lldb-dap handles invalid message headers.
53+
"""
54+
process = self.launch()
55+
process.stdin.write(b"not the corret message header")
56+
process.stdin.close()
57+
self.assertEqual(process.wait(timeout=5.0), 1)
58+
4859
def test_partial_header(self):
4960
"""
5061
lldb-dap handles parital message headers.
5162
"""
5263
process = self.launch()
5364
process.stdin.write(b"Content-Length: ")
5465
process.stdin.close()
55-
self.assertEqual(process.wait(timeout=5.0), 0)
66+
self.assertEqual(process.wait(timeout=5.0), 1)
5667

5768
def test_incorrect_content_length(self):
5869
"""
@@ -61,13 +72,13 @@ def test_incorrect_content_length(self):
6172
process = self.launch()
6273
process.stdin.write(b"Content-Length: abc")
6374
process.stdin.close()
64-
self.assertEqual(process.wait(timeout=5.0), 0)
75+
self.assertEqual(process.wait(timeout=5.0), 1)
6576

6677
def test_partial_content_length(self):
6778
"""
6879
lldb-dap handles partial messages.
6980
"""
7081
process = self.launch()
71-
process.stdin.write(b"Content-Length: 10{")
82+
process.stdin.write(b"Content-Length: 10\r\n\r\n{")
7283
process.stdin.close()
73-
self.assertEqual(process.wait(timeout=5.0), 0)
84+
self.assertEqual(process.wait(timeout=5.0), 1)

lldb/tools/lldb-dap/DAP.cpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -769,11 +769,15 @@ llvm::Error DAP::Loop() {
769769
StopEventHandlers();
770770
});
771771
while (!disconnecting) {
772-
std::optional<protocol::Message> next = transport.Read();
772+
llvm::Expected<std::optional<protocol::Message>> next = transport.Read();
773773
if (!next)
774+
return next.takeError();
775+
776+
// nullopt on EOF
777+
if (!*next)
774778
break;
775779

776-
if (!HandleObject(*next)) {
780+
if (!HandleObject(**next)) {
777781
return llvm::createStringError(llvm::inconvertibleErrorCode(),
778782
"unhandled packet");
779783
}

lldb/tools/lldb-dap/Transport.cpp

Lines changed: 34 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,19 @@ using namespace lldb_private;
2525
using namespace lldb_dap;
2626
using namespace lldb_dap::protocol;
2727

28-
static Expected<std::string> ReadFull(IOObject *descriptor, size_t length) {
28+
/// ReadFull attempts to read the specified number of bytes. If EOF is
29+
/// encountered, '' is returned.
30+
static Expected<std::string> ReadFull(IOObject &descriptor, size_t length) {
2931
std::string data;
3032
data.resize(length);
31-
auto status = descriptor->Read(data.data(), length);
33+
auto status = descriptor.Read(data.data(), length);
3234
if (status.Fail())
3335
return status.takeError();
36+
// Return the actual number of bytes read.
3437
return data.substr(0, length);
3538
}
3639

37-
static Expected<std::string> ReadUntil(IOObject *descriptor,
40+
static Expected<std::string> ReadUntil(IOObject &descriptor,
3841
StringRef delimiter) {
3942
std::string buffer;
4043
buffer.reserve(delimiter.size() + 1);
@@ -43,9 +46,9 @@ static Expected<std::string> ReadUntil(IOObject *descriptor,
4346
ReadFull(descriptor, buffer.empty() ? delimiter.size() : 1);
4447
if (auto Err = next.takeError())
4548
return std::move(Err);
46-
// '' is returned on EOF.
49+
// Return "" if EOF is encountered.
4750
if (next->empty())
48-
return buffer;
51+
return "";
4952
buffer += *next;
5053
}
5154
return buffer.substr(0, buffer.size() - delimiter.size());
@@ -65,65 +68,44 @@ Transport::Transport(StringRef client_name, std::ofstream *log,
6568
: m_client_name(client_name), m_log(log), m_input(std::move(input)),
6669
m_output(std::move(output)) {}
6770

68-
std::optional<Message> Transport::Read() {
69-
if (!m_input || !m_input->IsValid()) {
70-
DAP_LOG(m_log, "({0}) input is closed", m_client_name);
71-
return std::nullopt;
72-
}
71+
Expected<std::optional<Message>> Transport::Read() {
72+
if (!m_input || !m_input->IsValid())
73+
return createStringError("transport output is closed");
74+
7375
IOObject *input = m_input.get();
7476
Expected<std::string> message_header =
75-
ReadFull(input, kHeaderContentLength.size());
76-
if (!message_header) {
77-
DAP_LOG_ERROR(m_log, message_header.takeError(), "({1}) read failed: {0}",
78-
m_client_name);
79-
return std::nullopt;
80-
}
81-
77+
ReadFull(*input, kHeaderContentLength.size());
78+
if (!message_header)
79+
return message_header.takeError();
8280
// '' returned on EOF.
8381
if (message_header->empty())
8482
return std::nullopt;
85-
if (*message_header != kHeaderContentLength) {
86-
DAP_LOG(m_log, "({0}) read failed: expected '{1}' and got '{2}'",
87-
m_client_name, kHeaderContentLength, *message_header);
88-
return std::nullopt;
89-
}
83+
if (*message_header != kHeaderContentLength)
84+
return createStringError(formatv("expected '{0}' and got '{1}'",
85+
kHeaderContentLength, *message_header)
86+
.str());
9087

91-
Expected<std::string> raw_length = ReadUntil(input, kHeaderSeparator);
92-
if (!raw_length) {
93-
DAP_LOG_ERROR(m_log, raw_length.takeError(), "({1}) read failed: {0}",
94-
m_client_name);
95-
return std::nullopt;
96-
}
88+
Expected<std::string> raw_length = ReadUntil(*input, kHeaderSeparator);
89+
if (!raw_length)
90+
return raw_length.takeError();
91+
if (raw_length->empty())
92+
return createStringError("unexpected EOF parsing DAP header");
9793

9894
size_t length;
99-
if (!to_integer(*raw_length, length)) {
100-
DAP_LOG(m_log, "({0}) read failed: invalid content length {1}",
101-
m_client_name, *raw_length);
102-
return std::nullopt;
103-
}
95+
if (!to_integer(*raw_length, length))
96+
return createStringError(
97+
formatv("invalid content length {0}", *raw_length).str());
10498

105-
Expected<std::string> raw_json = ReadFull(input, length);
106-
if (!raw_json) {
107-
DAP_LOG_ERROR(m_log, raw_json.takeError(), "({1}) read failed: {0}",
108-
m_client_name);
109-
return std::nullopt;
110-
}
111-
if (raw_json->length() != length) {
112-
DAP_LOG(m_log, "({0}) read failed: expected {1} bytes and got {2} bytes",
113-
m_client_name, length, raw_json->length());
114-
return std::nullopt;
115-
}
99+
Expected<std::string> raw_json = ReadFull(*input, length);
100+
if (!raw_json)
101+
return raw_json.takeError();
102+
// If we got less than the expected number of bytes then we hit EOF.
103+
if (raw_json->length() != length)
104+
return createStringError("unexpected EOF parse DAP message body");
116105

117106
DAP_LOG(m_log, "<-- ({0}) {1}", m_client_name, *raw_json);
118107

119-
llvm::Expected<Message> message = json::parse<Message>(*raw_json);
120-
if (!message) {
121-
DAP_LOG_ERROR(m_log, message.takeError(), "({1}) read failed: {0}",
122-
m_client_name);
123-
return std::nullopt;
124-
}
125-
126-
return std::move(*message);
108+
return json::parse<Message>(*raw_json);
127109
}
128110

129111
Error Transport::Write(const Message &message) {

lldb/tools/lldb-dap/Transport.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ class Transport {
4141
llvm::Error Write(const protocol::Message &M);
4242

4343
/// Reads the next Debug Adater Protocol message from the input stream.
44-
std::optional<protocol::Message> Read();
44+
llvm::Expected<std::optional<protocol::Message>> Read();
4545

4646
private:
4747
llvm::StringRef m_client_name;

lldb/tools/lldb-dap/lldb-dap.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,8 @@ serveConnection(const Socket::SocketProtocol &protocol, const std::string &name,
345345

346346
if (auto Err = dap.Loop()) {
347347
llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(),
348-
"DAP session error: ");
348+
"DAP session (" + client_name +
349+
") error: ");
349350
}
350351

351352
DAP_LOG(log, "({0}) client disconnected", client_name);
@@ -587,7 +588,7 @@ int main(int argc, char *argv[]) {
587588

588589
if (auto Err = dap.Loop()) {
589590
llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(),
590-
"DAP session error: ");
591+
"DAP session (" + client_name + ") error: ");
591592
return EXIT_FAILURE;
592593
}
593594
return EXIT_SUCCESS;

0 commit comments

Comments
 (0)