LLVM 24.0.0git
Transport.cpp
Go to the documentation of this file.
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
11#include "llvm/Support/Error.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(true)) {
65 Logger::error("Replied twice to message {0}({1})", Method, 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("--> reply:{0}({1})", Method, Id);
74 Transport->reply(std::move(Id), std::move(Reply));
75 } else {
76 llvm::Error Error = Reply.takeError();
77 Logger::info("--> reply:{0}({1}): {2}", Method, Id, Error);
78 Transport->reply(std::move(Id), std::move(Error));
79 }
80}
81
82//===----------------------------------------------------------------------===//
83// MessageHandler
84//===----------------------------------------------------------------------===//
85
86// Keep handleParseError out of every parse<T> instantiation.
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).
98 llvm::formatv("failed to decode {0} {1}: {2}", PayloadName, PayloadKind,
99 fmt_consume(Root.getError())),
100 ErrorCode::InvalidParams);
101}
102
104 Logger::info("--> {0}", 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(Method);
112 if (It != NotificationHandlers.end())
113 It->second(std::move(Value));
114 }
115 return true;
116}
117
120 Logger::info("--> {0}({1})", Method, Id);
121
122 Reply Reply(Id, Method, Transport, TransportOutputMutex);
123
124 auto It = MethodHandlers.find(Method);
125 if (It != MethodHandlers.end()) {
126 It->second(std::move(Params), std::move(Reply));
127 } else {
128 Reply(llvm::make_error<LSPError>("method not found: " + Method.str(),
130 }
131 return true;
132}
133
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(debugString(Id));
142 if (It != ResponseHandlers.end()) {
143 ResponseHandler = std::move(It->second);
144 ResponseHandlers.erase(It);
145 }
146 }
147
148 // If we found a response handler, invoke it. Otherwise, log an error.
149 if (ResponseHandler.second) {
150 Logger::info("--> reply:{0}({1})", ResponseHandler.first, Id);
151 ResponseHandler.second(std::move(Id), std::move(Result));
152 } else {
154 "received a reply with ID {0}, but there was no such outgoing request",
155 Id);
156 if (!Result)
157 llvm::consumeError(Result.takeError());
158 }
159 return true;
160}
161
162//===----------------------------------------------------------------------===//
163// JSONTransport
164//===----------------------------------------------------------------------===//
165
166/// Encode the given error as a JSON object.
168 std::string Message;
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(std::move(Error), HandlerFn))
176 Message = llvm::toString(std::move(Unhandled));
177
178 return llvm::json::Object{
179 {"message", std::move(Message)},
180 {"code", int64_t(Code)},
181 };
182}
183
184/// Decode the given JSON object into an error.
186 StringRef Msg = O.getString("message").value_or("Unspecified error");
187 if (std::optional<int64_t> Code = O.getInteger("code"))
188 return llvm::make_error<LSPError>(Msg.str(), ErrorCode(*Code));
190 Msg.str());
191}
192
194 sendMessage(llvm::json::Object{
195 {"jsonrpc", "2.0"},
196 {"method", Method},
197 {"params", std::move(Params)},
198 });
199}
202 sendMessage(llvm::json::Object{
203 {"jsonrpc", "2.0"},
204 {"id", std::move(Id)},
205 {"method", Method},
206 {"params", std::move(Params)},
207 });
208}
211 if (Result) {
212 return sendMessage(llvm::json::Object{
213 {"jsonrpc", "2.0"},
214 {"id", std::move(Id)},
215 {"result", std::move(*Result)},
216 });
217 }
218
219 sendMessage(llvm::json::Object{
220 {"jsonrpc", "2.0"},
221 {"id", std::move(Id)},
222 {"error", encodeError(Result.takeError())},
223 });
224}
225
227 std::string Json;
228 while (!In->isEndOfInput()) {
229 if (In->hasError()) {
231 std::error_code(errno, std::system_category()));
232 }
233
234 if (succeeded(In->readMessage(Json))) {
236 if (!handleMessage(std::move(*Doc), Handler))
237 return llvm::Error::success();
238 } else {
239 Logger::error("JSON parse error: {0}", llvm::toString(Doc.takeError()));
240 }
241 }
242 }
243 return llvm::errorCodeToError(std::make_error_code(std::errc::io_error));
244}
245
246void JSONTransport::sendMessage(llvm::json::Value Msg) {
247 OutputBuffer.clear();
249 os << llvm::formatv(PrettyOutput ? "{0:2}\n" : "{0}", Msg);
250 Out << "Content-Length: " << OutputBuffer.size() << "\r\n\r\n"
251 << OutputBuffer;
252 Out.flush();
253 Logger::debug(">>> {0}\n", 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("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("id"))
267 Id = std::move(*I);
268 std::optional<StringRef> Method = Object->getString("method");
269
270 // This is a response.
271 if (!Method) {
272 if (!Id)
273 return false;
274 if (auto *Err = Object->getObject("error"))
275 return Handler.onReply(std::move(*Id), decodeError(*Err));
276 // result should be given, use null if not.
277 llvm::json::Value Result = nullptr;
278 if (llvm::json::Value *R = Object->get("result"))
279 Result = std::move(*R);
280 return Handler.onReply(std::move(*Id), 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("params"))
286 Params = std::move(*P);
287
288 if (Id)
289 return Handler.onCall(*Method, std::move(Params), std::move(*Id));
290 return Handler.onNotify(*Method, 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.
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(Size + BufSize);
303 if (!std::fgets(&Out[Size], BufSize, In))
304 return failure();
305
306 clearerr(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(&Out[Size]);
311 if (Read > 0 && Out[Size + Read - 1] == '\n') {
312 Out.resize(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
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;
328 while (true) {
329 if (feof(In) || hasError() || failed(readLine(In, 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("Content-Length: ")) {
335 llvm::getAsUnsignedInteger(LineRef.trim(), 0, 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(ContentLength);
350 for (size_t Pos = 0, Read; Pos < ContentLength; Pos += Read) {
351 Read = std::fread(&Json[Pos], 1, ContentLength - Pos, 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(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.
371 Json.clear();
373 while (succeeded(readLine(In, Line))) {
374 StringRef LineRef = Line.str().trim();
375 if (LineRef.starts_with("//")) {
376 // Found a delimiter for the message.
377 if (LineRef == "// -----")
378 break;
379 continue;
380 }
381
382 Json += Line;
383 }
384
385 return failure(ferror(In));
386}
assert(UImm &&(UImm !=~static_cast< T >(0)) &&"Invalid immediate!")
AMDGPU Mark last scratch load
#define LLVM_ATTRIBUTE_NOINLINE
LLVM_ATTRIBUTE_NOINLINE - On compilers where we have a directive to do so, mark a method "not for inl...
Definition Compiler.h:348
#define I(x, y, z)
Definition MD5.cpp:57
#define P(N)
const char * Msg
This file defines the SmallString class.
static llvm::json::Object encodeError(llvm::Error Error)
Encode the given error as a JSON object.
LogicalResult readLine(std::FILE *In, SmallVectorImpl< char > &Out)
Tries to read a line up to and including .
llvm::Error decodeError(const llvm::json::Object &O)
Decode the given JSON object into an error.
Lightweight error class with error context and mandatory checking.
Definition Error.h:159
static ErrorSuccess success()
Create a success value.
Definition Error.h:336
Tagged union holding either a T or a Error.
Definition Error.h:485
Error takeError()
Take ownership of the stored error.
Definition Error.h:612
SmallString - A SmallString is just a SmallVector with methods and accessors that make it work better...
Definition SmallString.h:26
This class consists of common code factored out of the SmallVector class to reduce code duplication b...
void resize_for_overwrite(size_type N)
Like resize, but T is POD, the new values won't be initialized.
void resize(size_type N)
Represent a constant reference to a string, i.e.
Definition StringRef.h:56
std::string str() const
Get the contents as an std::string.
Definition StringRef.h:222
bool starts_with(StringRef Prefix) const
Check if this string starts with the given Prefix.
Definition StringRef.h:258
constexpr bool empty() const
Check if the string is empty.
Definition StringRef.h:141
StringRef trim(char Char) const
Return string with consecutive Char characters starting from the left and right removed.
Definition StringRef.h:850
bool consume_front(char Prefix)
Returns true if this StringRef has the given prefix and removes that prefix.
Definition StringRef.h:661
An Object is a JSON object, which maps strings to heterogenous JSON values.
Definition JSON.h:98
LLVM_ABI void printErrorContext(const Value &, llvm::raw_ostream &) const
Print the root value with the error shown inline as a comment.
Definition JSON.cpp:306
LLVM_ABI Error getError() const
Returns the last error reported, or else a generic error.
Definition JSON.cpp:225
A Value is an JSON value of unknown type.
Definition JSON.h:291
LogicalResult readStandardMessage(std::string &Json) final
LogicalResult readDelimitedMessage(std::string &Json) final
For lit tests we support a simplified syntax:
A transport class that performs the JSON-RPC communication with the LSP client.
Definition Transport.h:94
LLVM_ABI void reply(llvm::json::Value Id, llvm::Expected< llvm::json::Value > Result)
LLVM_ABI llvm::Error run(MessageHandler &Handler)
Start executing the JSON-RPC transport.
LLVM_ABI void notify(StringRef Method, llvm::json::Value Params)
The following methods are used to send a message to the LSP client.
LLVM_ABI void call(StringRef Method, llvm::json::Value Params, llvm::json::Value Id)
This class models an LSP error as an llvm::Error.
Definition Protocol.h:80
static void error(const char *Fmt, Ts &&...Vals)
Definition Logging.h:37
static void debug(const char *Fmt, Ts &&...Vals)
Initiate a log message at various severity levels.
Definition Logging.h:31
static void info(const char *Fmt, Ts &&...Vals)
Definition Logging.h:34
A handler used to process the incoming transport messages.
Definition Transport.h:160
LLVM_ABI bool onNotify(StringRef Method, llvm::json::Value Value)
LLVM_ABI bool onCall(StringRef Method, llvm::json::Value Params, llvm::json::Value Id)
LLVM_ABI bool onReply(llvm::json::Value Id, llvm::Expected< llvm::json::Value > Result)
A raw_ostream that writes to an SmallVector or SmallString.
LLVM_ABI llvm::Expected< Value > parse(llvm::StringRef JSON)
Parses the provided JSON source, or returns a ParseError.
Definition JSON.cpp:681
This is an optimization pass for GlobalISel generic memory operations.
support::detail::ErrorAdapter fmt_consume(Error &&Item)
bool succeeded(LogicalResult Result)
Utility function that returns true if the provided LogicalResult corresponds to a success value.
bool failed(LogicalResult Result)
Utility function that returns true if the provided LogicalResult corresponds to a failure value.
LLVM_ABI std::error_code inconvertibleErrorCode()
The value returned by this function can be returned from convertToErrorCode for Error values where no...
Definition Error.cpp:94
Error handleErrors(Error E, HandlerTs &&... Hs)
Pass the ErrorInfo(s) contained in E to their respective handlers.
Definition Error.h:990
LogicalResult failure(bool IsFailure=true)
Utility function to generate a LogicalResult.
auto formatv(bool Validate, const char *Fmt, Ts &&...Vals)
static std::string debugString(T &&Op)
Definition Transport.h:31
Error make_error(ArgTs &&... Args)
Make a Error instance representing failure using the given error info type.
Definition Error.h:340
@ Other
Any other memory.
Definition ModRef.h:68
std::string toString(const APInt &I, unsigned Radix, bool Signed, bool formatAsCLiteral=false, bool UpperCase=true, bool InsertSeparators=false)
OutputIt move(R &&Range, OutputIt Out)
Provide wrappers to std::move which take ranges instead of having to pass begin/end explicitly.
Definition STLExtras.h:1917
LLVM_ABI bool getAsUnsignedInteger(StringRef Str, unsigned Radix, unsigned long long &Result)
Helper functions for StringRef::getAsInteger.
LLVM_ABI Error errorCodeToError(std::error_code EC)
Helper for converting an std::error_code to a Error.
Definition Error.cpp:107
void consumeError(Error Err)
Consume a Error without doing anything.
Definition Error.h:1106
Implement std::hash so that hash_code can be used in STL containers.
Definition BitVector.h:878
This class represents an efficient way to signal success or failure.