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