blob: 74d1dc81fb3302011e815ad6948450a30ba3c3a3 [file] [log] [blame]
Benjamin Kramerbb1cdb62017-02-07 10:28:20 +00001//===--- JSONRPCDispatcher.cpp - Main JSON parser entry point -------------===//
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
10#include "JSONRPCDispatcher.h"
11#include "ProtocolHandlers.h"
Sam McCall8567cb32017-11-02 09:21:51 +000012#include "Trace.h"
Benjamin Kramerbb1cdb62017-02-07 10:28:20 +000013#include "llvm/ADT/SmallString.h"
14#include "llvm/Support/SourceMgr.h"
15#include "llvm/Support/YAMLParser.h"
Ilya Biryukov687b92a2017-05-16 15:23:55 +000016#include <istream>
17
Benjamin Kramerbb1cdb62017-02-07 10:28:20 +000018using namespace clang;
19using namespace clangd;
20
Benjamin Kramerd0b2ccd2017-02-10 14:08:40 +000021void JSONOutput::writeMessage(const Twine &Message) {
Benjamin Kramerbb1cdb62017-02-07 10:28:20 +000022 llvm::SmallString<128> Storage;
23 StringRef M = Message.toStringRef(Storage);
24
Benjamin Kramerd0b2ccd2017-02-10 14:08:40 +000025 std::lock_guard<std::mutex> Guard(StreamMutex);
Benjamin Kramerbb1cdb62017-02-07 10:28:20 +000026 // Log without headers.
27 Logs << "--> " << M << '\n';
28 Logs.flush();
29
30 // Emit message with header.
31 Outs << "Content-Length: " << M.size() << "\r\n\r\n" << M;
32 Outs.flush();
33}
34
Benjamin Kramere14bd422017-02-15 16:44:11 +000035void JSONOutput::log(const Twine &Message) {
Sam McCall8567cb32017-11-02 09:21:51 +000036 trace::log(Message);
Benjamin Kramere14bd422017-02-15 16:44:11 +000037 std::lock_guard<std::mutex> Guard(StreamMutex);
38 Logs << Message;
39 Logs.flush();
40}
41
Ilya Biryukove6dbb582017-10-10 09:08:47 +000042void JSONOutput::mirrorInput(const Twine &Message) {
43 if (!InputMirror)
44 return;
45
46 *InputMirror << Message;
47 InputMirror->flush();
48}
49
Sam McCall8a5dded2017-10-12 13:29:58 +000050void RequestContext::reply(const llvm::Twine &Result) {
51 if (ID.empty()) {
52 Out.log("Attempted to reply to a notification!\n");
53 return;
54 }
55 Out.writeMessage(llvm::Twine(R"({"jsonrpc":"2.0","id":)") + ID +
56 R"(,"result":)" + Result + "}");
Benjamin Kramerbb1cdb62017-02-07 10:28:20 +000057}
58
Sam McCall8a5dded2017-10-12 13:29:58 +000059void RequestContext::replyError(int code, const llvm::StringRef &Message) {
60 Out.log("Error " + llvm::Twine(code) + ": " + Message + "\n");
61 if (!ID.empty()) {
62 Out.writeMessage(llvm::Twine(R"({"jsonrpc":"2.0","id":)") + ID +
63 R"(,"error":{"code":)" + llvm::Twine(code) +
64 R"(,"message":")" + llvm::yaml::escape(Message) +
65 R"("}})");
66 }
Benjamin Kramerbb1cdb62017-02-07 10:28:20 +000067}
68
Marc-Andre Laperlee7ec16a2017-11-03 13:39:15 +000069void RequestContext::call(StringRef Method, StringRef Params) {
70 // FIXME: Generate/Increment IDs for every request so that we can get proper
71 // replies once we need to.
72 Out.writeMessage(llvm::Twine(R"({"jsonrpc":"2.0","id":1,"method":")" +
73 Method + R"(","params":)" + Params + R"(})"));
74}
75
Sam McCall8a5dded2017-10-12 13:29:58 +000076void JSONRPCDispatcher::registerHandler(StringRef Method, Handler H) {
Benjamin Kramerbb1cdb62017-02-07 10:28:20 +000077 assert(!Handlers.count(Method) && "Handler already registered!");
78 Handlers[Method] = std::move(H);
79}
80
81static void
Sam McCall8a5dded2017-10-12 13:29:58 +000082callHandler(const llvm::StringMap<JSONRPCDispatcher::Handler> &Handlers,
Benjamin Kramerbb1cdb62017-02-07 10:28:20 +000083 llvm::yaml::ScalarNode *Method, llvm::yaml::ScalarNode *Id,
Sam McCall8a5dded2017-10-12 13:29:58 +000084 llvm::yaml::MappingNode *Params,
85 const JSONRPCDispatcher::Handler &UnknownHandler, JSONOutput &Out) {
86 llvm::SmallString<64> MethodStorage;
Sam McCall8567cb32017-11-02 09:21:51 +000087 llvm::StringRef MethodStr = Method->getValue(MethodStorage);
88 auto I = Handlers.find(MethodStr);
Sam McCall8a5dded2017-10-12 13:29:58 +000089 auto &Handler = I != Handlers.end() ? I->second : UnknownHandler;
Sam McCall8567cb32017-11-02 09:21:51 +000090 trace::Span Tracer(MethodStr);
Sam McCall8a5dded2017-10-12 13:29:58 +000091 Handler(RequestContext(Out, Id ? Id->getRawValue() : ""), Params);
Benjamin Kramerbb1cdb62017-02-07 10:28:20 +000092}
93
Sam McCall8a5dded2017-10-12 13:29:58 +000094bool JSONRPCDispatcher::call(StringRef Content, JSONOutput &Out) const {
Benjamin Kramerbb1cdb62017-02-07 10:28:20 +000095 llvm::SourceMgr SM;
96 llvm::yaml::Stream YAMLStream(Content, SM);
97
98 auto Doc = YAMLStream.begin();
99 if (Doc == YAMLStream.end())
100 return false;
101
Benjamin Kramerdecd8a72017-10-27 16:33:15 +0000102 auto *Object = dyn_cast_or_null<llvm::yaml::MappingNode>(Doc->getRoot());
Benjamin Kramerbb1cdb62017-02-07 10:28:20 +0000103 if (!Object)
104 return false;
105
106 llvm::yaml::ScalarNode *Version = nullptr;
107 llvm::yaml::ScalarNode *Method = nullptr;
108 llvm::yaml::MappingNode *Params = nullptr;
109 llvm::yaml::ScalarNode *Id = nullptr;
110 for (auto &NextKeyValue : *Object) {
Benjamin Kramerdecd8a72017-10-27 16:33:15 +0000111 auto *KeyString =
112 dyn_cast_or_null<llvm::yaml::ScalarNode>(NextKeyValue.getKey());
Benjamin Kramerbb1cdb62017-02-07 10:28:20 +0000113 if (!KeyString)
114 return false;
115
116 llvm::SmallString<10> KeyStorage;
117 StringRef KeyValue = KeyString->getValue(KeyStorage);
118 llvm::yaml::Node *Value = NextKeyValue.getValue();
119 if (!Value)
120 return false;
121
122 if (KeyValue == "jsonrpc") {
123 // This should be "2.0". Always.
124 Version = dyn_cast<llvm::yaml::ScalarNode>(Value);
125 if (!Version || Version->getRawValue() != "\"2.0\"")
126 return false;
127 } else if (KeyValue == "method") {
128 Method = dyn_cast<llvm::yaml::ScalarNode>(Value);
129 } else if (KeyValue == "id") {
130 Id = dyn_cast<llvm::yaml::ScalarNode>(Value);
131 } else if (KeyValue == "params") {
132 if (!Method)
133 return false;
134 // We have to interleave the call of the function here, otherwise the
135 // YAMLParser will die because it can't go backwards. This is unfortunate
136 // because it will break clients that put the id after params. A possible
137 // fix would be to split the parsing and execution phases.
138 Params = dyn_cast<llvm::yaml::MappingNode>(Value);
Sam McCall8a5dded2017-10-12 13:29:58 +0000139 callHandler(Handlers, Method, Id, Params, UnknownHandler, Out);
Benjamin Kramerbb1cdb62017-02-07 10:28:20 +0000140 return true;
141 } else {
142 return false;
143 }
144 }
145
146 // In case there was a request with no params, call the handler on the
147 // leftovers.
148 if (!Method)
149 return false;
Sam McCall8a5dded2017-10-12 13:29:58 +0000150 callHandler(Handlers, Method, Id, nullptr, UnknownHandler, Out);
Benjamin Kramerbb1cdb62017-02-07 10:28:20 +0000151
152 return true;
153}
Ilya Biryukovafb55542017-05-16 14:40:30 +0000154
155void clangd::runLanguageServerLoop(std::istream &In, JSONOutput &Out,
156 JSONRPCDispatcher &Dispatcher,
157 bool &IsDone) {
158 while (In.good()) {
Ilya Biryukov1fab4f82017-09-04 12:28:15 +0000159 // A Language Server Protocol message starts with a set of HTTP headers,
160 // delimited by \r\n, and terminated by an empty line (\r\n).
161 unsigned long long ContentLength = 0;
162 while (In.good()) {
163 std::string Line;
164 std::getline(In, Line);
165 if (!In.good() && errno == EINTR) {
166 In.clear();
167 continue;
168 }
169
Ilya Biryukove6dbb582017-10-10 09:08:47 +0000170 Out.mirrorInput(Line);
171 // Mirror '\n' that gets consumed by std::getline, but is not included in
172 // the resulting Line.
173 // Note that '\r' is part of Line, so we don't need to mirror it
174 // separately.
175 if (!In.eof())
176 Out.mirrorInput("\n");
177
Ilya Biryukov1fab4f82017-09-04 12:28:15 +0000178 llvm::StringRef LineRef(Line);
179
180 // We allow YAML-style comments in headers. Technically this isn't part
181 // of the LSP specification, but makes writing tests easier.
182 if (LineRef.startswith("#"))
183 continue;
184
185 // Content-Type is a specified header, but does nothing.
186 // Content-Length is a mandatory header. It specifies the length of the
187 // following JSON.
188 // It is unspecified what sequence headers must be supplied in, so we
189 // allow any sequence.
190 // The end of headers is signified by an empty line.
191 if (LineRef.consume_front("Content-Length: ")) {
192 if (ContentLength != 0) {
193 Out.log("Warning: Duplicate Content-Length header received. "
Ilya Biryukove6dbb582017-10-10 09:08:47 +0000194 "The previous value for this message (" +
195 std::to_string(ContentLength) + ") was ignored.\n");
Ilya Biryukov1fab4f82017-09-04 12:28:15 +0000196 }
197
198 llvm::getAsUnsignedInteger(LineRef.trim(), 0, ContentLength);
199 continue;
200 } else if (!LineRef.trim().empty()) {
201 // It's another header, ignore it.
202 continue;
203 } else {
204 // An empty line indicates the end of headers.
205 // Go ahead and read the JSON.
206 break;
207 }
Ilya Biryukovafb55542017-05-16 14:40:30 +0000208 }
209
Benjamin Kramer1d053792017-10-27 17:06:41 +0000210 // Guard against large messages. This is usually a bug in the client code
211 // and we don't want to crash downstream because of it.
212 if (ContentLength > 1 << 30) { // 1024M
213 In.ignore(ContentLength);
214 Out.log("Skipped overly large message of " + Twine(ContentLength) +
215 " bytes.\n");
216 continue;
217 }
218
Ilya Biryukov1fab4f82017-09-04 12:28:15 +0000219 if (ContentLength > 0) {
Ilya Biryukov1fab4f82017-09-04 12:28:15 +0000220 std::vector<char> JSON(ContentLength + 1, '\0');
Sam McCall8567cb32017-11-02 09:21:51 +0000221 llvm::StringRef JSONRef;
222 {
223 trace::Span Tracer("Reading request");
224 // Now read the JSON. Insert a trailing null byte as required by the
225 // YAML parser.
226 In.read(JSON.data(), ContentLength);
227 Out.mirrorInput(StringRef(JSON.data(), In.gcount()));
Ilya Biryukovafb55542017-05-16 14:40:30 +0000228
Sam McCall8567cb32017-11-02 09:21:51 +0000229 // If the stream is aborted before we read ContentLength bytes, In
230 // will have eofbit and failbit set.
231 if (!In) {
232 Out.log("Input was aborted. Read only " +
233 std::to_string(In.gcount()) + " bytes of expected " +
234 std::to_string(ContentLength) + ".\n");
235 break;
236 }
237
238 JSONRef = StringRef(JSON.data(), ContentLength);
Ilya Biryukov1fab4f82017-09-04 12:28:15 +0000239 }
Ilya Biryukovafb55542017-05-16 14:40:30 +0000240
Ilya Biryukovafb55542017-05-16 14:40:30 +0000241 // Log the message.
242 Out.log("<-- " + JSONRef + "\n");
243
244 // Finally, execute the action for this JSON message.
Sam McCall8a5dded2017-10-12 13:29:58 +0000245 if (!Dispatcher.call(JSONRef, Out))
Ilya Biryukovafb55542017-05-16 14:40:30 +0000246 Out.log("JSON dispatch failed!\n");
247
248 // If we're done, exit the loop.
249 if (IsDone)
250 break;
Ilya Biryukov1fab4f82017-09-04 12:28:15 +0000251 } else {
Ilya Biryukove6dbb582017-10-10 09:08:47 +0000252 Out.log("Warning: Missing Content-Length header, or message has zero "
253 "length.\n");
Ilya Biryukovafb55542017-05-16 14:40:30 +0000254 }
255 }
256}