blob: 83ef49b03244f7baa7dc14ed5b76e16408238e0a [file] [log] [blame]
Sam McCalldc8f3cf2018-10-17 07:32:05 +00001//===--- JSONTransport.cpp - sending and receiving LSP messages over JSON -===//
2//
3// The LLVM Compiler Infrastructure
4//
5// This file is distributed under the University of Illinois Open Source
6// License. See LICENSE.TXT for details.
7//
8//===----------------------------------------------------------------------===//
9#include "Logger.h"
10#include "Protocol.h" // For LSPError
11#include "Transport.h"
12#include "llvm/Support/Errno.h"
13
14using namespace llvm;
15namespace clang {
16namespace clangd {
17namespace {
18
19json::Object encodeError(Error E) {
20 std::string Message;
21 ErrorCode Code = ErrorCode::UnknownErrorCode;
22 if (Error Unhandled =
23 handleErrors(std::move(E), [&](const LSPError &L) -> Error {
24 Message = L.Message;
25 Code = L.Code;
26 return Error::success();
27 }))
Sam McCallc008af62018-10-20 15:30:37 +000028 Message = toString(std::move(Unhandled));
Sam McCalldc8f3cf2018-10-17 07:32:05 +000029
30 return json::Object{
31 {"message", std::move(Message)},
32 {"code", int64_t(Code)},
33 };
34}
35
36Error decodeError(const json::Object &O) {
37 std::string Msg = O.getString("message").getValueOr("Unspecified error");
38 if (auto Code = O.getInteger("code"))
39 return make_error<LSPError>(std::move(Msg), ErrorCode(*Code));
40 return make_error<StringError>(std::move(Msg), inconvertibleErrorCode());
41}
42
43class JSONTransport : public Transport {
44public:
Sam McCallc008af62018-10-20 15:30:37 +000045 JSONTransport(std::FILE *In, raw_ostream &Out, raw_ostream *InMirror,
46 bool Pretty, JSONStreamStyle Style)
Sam McCalldc8f3cf2018-10-17 07:32:05 +000047 : In(In), Out(Out), InMirror(InMirror ? *InMirror : nulls()),
48 Pretty(Pretty), Style(Style) {}
49
50 void notify(StringRef Method, json::Value Params) override {
51 sendMessage(json::Object{
52 {"jsonrpc", "2.0"},
53 {"method", Method},
54 {"params", std::move(Params)},
55 });
56 }
57 void call(StringRef Method, json::Value Params, json::Value ID) override {
58 sendMessage(json::Object{
59 {"jsonrpc", "2.0"},
60 {"id", std::move(ID)},
61 {"method", Method},
62 {"params", std::move(Params)},
63 });
64 }
65 void reply(json::Value ID, Expected<json::Value> Result) override {
66 if (Result) {
67 sendMessage(json::Object{
68 {"jsonrpc", "2.0"},
69 {"id", std::move(ID)},
70 {"result", std::move(*Result)},
71 });
72 } else {
73 sendMessage(json::Object{
74 {"jsonrpc", "2.0"},
75 {"id", std::move(ID)},
76 {"error", encodeError(Result.takeError())},
77 });
78 }
79 }
80
81 Error loop(MessageHandler &Handler) override {
82 while (!feof(In)) {
83 if (ferror(In))
84 return errorCodeToError(std::error_code(errno, std::system_category()));
85 if (auto JSON = readRawMessage()) {
86 if (auto Doc = json::parse(*JSON)) {
87 vlog(Pretty ? "<<< {0:2}\n" : "<<< {0}\n", *Doc);
88 if (!handleMessage(std::move(*Doc), Handler))
89 return Error::success(); // we saw the "exit" notification.
90 } else {
91 // Parse error. Log the raw message.
92 vlog("<<< {0}\n", *JSON);
Sam McCallc008af62018-10-20 15:30:37 +000093 elog("JSON parse error: {0}", toString(Doc.takeError()));
Sam McCalldc8f3cf2018-10-17 07:32:05 +000094 }
95 }
96 }
97 return errorCodeToError(std::make_error_code(std::errc::io_error));
98 }
99
100private:
101 // Dispatches incoming message to Handler onNotify/onCall/onReply.
Sam McCallc008af62018-10-20 15:30:37 +0000102 bool handleMessage(json::Value Message, MessageHandler &Handler);
Sam McCalldc8f3cf2018-10-17 07:32:05 +0000103 // Writes outgoing message to Out stream.
Sam McCallc008af62018-10-20 15:30:37 +0000104 void sendMessage(json::Value Message) {
Sam McCalldc8f3cf2018-10-17 07:32:05 +0000105 std::string S;
Sam McCallc008af62018-10-20 15:30:37 +0000106 raw_string_ostream OS(S);
107 OS << formatv(Pretty ? "{0:2}" : "{0}", Message);
Sam McCalldc8f3cf2018-10-17 07:32:05 +0000108 OS.flush();
109 Out << "Content-Length: " << S.size() << "\r\n\r\n" << S;
110 Out.flush();
111 vlog(">>> {0}\n", S);
112 }
113
114 // Read raw string messages from input stream.
Sam McCallc008af62018-10-20 15:30:37 +0000115 Optional<std::string> readRawMessage() {
Sam McCalldc8f3cf2018-10-17 07:32:05 +0000116 return Style == JSONStreamStyle::Delimited ? readDelimitedMessage()
117 : readStandardMessage();
118 }
Sam McCallc008af62018-10-20 15:30:37 +0000119 Optional<std::string> readDelimitedMessage();
120 Optional<std::string> readStandardMessage();
Sam McCalldc8f3cf2018-10-17 07:32:05 +0000121
122 std::FILE *In;
Sam McCallc008af62018-10-20 15:30:37 +0000123 raw_ostream &Out;
124 raw_ostream &InMirror;
Sam McCalldc8f3cf2018-10-17 07:32:05 +0000125 bool Pretty;
126 JSONStreamStyle Style;
127};
128
Sam McCallc008af62018-10-20 15:30:37 +0000129bool JSONTransport::handleMessage(json::Value Message,
Sam McCalldc8f3cf2018-10-17 07:32:05 +0000130 MessageHandler &Handler) {
131 // Message must be an object with "jsonrpc":"2.0".
132 auto *Object = Message.getAsObject();
133 if (!Object || Object->getString("jsonrpc") != Optional<StringRef>("2.0")) {
134 elog("Not a JSON-RPC 2.0 message: {0:2}", Message);
135 return false;
136 }
137 // ID may be any JSON value. If absent, this is a notification.
Sam McCallc008af62018-10-20 15:30:37 +0000138 Optional<json::Value> ID;
Sam McCalldc8f3cf2018-10-17 07:32:05 +0000139 if (auto *I = Object->get("id"))
140 ID = std::move(*I);
141 auto Method = Object->getString("method");
142 if (!Method) { // This is a response.
143 if (!ID) {
144 elog("No method and no response ID: {0:2}", Message);
145 return false;
146 }
147 if (auto *Err = Object->getObject("error"))
148 return Handler.onReply(std::move(*ID), decodeError(*Err));
149 // Result should be given, use null if not.
150 json::Value Result = nullptr;
151 if (auto *R = Object->get("result"))
152 Result = std::move(*R);
153 return Handler.onReply(std::move(*ID), std::move(Result));
154 }
155 // Params should be given, use null if not.
156 json::Value Params = nullptr;
157 if (auto *P = Object->get("params"))
158 Params = std::move(*P);
159
160 if (ID)
161 return Handler.onCall(*Method, std::move(Params), std::move(*ID));
162 else
163 return Handler.onNotify(*Method, std::move(Params));
164}
165
166// Tries to read a line up to and including \n.
167// If failing, feof() or ferror() will be set.
168bool readLine(std::FILE *In, std::string &Out) {
169 static constexpr int BufSize = 1024;
170 size_t Size = 0;
171 Out.clear();
172 for (;;) {
173 Out.resize(Size + BufSize);
174 // Handle EINTR which is sent when a debugger attaches on some platforms.
Sam McCallc008af62018-10-20 15:30:37 +0000175 if (!sys::RetryAfterSignal(nullptr, ::fgets, &Out[Size], BufSize, In))
Sam McCalldc8f3cf2018-10-17 07:32:05 +0000176 return false;
177 clearerr(In);
178 // If the line contained null bytes, anything after it (including \n) will
179 // be ignored. Fortunately this is not a legal header or JSON.
180 size_t Read = std::strlen(&Out[Size]);
181 if (Read > 0 && Out[Size + Read - 1] == '\n') {
182 Out.resize(Size + Read);
183 return true;
184 }
185 Size += Read;
186 }
187}
188
189// Returns None when:
190// - ferror() or feof() are set.
191// - Content-Length is missing or empty (protocol error)
Sam McCallc008af62018-10-20 15:30:37 +0000192Optional<std::string> JSONTransport::readStandardMessage() {
Sam McCalldc8f3cf2018-10-17 07:32:05 +0000193 // A Language Server Protocol message starts with a set of HTTP headers,
194 // delimited by \r\n, and terminated by an empty line (\r\n).
195 unsigned long long ContentLength = 0;
196 std::string Line;
197 while (true) {
198 if (feof(In) || ferror(In) || !readLine(In, Line))
Sam McCallc008af62018-10-20 15:30:37 +0000199 return None;
Sam McCalldc8f3cf2018-10-17 07:32:05 +0000200 InMirror << Line;
201
Sam McCallc008af62018-10-20 15:30:37 +0000202 StringRef LineRef(Line);
Sam McCalldc8f3cf2018-10-17 07:32:05 +0000203
204 // We allow comments in headers. Technically this isn't part
205
206 // of the LSP specification, but makes writing tests easier.
207 if (LineRef.startswith("#"))
208 continue;
209
210 // Content-Length is a mandatory header, and the only one we handle.
211 if (LineRef.consume_front("Content-Length: ")) {
212 if (ContentLength != 0) {
213 elog("Warning: Duplicate Content-Length header received. "
214 "The previous value for this message ({0}) was ignored.",
215 ContentLength);
216 }
Sam McCallc008af62018-10-20 15:30:37 +0000217 getAsUnsignedInteger(LineRef.trim(), 0, ContentLength);
Sam McCalldc8f3cf2018-10-17 07:32:05 +0000218 continue;
219 } else if (!LineRef.trim().empty()) {
220 // It's another header, ignore it.
221 continue;
222 } else {
223 // An empty line indicates the end of headers.
224 // Go ahead and read the JSON.
225 break;
226 }
227 }
228
229 // The fuzzer likes crashing us by sending "Content-Length: 9999999999999999"
230 if (ContentLength > 1 << 30) { // 1024M
231 elog("Refusing to read message with long Content-Length: {0}. "
232 "Expect protocol errors",
233 ContentLength);
Sam McCallc008af62018-10-20 15:30:37 +0000234 return None;
Sam McCalldc8f3cf2018-10-17 07:32:05 +0000235 }
236 if (ContentLength == 0) {
237 log("Warning: Missing Content-Length header, or zero-length message.");
Sam McCallc008af62018-10-20 15:30:37 +0000238 return None;
Sam McCalldc8f3cf2018-10-17 07:32:05 +0000239 }
240
241 std::string JSON(ContentLength, '\0');
242 for (size_t Pos = 0, Read; Pos < ContentLength; Pos += Read) {
243 // Handle EINTR which is sent when a debugger attaches on some platforms.
Sam McCallc008af62018-10-20 15:30:37 +0000244 Read = sys::RetryAfterSignal(0u, ::fread, &JSON[Pos], 1,
245 ContentLength - Pos, In);
Sam McCalldc8f3cf2018-10-17 07:32:05 +0000246 if (Read == 0) {
247 elog("Input was aborted. Read only {0} bytes of expected {1}.", Pos,
248 ContentLength);
Sam McCallc008af62018-10-20 15:30:37 +0000249 return None;
Sam McCalldc8f3cf2018-10-17 07:32:05 +0000250 }
251 InMirror << StringRef(&JSON[Pos], Read);
252 clearerr(In); // If we're done, the error was transient. If we're not done,
253 // either it was transient or we'll see it again on retry.
254 Pos += Read;
255 }
256 return std::move(JSON);
257}
258
259// For lit tests we support a simplified syntax:
260// - messages are delimited by '---' on a line by itself
261// - lines starting with # are ignored.
262// This is a testing path, so favor simplicity over performance here.
263// When returning None, feof() or ferror() will be set.
Sam McCallc008af62018-10-20 15:30:37 +0000264Optional<std::string> JSONTransport::readDelimitedMessage() {
Sam McCalldc8f3cf2018-10-17 07:32:05 +0000265 std::string JSON;
266 std::string Line;
267 while (readLine(In, Line)) {
268 InMirror << Line;
Sam McCallc008af62018-10-20 15:30:37 +0000269 auto LineRef = StringRef(Line).trim();
Sam McCalldc8f3cf2018-10-17 07:32:05 +0000270 if (LineRef.startswith("#")) // comment
271 continue;
272
273 // found a delimiter
274 if (LineRef.rtrim() == "---")
275 break;
276
277 JSON += Line;
278 }
279
280 if (ferror(In)) {
281 elog("Input error while reading message!");
Sam McCallc008af62018-10-20 15:30:37 +0000282 return None;
Sam McCalldc8f3cf2018-10-17 07:32:05 +0000283 }
284 return std::move(JSON); // Including at EOF
285}
286
287} // namespace
288
Sam McCallc008af62018-10-20 15:30:37 +0000289std::unique_ptr<Transport> newJSONTransport(std::FILE *In, raw_ostream &Out,
290 raw_ostream *InMirror, bool Pretty,
Sam McCalldc8f3cf2018-10-17 07:32:05 +0000291 JSONStreamStyle Style) {
292 return llvm::make_unique<JSONTransport>(In, Out, InMirror, Pretty, Style);
293}
294
295} // namespace clangd
296} // namespace clang