clang-tools  11.0.0
JSONTransport.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 #include "Protocol.h" // For LSPError
9 #include "Transport.h"
10 #include "support/Cancellation.h"
11 #include "support/Logger.h"
12 #include "support/Shutdown.h"
13 #include "llvm/Support/Errno.h"
14 #include "llvm/Support/Error.h"
15 
16 namespace clang {
17 namespace clangd {
18 namespace {
19 
20 llvm::json::Object encodeError(llvm::Error E) {
21  std::string Message;
23  // FIXME: encode cancellation errors using RequestCancelled or ContentModified
24  // as appropriate.
25  if (llvm::Error Unhandled = llvm::handleErrors(
26  std::move(E),
27  [&](const CancelledError &C) -> llvm::Error {
28  switch (C.Reason) {
29  case static_cast<int>(ErrorCode::ContentModified):
30  Code = ErrorCode::ContentModified;
31  Message = "Request cancelled because the document was modified";
32  break;
33  default:
34  Code = ErrorCode::RequestCancelled;
35  Message = "Request cancelled";
36  break;
37  }
38  return llvm::Error::success();
39  },
40  [&](const LSPError &L) -> llvm::Error {
41  Message = L.Message;
42  Code = L.Code;
43  return llvm::Error::success();
44  }))
45  Message = llvm::toString(std::move(Unhandled));
46 
47  return llvm::json::Object{
48  {"message", std::move(Message)},
49  {"code", int64_t(Code)},
50  };
51 }
52 
53 llvm::Error decodeError(const llvm::json::Object &O) {
54  std::string Msg =
55  std::string(O.getString("message").getValueOr("Unspecified error"));
56  if (auto Code = O.getInteger("code"))
57  return llvm::make_error<LSPError>(std::move(Msg), ErrorCode(*Code));
58  return llvm::make_error<llvm::StringError>(std::move(Msg),
59  llvm::inconvertibleErrorCode());
60 }
61 
62 class JSONTransport : public Transport {
63 public:
64  JSONTransport(std::FILE *In, llvm::raw_ostream &Out,
65  llvm::raw_ostream *InMirror, bool Pretty, JSONStreamStyle Style)
66  : In(In), Out(Out), InMirror(InMirror ? *InMirror : llvm::nulls()),
67  Pretty(Pretty), Style(Style) {}
68 
69  void notify(llvm::StringRef Method, llvm::json::Value Params) override {
70  sendMessage(llvm::json::Object{
71  {"jsonrpc", "2.0"},
72  {"method", Method},
73  {"params", std::move(Params)},
74  });
75  }
76  void call(llvm::StringRef Method, llvm::json::Value Params,
77  llvm::json::Value ID) override {
78  sendMessage(llvm::json::Object{
79  {"jsonrpc", "2.0"},
80  {"id", std::move(ID)},
81  {"method", Method},
82  {"params", std::move(Params)},
83  });
84  }
85  void reply(llvm::json::Value ID,
86  llvm::Expected<llvm::json::Value> Result) override {
87  if (Result) {
88  sendMessage(llvm::json::Object{
89  {"jsonrpc", "2.0"},
90  {"id", std::move(ID)},
91  {"result", std::move(*Result)},
92  });
93  } else {
94  sendMessage(llvm::json::Object{
95  {"jsonrpc", "2.0"},
96  {"id", std::move(ID)},
97  {"error", encodeError(Result.takeError())},
98  });
99  }
100  }
101 
102  llvm::Error loop(MessageHandler &Handler) override {
103  while (!feof(In)) {
104  if (shutdownRequested())
105  return llvm::createStringError(
106  std::make_error_code(std::errc::operation_canceled),
107  "Got signal, shutting down");
108  if (ferror(In))
109  return llvm::errorCodeToError(
110  std::error_code(errno, std::system_category()));
111  if (auto JSON = readRawMessage()) {
112  if (auto Doc = llvm::json::parse(*JSON)) {
113  vlog(Pretty ? "<<< {0:2}\n" : "<<< {0}\n", *Doc);
114  if (!handleMessage(std::move(*Doc), Handler))
115  return llvm::Error::success(); // we saw the "exit" notification.
116  } else {
117  // Parse error. Log the raw message.
118  vlog("<<< {0}\n", *JSON);
119  elog("JSON parse error: {0}", llvm::toString(Doc.takeError()));
120  }
121  }
122  }
123  return llvm::errorCodeToError(std::make_error_code(std::errc::io_error));
124  }
125 
126 private:
127  // Dispatches incoming message to Handler onNotify/onCall/onReply.
128  bool handleMessage(llvm::json::Value Message, MessageHandler &Handler);
129  // Writes outgoing message to Out stream.
130  void sendMessage(llvm::json::Value Message) {
131  std::string S;
132  llvm::raw_string_ostream OS(S);
133  OS << llvm::formatv(Pretty ? "{0:2}" : "{0}", Message);
134  OS.flush();
135  Out << "Content-Length: " << S.size() << "\r\n\r\n" << S;
136  Out.flush();
137  vlog(">>> {0}\n", S);
138  }
139 
140  // Read raw string messages from input stream.
141  llvm::Optional<std::string> readRawMessage() {
142  return Style == JSONStreamStyle::Delimited ? readDelimitedMessage()
143  : readStandardMessage();
144  }
145  llvm::Optional<std::string> readDelimitedMessage();
146  llvm::Optional<std::string> readStandardMessage();
147 
148  std::FILE *In;
149  llvm::raw_ostream &Out;
150  llvm::raw_ostream &InMirror;
151  bool Pretty;
152  JSONStreamStyle Style;
153 };
154 
155 bool JSONTransport::handleMessage(llvm::json::Value Message,
156  MessageHandler &Handler) {
157  // Message must be an object with "jsonrpc":"2.0".
158  auto *Object = Message.getAsObject();
159  if (!Object ||
160  Object->getString("jsonrpc") != llvm::Optional<llvm::StringRef>("2.0")) {
161  elog("Not a JSON-RPC 2.0 message: {0:2}", Message);
162  return false;
163  }
164  // ID may be any JSON value. If absent, this is a notification.
165  llvm::Optional<llvm::json::Value> ID;
166  if (auto *I = Object->get("id"))
167  ID = std::move(*I);
168  auto Method = Object->getString("method");
169  if (!Method) { // This is a response.
170  if (!ID) {
171  elog("No method and no response ID: {0:2}", Message);
172  return false;
173  }
174  if (auto *Err = Object->getObject("error"))
175  return Handler.onReply(std::move(*ID), decodeError(*Err));
176  // Result should be given, use null if not.
177  llvm::json::Value Result = nullptr;
178  if (auto *R = Object->get("result"))
179  Result = std::move(*R);
180  return Handler.onReply(std::move(*ID), std::move(Result));
181  }
182  // Params should be given, use null if not.
183  llvm::json::Value Params = nullptr;
184  if (auto *P = Object->get("params"))
185  Params = std::move(*P);
186 
187  if (ID)
188  return Handler.onCall(*Method, std::move(Params), std::move(*ID));
189  else
190  return Handler.onNotify(*Method, std::move(Params));
191 }
192 
193 // Tries to read a line up to and including \n.
194 // If failing, feof(), ferror(), or shutdownRequested() will be set.
195 bool readLine(std::FILE *In, std::string &Out) {
196  static constexpr int BufSize = 1024;
197  size_t Size = 0;
198  Out.clear();
199  for (;;) {
200  Out.resize(Size + BufSize);
201  // Handle EINTR which is sent when a debugger attaches on some platforms.
203  nullptr, [&] { return std::fgets(&Out[Size], BufSize, In); }))
204  return false;
205  clearerr(In);
206  // If the line contained null bytes, anything after it (including \n) will
207  // be ignored. Fortunately this is not a legal header or JSON.
208  size_t Read = std::strlen(&Out[Size]);
209  if (Read > 0 && Out[Size + Read - 1] == '\n') {
210  Out.resize(Size + Read);
211  return true;
212  }
213  Size += Read;
214  }
215 }
216 
217 // Returns None when:
218 // - ferror(), feof(), or shutdownRequested() are set.
219 // - Content-Length is missing or empty (protocol error)
220 llvm::Optional<std::string> JSONTransport::readStandardMessage() {
221  // A Language Server Protocol message starts with a set of HTTP headers,
222  // delimited by \r\n, and terminated by an empty line (\r\n).
223  unsigned long long ContentLength = 0;
224  std::string Line;
225  while (true) {
226  if (feof(In) || ferror(In) || !readLine(In, Line))
227  return llvm::None;
228  InMirror << Line;
229 
230  llvm::StringRef LineRef(Line);
231 
232  // We allow comments in headers. Technically this isn't part
233 
234  // of the LSP specification, but makes writing tests easier.
235  if (LineRef.startswith("#"))
236  continue;
237 
238  // Content-Length is a mandatory header, and the only one we handle.
239  if (LineRef.consume_front("Content-Length: ")) {
240  if (ContentLength != 0) {
241  elog("Warning: Duplicate Content-Length header received. "
242  "The previous value for this message ({0}) was ignored.",
243  ContentLength);
244  }
245  llvm::getAsUnsignedInteger(LineRef.trim(), 0, ContentLength);
246  continue;
247  } else if (!LineRef.trim().empty()) {
248  // It's another header, ignore it.
249  continue;
250  } else {
251  // An empty line indicates the end of headers.
252  // Go ahead and read the JSON.
253  break;
254  }
255  }
256 
257  // The fuzzer likes crashing us by sending "Content-Length: 9999999999999999"
258  if (ContentLength > 1 << 30) { // 1024M
259  elog("Refusing to read message with long Content-Length: {0}. "
260  "Expect protocol errors",
261  ContentLength);
262  return llvm::None;
263  }
264  if (ContentLength == 0) {
265  log("Warning: Missing Content-Length header, or zero-length message.");
266  return llvm::None;
267  }
268 
269  std::string JSON(ContentLength, '\0');
270  for (size_t Pos = 0, Read; Pos < ContentLength; Pos += Read) {
271  // Handle EINTR which is sent when a debugger attaches on some platforms.
273  return std::fread(&JSON[Pos], 1, ContentLength - Pos, In);
274  });
275  if (Read == 0) {
276  elog("Input was aborted. Read only {0} bytes of expected {1}.", Pos,
277  ContentLength);
278  return llvm::None;
279  }
280  InMirror << llvm::StringRef(&JSON[Pos], Read);
281  clearerr(In); // If we're done, the error was transient. If we're not done,
282  // either it was transient or we'll see it again on retry.
283  Pos += Read;
284  }
285  return std::move(JSON);
286 }
287 
288 // For lit tests we support a simplified syntax:
289 // - messages are delimited by '---' on a line by itself
290 // - lines starting with # are ignored.
291 // This is a testing path, so favor simplicity over performance here.
292 // When returning None, feof(), ferror(), or shutdownRequested() will be set.
293 llvm::Optional<std::string> JSONTransport::readDelimitedMessage() {
294  std::string JSON;
295  std::string Line;
296  while (readLine(In, Line)) {
297  InMirror << Line;
298  auto LineRef = llvm::StringRef(Line).trim();
299  if (LineRef.startswith("#")) // comment
300  continue;
301 
302  // found a delimiter
303  if (LineRef.rtrim() == "---")
304  break;
305 
306  JSON += Line;
307  }
308 
309  if (shutdownRequested())
310  return llvm::None;
311  if (ferror(In)) {
312  elog("Input error while reading message!");
313  return llvm::None;
314  }
315  return std::move(JSON); // Including at EOF
316 }
317 
318 } // namespace
319 
320 std::unique_ptr<Transport> newJSONTransport(std::FILE *In,
321  llvm::raw_ostream &Out,
322  llvm::raw_ostream *InMirror,
323  bool Pretty,
324  JSONStreamStyle Style) {
325  return std::make_unique<JSONTransport>(In, Out, InMirror, Pretty, Style);
326 }
327 
328 } // namespace clangd
329 } // namespace clang
llvm
Some operations such as code completion produce a set of candidates.
Definition: YAMLGenerator.cpp:28
Shutdown.h
E
const Expr * E
Definition: AvoidBindCheck.cpp:88
clang::clangd::SymbolKind::Object
clang::tidy::bugprone::Message
static const char Message[]
Definition: ReservedIdentifierCheck.cpp:31
clang::clangd::TextDocumentSyncKind::None
Documents should not be synced at all.
clang::clangd::ErrorCode
ErrorCode
Definition: Protocol.h:40
Cancellation.h
Protocol.h
Code
std::string Code
Definition: FindTargetTests.cpp:67
clang::clangd::retryAfterSignalUnlessShutdown
Ret retryAfterSignalUnlessShutdown(const std::enable_if_t< true, Ret > &Fail, const Fun &F)
Retry an operation if it gets interrupted by a signal.
Definition: Shutdown.h:68
clang::clangd::JSONStreamStyle
JSONStreamStyle
Definition: Transport.h:65
clang::tidy::handleErrors
void handleErrors(llvm::ArrayRef< ClangTidyError > Errors, ClangTidyContext &Context, bool Fix, unsigned &WarningsAsErrorsCount, llvm::IntrusiveRefCntPtr< llvm::vfs::FileSystem > BaseFS)
Displays the found Errors to the users.
Definition: ClangTidy.cpp:576
Line
int Line
Definition: PreprocessorTracker.cpp:513
Logger.h
clang::clangd::ErrorCode::UnknownErrorCode
clang::clangd::vlog
void vlog(const char *Fmt, Ts &&... Vals)
Definition: Logger.h:67
clang::clangd::CompletionItemKind::Method
clang::clangd::log
void log(const char *Fmt, Ts &&... Vals)
Definition: Logger.h:62
Transport.h
clang
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Definition: ApplyReplacements.h:27
OS
llvm::raw_string_ostream OS
Definition: TraceTests.cpp:162
clang::clangd::newJSONTransport
std::unique_ptr< Transport > newJSONTransport(std::FILE *In, llvm::raw_ostream &Out, llvm::raw_ostream *InMirror, bool Pretty, JSONStreamStyle Style)
Definition: JSONTransport.cpp:320
clang::tidy::cppcoreguidelines::toString
static llvm::StringRef toString(SpecialMemberFunctionsCheck::SpecialMemberFunctionKind K)
Definition: SpecialMemberFunctionsCheck.cpp:60
clang::clangd::DocumentHighlightKind::Read
clang::clangd::Delimited
Definition: Transport.h:69
Pos
Position Pos
Definition: SourceCode.cpp:649
Out
CompiledFragmentImpl & Out
Definition: ConfigCompile.cpp:70
clang::clangd::elog
void elog(const char *Fmt, Ts &&... Vals)
Definition: Logger.h:56
clang::clangd::MessageType::Error
An error message.
clang::clangd::shutdownRequested
bool shutdownRequested()
Checks whether requestShutdown() was called.
Definition: Shutdown.cpp:34