blob: 121ddb9bc8f1e83306e357f8c5dc5863a3bca5e4 [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
Sam McCall8a5dded2017-10-12 13:29:58 +000069void JSONRPCDispatcher::registerHandler(StringRef Method, Handler H) {
Benjamin Kramerbb1cdb62017-02-07 10:28:20 +000070 assert(!Handlers.count(Method) && "Handler already registered!");
71 Handlers[Method] = std::move(H);
72}
73
74static void
Sam McCall8a5dded2017-10-12 13:29:58 +000075callHandler(const llvm::StringMap<JSONRPCDispatcher::Handler> &Handlers,
Benjamin Kramerbb1cdb62017-02-07 10:28:20 +000076 llvm::yaml::ScalarNode *Method, llvm::yaml::ScalarNode *Id,
Sam McCall8a5dded2017-10-12 13:29:58 +000077 llvm::yaml::MappingNode *Params,
78 const JSONRPCDispatcher::Handler &UnknownHandler, JSONOutput &Out) {
79 llvm::SmallString<64> MethodStorage;
Sam McCall8567cb32017-11-02 09:21:51 +000080 llvm::StringRef MethodStr = Method->getValue(MethodStorage);
81 auto I = Handlers.find(MethodStr);
Sam McCall8a5dded2017-10-12 13:29:58 +000082 auto &Handler = I != Handlers.end() ? I->second : UnknownHandler;
Sam McCall8567cb32017-11-02 09:21:51 +000083 trace::Span Tracer(MethodStr);
Sam McCall8a5dded2017-10-12 13:29:58 +000084 Handler(RequestContext(Out, Id ? Id->getRawValue() : ""), Params);
Benjamin Kramerbb1cdb62017-02-07 10:28:20 +000085}
86
Sam McCall8a5dded2017-10-12 13:29:58 +000087bool JSONRPCDispatcher::call(StringRef Content, JSONOutput &Out) const {
Benjamin Kramerbb1cdb62017-02-07 10:28:20 +000088 llvm::SourceMgr SM;
89 llvm::yaml::Stream YAMLStream(Content, SM);
90
91 auto Doc = YAMLStream.begin();
92 if (Doc == YAMLStream.end())
93 return false;
94
Benjamin Kramerdecd8a72017-10-27 16:33:15 +000095 auto *Object = dyn_cast_or_null<llvm::yaml::MappingNode>(Doc->getRoot());
Benjamin Kramerbb1cdb62017-02-07 10:28:20 +000096 if (!Object)
97 return false;
98
99 llvm::yaml::ScalarNode *Version = nullptr;
100 llvm::yaml::ScalarNode *Method = nullptr;
101 llvm::yaml::MappingNode *Params = nullptr;
102 llvm::yaml::ScalarNode *Id = nullptr;
103 for (auto &NextKeyValue : *Object) {
Benjamin Kramerdecd8a72017-10-27 16:33:15 +0000104 auto *KeyString =
105 dyn_cast_or_null<llvm::yaml::ScalarNode>(NextKeyValue.getKey());
Benjamin Kramerbb1cdb62017-02-07 10:28:20 +0000106 if (!KeyString)
107 return false;
108
109 llvm::SmallString<10> KeyStorage;
110 StringRef KeyValue = KeyString->getValue(KeyStorage);
111 llvm::yaml::Node *Value = NextKeyValue.getValue();
112 if (!Value)
113 return false;
114
115 if (KeyValue == "jsonrpc") {
116 // This should be "2.0". Always.
117 Version = dyn_cast<llvm::yaml::ScalarNode>(Value);
118 if (!Version || Version->getRawValue() != "\"2.0\"")
119 return false;
120 } else if (KeyValue == "method") {
121 Method = dyn_cast<llvm::yaml::ScalarNode>(Value);
122 } else if (KeyValue == "id") {
123 Id = dyn_cast<llvm::yaml::ScalarNode>(Value);
124 } else if (KeyValue == "params") {
125 if (!Method)
126 return false;
127 // We have to interleave the call of the function here, otherwise the
128 // YAMLParser will die because it can't go backwards. This is unfortunate
129 // because it will break clients that put the id after params. A possible
130 // fix would be to split the parsing and execution phases.
131 Params = dyn_cast<llvm::yaml::MappingNode>(Value);
Sam McCall8a5dded2017-10-12 13:29:58 +0000132 callHandler(Handlers, Method, Id, Params, UnknownHandler, Out);
Benjamin Kramerbb1cdb62017-02-07 10:28:20 +0000133 return true;
134 } else {
135 return false;
136 }
137 }
138
139 // In case there was a request with no params, call the handler on the
140 // leftovers.
141 if (!Method)
142 return false;
Sam McCall8a5dded2017-10-12 13:29:58 +0000143 callHandler(Handlers, Method, Id, nullptr, UnknownHandler, Out);
Benjamin Kramerbb1cdb62017-02-07 10:28:20 +0000144
145 return true;
146}
Ilya Biryukovafb55542017-05-16 14:40:30 +0000147
148void clangd::runLanguageServerLoop(std::istream &In, JSONOutput &Out,
149 JSONRPCDispatcher &Dispatcher,
150 bool &IsDone) {
151 while (In.good()) {
Ilya Biryukov1fab4f82017-09-04 12:28:15 +0000152 // A Language Server Protocol message starts with a set of HTTP headers,
153 // delimited by \r\n, and terminated by an empty line (\r\n).
154 unsigned long long ContentLength = 0;
155 while (In.good()) {
156 std::string Line;
157 std::getline(In, Line);
158 if (!In.good() && errno == EINTR) {
159 In.clear();
160 continue;
161 }
162
Ilya Biryukove6dbb582017-10-10 09:08:47 +0000163 Out.mirrorInput(Line);
164 // Mirror '\n' that gets consumed by std::getline, but is not included in
165 // the resulting Line.
166 // Note that '\r' is part of Line, so we don't need to mirror it
167 // separately.
168 if (!In.eof())
169 Out.mirrorInput("\n");
170
Ilya Biryukov1fab4f82017-09-04 12:28:15 +0000171 llvm::StringRef LineRef(Line);
172
173 // We allow YAML-style comments in headers. Technically this isn't part
174 // of the LSP specification, but makes writing tests easier.
175 if (LineRef.startswith("#"))
176 continue;
177
178 // Content-Type is a specified header, but does nothing.
179 // Content-Length is a mandatory header. It specifies the length of the
180 // following JSON.
181 // It is unspecified what sequence headers must be supplied in, so we
182 // allow any sequence.
183 // The end of headers is signified by an empty line.
184 if (LineRef.consume_front("Content-Length: ")) {
185 if (ContentLength != 0) {
186 Out.log("Warning: Duplicate Content-Length header received. "
Ilya Biryukove6dbb582017-10-10 09:08:47 +0000187 "The previous value for this message (" +
188 std::to_string(ContentLength) + ") was ignored.\n");
Ilya Biryukov1fab4f82017-09-04 12:28:15 +0000189 }
190
191 llvm::getAsUnsignedInteger(LineRef.trim(), 0, ContentLength);
192 continue;
193 } else if (!LineRef.trim().empty()) {
194 // It's another header, ignore it.
195 continue;
196 } else {
197 // An empty line indicates the end of headers.
198 // Go ahead and read the JSON.
199 break;
200 }
Ilya Biryukovafb55542017-05-16 14:40:30 +0000201 }
202
Benjamin Kramer1d053792017-10-27 17:06:41 +0000203 // Guard against large messages. This is usually a bug in the client code
204 // and we don't want to crash downstream because of it.
205 if (ContentLength > 1 << 30) { // 1024M
206 In.ignore(ContentLength);
207 Out.log("Skipped overly large message of " + Twine(ContentLength) +
208 " bytes.\n");
209 continue;
210 }
211
Ilya Biryukov1fab4f82017-09-04 12:28:15 +0000212 if (ContentLength > 0) {
Ilya Biryukov1fab4f82017-09-04 12:28:15 +0000213 std::vector<char> JSON(ContentLength + 1, '\0');
Sam McCall8567cb32017-11-02 09:21:51 +0000214 llvm::StringRef JSONRef;
215 {
216 trace::Span Tracer("Reading request");
217 // Now read the JSON. Insert a trailing null byte as required by the
218 // YAML parser.
219 In.read(JSON.data(), ContentLength);
220 Out.mirrorInput(StringRef(JSON.data(), In.gcount()));
Ilya Biryukovafb55542017-05-16 14:40:30 +0000221
Sam McCall8567cb32017-11-02 09:21:51 +0000222 // If the stream is aborted before we read ContentLength bytes, In
223 // will have eofbit and failbit set.
224 if (!In) {
225 Out.log("Input was aborted. Read only " +
226 std::to_string(In.gcount()) + " bytes of expected " +
227 std::to_string(ContentLength) + ".\n");
228 break;
229 }
230
231 JSONRef = StringRef(JSON.data(), ContentLength);
Ilya Biryukov1fab4f82017-09-04 12:28:15 +0000232 }
Ilya Biryukovafb55542017-05-16 14:40:30 +0000233
Ilya Biryukovafb55542017-05-16 14:40:30 +0000234 // Log the message.
235 Out.log("<-- " + JSONRef + "\n");
236
237 // Finally, execute the action for this JSON message.
Sam McCall8a5dded2017-10-12 13:29:58 +0000238 if (!Dispatcher.call(JSONRef, Out))
Ilya Biryukovafb55542017-05-16 14:40:30 +0000239 Out.log("JSON dispatch failed!\n");
240
241 // If we're done, exit the loop.
242 if (IsDone)
243 break;
Ilya Biryukov1fab4f82017-09-04 12:28:15 +0000244 } else {
Ilya Biryukove6dbb582017-10-10 09:08:47 +0000245 Out.log("Warning: Missing Content-Length header, or message has zero "
246 "length.\n");
Ilya Biryukovafb55542017-05-16 14:40:30 +0000247 }
248 }
249}