nixd
Loading...
Searching...
No Matches
Connection.cpp
Go to the documentation of this file.
2#include "lspserver/Logger.h"
4
5#include <llvm/ADT/SmallString.h>
6#include <llvm/Support/CommandLine.h>
7
8#include <poll.h>
9#include <sys/poll.h>
10#include <sys/stat.h>
11
12#include <csignal>
13#include <cstdint>
14#include <cstdio>
15#include <memory>
16#include <optional>
17#include <system_error>
18
19namespace {
20
21llvm::cl::opt<int> ClientProcessID{
22 "clientProcessId",
23 llvm::cl::desc(
24 "Client process ID, if this PID died, the server should exit."),
25 llvm::cl::init(getppid())};
26
27std::string jsonToString(llvm::json::Value &Message) {
28 std::string Result;
29 llvm::raw_string_ostream OS(Result);
30 OS << Message;
31 return Result;
32}
33
34class ReadEOF : public llvm::ErrorInfo<ReadEOF> {
35public:
36 static char ID;
37 ReadEOF() = default;
38
39 /// No need to implement this, we don't need to log this error.
40 void log(llvm::raw_ostream &OS) const override { __builtin_unreachable(); }
41
42 std::error_code convertToErrorCode() const override {
43 return std::make_error_code(std::errc::io_error);
44 }
45};
46
47char ReadEOF::ID;
48
49} // namespace
50
51namespace lspserver {
52
53static llvm::json::Object encodeError(llvm::Error Error) {
54 std::string Message;
56 auto HandlerFn = [&](const LSPError &E) -> llvm::Error {
57 Message = E.Message;
58 Code = E.Code;
59 return llvm::Error::success();
60 };
61 if (llvm::Error Unhandled = llvm::handleErrors(std::move(Error), HandlerFn))
62 Message = llvm::toString(std::move(Unhandled));
63
64 return llvm::json::Object{
65 {"message", std::move(Message)},
66 {"code", int64_t(Code)},
67 };
68}
69
70/// Decode the given JSON object into an error.
71llvm::Error decodeError(const llvm::json::Object &O) {
72 llvm::StringRef Message =
73 O.getString("message").value_or("Unspecified error");
74 if (std::optional<int64_t> Code = O.getInteger("code"))
75 return llvm::make_error<LSPError>(Message.str(), ErrorCode(*Code));
76 return llvm::make_error<llvm::StringError>(llvm::inconvertibleErrorCode(),
77 Message.str());
78}
79
80void OutboundPort::notify(llvm::StringRef Method, llvm::json::Value Params) {
81 sendMessage(llvm::json::Object{
82 {"jsonrpc", "2.0"},
83 {"method", Method},
84 {"params", std::move(Params)},
85 });
86}
87void OutboundPort::call(llvm::StringRef Method, llvm::json::Value Params,
88 llvm::json::Value ID) {
89 sendMessage(llvm::json::Object{
90 {"jsonrpc", "2.0"},
91 {"id", std::move(ID)},
92 {"method", Method},
93 {"params", std::move(Params)},
94 });
95}
96void OutboundPort::reply(llvm::json::Value ID,
97 llvm::Expected<llvm::json::Value> Result) {
98 if (Result) {
99 sendMessage(llvm::json::Object{
100 {"jsonrpc", "2.0"},
101 {"id", std::move(ID)},
102 {"result", std::move(*Result)},
103 });
104 } else {
105 sendMessage(llvm::json::Object{
106 {"jsonrpc", "2.0"},
107 {"id", std::move(ID)},
108 {"error", encodeError(Result.takeError())},
109 });
110 }
111}
112
113void OutboundPort::sendMessage(llvm::json::Value Message) {
114 // Make sure our outputs are not interleaving between messages (json)
115 vlog(">>> {0}", Message);
116 std::lock_guard<std::mutex> Guard(Mutex);
117 OutputBuffer.clear();
118 llvm::raw_svector_ostream SVecOS(OutputBuffer);
119 SVecOS << (Pretty ? llvm::formatv("{0:2}", Message)
120 : llvm::formatv("{0}", Message));
121 Outs << "Content-Length: " << OutputBuffer.size() << "\r\n\r\n"
122 << OutputBuffer;
123 Outs.flush();
124}
125
126bool InboundPort::dispatch(llvm::json::Value Message, MessageHandler &Handler) {
127 // Message must be an object with "jsonrpc":"2.0".
128 auto *Object = Message.getAsObject();
129 if (!Object ||
130 Object->getString("jsonrpc") != std::optional<llvm::StringRef>("2.0")) {
131 elog("Not a JSON-RPC 2.0 message: {0:2}", Message);
132 return false;
133 }
134 // ID may be any JSON value. If absent, this is a notification.
135 std::optional<llvm::json::Value> ID;
136 if (auto *I = Object->get("id"))
137 ID = std::move(*I);
138 auto Method = Object->getString("method");
139 if (!Method) { // This is a response.
140 if (!ID) {
141 elog("No method and no response ID: {0:2}", Message);
142 return false;
143 }
144 if (auto *Err = Object->getObject("error"))
145 // TODO: Logging & reply errors.
146 return Handler.onReply(std::move(*ID), decodeError(*Err));
147 // Result should be given, use null if not.
148 llvm::json::Value Result = nullptr;
149 if (auto *R = Object->get("result"))
150 Result = std::move(*R);
151 return Handler.onReply(std::move(*ID), std::move(Result));
152 }
153 // Params should be given, use null if not.
154 llvm::json::Value Params = nullptr;
155 if (auto *P = Object->get("params"))
156 Params = std::move(*P);
157
158 if (ID)
159 return Handler.onCall(*Method, std::move(Params), std::move(*ID));
160 return Handler.onNotify(*Method, std::move(Params));
161}
162
163bool readLine(int fd, const std::atomic<bool> &Close,
164 llvm::SmallString<128> &Line) {
165 Line.clear();
166
167 std::vector<pollfd> FDs;
168 FDs.emplace_back(pollfd{
169 fd,
170 POLLIN | POLLPRI,
171 0,
172 });
173 for (;;) {
174 char Ch;
175 // FIXME: inefficient
176 int Poll = poll(FDs.data(), FDs.size(), 1000);
177 if (Poll < 0)
178 return false;
179 if (Close)
180 return false;
181
182 // POLLHUB: hang up from the writer side
183 // POLLNVAL: invalid request (fd not open)
184 if (FDs[0].revents & (POLLHUP | POLLNVAL)) {
185 return false;
186 }
187
188 if (FDs[0].revents & POLLIN) {
189 ssize_t BytesRead = read(fd, &Ch, 1);
190 if (BytesRead == -1) {
191 if (errno != EINTR)
192 return false;
193 } else if (BytesRead == 0)
194 return false;
195 else {
196 if (Ch == '\n')
197 return true;
198 Line += Ch;
199 }
200 }
201
202 if (kill(ClientProcessID, 0) < 0) {
203 // Parent died.
204 return false;
205 }
206 }
207}
208
209llvm::Expected<llvm::json::Value>
211 unsigned long long ContentLength = 0;
212 llvm::SmallString<128> Line;
213 while (true) {
214 if (!readLine(In, Close, Line))
215 return llvm::make_error<ReadEOF>(); // EOF
216
217 llvm::StringRef LineRef = Line;
218
219 // Content-Length is a mandatory header, and the only one we handle.
220 if (LineRef.consume_front("Content-Length: ")) {
221 llvm::getAsUnsignedInteger(LineRef.trim(), 0, ContentLength);
222 continue;
223 }
224 // An empty line indicates the end of headers.
225 // Go ahead and read the JSON.
226 if (LineRef.trim().empty())
227 break;
228 // It's another header, ignore it.
229 }
230
231 Buffer.resize(ContentLength);
232 for (size_t Pos = 0, Read; Pos < ContentLength; Pos += Read) {
233
234 Read = read(In, Buffer.data() + Pos, ContentLength - Pos);
235
236 if (Read == 0) {
237 elog("Input was aborted. Read only {0} bytes of expected {1}.", Pos,
238 ContentLength);
239 return llvm::make_error<ReadEOF>();
240 }
241 }
242 return llvm::json::parse(Buffer);
243}
244
245llvm::Expected<llvm::json::Value>
247 enum class State { Prose, JSONBlock, NixBlock };
248 State State = State::Prose;
249 Buffer.clear();
250 llvm::SmallString<128> Line;
251 std::string NixDocURI;
252 while (readLine(In, Close, Line)) {
253 auto LineRef = Line.str().trim();
254 if (State == State::Prose) {
255 if (LineRef.starts_with("```json"))
256 State = State::JSONBlock;
257 else if (LineRef.consume_front("```nix ")) {
258 State = State::NixBlock;
259 NixDocURI = LineRef.str();
260 }
261 } else if (State == State::JSONBlock) {
262 // We are in a JSON block, read lines and append JSONString.
263 if (LineRef.starts_with("#")) // comment
264 continue;
265
266 // End of the block
267 if (LineRef.starts_with("```")) {
268 return llvm::json::parse(Buffer);
269 }
270
271 Buffer += Line;
272 } else if (State == State::NixBlock) {
273 // We are in a Nix block. (This was implemented to make the .md test
274 // files more readable, particularly regarding multiline Nix documents,
275 // so that the newlines don't have to be \n escaped.)
276
277 if (LineRef.starts_with("```")) {
278 return llvm::json::Object{
279 {"jsonrpc", "2.0"},
280 {"method", "textDocument/didOpen"},
281 {
282 "params",
283 llvm::json::Object{
284 {
285 "textDocument",
286 llvm::json::Object{
287 {"uri", NixDocURI},
288 {"languageId", "nix"},
289 {"version", 1},
290 {"text", llvm::StringRef(Buffer).rtrim().str()},
291 },
292 },
293 },
294 }};
295 }
296 Buffer += Line;
297 Buffer += "\n";
298 } else {
299 assert(false && "unreachable");
300 }
301 }
302 return llvm::make_error<ReadEOF>(); // EOF
303}
304
305llvm::Expected<llvm::json::Value>
306InboundPort::readMessage(std::string &Buffer) {
307 switch (StreamStyle) {
308
310 return readStandardMessage(Buffer);
312 return readLitTestMessage(Buffer);
313 }
314 assert(false && "Invalid stream style");
315 __builtin_unreachable();
316}
317
319 std::string Buffer;
320
321 for (;;) {
322 if (auto Message = readMessage(Buffer)) {
323 vlog("<<< {0}", jsonToString(*Message));
324 if (!dispatch(*Message, Handler))
325 return;
326 } else {
327 // Handle error while reading message.
328 return [&]() {
329 auto Err = Message.takeError();
330 if (Err.isA<ReadEOF>())
331 return; // Stop reading.
332 else if (Err.isA<llvm::json::ParseError>())
333 elog("The received json cannot be parsed, reason: {0}", Err);
334 else
335 elog("Error reading message: {0}", llvm::toString(std::move(Err)));
336 }();
337 }
338 }
339}
340
341} // namespace lspserver
llvm::Expected< llvm::json::Value > readMessage(std::string &Buffer)
bool dispatch(llvm::json::Value Message, MessageHandler &Handler)
JSONStreamStyle StreamStyle
Definition Connection.h:41
llvm::Expected< llvm::json::Value > readStandardMessage(std::string &Buffer)
void loop(MessageHandler &Handler)
llvm::Expected< llvm::json::Value > readLitTestMessage(std::string &Buffer)
Read one message, expecting the input to be one of our Markdown lit-tests.
virtual bool onReply(llvm::json::Value ID, llvm::Expected< llvm::json::Value > Result)=0
virtual bool onNotify(llvm::StringRef Method, llvm::json::Value)=0
virtual bool onCall(llvm::StringRef Method, llvm::json::Value Params, llvm::json::Value ID)=0
void sendMessage(llvm::json::Value Message)
void notify(llvm::StringRef Method, llvm::json::Value Params)
void reply(llvm::json::Value ID, llvm::Expected< llvm::json::Value > Result)
void call(llvm::StringRef Method, llvm::json::Value Params, llvm::json::Value ID)
void log(Logger::Level L, const char *Fmt, Ts &&...Vals)
Definition Logger.h:38
Whether current platform treats paths case insensitively.
Definition Connection.h:11
llvm::Error decodeError(const llvm::json::Object &O)
Decode the given JSON object into an error.
void elog(const char *Fmt, Ts &&...Vals)
Definition Logger.h:52
void vlog(const char *Fmt, Ts &&...Vals)
Definition Logger.h:63
bool readLine(int fd, const std::atomic< bool > &Close, llvm::SmallString< 128 > &Line)