MLIR  22.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 
13 #include "llvm/ADT/SmallString.h"
14 #include "llvm/Support/Error.h"
15 #include <optional>
16 #include <system_error>
17 #include <utility>
18 
19 using namespace mlir;
20 using namespace mlir::lsp;
21 
22 //===----------------------------------------------------------------------===//
23 // Reply
24 //===----------------------------------------------------------------------===//
25 
26 namespace {
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
31 class Reply {
32 public:
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 
42 private:
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 
51 Reply::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 
56 Reply::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 
63 void 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 bool MessageHandler::onNotify(llvm::StringRef method, llvm::json::Value value) {
87  Logger::info("--> {0}", 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(method);
95  if (it != notificationHandlers.end())
96  it->second(std::move(value));
97  }
98  return true;
99 }
100 
101 bool MessageHandler::onCall(llvm::StringRef method, llvm::json::Value params,
102  llvm::json::Value id) {
103  Logger::info("--> {0}({1})", method, id);
104 
105  Reply reply(id, method, transport, transportOutputMutex);
106 
107  auto it = methodHandlers.find(method);
108  if (it != methodHandlers.end()) {
109  it->second(std::move(params), std::move(reply));
110  } else {
111  reply(llvm::make_error<LSPError>("method not found: " + method.str(),
113  }
114  return true;
115 }
116 
117 bool MessageHandler::onReply(llvm::json::Value id,
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(debugString(id));
125  if (it != responseHandlers.end()) {
126  responseHandler = std::move(it->second);
127  responseHandlers.erase(it);
128  }
129  }
130 
131  // If we found a response handler, invoke it. Otherwise, log an error.
132  if (responseHandler.second) {
133  Logger::info("--> reply:{0}({1})", responseHandler.first, id);
134  responseHandler.second(std::move(id), std::move(result));
135  } else {
137  "received a reply with ID {0}, but there was no such outgoing request",
138  id);
139  if (!result)
140  llvm::consumeError(result.takeError());
141  }
142  return true;
143 }
144 
145 //===----------------------------------------------------------------------===//
146 // JSONTransport
147 //===----------------------------------------------------------------------===//
148 
149 /// Encode the given error as a JSON object.
150 static llvm::json::Object encodeError(llvm::Error error) {
151  std::string message;
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(std::move(error), handlerFn))
159  message = llvm::toString(std::move(unhandled));
160 
161  return llvm::json::Object{
162  {"message", std::move(message)},
163  {"code", int64_t(code)},
164  };
165 }
166 
167 /// Decode the given JSON object into an error.
168 llvm::Error decodeError(const llvm::json::Object &o) {
169  StringRef msg = o.getString("message").value_or("Unspecified error");
170  if (std::optional<int64_t> code = o.getInteger("code"))
171  return llvm::make_error<LSPError>(msg.str(), ErrorCode(*code));
172  return llvm::make_error<llvm::StringError>(llvm::inconvertibleErrorCode(),
173  msg.str());
174 }
175 
176 void JSONTransport::notify(StringRef method, llvm::json::Value params) {
177  sendMessage(llvm::json::Object{
178  {"jsonrpc", "2.0"},
179  {"method", method},
180  {"params", std::move(params)},
181  });
182 }
183 void JSONTransport::call(StringRef method, llvm::json::Value params,
184  llvm::json::Value id) {
185  sendMessage(llvm::json::Object{
186  {"jsonrpc", "2.0"},
187  {"id", std::move(id)},
188  {"method", method},
189  {"params", std::move(params)},
190  });
191 }
192 void JSONTransport::reply(llvm::json::Value id,
194  if (result) {
195  return sendMessage(llvm::json::Object{
196  {"jsonrpc", "2.0"},
197  {"id", std::move(id)},
198  {"result", std::move(*result)},
199  });
200  }
201 
202  sendMessage(llvm::json::Object{
203  {"jsonrpc", "2.0"},
204  {"id", std::move(id)},
205  {"error", encodeError(result.takeError())},
206  });
207 }
208 
209 llvm::Error JSONTransport::run(MessageHandler &handler) {
210  std::string json;
211  while (!in->isEndOfInput()) {
212  if (in->hasError()) {
213  return llvm::errorCodeToError(
214  std::error_code(errno, std::system_category()));
215  }
216 
217  if (succeeded(in->readMessage(json))) {
219  if (!handleMessage(std::move(*doc), handler))
220  return llvm::Error::success();
221  } else {
222  Logger::error("JSON parse error: {0}", llvm::toString(doc.takeError()));
223  }
224  }
225  }
226  return llvm::errorCodeToError(std::make_error_code(std::errc::io_error));
227 }
228 
229 void JSONTransport::sendMessage(llvm::json::Value msg) {
230  outputBuffer.clear();
231  llvm::raw_svector_ostream os(outputBuffer);
232  os << llvm::formatv(prettyOutput ? "{0:2}\n" : "{0}", msg);
233  out << "Content-Length: " << outputBuffer.size() << "\r\n\r\n"
234  << outputBuffer;
235  out.flush();
236  Logger::debug(">>> {0}\n", outputBuffer);
237 }
238 
239 bool 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("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("id"))
250  id = std::move(*i);
251  std::optional<StringRef> method = object->getString("method");
252 
253  // This is a response.
254  if (!method) {
255  if (!id)
256  return false;
257  if (auto *err = object->getObject("error"))
258  return handler.onReply(std::move(*id), decodeError(*err));
259  // result should be given, use null if not.
260  llvm::json::Value result = nullptr;
261  if (llvm::json::Value *r = object->get("result"))
262  result = std::move(*r);
263  return handler.onReply(std::move(*id), 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("params"))
269  params = std::move(*p);
270 
271  if (id)
272  return handler.onCall(*method, std::move(params), std::move(*id));
273  return handler.onNotify(*method, 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.
278 LogicalResult 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(size + bufSize);
286  if (!std::fgets(&out[size], bufSize, in))
287  return failure();
288 
289  clearerr(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(&out[size]);
294  if (read > 0 && out[size + read - 1] == '\n') {
295  out.resize(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)
305 LogicalResult
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;
311  while (true) {
312  if (feof(in) || hasError() || failed(readLine(in, 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("Content-Length: ")) {
318  llvm::getAsUnsignedInteger(lineRef.trim(), 0, 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(contentLength);
333  for (size_t pos = 0, read; pos < contentLength; pos += read) {
334  read = std::fread(&json[pos], 1, contentLength - pos, 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(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.
352 LogicalResult
354  json.clear();
356  while (succeeded(readLine(in, line))) {
357  StringRef lineRef = line.str().trim();
358  if (lineRef.starts_with("//")) {
359  // Found a delimiter for the message.
360  if (lineRef == kDefaultSplitMarker)
361  break;
362  continue;
363  }
364 
365  json += line;
366  }
367 
368  return failure(ferror(in));
369 }
static std::string toString(bytecode::Section::ID sectionID)
Stringify the given section ID.
static llvm::json::Object encodeError(llvm::Error error)
Encode the given error as a JSON object.
Definition: Transport.cpp:150
llvm::Error decodeError(const llvm::json::Object &o)
Decode the given JSON object into an error.
Definition: Transport.cpp:168
LogicalResult readLine(std::FILE *in, SmallVectorImpl< char > &out)
Tries to read a line up to and including .
Definition: Transport.cpp:278
LogicalResult readDelimitedMessage(std::string &json) final
For lit tests we support a simplified syntax:
Definition: Transport.cpp:353
LogicalResult readStandardMessage(std::string &json) final
Definition: Transport.cpp:306
A transport class that performs the JSON-RPC communication with the LSP client.
Definition: Transport.h:88
void notify(StringRef method, llvm::json::Value params)
The following methods are used to send a message to the LSP client.
Definition: Transport.cpp:176
void call(StringRef method, llvm::json::Value params, llvm::json::Value id)
Definition: Transport.cpp:183
llvm::Error run(MessageHandler &handler)
Start executing the JSON-RPC transport.
Definition: Transport.cpp:209
void reply(llvm::json::Value id, llvm::Expected< llvm::json::Value > result)
Definition: Transport.cpp:192
This class models an LSP error as an llvm::Error.
Definition: Protocol.h:75
static void debug(const char *fmt, Ts &&...vals)
Initiate a log message at various severity levels.
Definition: Logging.h:34
static void info(const char *fmt, Ts &&...vals)
Definition: Logging.h:38
static void error(const char *fmt, Ts &&...vals)
Definition: Logging.h:42
A handler used to process the incoming transport messages.
Definition: Transport.h:152
bool onCall(StringRef method, llvm::json::Value params, llvm::json::Value id)
Definition: Transport.cpp:101
void method(llvm::StringLiteral method, ThisT *thisPtr, void(ThisT::*handler)(const Param &, Callback< Result >))
Definition: Transport.h:181
bool onReply(llvm::json::Value id, llvm::Expected< llvm::json::Value > result)
Definition: Transport.cpp:117
bool onNotify(StringRef method, llvm::json::Value value)
Definition: Transport.cpp:86
QueryRef parse(llvm::StringRef line, const QuerySession &qs)
Definition: Query.cpp:21
Include the generated interface declarations.
const char *const kDefaultSplitMarker
static std::string debugString(T &&op)