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