1//===--- JSONTransport.cpp - sending and receiving LSP messages over JSON -===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "llvm/Support/LSP/Transport.h"
10#include "llvm/ADT/SmallString.h"
11#include "llvm/Support/Error.h"
12#include "llvm/Support/LSP/Logging.h"
13#include "llvm/Support/LSP/Protocol.h"
14#include <atomic>
15#include <optional>
16#include <system_error>
17#include <utility>
18
19using namespace llvm;
20using namespace llvm::lsp;
21
22//===----------------------------------------------------------------------===//
23// Reply
24//===----------------------------------------------------------------------===//
25
26namespace {
27/// Function object to reply to an LSP call.
28/// Each instance must be called exactly once, otherwise:
29/// - if there was no reply, an error reply is sent
30/// - if there were multiple replies, only the first is sent
31class Reply {
32public:
33 Reply(const llvm::json::Value &Id, StringRef Method, JSONTransport &Transport,
34 std::mutex &TransportOutputMutex);
35 Reply(Reply &&Other);
36 Reply &operator=(Reply &&) = delete;
37 Reply(const Reply &) = delete;
38 Reply &operator=(const Reply &) = delete;
39
40 void operator()(llvm::Expected<llvm::json::Value> Reply);
41
42private:
43 std::string Method;
44 std::atomic<bool> Replied = {false};
45 llvm::json::Value Id;
46 JSONTransport *Transport;
47 std::mutex &TransportOutputMutex;
48};
49} // namespace
50
51Reply::Reply(const llvm::json::Value &Id, llvm::StringRef Method,
52 JSONTransport &Transport, std::mutex &TransportOutputMutex)
53 : Method(Method), Id(Id), Transport(&Transport),
54 TransportOutputMutex(TransportOutputMutex) {}
55
56Reply::Reply(Reply &&Other)
57 : Method(Other.Method), Replied(Other.Replied.load()),
58 Id(std::move(Other.Id)), Transport(Other.Transport),
59 TransportOutputMutex(Other.TransportOutputMutex) {
60 Other.Transport = nullptr;
61}
62
63void Reply::operator()(llvm::Expected<llvm::json::Value> Reply) {
64 if (Replied.exchange(i: true)) {
65 Logger::error(Fmt: "Replied twice to message {0}({1})", Vals&: Method, Vals&: Id);
66 assert(false && "must reply to each call only once!");
67 return;
68 }
69 assert(Transport && "expected valid transport to reply to");
70
71 std::lock_guard<std::mutex> TransportLock(TransportOutputMutex);
72 if (Reply) {
73 Logger::info(Fmt: "--> reply:{0}({1})", Vals&: Method, Vals&: Id);
74 Transport->reply(Id: std::move(Id), Result: std::move(Reply));
75 } else {
76 llvm::Error Error = Reply.takeError();
77 Logger::info(Fmt: "--> reply:{0}({1}): {2}", Vals&: Method, Vals&: Id, Vals&: Error);
78 Transport->reply(Id: std::move(Id), Result: std::move(Error));
79 }
80}
81
82//===----------------------------------------------------------------------===//
83// MessageHandler
84//===----------------------------------------------------------------------===//
85
86bool MessageHandler::onNotify(llvm::StringRef Method, llvm::json::Value Value) {
87 Logger::info(Fmt: "--> {0}", Vals&: Method);
88
89 if (Method == "exit")
90 return false;
91 if (Method == "$cancel") {
92 // TODO: Add support for cancelling requests.
93 } else {
94 auto It = NotificationHandlers.find(Key: Method);
95 if (It != NotificationHandlers.end())
96 It->second(std::move(Value));
97 }
98 return true;
99}
100
101bool MessageHandler::onCall(llvm::StringRef Method, llvm::json::Value Params,
102 llvm::json::Value Id) {
103 Logger::info(Fmt: "--> {0}({1})", Vals&: Method, Vals&: Id);
104
105 Reply Reply(Id, Method, Transport, TransportOutputMutex);
106
107 auto It = MethodHandlers.find(Key: Method);
108 if (It != MethodHandlers.end()) {
109 It->second(std::move(Params), std::move(Reply));
110 } else {
111 Reply(llvm::make_error<LSPError>(Args: "method not found: " + Method.str(),
112 Args: ErrorCode::MethodNotFound));
113 }
114 return true;
115}
116
117bool MessageHandler::onReply(llvm::json::Value Id,
118 llvm::Expected<llvm::json::Value> Result) {
119 // Find the response handler in the mapping. If it exists, move it out of the
120 // mapping and erase it.
121 ResponseHandlerTy ResponseHandler;
122 {
123 std::lock_guard<std::mutex> responseHandlersLock(ResponseHandlersMutex);
124 auto It = ResponseHandlers.find(Key: debugString(Op&: Id));
125 if (It != ResponseHandlers.end()) {
126 ResponseHandler = std::move(It->second);
127 ResponseHandlers.erase(I: It);
128 }
129 }
130
131 // If we found a response handler, invoke it. Otherwise, log an error.
132 if (ResponseHandler.second) {
133 Logger::info(Fmt: "--> reply:{0}({1})", Vals&: ResponseHandler.first, Vals&: Id);
134 ResponseHandler.second(std::move(Id), std::move(Result));
135 } else {
136 Logger::error(
137 Fmt: "received a reply with ID {0}, but there was no such outgoing request",
138 Vals&: Id);
139 if (!Result)
140 llvm::consumeError(Err: Result.takeError());
141 }
142 return true;
143}
144
145//===----------------------------------------------------------------------===//
146// JSONTransport
147//===----------------------------------------------------------------------===//
148
149/// Encode the given error as a JSON object.
150static llvm::json::Object encodeError(llvm::Error Error) {
151 std::string Message;
152 ErrorCode Code = ErrorCode::UnknownErrorCode;
153 auto HandlerFn = [&](const LSPError &LspError) -> llvm::Error {
154 Message = LspError.message;
155 Code = LspError.code;
156 return llvm::Error::success();
157 };
158 if (llvm::Error Unhandled = llvm::handleErrors(E: std::move(Error), Hs&: HandlerFn))
159 Message = llvm::toString(E: std::move(Unhandled));
160
161 return llvm::json::Object{
162 {.K: "message", .V: std::move(Message)},
163 {.K: "code", .V: int64_t(Code)},
164 };
165}
166
167/// Decode the given JSON object into an error.
168llvm::Error decodeError(const llvm::json::Object &O) {
169 StringRef Msg = O.getString(K: "message").value_or(u: "Unspecified error");
170 if (std::optional<int64_t> Code = O.getInteger(K: "code"))
171 return llvm::make_error<LSPError>(Args: Msg.str(), Args: ErrorCode(*Code));
172 return llvm::make_error<llvm::StringError>(Args: llvm::inconvertibleErrorCode(),
173 Args: Msg.str());
174}
175
176void JSONTransport::notify(StringRef Method, llvm::json::Value Params) {
177 sendMessage(Msg: llvm::json::Object{
178 {.K: "jsonrpc", .V: "2.0"},
179 {.K: "method", .V: Method},
180 {.K: "params", .V: std::move(Params)},
181 });
182}
183void JSONTransport::call(StringRef Method, llvm::json::Value Params,
184 llvm::json::Value Id) {
185 sendMessage(Msg: llvm::json::Object{
186 {.K: "jsonrpc", .V: "2.0"},
187 {.K: "id", .V: std::move(Id)},
188 {.K: "method", .V: Method},
189 {.K: "params", .V: std::move(Params)},
190 });
191}
192void JSONTransport::reply(llvm::json::Value Id,
193 llvm::Expected<llvm::json::Value> Result) {
194 if (Result) {
195 return sendMessage(Msg: llvm::json::Object{
196 {.K: "jsonrpc", .V: "2.0"},
197 {.K: "id", .V: std::move(Id)},
198 {.K: "result", .V: std::move(*Result)},
199 });
200 }
201
202 sendMessage(Msg: llvm::json::Object{
203 {.K: "jsonrpc", .V: "2.0"},
204 {.K: "id", .V: std::move(Id)},
205 {.K: "error", .V: encodeError(Error: Result.takeError())},
206 });
207}
208
209llvm::Error JSONTransport::run(MessageHandler &Handler) {
210 std::string Json;
211 while (!In->isEndOfInput()) {
212 if (In->hasError()) {
213 return llvm::errorCodeToError(
214 EC: std::error_code(errno, std::system_category()));
215 }
216
217 if (succeeded(Result: In->readMessage(Json))) {
218 if (llvm::Expected<llvm::json::Value> Doc = llvm::json::parse(JSON: Json)) {
219 if (!handleMessage(Msg: std::move(*Doc), Handler))
220 return llvm::Error::success();
221 } else {
222 Logger::error(Fmt: "JSON parse error: {0}", Vals: llvm::toString(E: Doc.takeError()));
223 }
224 }
225 }
226 return llvm::errorCodeToError(EC: std::make_error_code(e: std::errc::io_error));
227}
228
229void JSONTransport::sendMessage(llvm::json::Value Msg) {
230 OutputBuffer.clear();
231 llvm::raw_svector_ostream os(OutputBuffer);
232 os << llvm::formatv(Fmt: PrettyOutput ? "{0:2}\n" : "{0}", Vals&: Msg);
233 Out << "Content-Length: " << OutputBuffer.size() << "\r\n\r\n"
234 << OutputBuffer;
235 Out.flush();
236 Logger::debug(Fmt: ">>> {0}\n", Vals&: OutputBuffer);
237}
238
239bool JSONTransport::handleMessage(llvm::json::Value Msg,
240 MessageHandler &Handler) {
241 // Message must be an object with "jsonrpc":"2.0".
242 llvm::json::Object *Object = Msg.getAsObject();
243 if (!Object ||
244 Object->getString(K: "jsonrpc") != std::optional<StringRef>("2.0"))
245 return false;
246
247 // `id` may be any JSON value. If absent, this is a notification.
248 std::optional<llvm::json::Value> Id;
249 if (llvm::json::Value *I = Object->get(K: "id"))
250 Id = std::move(*I);
251 std::optional<StringRef> Method = Object->getString(K: "method");
252
253 // This is a response.
254 if (!Method) {
255 if (!Id)
256 return false;
257 if (auto *Err = Object->getObject(K: "error"))
258 return Handler.onReply(Id: std::move(*Id), Result: decodeError(O: *Err));
259 // result should be given, use null if not.
260 llvm::json::Value Result = nullptr;
261 if (llvm::json::Value *R = Object->get(K: "result"))
262 Result = std::move(*R);
263 return Handler.onReply(Id: std::move(*Id), Result: std::move(Result));
264 }
265
266 // Params should be given, use null if not.
267 llvm::json::Value Params = nullptr;
268 if (llvm::json::Value *P = Object->get(K: "params"))
269 Params = std::move(*P);
270
271 if (Id)
272 return Handler.onCall(Method: *Method, Params: std::move(Params), Id: std::move(*Id));
273 return Handler.onNotify(Method: *Method, Value: std::move(Params));
274}
275
276/// Tries to read a line up to and including \n.
277/// If failing, feof(), ferror(), or shutdownRequested() will be set.
278LogicalResult readLine(std::FILE *In, SmallVectorImpl<char> &Out) {
279 // Big enough to hold any reasonable header line. May not fit content lines
280 // in delimited mode, but performance doesn't matter for that mode.
281 static constexpr int BufSize = 128;
282 size_t Size = 0;
283 Out.clear();
284 for (;;) {
285 Out.resize_for_overwrite(N: Size + BufSize);
286 if (!std::fgets(s: &Out[Size], n: BufSize, stream: In))
287 return failure();
288
289 clearerr(stream: In);
290
291 // If the line contained null bytes, anything after it (including \n) will
292 // be ignored. Fortunately this is not a legal header or JSON.
293 size_t Read = std::strlen(s: &Out[Size]);
294 if (Read > 0 && Out[Size + Read - 1] == '\n') {
295 Out.resize(N: Size + Read);
296 return success();
297 }
298 Size += Read;
299 }
300}
301
302// Returns std::nullopt when:
303// - ferror(), feof(), or shutdownRequested() are set.
304// - Content-Length is missing or empty (protocol error)
305LogicalResult
306JSONTransportInputOverFile::readStandardMessage(std::string &Json) {
307 // A Language Server Protocol message starts with a set of HTTP headers,
308 // delimited by \r\n, and terminated by an empty line (\r\n).
309 unsigned long long ContentLength = 0;
310 llvm::SmallString<128> Line;
311 while (true) {
312 if (feof(stream: In) || hasError() || failed(Result: readLine(In, Out&: Line)))
313 return failure();
314
315 // Content-Length is a mandatory header, and the only one we handle.
316 StringRef LineRef = Line;
317 if (LineRef.consume_front(Prefix: "Content-Length: ")) {
318 llvm::getAsUnsignedInteger(Str: LineRef.trim(), Radix: 0, Result&: ContentLength);
319 } else if (!LineRef.trim().empty()) {
320 // It's another header, ignore it.
321 continue;
322 } else {
323 // An empty line indicates the end of headers. Go ahead and read the JSON.
324 break;
325 }
326 }
327
328 // The fuzzer likes crashing us by sending "Content-Length: 9999999999999999"
329 if (ContentLength == 0 || ContentLength > 1 << 30)
330 return failure();
331
332 Json.resize(n: ContentLength);
333 for (size_t Pos = 0, Read; Pos < ContentLength; Pos += Read) {
334 Read = std::fread(ptr: &Json[Pos], size: 1, n: ContentLength - Pos, stream: In);
335 if (Read == 0)
336 return failure();
337
338 // If we're done, the error was transient. If we're not done, either it was
339 // transient or we'll see it again on retry.
340 clearerr(stream: In);
341 Pos += Read;
342 }
343 return success();
344}
345
346/// For lit tests we support a simplified syntax:
347/// - messages are delimited by '// -----' on a line by itself
348/// - lines starting with // are ignored.
349/// This is a testing path, so favor simplicity over performance here.
350/// When returning failure: feof(), ferror(), or shutdownRequested() will be
351/// set.
352LogicalResult
353JSONTransportInputOverFile::readDelimitedMessage(std::string &Json) {
354 Json.clear();
355 llvm::SmallString<128> Line;
356 while (succeeded(Result: readLine(In, Out&: Line))) {
357 StringRef LineRef = Line.str().trim();
358 if (LineRef.starts_with(Prefix: "//")) {
359 // Found a delimiter for the message.
360 if (LineRef == "// -----")
361 break;
362 continue;
363 }
364
365 Json += Line;
366 }
367
368 return failure(IsFailure: ferror(stream: In));
369}
370