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
86// Keep handleParseError out of every parse<T> instantiation.
87LLVM_ATTRIBUTE_NOINLINE llvm::Error
88MessageHandler::handleParseError(const llvm::json::Value &Raw,
89 StringRef PayloadName, StringRef PayloadKind,
90 const llvm::json::Path::Root &Root) {
91 // Dump the relevant parts of the broken message.
92 std::string Context;
93 llvm::raw_string_ostream Os(Context);
94 Root.printErrorContext(Raw, Os);
95
96 // Report the error (e.g. to the client).
97 return llvm::make_error<LSPError>(
98 Args: llvm::formatv(Fmt: "failed to decode {0} {1}: {2}", Vals&: PayloadName, Vals&: PayloadKind,
99 Vals: fmt_consume(Item: Root.getError())),
100 Args: ErrorCode::InvalidParams);
101}
102
103bool MessageHandler::onNotify(llvm::StringRef Method, llvm::json::Value Value) {
104 Logger::info(Fmt: "--> {0}", Vals&: Method);
105
106 if (Method == "exit")
107 return false;
108 if (Method == "$cancel") {
109 // TODO: Add support for cancelling requests.
110 } else {
111 auto It = NotificationHandlers.find(Key: Method);
112 if (It != NotificationHandlers.end())
113 It->second(std::move(Value));
114 }
115 return true;
116}
117
118bool MessageHandler::onCall(llvm::StringRef Method, llvm::json::Value Params,
119 llvm::json::Value Id) {
120 Logger::info(Fmt: "--> {0}({1})", Vals&: Method, Vals&: Id);
121
122 Reply Reply(Id, Method, Transport, TransportOutputMutex);
123
124 auto It = MethodHandlers.find(Key: Method);
125 if (It != MethodHandlers.end()) {
126 It->second(std::move(Params), std::move(Reply));
127 } else {
128 Reply(llvm::make_error<LSPError>(Args: "method not found: " + Method.str(),
129 Args: ErrorCode::MethodNotFound));
130 }
131 return true;
132}
133
134bool MessageHandler::onReply(llvm::json::Value Id,
135 llvm::Expected<llvm::json::Value> Result) {
136 // Find the response handler in the mapping. If it exists, move it out of the
137 // mapping and erase it.
138 ResponseHandlerTy ResponseHandler;
139 {
140 std::lock_guard<std::mutex> responseHandlersLock(ResponseHandlersMutex);
141 auto It = ResponseHandlers.find(Key: debugString(Op&: Id));
142 if (It != ResponseHandlers.end()) {
143 ResponseHandler = std::move(It->second);
144 ResponseHandlers.erase(I: It);
145 }
146 }
147
148 // If we found a response handler, invoke it. Otherwise, log an error.
149 if (ResponseHandler.second) {
150 Logger::info(Fmt: "--> reply:{0}({1})", Vals&: ResponseHandler.first, Vals&: Id);
151 ResponseHandler.second(std::move(Id), std::move(Result));
152 } else {
153 Logger::error(
154 Fmt: "received a reply with ID {0}, but there was no such outgoing request",
155 Vals&: Id);
156 if (!Result)
157 llvm::consumeError(Err: Result.takeError());
158 }
159 return true;
160}
161
162//===----------------------------------------------------------------------===//
163// JSONTransport
164//===----------------------------------------------------------------------===//
165
166/// Encode the given error as a JSON object.
167static llvm::json::Object encodeError(llvm::Error Error) {
168 std::string Message;
169 ErrorCode Code = ErrorCode::UnknownErrorCode;
170 auto HandlerFn = [&](const LSPError &LspError) -> llvm::Error {
171 Message = LspError.message;
172 Code = LspError.code;
173 return llvm::Error::success();
174 };
175 if (llvm::Error Unhandled = llvm::handleErrors(E: std::move(Error), Hs&: HandlerFn))
176 Message = llvm::toString(E: std::move(Unhandled));
177
178 return llvm::json::Object{
179 {.K: "message", .V: std::move(Message)},
180 {.K: "code", .V: int64_t(Code)},
181 };
182}
183
184/// Decode the given JSON object into an error.
185llvm::Error decodeError(const llvm::json::Object &O) {
186 StringRef Msg = O.getString(K: "message").value_or(u: "Unspecified error");
187 if (std::optional<int64_t> Code = O.getInteger(K: "code"))
188 return llvm::make_error<LSPError>(Args: Msg.str(), Args: ErrorCode(*Code));
189 return llvm::make_error<llvm::StringError>(Args: llvm::inconvertibleErrorCode(),
190 Args: Msg.str());
191}
192
193void JSONTransport::notify(StringRef Method, llvm::json::Value Params) {
194 sendMessage(Msg: llvm::json::Object{
195 {.K: "jsonrpc", .V: "2.0"},
196 {.K: "method", .V: Method},
197 {.K: "params", .V: std::move(Params)},
198 });
199}
200void JSONTransport::call(StringRef Method, llvm::json::Value Params,
201 llvm::json::Value Id) {
202 sendMessage(Msg: llvm::json::Object{
203 {.K: "jsonrpc", .V: "2.0"},
204 {.K: "id", .V: std::move(Id)},
205 {.K: "method", .V: Method},
206 {.K: "params", .V: std::move(Params)},
207 });
208}
209void JSONTransport::reply(llvm::json::Value Id,
210 llvm::Expected<llvm::json::Value> Result) {
211 if (Result) {
212 return sendMessage(Msg: llvm::json::Object{
213 {.K: "jsonrpc", .V: "2.0"},
214 {.K: "id", .V: std::move(Id)},
215 {.K: "result", .V: std::move(*Result)},
216 });
217 }
218
219 sendMessage(Msg: llvm::json::Object{
220 {.K: "jsonrpc", .V: "2.0"},
221 {.K: "id", .V: std::move(Id)},
222 {.K: "error", .V: encodeError(Error: Result.takeError())},
223 });
224}
225
226llvm::Error JSONTransport::run(MessageHandler &Handler) {
227 std::string Json;
228 while (!In->isEndOfInput()) {
229 if (In->hasError()) {
230 return llvm::errorCodeToError(
231 EC: std::error_code(errno, std::system_category()));
232 }
233
234 if (succeeded(Result: In->readMessage(Json))) {
235 if (llvm::Expected<llvm::json::Value> Doc = llvm::json::parse(JSON: Json)) {
236 if (!handleMessage(Msg: std::move(*Doc), Handler))
237 return llvm::Error::success();
238 } else {
239 Logger::error(Fmt: "JSON parse error: {0}", Vals: llvm::toString(E: Doc.takeError()));
240 }
241 }
242 }
243 return llvm::errorCodeToError(EC: std::make_error_code(e: std::errc::io_error));
244}
245
246void JSONTransport::sendMessage(llvm::json::Value Msg) {
247 OutputBuffer.clear();
248 llvm::raw_svector_ostream os(OutputBuffer);
249 os << llvm::formatv(Fmt: PrettyOutput ? "{0:2}\n" : "{0}", Vals&: Msg);
250 Out << "Content-Length: " << OutputBuffer.size() << "\r\n\r\n"
251 << OutputBuffer;
252 Out.flush();
253 Logger::debug(Fmt: ">>> {0}\n", Vals&: OutputBuffer);
254}
255
256bool JSONTransport::handleMessage(llvm::json::Value Msg,
257 MessageHandler &Handler) {
258 // Message must be an object with "jsonrpc":"2.0".
259 llvm::json::Object *Object = Msg.getAsObject();
260 if (!Object ||
261 Object->getString(K: "jsonrpc") != std::optional<StringRef>("2.0"))
262 return false;
263
264 // `id` may be any JSON value. If absent, this is a notification.
265 std::optional<llvm::json::Value> Id;
266 if (llvm::json::Value *I = Object->get(K: "id"))
267 Id = std::move(*I);
268 std::optional<StringRef> Method = Object->getString(K: "method");
269
270 // This is a response.
271 if (!Method) {
272 if (!Id)
273 return false;
274 if (auto *Err = Object->getObject(K: "error"))
275 return Handler.onReply(Id: std::move(*Id), Result: decodeError(O: *Err));
276 // result should be given, use null if not.
277 llvm::json::Value Result = nullptr;
278 if (llvm::json::Value *R = Object->get(K: "result"))
279 Result = std::move(*R);
280 return Handler.onReply(Id: std::move(*Id), Result: std::move(Result));
281 }
282
283 // Params should be given, use null if not.
284 llvm::json::Value Params = nullptr;
285 if (llvm::json::Value *P = Object->get(K: "params"))
286 Params = std::move(*P);
287
288 if (Id)
289 return Handler.onCall(Method: *Method, Params: std::move(Params), Id: std::move(*Id));
290 return Handler.onNotify(Method: *Method, Value: std::move(Params));
291}
292
293/// Tries to read a line up to and including \n.
294/// If failing, feof(), ferror(), or shutdownRequested() will be set.
295LogicalResult readLine(std::FILE *In, SmallVectorImpl<char> &Out) {
296 // Big enough to hold any reasonable header line. May not fit content lines
297 // in delimited mode, but performance doesn't matter for that mode.
298 static constexpr int BufSize = 128;
299 size_t Size = 0;
300 Out.clear();
301 for (;;) {
302 Out.resize_for_overwrite(N: Size + BufSize);
303 if (!std::fgets(s: &Out[Size], n: BufSize, stream: In))
304 return failure();
305
306 clearerr(stream: In);
307
308 // If the line contained null bytes, anything after it (including \n) will
309 // be ignored. Fortunately this is not a legal header or JSON.
310 size_t Read = std::strlen(s: &Out[Size]);
311 if (Read > 0 && Out[Size + Read - 1] == '\n') {
312 Out.resize(N: Size + Read);
313 return success();
314 }
315 Size += Read;
316 }
317}
318
319// Returns std::nullopt when:
320// - ferror(), feof(), or shutdownRequested() are set.
321// - Content-Length is missing or empty (protocol error)
322LogicalResult
323JSONTransportInputOverFile::readStandardMessage(std::string &Json) {
324 // A Language Server Protocol message starts with a set of HTTP headers,
325 // delimited by \r\n, and terminated by an empty line (\r\n).
326 unsigned long long ContentLength = 0;
327 llvm::SmallString<128> Line;
328 while (true) {
329 if (feof(stream: In) || hasError() || failed(Result: readLine(In, Out&: Line)))
330 return failure();
331
332 // Content-Length is a mandatory header, and the only one we handle.
333 StringRef LineRef = Line;
334 if (LineRef.consume_front(Prefix: "Content-Length: ")) {
335 llvm::getAsUnsignedInteger(Str: LineRef.trim(), Radix: 0, Result&: ContentLength);
336 } else if (!LineRef.trim().empty()) {
337 // It's another header, ignore it.
338 continue;
339 } else {
340 // An empty line indicates the end of headers. Go ahead and read the JSON.
341 break;
342 }
343 }
344
345 // The fuzzer likes crashing us by sending "Content-Length: 9999999999999999"
346 if (ContentLength == 0 || ContentLength > 1 << 30)
347 return failure();
348
349 Json.resize(n: ContentLength);
350 for (size_t Pos = 0, Read; Pos < ContentLength; Pos += Read) {
351 Read = std::fread(ptr: &Json[Pos], size: 1, n: ContentLength - Pos, stream: In);
352 if (Read == 0)
353 return failure();
354
355 // If we're done, the error was transient. If we're not done, either it was
356 // transient or we'll see it again on retry.
357 clearerr(stream: In);
358 Pos += Read;
359 }
360 return success();
361}
362
363/// For lit tests we support a simplified syntax:
364/// - messages are delimited by '// -----' on a line by itself
365/// - lines starting with // are ignored.
366/// This is a testing path, so favor simplicity over performance here.
367/// When returning failure: feof(), ferror(), or shutdownRequested() will be
368/// set.
369LogicalResult
370JSONTransportInputOverFile::readDelimitedMessage(std::string &Json) {
371 Json.clear();
372 llvm::SmallString<128> Line;
373 while (succeeded(Result: readLine(In, Out&: Line))) {
374 StringRef LineRef = Line.str().trim();
375 if (LineRef.starts_with(Prefix: "//")) {
376 // Found a delimiter for the message.
377 if (LineRef == "// -----")
378 break;
379 continue;
380 }
381
382 Json += Line;
383 }
384
385 return failure(IsFailure: ferror(stream: In));
386}
387