summaryrefslogtreecommitdiffstats
path: root/clang-tools-extra/clangd/JSONRPCDispatcher.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'clang-tools-extra/clangd/JSONRPCDispatcher.cpp')
-rw-r--r--clang-tools-extra/clangd/JSONRPCDispatcher.cpp203
1 files changed, 121 insertions, 82 deletions
diff --git a/clang-tools-extra/clangd/JSONRPCDispatcher.cpp b/clang-tools-extra/clangd/JSONRPCDispatcher.cpp
index 24cbd1e86a7..4a7250256ca 100644
--- a/clang-tools-extra/clangd/JSONRPCDispatcher.cpp
+++ b/clang-tools-extra/clangd/JSONRPCDispatcher.cpp
@@ -180,89 +180,132 @@ bool JSONRPCDispatcher::call(const json::Expr &Message, JSONOutput &Out) const {
return true;
}
-void clangd::runLanguageServerLoop(std::istream &In, JSONOutput &Out,
- JSONRPCDispatcher &Dispatcher,
- bool &IsDone) {
+static llvm::Optional<std::string> readStandardMessage(std::istream &In,
+ JSONOutput &Out) {
+ // A Language Server Protocol message starts with a set of HTTP headers,
+ // delimited by \r\n, and terminated by an empty line (\r\n).
+ unsigned long long ContentLength = 0;
while (In.good()) {
- // A Language Server Protocol message starts with a set of HTTP headers,
- // delimited by \r\n, and terminated by an empty line (\r\n).
- unsigned long long ContentLength = 0;
- while (In.good()) {
- std::string Line;
- std::getline(In, Line);
- if (!In.good() && errno == EINTR) {
- In.clear();
- continue;
- }
+ std::string Line;
+ std::getline(In, Line);
+ if (!In.good() && errno == EINTR) {
+ In.clear();
+ continue;
+ }
- Out.mirrorInput(Line);
- // Mirror '\n' that gets consumed by std::getline, but is not included in
- // the resulting Line.
- // Note that '\r' is part of Line, so we don't need to mirror it
- // separately.
- if (!In.eof())
- Out.mirrorInput("\n");
-
- llvm::StringRef LineRef(Line);
-
- // We allow comments in headers. Technically this isn't part
- // of the LSP specification, but makes writing tests easier.
- if (LineRef.startswith("#"))
- continue;
-
- // Content-Type is a specified header, but does nothing.
- // Content-Length is a mandatory header. It specifies the length of the
- // following JSON.
- // It is unspecified what sequence headers must be supplied in, so we
- // allow any sequence.
- // The end of headers is signified by an empty line.
- if (LineRef.consume_front("Content-Length: ")) {
- if (ContentLength != 0) {
- log("Warning: Duplicate Content-Length header received. "
- "The previous value for this message (" +
- llvm::Twine(ContentLength) + ") was ignored.\n");
- }
-
- llvm::getAsUnsignedInteger(LineRef.trim(), 0, ContentLength);
- continue;
- } else if (!LineRef.trim().empty()) {
- // It's another header, ignore it.
- continue;
- } else {
- // An empty line indicates the end of headers.
- // Go ahead and read the JSON.
- break;
+ Out.mirrorInput(Line);
+ // Mirror '\n' that gets consumed by std::getline, but is not included in
+ // the resulting Line.
+ // Note that '\r' is part of Line, so we don't need to mirror it
+ // separately.
+ if (!In.eof())
+ Out.mirrorInput("\n");
+
+ llvm::StringRef LineRef(Line);
+
+ // We allow comments in headers. Technically this isn't part
+ // of the LSP specification, but makes writing tests easier.
+ if (LineRef.startswith("#"))
+ continue;
+
+ // Content-Type is a specified header, but does nothing.
+ // Content-Length is a mandatory header. It specifies the length of the
+ // following JSON.
+ // It is unspecified what sequence headers must be supplied in, so we
+ // allow any sequence.
+ // The end of headers is signified by an empty line.
+ if (LineRef.consume_front("Content-Length: ")) {
+ if (ContentLength != 0) {
+ log("Warning: Duplicate Content-Length header received. "
+ "The previous value for this message (" +
+ llvm::Twine(ContentLength) + ") was ignored.\n");
}
+
+ llvm::getAsUnsignedInteger(LineRef.trim(), 0, ContentLength);
+ continue;
+ } else if (!LineRef.trim().empty()) {
+ // It's another header, ignore it.
+ continue;
+ } else {
+ // An empty line indicates the end of headers.
+ // Go ahead and read the JSON.
+ break;
}
+ }
- // Guard against large messages. This is usually a bug in the client code
- // and we don't want to crash downstream because of it.
- if (ContentLength > 1 << 30) { // 1024M
- In.ignore(ContentLength);
- log("Skipped overly large message of " + Twine(ContentLength) +
- " bytes.\n");
+ // Guard against large messages. This is usually a bug in the client code
+ // and we don't want to crash downstream because of it.
+ if (ContentLength > 1 << 30) { // 1024M
+ In.ignore(ContentLength);
+ log("Skipped overly large message of " + Twine(ContentLength) +
+ " bytes.\n");
+ return llvm::None;
+ }
+
+ if (ContentLength > 0) {
+ std::string JSON(ContentLength, '\0');
+ llvm::StringRef JSONRef;
+ In.read(&JSON[0], ContentLength);
+ Out.mirrorInput(StringRef(JSON.data(), In.gcount()));
+
+ // If the stream is aborted before we read ContentLength bytes, In
+ // will have eofbit and failbit set.
+ if (!In) {
+ log("Input was aborted. Read only " + llvm::Twine(In.gcount()) +
+ " bytes of expected " + llvm::Twine(ContentLength) + ".\n");
+ return llvm::None;
+ }
+ return std::move(JSON);
+ } else {
+ log("Warning: Missing Content-Length header, or message has zero "
+ "length.\n");
+ return llvm::None;
+ }
+}
+
+// For lit tests we support a simplified syntax:
+// - messages are delimited by '---' on a line by itself
+// - lines starting with # are ignored.
+// This is a testing path, so favor simplicity over performance here.
+static llvm::Optional<std::string> readDelimitedMessage(std::istream &In,
+ JSONOutput &Out) {
+ std::string JSON;
+ std::string Line;
+ while (std::getline(In, Line)) {
+ if (!In.eof()) // getline() consumed the newline.
+ Line.push_back('\n');
+ auto LineRef = llvm::StringRef(Line).trim();
+ if (LineRef.startswith("#")) // comment
continue;
+
+ bool IsDelim = LineRef.find_first_not_of('-') == llvm::StringRef::npos;
+ if (!IsDelim) // Line is part of a JSON message.
+ JSON += Line;
+ if (IsDelim || In.eof()) {
+ Out.mirrorInput(
+ llvm::formatv("Content-Length: {0}\r\n\r\n{1}", JSON.size(), JSON));
+ return std::move(JSON);
}
+ }
- if (ContentLength > 0) {
- std::vector<char> JSON(ContentLength);
- llvm::StringRef JSONRef;
- {
- In.read(JSON.data(), ContentLength);
- Out.mirrorInput(StringRef(JSON.data(), In.gcount()));
-
- // If the stream is aborted before we read ContentLength bytes, In
- // will have eofbit and failbit set.
- if (!In) {
- log("Input was aborted. Read only " + llvm::Twine(In.gcount()) +
- " bytes of expected " + llvm::Twine(ContentLength) + ".\n");
- break;
- }
-
- JSONRef = StringRef(JSON.data(), ContentLength);
- }
+ if (In.bad()) {
+ log("Input error while reading message!");
+ return llvm::None;
+ } else {
+ log("Input message terminated by EOF");
+ return std::move(JSON);
+ }
+}
- if (auto Doc = json::parse(JSONRef)) {
+void clangd::runLanguageServerLoop(std::istream &In, JSONOutput &Out,
+ JSONStreamStyle InputStyle,
+ JSONRPCDispatcher &Dispatcher,
+ bool &IsDone) {
+ auto &ReadMessage =
+ (InputStyle == Delimited) ? readDelimitedMessage : readStandardMessage;
+ while (In.good()) {
+ if (auto JSON = ReadMessage(In, Out)) {
+ if (auto Doc = json::parse(*JSON)) {
// Log the formatted message.
log(llvm::formatv(Out.Pretty ? "<-- {0:2}\n" : "<-- {0}\n", *Doc));
// Finally, execute the action for this JSON message.
@@ -270,17 +313,13 @@ void clangd::runLanguageServerLoop(std::istream &In, JSONOutput &Out,
log("JSON dispatch failed!\n");
} else {
// Parse error. Log the raw message.
- log("<-- " + JSONRef + "\n");
+ log(llvm::formatv("<-- {0}\n" , *JSON));
log(llvm::Twine("JSON parse error: ") +
llvm::toString(Doc.takeError()) + "\n");
}
-
- // If we're done, exit the loop.
- if (IsDone)
- break;
- } else {
- log("Warning: Missing Content-Length header, or message has zero "
- "length.\n");
}
+ // If we're done, exit the loop.
+ if (IsDone)
+ break;
}
}
OpenPOWER on IntegriCloud