xref: /llvm-project/clang-tools-extra/clangd/JSONTransport.cpp (revision ba13fa2a5d57581bff1a7e9322234af30f4882f6)
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 "support/ThreadCrashReporter.h"
14 #include "llvm/ADT/SmallString.h"
15 #include "llvm/Support/Error.h"
16 #include <optional>
17 #include <system_error>
18 
19 namespace clang {
20 namespace clangd {
21 namespace {
22 
encodeError(llvm::Error E)23 llvm::json::Object encodeError(llvm::Error E) {
24   std::string Message;
25   ErrorCode Code = ErrorCode::UnknownErrorCode;
26   // FIXME: encode cancellation errors using RequestCancelled or ContentModified
27   // as appropriate.
28   if (llvm::Error Unhandled = llvm::handleErrors(
29           std::move(E),
30           [&](const CancelledError &C) -> llvm::Error {
31             switch (C.Reason) {
32             case static_cast<int>(ErrorCode::ContentModified):
33               Code = ErrorCode::ContentModified;
34               Message = "Request cancelled because the document was modified";
35               break;
36             default:
37               Code = ErrorCode::RequestCancelled;
38               Message = "Request cancelled";
39               break;
40             }
41             return llvm::Error::success();
42           },
43           [&](const LSPError &L) -> llvm::Error {
44             Message = L.Message;
45             Code = L.Code;
46             return llvm::Error::success();
47           }))
48     Message = llvm::toString(std::move(Unhandled));
49 
50   return llvm::json::Object{
51       {"message", std::move(Message)},
52       {"code", int64_t(Code)},
53   };
54 }
55 
decodeError(const llvm::json::Object & O)56 llvm::Error decodeError(const llvm::json::Object &O) {
57   llvm::StringRef Msg = O.getString("message").value_or("Unspecified error");
58   if (auto Code = O.getInteger("code"))
59     return llvm::make_error<LSPError>(Msg.str(), ErrorCode(*Code));
60   return error(Msg.str());
61 }
62 
63 class JSONTransport : public Transport {
64 public:
JSONTransport(std::FILE * In,llvm::raw_ostream & Out,llvm::raw_ostream * InMirror,bool Pretty,JSONStreamStyle Style)65   JSONTransport(std::FILE *In, llvm::raw_ostream &Out,
66                 llvm::raw_ostream *InMirror, bool Pretty, JSONStreamStyle Style)
67       : In(In), Out(Out), InMirror(InMirror ? *InMirror : llvm::nulls()),
68         Pretty(Pretty), Style(Style) {}
69 
notify(llvm::StringRef Method,llvm::json::Value Params)70   void notify(llvm::StringRef Method, llvm::json::Value Params) override {
71     sendMessage(llvm::json::Object{
72         {"jsonrpc", "2.0"},
73         {"method", Method},
74         {"params", std::move(Params)},
75     });
76   }
call(llvm::StringRef Method,llvm::json::Value Params,llvm::json::Value ID)77   void call(llvm::StringRef Method, llvm::json::Value Params,
78             llvm::json::Value ID) override {
79     sendMessage(llvm::json::Object{
80         {"jsonrpc", "2.0"},
81         {"id", std::move(ID)},
82         {"method", Method},
83         {"params", std::move(Params)},
84     });
85   }
reply(llvm::json::Value ID,llvm::Expected<llvm::json::Value> Result)86   void reply(llvm::json::Value ID,
87              llvm::Expected<llvm::json::Value> Result) override {
88     if (Result) {
89       sendMessage(llvm::json::Object{
90           {"jsonrpc", "2.0"},
91           {"id", std::move(ID)},
92           {"result", std::move(*Result)},
93       });
94     } else {
95       sendMessage(llvm::json::Object{
96           {"jsonrpc", "2.0"},
97           {"id", std::move(ID)},
98           {"error", encodeError(Result.takeError())},
99       });
100     }
101   }
102 
loop(MessageHandler & Handler)103   llvm::Error loop(MessageHandler &Handler) override {
104     std::string JSON; // Messages may be large, reuse same big buffer.
105     while (!feof(In)) {
106       if (shutdownRequested())
107         return error(std::make_error_code(std::errc::operation_canceled),
108                      "Got signal, shutting down");
109       if (ferror(In))
110         return llvm::errorCodeToError(llvm::errnoAsErrorCode());
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.
sendMessage(llvm::json::Value Message)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.
readRawMessage(std::string & JSON)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 
handleMessage(llvm::json::Value Message,MessageHandler & Handler)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") != std::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   std::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.
readLine(std::FILE * In,llvm::SmallVectorImpl<char> & Out)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.
209     if (!retryAfterSignalUnlessShutdown(
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)
readStandardMessage(std::string & JSON)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.starts_with("#"))
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.
279     Read = retryAfterSignalUnlessShutdown(0, [&]{
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.
readDelimitedMessage(std::string & JSON)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.starts_with("#")) // 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 
newJSONTransport(std::FILE * In,llvm::raw_ostream & Out,llvm::raw_ostream * InMirror,bool Pretty,JSONStreamStyle Style)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
337