Skip to content

Commit e43e1a6

Browse files
committed
Addressing reviewer feedback.
1 parent 566aca7 commit e43e1a6

File tree

4 files changed

+99
-52
lines changed

4 files changed

+99
-52
lines changed

lldb/include/lldb/Host/JSONTransport.h

Lines changed: 34 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -30,27 +30,33 @@ class TransportEOFError : public llvm::ErrorInfo<TransportEOFError> {
3030
static char ID;
3131

3232
TransportEOFError() = default;
33-
34-
void log(llvm::raw_ostream &OS) const override {
35-
OS << "transport end of file reached";
36-
}
33+
void log(llvm::raw_ostream &OS) const override { OS << "transport EOF"; }
3734
std::error_code convertToErrorCode() const override {
38-
return llvm::inconvertibleErrorCode();
35+
return std::make_error_code(std::errc::io_error);
3936
}
4037
};
4138

42-
class TransportTimeoutError : public llvm::ErrorInfo<TransportTimeoutError> {
39+
class TransportUnhandledContentsError
40+
: public llvm::ErrorInfo<TransportUnhandledContentsError> {
4341
public:
4442
static char ID;
4543

46-
TransportTimeoutError() = default;
44+
explicit TransportUnhandledContentsError(std::string unhandled_contents)
45+
: m_unhandled_contents(unhandled_contents) {}
4746

4847
void log(llvm::raw_ostream &OS) const override {
49-
OS << "transport operation timed out";
48+
OS << "transport EOF with unhandled contents " << m_unhandled_contents;
5049
}
5150
std::error_code convertToErrorCode() const override {
52-
return std::make_error_code(std::errc::timed_out);
51+
return std::make_error_code(std::errc::bad_message);
5352
}
53+
54+
const std::string &getUnhandledContents() const {
55+
return m_unhandled_contents;
56+
}
57+
58+
private:
59+
std::string m_unhandled_contents;
5460
};
5561

5662
class TransportInvalidError : public llvm::ErrorInfo<TransportInvalidError> {
@@ -97,19 +103,14 @@ class JSONTransport {
97103
ReadHandleUP handle = loop.RegisterReadObject(
98104
m_input,
99105
[&](MainLoopBase &loop) {
100-
char buf[1024];
101-
size_t len = sizeof(buf);
102-
do {
103-
if (llvm::Error error = m_input->Read(buf, len).takeError()) {
104-
callback(loop, std::move(error));
105-
return;
106-
}
107-
108-
if (len == 0) // EOF
109-
break;
106+
char buffer[kReadBufferSize];
107+
size_t len = sizeof(buffer);
108+
if (llvm::Error error = m_input->Read(buffer, len).takeError()) {
109+
callback(loop, std::move(error));
110+
return;
111+
}
110112

111-
m_buffer.append(std::string(buf, len));
112-
} while (len == sizeof(buf));
113+
m_buffer.append(std::string(buffer, len));
113114

114115
llvm::Expected<std::vector<std::string>> messages = Parse();
115116
if (llvm::Error error = messages.takeError()) {
@@ -125,8 +126,13 @@ class JSONTransport {
125126

126127
// On EOF, notify the callback after the remaining messages were
127128
// handled.
128-
if (len == 0)
129-
callback(loop, llvm::make_error<TransportEOFError>());
129+
if (len == 0) {
130+
if (m_buffer.empty())
131+
callback(loop, llvm::make_error<TransportEOFError>());
132+
else
133+
callback(loop, llvm::make_error<TransportUnhandledContentsError>(
134+
m_buffer));
135+
}
130136
},
131137
error);
132138
if (error.Fail())
@@ -135,6 +141,9 @@ class JSONTransport {
135141
}
136142

137143
protected:
144+
template <typename... Ts> inline auto Logv(const char *Fmt, Ts &&...Vals) {
145+
Log(llvm::formatv(Fmt, std::forward<Ts>(Vals)...).str());
146+
}
138147
virtual void Log(llvm::StringRef message);
139148

140149
virtual llvm::Error WriteImpl(const std::string &message) = 0;
@@ -143,6 +152,8 @@ class JSONTransport {
143152
lldb::IOObjectSP m_input;
144153
lldb::IOObjectSP m_output;
145154
std::string m_buffer;
155+
156+
static constexpr size_t kReadBufferSize = 1024;
146157
};
147158

148159
/// A transport class for JSON with a HTTP header.

lldb/source/Host/common/JSONTransport.cpp

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -29,56 +29,58 @@ void JSONTransport::Log(llvm::StringRef message) {
2929
LLDB_LOG(GetLog(LLDBLog::Host), "{0}", message);
3030
}
3131

32+
// Parses messages based on
33+
// https://microsoft.github.io/debug-adapter-protocol/overview#base-protocol
3234
Expected<std::vector<std::string>> HTTPDelimitedJSONTransport::Parse() {
3335
if (m_buffer.empty())
3436
return std::vector<std::string>{};
3537

3638
std::vector<std::string> messages;
37-
llvm::StringRef buf = m_buffer;
39+
StringRef buffer = m_buffer;
3840
size_t content_length = 0, end_of_last_message = 0, cursor = 0;
3941
do {
40-
auto idx = buf.find(kHeaderSeparator, cursor);
42+
auto idx = buffer.find(kHeaderSeparator, cursor);
43+
// Separator not found, we need more data.
4144
if (idx == StringRef::npos)
4245
break;
4346

44-
auto header = buf.slice(cursor, idx);
47+
auto header = buffer.slice(cursor, idx);
4548
cursor = idx + kHeaderSeparator.size();
4649

4750
// An empty line separates the headers from the message body.
4851
if (header.empty()) {
49-
// Not enough data, wait for the next chunk to arrive.
50-
if (content_length + cursor > buf.size())
52+
// Check if we have enough data or wait for the next chunk to arrive.
53+
if (content_length + cursor > buffer.size())
5154
break;
5255

53-
std::string body = buf.substr(cursor, content_length).str();
56+
std::string body = buffer.substr(cursor, content_length).str();
5457
end_of_last_message = cursor + content_length;
5558
cursor += content_length;
56-
Log(llvm::formatv("--> {0}", body).str());
57-
messages.push_back(body);
59+
Logv("--> {0}", body);
60+
messages.emplace_back(std::move(body));
5861
content_length = 0;
5962
continue;
6063
}
6164

6265
// HTTP Headers are formatted like `<field-name> ':' [<field-value>]`.
6366
if (!header.contains(kHeaderFieldSeparator))
64-
return make_error<StringError>("malformed content header",
65-
inconvertibleErrorCode());
67+
return createStringError("malformed content header");
6668

6769
auto [name, value] = header.split(kHeaderFieldSeparator);
6870

69-
// Handle known headers, at the moment only "Content-Length" is supported,
71+
// Handle known headers, at the moment only "Content-Length" is specified,
7072
// other headers are ignored.
7173
if (name.lower() == kHeaderContentLength.lower()) {
7274
value = value.trim();
7375
if (value.trim().consumeInteger(10, content_length))
74-
return make_error<StringError>(
75-
formatv("invalid content length: {0}", value).str(),
76-
inconvertibleErrorCode());
76+
return createStringError(std::errc::invalid_argument,
77+
"invalid content length: %s",
78+
value.str().c_str());
7779
}
78-
} while (cursor < buf.size());
80+
} while (cursor < buffer.size());
7981

8082
// Store the remainder of the buffer for the next read callback.
81-
m_buffer = buf.substr(end_of_last_message);
83+
m_buffer = buffer.substr(end_of_last_message);
8284

8385
return messages;
8486
}
@@ -87,7 +89,7 @@ Error HTTPDelimitedJSONTransport::WriteImpl(const std::string &message) {
8789
if (!m_output || !m_output->IsValid())
8890
return llvm::make_error<TransportInvalidError>();
8991

90-
Log(llvm::formatv("<-- {0}", message).str());
92+
Logv("<-- {0}", message);
9193

9294
std::string Output;
9395
raw_string_ostream OS(Output);
@@ -106,7 +108,7 @@ Expected<std::vector<std::string>> JSONRPCTransport::Parse() {
106108
break;
107109
std::string raw_json = buf.substr(0, idx).str();
108110
buf = buf.substr(idx + 1);
109-
Log(llvm::formatv("--> {0}", raw_json).str());
111+
Logv("--> {0}", raw_json);
110112
messages.push_back(raw_json);
111113
} while (!buf.empty());
112114

@@ -120,7 +122,7 @@ Error JSONRPCTransport::WriteImpl(const std::string &message) {
120122
if (!m_output || !m_output->IsValid())
121123
return llvm::make_error<TransportInvalidError>();
122124

123-
Log(llvm::formatv("<-- {0}", message).str());
125+
Logv("<-- {0}", message);
124126

125127
std::string Output;
126128
llvm::raw_string_ostream OS(Output);
@@ -130,5 +132,5 @@ Error JSONRPCTransport::WriteImpl(const std::string &message) {
130132
}
131133

132134
char TransportEOFError::ID;
133-
char TransportTimeoutError::ID;
135+
char TransportUnhandledContentsError::ID;
134136
char TransportInvalidError::ID;

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

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
import lldbdap_testcase
99
import dap_server
1010

11+
EXIT_FAILURE = 1
12+
EXIT_SUCCESS = 0
13+
1114

1215
class TestDAP_io(lldbdap_testcase.DAPTestCaseBase):
1316
def launch(self):
@@ -41,40 +44,44 @@ def test_eof_immediately(self):
4144
"""
4245
process = self.launch()
4346
process.stdin.close()
44-
self.assertEqual(process.wait(timeout=5.0), 0)
47+
self.assertEqual(process.wait(timeout=5.0), EXIT_SUCCESS)
4548

4649
def test_invalid_header(self):
4750
"""
48-
lldb-dap handles invalid message headers.
51+
lldb-dap returns a failure exit code when the input stream is closed
52+
with a malformed request header.
4953
"""
5054
process = self.launch()
5155
process.stdin.write(b"not the correct message header")
5256
process.stdin.close()
53-
self.assertEqual(process.wait(timeout=5.0), 0)
57+
self.assertEqual(process.wait(timeout=5.0), EXIT_FAILURE)
5458

5559
def test_partial_header(self):
5660
"""
57-
lldb-dap handles partial message headers.
61+
lldb-dap returns a failure exit code when the input stream is closed
62+
with an incomplete message header is in the message buffer.
5863
"""
5964
process = self.launch()
6065
process.stdin.write(b"Content-Length: ")
6166
process.stdin.close()
62-
self.assertEqual(process.wait(timeout=5.0), 0)
67+
self.assertEqual(process.wait(timeout=5.0), EXIT_FAILURE)
6368

6469
def test_incorrect_content_length(self):
6570
"""
66-
lldb-dap handles malformed content length headers.
71+
lldb-dap returns a failure exit code when reading malformed content
72+
length headers.
6773
"""
6874
process = self.launch()
6975
process.stdin.write(b"Content-Length: abc")
7076
process.stdin.close()
71-
self.assertEqual(process.wait(timeout=5.0), 0)
77+
self.assertEqual(process.wait(timeout=5.0), EXIT_FAILURE)
7278

7379
def test_partial_content_length(self):
7480
"""
75-
lldb-dap handles partial messages.
81+
lldb-dap returns a failure exit code when the input stream is closed
82+
with a partial message in the message buffer.
7683
"""
7784
process = self.launch()
7885
process.stdin.write(b"Content-Length: 10\r\n\r\n{")
7986
process.stdin.close()
80-
self.assertEqual(process.wait(timeout=5.0), 0)
87+
self.assertEqual(process.wait(timeout=5.0), EXIT_FAILURE)

lldb/unittests/Host/JSONTransportTest.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,21 @@ TEST_F(HTTPDelimitedJSONTransportTest, ReadWithEOF) {
146146
});
147147
}
148148

149+
TEST_F(HTTPDelimitedJSONTransportTest, ReaderWithUnhandledData) {
150+
std::string json = R"json({"str": "foo"})json";
151+
std::string message =
152+
formatv("Content-Length: {0}\r\nContent-type: text/json\r\n\r\n{1}",
153+
json.size(), json)
154+
.str();
155+
// Write an incomplete message and close the handle.
156+
ASSERT_THAT_EXPECTED(input.Write(message.data(), message.size() - 1),
157+
Succeeded());
158+
input.CloseWriteFileDescriptor();
159+
RunOnce<JSONTestType>([&](llvm::Expected<JSONTestType> message) {
160+
ASSERT_THAT_EXPECTED(message, Failed<TransportUnhandledContentsError>());
161+
});
162+
}
163+
149164
TEST_F(HTTPDelimitedJSONTransportTest, InvalidTransport) {
150165
transport = std::make_unique<HTTPDelimitedJSONTransport>(nullptr, nullptr);
151166
auto handle = transport->RegisterReadObject<JSONTestType>(
@@ -219,6 +234,18 @@ TEST_F(JSONRPCTransportTest, ReadWithEOF) {
219234
});
220235
}
221236

237+
TEST_F(JSONRPCTransportTest, ReaderWithUnhandledData) {
238+
std::string message = R"json({"str": "foo"})json"
239+
"\n";
240+
// Write an incomplete message and close the handle.
241+
ASSERT_THAT_EXPECTED(input.Write(message.data(), message.size() - 1),
242+
Succeeded());
243+
input.CloseWriteFileDescriptor();
244+
RunOnce<JSONTestType>([&](llvm::Expected<JSONTestType> message) {
245+
ASSERT_THAT_EXPECTED(message, Failed<TransportUnhandledContentsError>());
246+
});
247+
}
248+
222249
TEST_F(JSONRPCTransportTest, Write) {
223250
ASSERT_THAT_ERROR(transport->Write(JSONTestType{"foo"}), Succeeded());
224251
output.CloseWriteFileDescriptor();

0 commit comments

Comments
 (0)