[clang-tools-extra] r344637 - Revert "[clangd] Refactor JSON-over-stdin/stdout code into Transport abstraction."
Krasimir Georgiev via cfe-commits
cfe-commits at lists.llvm.org
Tue Oct 16 11:44:41 PDT 2018
Author: krasimir
Date: Tue Oct 16 11:44:41 2018
New Revision: 344637
URL: http://llvm.org/viewvc/llvm-project?rev=344637&view=rev
Log:
Revert "[clangd] Refactor JSON-over-stdin/stdout code into Transport abstraction."
This reverts commit r344620.
Breaks upstream bots.
Removed:
clang-tools-extra/trunk/clangd/JSONTransport.cpp
clang-tools-extra/trunk/clangd/Transport.h
clang-tools-extra/trunk/unittests/clangd/JSONTransportTests.cpp
Modified:
clang-tools-extra/trunk/clangd/CMakeLists.txt
clang-tools-extra/trunk/clangd/ClangdLSPServer.cpp
clang-tools-extra/trunk/clangd/ClangdLSPServer.h
clang-tools-extra/trunk/clangd/JSONRPCDispatcher.cpp
clang-tools-extra/trunk/clangd/JSONRPCDispatcher.h
clang-tools-extra/trunk/clangd/Protocol.cpp
clang-tools-extra/trunk/clangd/Protocol.h
clang-tools-extra/trunk/clangd/ProtocolHandlers.cpp
clang-tools-extra/trunk/clangd/tool/ClangdMain.cpp
clang-tools-extra/trunk/test/clangd/compile-commands-path-in-initialize.test
clang-tools-extra/trunk/test/clangd/completion-snippets.test
clang-tools-extra/trunk/test/clangd/completion.test
clang-tools-extra/trunk/test/clangd/crash-non-added-files.test
clang-tools-extra/trunk/test/clangd/execute-command.test
clang-tools-extra/trunk/test/clangd/input-mirror.test
clang-tools-extra/trunk/test/clangd/signature-help.test
clang-tools-extra/trunk/test/clangd/textdocument-didchange-fail.test
clang-tools-extra/trunk/test/clangd/trace.test
clang-tools-extra/trunk/test/clangd/xrefs.test
clang-tools-extra/trunk/unittests/clangd/CMakeLists.txt
Modified: clang-tools-extra/trunk/clangd/CMakeLists.txt
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/CMakeLists.txt?rev=344637&r1=344636&r2=344637&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/CMakeLists.txt (original)
+++ clang-tools-extra/trunk/clangd/CMakeLists.txt Tue Oct 16 11:44:41 2018
@@ -26,7 +26,6 @@ add_clang_library(clangDaemon
GlobalCompilationDatabase.cpp
Headers.cpp
JSONRPCDispatcher.cpp
- JSONTransport.cpp
Logger.cpp
Protocol.cpp
ProtocolHandlers.cpp
Modified: clang-tools-extra/trunk/clangd/ClangdLSPServer.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/ClangdLSPServer.cpp?rev=344637&r1=344636&r2=344637&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/ClangdLSPServer.cpp (original)
+++ clang-tools-extra/trunk/clangd/ClangdLSPServer.cpp Tue Oct 16 11:44:41 2018
@@ -162,10 +162,7 @@ void ClangdLSPServer::onShutdown(Shutdow
reply(nullptr);
}
-void ClangdLSPServer::onExit(ExitParams &Params) {
- // No work to do.
- // JSONRPCDispatcher shuts down the transport after this notification.
-}
+void ClangdLSPServer::onExit(ExitParams &Params) { IsDone = true; }
void ClangdLSPServer::onDocumentDidOpen(DidOpenTextDocumentParams &Params) {
PathRef File = Params.textDocument.uri.file();
@@ -500,41 +497,39 @@ void ClangdLSPServer::onReference(Refere
});
}
-ClangdLSPServer::ClangdLSPServer(class Transport &Transport,
+ClangdLSPServer::ClangdLSPServer(JSONOutput &Out,
const clangd::CodeCompleteOptions &CCOpts,
llvm::Optional<Path> CompileCommandsDir,
bool ShouldUseInMemoryCDB,
const ClangdServer::Options &Opts)
- : Transport(Transport),
- CDB(ShouldUseInMemoryCDB ? CompilationDB::makeInMemory()
- : CompilationDB::makeDirectoryBased(
- std::move(CompileCommandsDir))),
+ : Out(Out), CDB(ShouldUseInMemoryCDB ? CompilationDB::makeInMemory()
+ : CompilationDB::makeDirectoryBased(
+ std::move(CompileCommandsDir))),
CCOpts(CCOpts), SupportedSymbolKinds(defaultSymbolKinds()),
SupportedCompletionItemKinds(defaultCompletionItemKinds()),
Server(new ClangdServer(CDB.getCDB(), FSProvider, /*DiagConsumer=*/*this,
Opts)) {}
-bool ClangdLSPServer::run() {
+bool ClangdLSPServer::run(std::FILE *In, JSONStreamStyle InputStyle) {
+ assert(!IsDone && "Run was called before");
assert(Server);
// Set up JSONRPCDispatcher.
JSONRPCDispatcher Dispatcher([](const json::Value &Params) {
replyError(ErrorCode::MethodNotFound, "method not found");
- return true;
});
registerCallbackHandlers(Dispatcher, /*Callbacks=*/*this);
// Run the Language Server loop.
- bool CleanExit = true;
- if (auto Err = Dispatcher.runLanguageServerLoop(Transport)) {
- elog("Transport error: {0}", std::move(Err));
- CleanExit = false;
- }
+ runLanguageServerLoop(In, Out, InputStyle, Dispatcher, IsDone);
+ // Make sure IsDone is set to true after this method exits to ensure assertion
+ // at the start of the method fires if it's ever executed again.
+ IsDone = true;
// Destroy ClangdServer to ensure all worker threads finish.
Server.reset();
- return CleanExit && ShutdownRequestReceived;
+ return ShutdownRequestReceived;
}
std::vector<Fix> ClangdLSPServer::getFixes(StringRef File,
@@ -594,11 +589,15 @@ void ClangdLSPServer::onDiagnosticsReady
}
// Publish diagnostics.
- Transport.notify("textDocument/publishDiagnostics",
- json::Object{
- {"uri", URIForFile{File}},
- {"diagnostics", std::move(DiagnosticsJSON)},
- });
+ Out.writeMessage(json::Object{
+ {"jsonrpc", "2.0"},
+ {"method", "textDocument/publishDiagnostics"},
+ {"params",
+ json::Object{
+ {"uri", URIForFile{File}},
+ {"diagnostics", std::move(DiagnosticsJSON)},
+ }},
+ });
}
void ClangdLSPServer::reparseOpenedFiles() {
Modified: clang-tools-extra/trunk/clangd/ClangdLSPServer.h
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/ClangdLSPServer.h?rev=344637&r1=344636&r2=344637&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/ClangdLSPServer.h (original)
+++ clang-tools-extra/trunk/clangd/ClangdLSPServer.h Tue Oct 16 11:44:41 2018
@@ -37,16 +37,18 @@ public:
/// If \p CompileCommandsDir has a value, compile_commands.json will be
/// loaded only from \p CompileCommandsDir. Otherwise, clangd will look
/// for compile_commands.json in all parent directories of each file.
- ClangdLSPServer(Transport &Transport,
- const clangd::CodeCompleteOptions &CCOpts,
+ ClangdLSPServer(JSONOutput &Out, const clangd::CodeCompleteOptions &CCOpts,
llvm::Optional<Path> CompileCommandsDir,
bool ShouldUseInMemoryCDB, const ClangdServer::Options &Opts);
- /// Run LSP server loop, communicating with the Transport provided in the
- /// constructor. This method must not be executed more than once.
+ /// Run LSP server loop, receiving input for it from \p In. \p In must be
+ /// opened in binary mode. Output will be written using Out variable passed to
+ /// class constructor. This method must not be executed more than once for
+ /// each instance of ClangdLSPServer.
///
- /// \return Whether we shut down cleanly with a 'shutdown' -> 'exit' sequence.
- bool run();
+ /// \return Whether we received a 'shutdown' request before an 'exit' request.
+ bool run(std::FILE *In,
+ JSONStreamStyle InputStyle = JSONStreamStyle::Standard);
private:
// Implement DiagnosticsConsumer.
@@ -87,10 +89,16 @@ private:
void reparseOpenedFiles();
void applyConfiguration(const ClangdConfigurationParamsChange &Settings);
+ JSONOutput &Out;
/// Used to indicate that the 'shutdown' request was received from the
/// Language Server client.
bool ShutdownRequestReceived = false;
+ /// Used to indicate that the 'exit' notification was received from the
+ /// Language Server client.
+ /// It's used to break out of the LSP parsing loop.
+ bool IsDone = false;
+
std::mutex FixItsMutex;
typedef std::map<clangd::Diagnostic, std::vector<Fix>, LSPDiagnosticCompare>
DiagnosticToReplacementMap;
@@ -145,7 +153,6 @@ private:
bool IsDirectoryBased;
};
- clangd::Transport &Transport;
// Various ClangdServer parameters go here. It's important they're created
// before ClangdServer.
CompilationDB CDB;
Modified: clang-tools-extra/trunk/clangd/JSONRPCDispatcher.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/JSONRPCDispatcher.cpp?rev=344637&r1=344636&r2=344637&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/JSONRPCDispatcher.cpp (original)
+++ clang-tools-extra/trunk/clangd/JSONRPCDispatcher.cpp Tue Oct 16 11:44:41 2018
@@ -11,7 +11,6 @@
#include "Cancellation.h"
#include "ProtocolHandlers.h"
#include "Trace.h"
-#include "Transport.h"
#include "llvm/ADT/ScopeExit.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/StringExtras.h"
@@ -29,7 +28,7 @@ using namespace clangd;
namespace {
static Key<json::Value> RequestID;
-static Key<Transport *> CurrentTransport;
+static Key<JSONOutput *> RequestOut;
// When tracing, we trace a request and attach the response in reply().
// Because the Span isn't available, we find the current request using Context.
@@ -59,6 +58,23 @@ public:
Key<std::unique_ptr<RequestSpan>> RequestSpan::RSKey;
} // namespace
+void JSONOutput::writeMessage(const json::Value &Message) {
+ std::string S;
+ llvm::raw_string_ostream OS(S);
+ if (Pretty)
+ OS << llvm::formatv("{0:2}", Message);
+ else
+ OS << Message;
+ OS.flush();
+
+ {
+ std::lock_guard<std::mutex> Guard(StreamMutex);
+ Outs << "Content-Length: " << S.size() << "\r\n\r\n" << S;
+ Outs.flush();
+ }
+ vlog(">>> {0}\n", S);
+}
+
void JSONOutput::log(Logger::Level Level,
const llvm::formatv_object_base &Message) {
if (Level < MinLevel)
@@ -71,6 +87,14 @@ void JSONOutput::log(Logger::Level Level
Logs.flush();
}
+void JSONOutput::mirrorInput(const Twine &Message) {
+ if (!InputMirror)
+ return;
+
+ *InputMirror << Message;
+ InputMirror->flush();
+}
+
void clangd::reply(json::Value &&Result) {
auto ID = getRequestId();
if (!ID) {
@@ -80,8 +104,12 @@ void clangd::reply(json::Value &&Result)
RequestSpan::attach([&](json::Object &Args) { Args["Reply"] = Result; });
log("--> reply({0})", *ID);
Context::current()
- .getExisting(CurrentTransport)
- ->reply(std::move(*ID), std::move(Result));
+ .getExisting(RequestOut)
+ ->writeMessage(json::Object{
+ {"jsonrpc", "2.0"},
+ {"id", *ID},
+ {"result", std::move(Result)},
+ });
}
void clangd::replyError(ErrorCode Code, const llvm::StringRef &Message) {
@@ -94,8 +122,13 @@ void clangd::replyError(ErrorCode Code,
if (auto ID = getRequestId()) {
log("--> reply({0}) error: {1}", *ID, Message);
Context::current()
- .getExisting(CurrentTransport)
- ->reply(std::move(*ID), make_error<LSPError>(Message, Code));
+ .getExisting(RequestOut)
+ ->writeMessage(json::Object{
+ {"jsonrpc", "2.0"},
+ {"id", *ID},
+ {"error", json::Object{{"code", static_cast<int>(Code)},
+ {"message", Message}}},
+ });
}
}
@@ -118,20 +151,22 @@ void clangd::call(StringRef Method, json
auto ID = 1;
log("--> {0}({1})", Method, ID);
Context::current()
- .getExisting(CurrentTransport)
- ->call(Method, std::move(Params), ID);
+ .getExisting(RequestOut)
+ ->writeMessage(json::Object{
+ {"jsonrpc", "2.0"},
+ {"id", ID},
+ {"method", Method},
+ {"params", std::move(Params)},
+ });
}
JSONRPCDispatcher::JSONRPCDispatcher(Handler UnknownHandler)
: UnknownHandler(std::move(UnknownHandler)) {
registerHandler("$/cancelRequest", [this](const json::Value &Params) {
if (auto *O = Params.getAsObject())
- if (auto *ID = O->get("id")) {
- cancelRequest(*ID);
- return true;
- }
+ if (auto *ID = O->get("id"))
+ return cancelRequest(*ID);
log("Bad cancellation request: {0}", Params);
- return true;
});
}
@@ -140,48 +175,64 @@ void JSONRPCDispatcher::registerHandler(
Handlers[Method] = std::move(H);
}
-bool JSONRPCDispatcher::onCall(StringRef Method, json::Value Params,
- json::Value ID) {
- log("<-- {0}({1})", Method, ID);
- auto I = Handlers.find(Method);
+static void logIncomingMessage(const llvm::Optional<json::Value> &ID,
+ llvm::Optional<StringRef> Method,
+ const json::Object *Error) {
+ if (Method) { // incoming request
+ if (ID) // call
+ log("<-- {0}({1})", *Method, *ID);
+ else // notification
+ log("<-- {0}", *Method);
+ } else if (ID) { // response, ID must be provided
+ if (Error)
+ log("<-- reply({0}) error: {1}", *ID,
+ Error->getString("message").getValueOr("<no message>"));
+ else
+ log("<-- reply({0})", *ID);
+ }
+}
+
+bool JSONRPCDispatcher::call(const json::Value &Message, JSONOutput &Out) {
+ // Message must be an object with "jsonrpc":"2.0".
+ auto *Object = Message.getAsObject();
+ if (!Object || Object->getString("jsonrpc") != Optional<StringRef>("2.0"))
+ return false;
+ // ID may be any JSON value. If absent, this is a notification.
+ llvm::Optional<json::Value> ID;
+ if (auto *I = Object->get("id"))
+ ID = std::move(*I);
+ auto Method = Object->getString("method");
+ logIncomingMessage(ID, Method, Object->getObject("error"));
+ if (!Method) // We only handle incoming requests, and ignore responses.
+ return false;
+ // Params should be given, use null if not.
+ json::Value Params = nullptr;
+ if (auto *P = Object->get("params"))
+ Params = std::move(*P);
+
+ auto I = Handlers.find(*Method);
auto &Handler = I != Handlers.end() ? I->second : UnknownHandler;
// Create a Context that contains request information.
- WithContextValue WithID(RequestID, ID);
+ WithContextValue WithRequestOut(RequestOut, &Out);
+ llvm::Optional<WithContextValue> WithID;
+ if (ID)
+ WithID.emplace(RequestID, *ID);
// Create a tracing Span covering the whole request lifetime.
- trace::Span Tracer(Method);
- SPAN_ATTACH(Tracer, "ID", ID);
+ trace::Span Tracer(*Method);
+ if (ID)
+ SPAN_ATTACH(Tracer, "ID", *ID);
SPAN_ATTACH(Tracer, "Params", Params);
- // Calls can be canceled by the client. Add cancellation context.
- WithContext WithCancel(cancelableRequestContext(ID));
-
- // Stash a reference to the span args, so later calls can add metadata.
- WithContext WithRequestSpan(RequestSpan::stash(Tracer));
- return Handler(std::move(Params));
-}
-
-bool JSONRPCDispatcher::onNotify(StringRef Method, json::Value Params) {
- log("<-- {0}", Method);
- auto I = Handlers.find(Method);
- auto &Handler = I != Handlers.end() ? I->second : UnknownHandler;
-
- // Create a tracing Span covering the whole request lifetime.
- trace::Span Tracer(Method);
- SPAN_ATTACH(Tracer, "Params", Params);
+ // Requests with IDs can be canceled by the client. Add cancellation context.
+ llvm::Optional<WithContext> WithCancel;
+ if (ID)
+ WithCancel.emplace(cancelableRequestContext(*ID));
// Stash a reference to the span args, so later calls can add metadata.
WithContext WithRequestSpan(RequestSpan::stash(Tracer));
- return Handler(std::move(Params));
-}
-
-bool JSONRPCDispatcher::onReply(json::Value ID, Expected<json::Value> Result) {
- // We ignore replies, just log them.
- if (Result)
- log("<-- reply({0})", ID);
- else
- log("<-- reply({0}) error: {1}", ID, llvm::toString(Result.takeError()));
+ Handler(std::move(Params));
return true;
}
@@ -215,10 +266,162 @@ void JSONRPCDispatcher::cancelRequest(co
It->second.first(); // Invoke the canceler.
}
-llvm::Error JSONRPCDispatcher::runLanguageServerLoop(Transport &Transport) {
- // Propagate transport to all handlers so they can reply.
- WithContextValue WithTransport(CurrentTransport, &Transport);
- return Transport.loop(*this);
+// Tries to read a line up to and including \n.
+// If failing, feof() or ferror() will be set.
+static bool readLine(std::FILE *In, std::string &Out) {
+ static constexpr int BufSize = 1024;
+ size_t Size = 0;
+ Out.clear();
+ for (;;) {
+ Out.resize(Size + BufSize);
+ // Handle EINTR which is sent when a debugger attaches on some platforms.
+ if (!llvm::sys::RetryAfterSignal(nullptr, ::fgets, &Out[Size], BufSize, In))
+ return false;
+ clearerr(In);
+ // If the line contained null bytes, anything after it (including \n) will
+ // be ignored. Fortunately this is not a legal header or JSON.
+ size_t Read = std::strlen(&Out[Size]);
+ if (Read > 0 && Out[Size + Read - 1] == '\n') {
+ Out.resize(Size + Read);
+ return true;
+ }
+ Size += Read;
+ }
+}
+
+// Returns None when:
+// - ferror() or feof() are set.
+// - Content-Length is missing or empty (protocol error)
+static llvm::Optional<std::string> readStandardMessage(std::FILE *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;
+ std::string Line;
+ while (true) {
+ if (feof(In) || ferror(In) || !readLine(In, Line))
+ return llvm::None;
+
+ Out.mirrorInput(Line);
+ 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-Length is a mandatory header, and the only one we handle.
+ if (LineRef.consume_front("Content-Length: ")) {
+ if (ContentLength != 0) {
+ elog("Warning: Duplicate Content-Length header received. "
+ "The previous value for this message ({0}) was ignored.",
+ ContentLength);
+ }
+ 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;
+ }
+ }
+
+ // The fuzzer likes crashing us by sending "Content-Length: 9999999999999999"
+ if (ContentLength > 1 << 30) { // 1024M
+ elog("Refusing to read message with long Content-Length: {0}. "
+ "Expect protocol errors",
+ ContentLength);
+ return llvm::None;
+ }
+ if (ContentLength == 0) {
+ log("Warning: Missing Content-Length header, or zero-length message.");
+ return llvm::None;
+ }
+
+ std::string JSON(ContentLength, '\0');
+ for (size_t Pos = 0, Read; Pos < ContentLength; Pos += Read) {
+ // Handle EINTR which is sent when a debugger attaches on some platforms.
+ Read = llvm::sys::RetryAfterSignal(0u, ::fread, &JSON[Pos], 1,
+ ContentLength - Pos, In);
+ Out.mirrorInput(StringRef(&JSON[Pos], Read));
+ if (Read == 0) {
+ elog("Input was aborted. Read only {0} bytes of expected {1}.", Pos,
+ ContentLength);
+ return llvm::None;
+ }
+ clearerr(In); // If we're done, the error was transient. If we're not done,
+ // either it was transient or we'll see it again on retry.
+ Pos += Read;
+ }
+ return std::move(JSON);
+}
+
+// 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.
+// When returning None, feof() or ferror() will be set.
+static llvm::Optional<std::string> readDelimitedMessage(std::FILE *In,
+ JSONOutput &Out) {
+ std::string JSON;
+ std::string Line;
+ while (readLine(In, Line)) {
+ auto LineRef = llvm::StringRef(Line).trim();
+ if (LineRef.startswith("#")) // comment
+ continue;
+
+ // found a delimiter
+ if (LineRef.rtrim() == "---")
+ break;
+
+ JSON += Line;
+ }
+
+ if (ferror(In)) {
+ elog("Input error while reading message!");
+ return llvm::None;
+ } else { // Including EOF
+ Out.mirrorInput(
+ llvm::formatv("Content-Length: {0}\r\n\r\n{1}", JSON.size(), JSON));
+ return std::move(JSON);
+ }
+}
+
+// The use of C-style std::FILE* IO deserves some explanation.
+// Previously, std::istream was used. When a debugger attached on MacOS, the
+// process received EINTR, the stream went bad, and clangd exited.
+// A retry-on-EINTR loop around reads solved this problem, but caused clangd to
+// sometimes hang rather than exit on other OSes. The interaction between
+// istreams and signals isn't well-specified, so it's hard to get this right.
+// The C APIs seem to be clearer in this respect.
+void clangd::runLanguageServerLoop(std::FILE *In, JSONOutput &Out,
+ JSONStreamStyle InputStyle,
+ JSONRPCDispatcher &Dispatcher,
+ bool &IsDone) {
+ auto &ReadMessage =
+ (InputStyle == Delimited) ? readDelimitedMessage : readStandardMessage;
+ while (!IsDone && !feof(In)) {
+ if (ferror(In)) {
+ elog("IO error: {0}", llvm::sys::StrError());
+ return;
+ }
+ if (auto JSON = ReadMessage(In, Out)) {
+ if (auto Doc = json::parse(*JSON)) {
+ // Log the formatted message.
+ vlog(Out.Pretty ? "<<< {0:2}\n" : "<<< {0}\n", *Doc);
+ // Finally, execute the action for this JSON message.
+ if (!Dispatcher.call(*Doc, Out))
+ elog("JSON dispatch failed!");
+ } else {
+ // Parse error. Log the raw message.
+ vlog("<<< {0}\n", *JSON);
+ elog("JSON parse error: {0}", llvm::toString(Doc.takeError()));
+ }
+ }
+ }
}
const json::Value *clangd::getRequestId() {
Modified: clang-tools-extra/trunk/clangd/JSONRPCDispatcher.h
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/JSONRPCDispatcher.h?rev=344637&r1=344636&r2=344637&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/JSONRPCDispatcher.h (original)
+++ clang-tools-extra/trunk/clangd/JSONRPCDispatcher.h Tue Oct 16 11:44:41 2018
@@ -14,7 +14,6 @@
#include "Logger.h"
#include "Protocol.h"
#include "Trace.h"
-#include "Transport.h"
#include "clang/Basic/LLVM.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/StringMap.h"
@@ -25,19 +24,37 @@
namespace clang {
namespace clangd {
-// Logs to an output stream, such as stderr.
-// FIXME: Rename to StreamLogger or such, and move to Logger.h.
+/// Encapsulates output and logs streams and provides thread-safe access to
+/// them.
class JSONOutput : public Logger {
+ // FIXME(ibiryukov): figure out if we can shrink the public interface of
+ // JSONOutput now that we pass Context everywhere.
public:
- JSONOutput(llvm::raw_ostream &Logs, Logger::Level MinLevel)
- : MinLevel(MinLevel), Logs(Logs) {}
+ JSONOutput(llvm::raw_ostream &Outs, llvm::raw_ostream &Logs,
+ Logger::Level MinLevel, llvm::raw_ostream *InputMirror = nullptr,
+ bool Pretty = false)
+ : Pretty(Pretty), MinLevel(MinLevel), Outs(Outs), Logs(Logs),
+ InputMirror(InputMirror) {}
+
+ /// Emit a JSONRPC message.
+ void writeMessage(const llvm::json::Value &Result);
/// Write a line to the logging stream.
void log(Level, const llvm::formatv_object_base &Message) override;
+ /// Mirror \p Message into InputMirror stream. Does nothing if InputMirror is
+ /// null.
+ /// Unlike other methods of JSONOutput, mirrorInput is not thread-safe.
+ void mirrorInput(const Twine &Message);
+
+ // Whether output should be pretty-printed.
+ const bool Pretty;
+
private:
Logger::Level MinLevel;
+ llvm::raw_ostream &Outs;
llvm::raw_ostream &Logs;
+ llvm::raw_ostream *InputMirror;
std::mutex StreamMutex;
};
@@ -64,15 +81,14 @@ void call(llvm::StringRef Method, llvm::
///
/// The `$/cancelRequest` notification is handled by the dispatcher itself.
/// It marks the matching request as cancelled, if it's still running.
-class JSONRPCDispatcher : private Transport::MessageHandler {
+class JSONRPCDispatcher {
public:
/// A handler responds to requests for a particular method name.
- /// It returns false if the server should now shut down.
///
/// JSONRPCDispatcher will mark the handler's context as cancelled if a
/// matching cancellation request is received. Handlers are encouraged to
/// check for cancellation and fail quickly in this case.
- using Handler = std::function<bool(const llvm::json::Value &)>;
+ using Handler = std::function<void(const llvm::json::Value &)>;
/// Create a new JSONRPCDispatcher. UnknownHandler is called when an unknown
/// method is received.
@@ -81,22 +97,10 @@ public:
/// Registers a Handler for the specified Method.
void registerHandler(StringRef Method, Handler H);
- /// Parses input queries from LSP client (coming from \p In) and runs call
- /// method for each query.
- ///
- /// Input stream(\p In) must be opened in binary mode to avoid
- /// preliminary replacements of \r\n with \n. We use C-style FILE* for reading
- /// as std::istream has unclear interaction with signals, which are sent by
- /// debuggers on some OSs.
- llvm::Error runLanguageServerLoop(Transport &);
+ /// Parses a JSONRPC message and calls the Handler for it.
+ bool call(const llvm::json::Value &Message, JSONOutput &Out);
private:
- bool onReply(llvm::json::Value ID,
- llvm::Expected<llvm::json::Value> Result) override;
- bool onNotify(llvm::StringRef Method, llvm::json::Value Message) override;
- bool onCall(llvm::StringRef Method, llvm::json::Value Message,
- llvm::json::Value ID) override;
-
// Tracking cancellations needs a mutex: handlers may finish on a different
// thread, and that's when we clean up entries in the map.
mutable std::mutex RequestCancelersMutex;
@@ -109,6 +113,25 @@ private:
Handler UnknownHandler;
};
+/// Controls the way JSON-RPC messages are encoded (both input and output).
+enum JSONStreamStyle {
+ /// Encoding per the LSP specification, with mandatory Content-Length header.
+ Standard,
+ /// Messages are delimited by a '---' line. Comment lines start with #.
+ Delimited
+};
+
+/// Parses input queries from LSP client (coming from \p In) and runs call
+/// method of \p Dispatcher for each query.
+/// After handling each query checks if \p IsDone is set true and exits the loop
+/// if it is.
+/// Input stream(\p In) must be opened in binary mode to avoid preliminary
+/// replacements of \r\n with \n.
+/// We use C-style FILE* for reading as std::istream has unclear interaction
+/// with signals, which are sent by debuggers on some OSs.
+void runLanguageServerLoop(std::FILE *In, JSONOutput &Out,
+ JSONStreamStyle InputStyle,
+ JSONRPCDispatcher &Dispatcher, bool &IsDone);
} // namespace clangd
} // namespace clang
Removed: clang-tools-extra/trunk/clangd/JSONTransport.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/JSONTransport.cpp?rev=344636&view=auto
==============================================================================
--- clang-tools-extra/trunk/clangd/JSONTransport.cpp (original)
+++ clang-tools-extra/trunk/clangd/JSONTransport.cpp (removed)
@@ -1,298 +0,0 @@
-//===--- JSONTransport.cpp - sending and receiving LSP messages over JSON -===//
-//
-// The LLVM Compiler Infrastructure
-//
-// This file is distributed under the University of Illinois Open Source
-// License. See LICENSE.TXT for details.
-//
-//===----------------------------------------------------------------------===//
-#include "Logger.h"
-#include "Protocol.h" // For LSPError
-#include "Transport.h"
-#include "llvm/Support/Errno.h"
-
-using namespace llvm;
-namespace clang {
-namespace clangd {
-namespace {
-
-json::Object encodeError(Error E) {
- std::string Message;
- ErrorCode Code = ErrorCode::UnknownErrorCode;
- if (Error Unhandled =
- handleErrors(std::move(E), [&](const LSPError &L) -> Error {
- Message = L.Message;
- Code = L.Code;
- return Error::success();
- }))
- Message = llvm::toString(std::move(Unhandled));
-
- return json::Object{
- {"message", std::move(Message)},
- {"code", int64_t(Code)},
- };
-}
-
-Error decodeError(const json::Object &O) {
- std::string Msg = O.getString("message").getValueOr("Unspecified error");
- if (auto Code = O.getInteger("code"))
- return make_error<LSPError>(std::move(Msg), ErrorCode(*Code));
- return make_error<StringError>(std::move(Msg), inconvertibleErrorCode());
-}
-
-class JSONTransport : public Transport {
-public:
- JSONTransport(std::FILE *In, llvm::raw_ostream &Out,
- llvm::raw_ostream *InMirror, bool Pretty, JSONStreamStyle Style)
- : In(In), Out(Out), InMirror(InMirror ? *InMirror : nulls()),
- Pretty(Pretty), Style(Style) {}
-
- void notify(StringRef Method, json::Value Params) override {
- sendMessage(json::Object{
- {"jsonrpc", "2.0"},
- {"method", Method},
- {"params", std::move(Params)},
- });
- }
- void call(StringRef Method, json::Value Params, json::Value ID) override {
- sendMessage(json::Object{
- {"jsonrpc", "2.0"},
- {"id", std::move(ID)},
- {"method", Method},
- {"params", std::move(Params)},
- });
- }
- void reply(json::Value ID, Expected<json::Value> Result) override {
- if (Result) {
- sendMessage(json::Object{
- {"jsonrpc", "2.0"},
- {"id", std::move(ID)},
- {"result", std::move(*Result)},
- });
- } else {
- sendMessage(json::Object{
- {"jsonrpc", "2.0"},
- {"id", std::move(ID)},
- {"error", encodeError(Result.takeError())},
- });
- }
- }
-
- Error loop(MessageHandler &Handler) override {
- while (!feof(In)) {
- if (ferror(In))
- return errorCodeToError(std::error_code(errno, std::system_category()));
- if (auto JSON = readRawMessage()) {
- if (auto Doc = json::parse(*JSON)) {
- vlog(Pretty ? "<<< {0:2}\n" : "<<< {0}\n", *Doc);
- if (!handleMessage(std::move(*Doc), Handler))
- return Error::success(); // we saw the "exit" notification.
- } else {
- // Parse error. Log the raw message.
- vlog("<<< {0}\n", *JSON);
- elog("JSON parse error: {0}", llvm::toString(Doc.takeError()));
- }
- }
- }
- return errorCodeToError(std::make_error_code(std::errc::io_error));
- }
-
-private:
- // Dispatches incoming message to Handler onNotify/onCall/onReply.
- bool handleMessage(llvm::json::Value Message, MessageHandler &Handler);
- // Writes outgoing message to Out stream.
- void sendMessage(llvm::json::Value Message) {
- std::string S;
- llvm::raw_string_ostream OS(S);
- OS << llvm::formatv(Pretty ? "{0:2}" : "{0}", Message);
- OS.flush();
- Out << "Content-Length: " << S.size() << "\r\n\r\n" << S;
- Out.flush();
- vlog(">>> {0}\n", S);
- }
-
- // Read raw string messages from input stream.
- llvm::Optional<std::string> readRawMessage() {
- return Style == JSONStreamStyle::Delimited ? readDelimitedMessage()
- : readStandardMessage();
- }
- llvm::Optional<std::string> readDelimitedMessage();
- llvm::Optional<std::string> readStandardMessage();
-
- std::FILE *In;
- llvm::raw_ostream &Out;
- llvm::raw_ostream &InMirror;
- bool Pretty;
- JSONStreamStyle Style;
-};
-
-bool JSONTransport::handleMessage(llvm::json::Value Message,
- MessageHandler &Handler) {
- // Message must be an object with "jsonrpc":"2.0".
- auto *Object = Message.getAsObject();
- if (!Object || Object->getString("jsonrpc") != Optional<StringRef>("2.0")) {
- elog("Not a JSON-RPC 2.0 message: {0:2}", Message);
- return false;
- }
- // ID may be any JSON value. If absent, this is a notification.
- llvm::Optional<json::Value> ID;
- if (auto *I = Object->get("id"))
- ID = std::move(*I);
- auto Method = Object->getString("method");
- if (!Method) { // This is a response.
- if (!ID) {
- elog("No method and no response ID: {0:2}", Message);
- return false;
- }
- if (auto *Err = Object->getObject("error"))
- return Handler.onReply(std::move(*ID), decodeError(*Err));
- // Result should be given, use null if not.
- json::Value Result = nullptr;
- if (auto *R = Object->get("result"))
- Result = std::move(*R);
- return Handler.onReply(std::move(*ID), std::move(Result));
- }
- // Params should be given, use null if not.
- json::Value Params = nullptr;
- if (auto *P = Object->get("params"))
- Params = std::move(*P);
-
- if (ID)
- return Handler.onCall(*Method, std::move(Params), std::move(*ID));
- else
- return Handler.onNotify(*Method, std::move(Params));
-}
-
-// Tries to read a line up to and including \n.
-// If failing, feof() or ferror() will be set.
-bool readLine(std::FILE *In, std::string &Out) {
- static constexpr int BufSize = 1024;
- size_t Size = 0;
- Out.clear();
- for (;;) {
- Out.resize(Size + BufSize);
- // Handle EINTR which is sent when a debugger attaches on some platforms.
- if (!llvm::sys::RetryAfterSignal(nullptr, ::fgets, &Out[Size], BufSize, In))
- return false;
- clearerr(In);
- // If the line contained null bytes, anything after it (including \n) will
- // be ignored. Fortunately this is not a legal header or JSON.
- size_t Read = std::strlen(&Out[Size]);
- if (Read > 0 && Out[Size + Read - 1] == '\n') {
- Out.resize(Size + Read);
- return true;
- }
- Size += Read;
- }
-}
-
-// Returns None when:
-// - ferror() or feof() are set.
-// - Content-Length is missing or empty (protocol error)
-llvm::Optional<std::string> JSONTransport::readStandardMessage() {
- // 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;
- std::string Line;
- while (true) {
- if (feof(In) || ferror(In) || !readLine(In, Line))
- return llvm::None;
- InMirror << Line;
-
- 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-Length is a mandatory header, and the only one we handle.
- if (LineRef.consume_front("Content-Length: ")) {
- if (ContentLength != 0) {
- elog("Warning: Duplicate Content-Length header received. "
- "The previous value for this message ({0}) was ignored.",
- ContentLength);
- }
- 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;
- }
- }
-
- // The fuzzer likes crashing us by sending "Content-Length: 9999999999999999"
- if (ContentLength > 1 << 30) { // 1024M
- elog("Refusing to read message with long Content-Length: {0}. "
- "Expect protocol errors",
- ContentLength);
- return llvm::None;
- }
- if (ContentLength == 0) {
- log("Warning: Missing Content-Length header, or zero-length message.");
- return llvm::None;
- }
-
- std::string JSON(ContentLength, '\0');
- for (size_t Pos = 0, Read; Pos < ContentLength; Pos += Read) {
- // Handle EINTR which is sent when a debugger attaches on some platforms.
- Read = llvm::sys::RetryAfterSignal(0u, ::fread, &JSON[Pos], 1,
- ContentLength - Pos, In);
- if (Read == 0) {
- elog("Input was aborted. Read only {0} bytes of expected {1}.", Pos,
- ContentLength);
- return llvm::None;
- }
- InMirror << StringRef(&JSON[Pos], Read);
- clearerr(In); // If we're done, the error was transient. If we're not done,
- // either it was transient or we'll see it again on retry.
- Pos += Read;
- }
- return std::move(JSON);
-}
-
-// 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.
-// When returning None, feof() or ferror() will be set.
-llvm::Optional<std::string> JSONTransport::readDelimitedMessage() {
- std::string JSON;
- std::string Line;
- while (readLine(In, Line)) {
- InMirror << Line;
- auto LineRef = llvm::StringRef(Line).trim();
- if (LineRef.startswith("#")) // comment
- continue;
-
- // found a delimiter
- if (LineRef.rtrim() == "---")
- break;
-
- JSON += Line;
- }
-
- if (ferror(In)) {
- elog("Input error while reading message!");
- return llvm::None;
- }
- return std::move(JSON); // Including at EOF
-}
-
-} // namespace
-
-std::unique_ptr<Transport> newJSONTransport(std::FILE *In,
- llvm::raw_ostream &Out,
- llvm::raw_ostream *InMirror,
- bool Pretty,
- JSONStreamStyle Style) {
- return llvm::make_unique<JSONTransport>(In, Out, InMirror, Pretty, Style);
-}
-
-} // namespace clangd
-} // namespace clang
Modified: clang-tools-extra/trunk/clangd/Protocol.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/Protocol.cpp?rev=344637&r1=344636&r2=344637&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/Protocol.cpp (original)
+++ clang-tools-extra/trunk/clangd/Protocol.cpp Tue Oct 16 11:44:41 2018
@@ -25,8 +25,6 @@ namespace clang {
namespace clangd {
using namespace llvm;
-char LSPError::ID;
-
URIForFile::URIForFile(std::string AbsPath) {
assert(llvm::sys::path::is_absolute(AbsPath) && "the path is relative");
File = std::move(AbsPath);
Modified: clang-tools-extra/trunk/clangd/Protocol.h
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/Protocol.h?rev=344637&r1=344636&r2=344637&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/Protocol.h (original)
+++ clang-tools-extra/trunk/clangd/Protocol.h Tue Oct 16 11:44:41 2018
@@ -48,23 +48,6 @@ enum class ErrorCode {
// Defined by the protocol.
RequestCancelled = -32800,
};
-// Models an LSP error as an llvm::Error.
-class LSPError : public llvm::ErrorInfo<LSPError> {
-public:
- std::string Message;
- ErrorCode Code;
- static char ID;
-
- LSPError(std::string Message, ErrorCode Code)
- : Message(std::move(Message)), Code(Code) {}
-
- void log(llvm::raw_ostream &OS) const override {
- OS << int(Code) << ": " << Message;
- }
- std::error_code convertToErrorCode() const override {
- return llvm::inconvertibleErrorCode();
- }
-};
struct URIForFile {
URIForFile() = default;
Modified: clang-tools-extra/trunk/clangd/ProtocolHandlers.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/ProtocolHandlers.cpp?rev=344637&r1=344636&r2=344637&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/ProtocolHandlers.cpp (original)
+++ clang-tools-extra/trunk/clangd/ProtocolHandlers.cpp Tue Oct 16 11:44:41 2018
@@ -35,7 +35,6 @@ struct HandlerRegisterer {
} else {
elog("Failed to decode {0} request.", Method);
}
- return Method != "exit"; // Shut down after exit notification.
});
}
Removed: clang-tools-extra/trunk/clangd/Transport.h
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/Transport.h?rev=344636&view=auto
==============================================================================
--- clang-tools-extra/trunk/clangd/Transport.h (original)
+++ clang-tools-extra/trunk/clangd/Transport.h (removed)
@@ -1,92 +0,0 @@
-//===--- Transport.h - sending and receiving LSP messages -------*- C++ -*-===//
-//
-// The LLVM Compiler Infrastructure
-//
-// This file is distributed under the University of Illinois Open Source
-// License. See LICENSE.TXT for details.
-//
-//===----------------------------------------------------------------------===//
-//
-// The language server protocol is usually implemented by writing messages as
-// JSON-RPC over the stdin/stdout of a subprocess. However other communications
-// mechanisms are possible, such as XPC on mac.
-//
-// The Transport interface allows the mechanism to be replaced, and the JSONRPC
-// Transport is the standard implementation.
-//
-//===----------------------------------------------------------------------===//
-
-#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_TRANSPORT_H_
-#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_TRANSPORT_H_
-
-#include "llvm/ADT/StringRef.h"
-#include "llvm/Support/JSON.h"
-#include "llvm/Support/raw_ostream.h"
-
-namespace clang {
-namespace clangd {
-
-// A transport is responsible for maintaining the connection to a client
-// application, and reading/writing structured messages to it.
-//
-// Transports have limited thread safety requirements:
-// - messages will not be sent concurrently
-// - messages MAY be sent while loop() is reading, or its callback is active
-class Transport {
-public:
- virtual ~Transport() = default;
-
- // Called by Clangd to send messages to the client.
- virtual void notify(llvm::StringRef Method, llvm::json::Value Params) = 0;
- virtual void call(llvm::StringRef Method, llvm::json::Value Params,
- llvm::json::Value ID) = 0;
- virtual void reply(llvm::json::Value ID,
- llvm::Expected<llvm::json::Value> Result) = 0;
-
- // Implemented by Clangd to handle incoming messages. (See loop() below).
- class MessageHandler {
- public:
- virtual ~MessageHandler() = default;
- // Handler returns true to keep processing messages, or false to shut down.
- virtual bool onNotify(llvm::StringRef Method, llvm::json::Value) = 0;
- virtual bool onCall(llvm::StringRef Method, llvm::json::Value Params,
- llvm::json::Value ID) = 0;
- virtual bool onReply(llvm::json::Value ID,
- llvm::Expected<llvm::json::Value> Result) = 0;
- };
- // Called by Clangd to receive messages from the client.
- // The transport should in turn invoke the handler to process messages.
- // If handler returns false, the transport should immedately exit the loop.
- // (This is used to implement the `exit` notification).
- // Otherwise, it returns an error when the transport becomes unusable.
- virtual llvm::Error loop(MessageHandler &) = 0;
-};
-
-// Controls the way JSON-RPC messages are encoded (both input and output).
-enum JSONStreamStyle {
- // Encoding per the LSP specification, with mandatory Content-Length header.
- Standard,
- // Messages are delimited by a '---' line. Comment lines start with #.
- Delimited
-};
-
-// Returns a Transport that speaks JSON-RPC over a pair of streams.
-// The input stream must be opened in binary mode.
-// If InMirror is set, data read will be echoed to it.
-//
-// The use of C-style std::FILE* input deserves some explanation.
-// Previously, std::istream was used. When a debugger attached on MacOS, the
-// process received EINTR, the stream went bad, and clangd exited.
-// A retry-on-EINTR loop around reads solved this problem, but caused clangd to
-// sometimes hang rather than exit on other OSes. The interaction between
-// istreams and signals isn't well-specified, so it's hard to get this right.
-// The C APIs seem to be clearer in this respect.
-std::unique_ptr<Transport>
-newJSONTransport(std::FILE *In, llvm::raw_ostream &Out,
- llvm::raw_ostream *InMirror, bool Pretty,
- JSONStreamStyle = JSONStreamStyle::Standard);
-
-} // namespace clangd
-} // namespace clang
-
-#endif
Modified: clang-tools-extra/trunk/clangd/tool/ClangdMain.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/tool/ClangdMain.cpp?rev=344637&r1=344636&r2=344637&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/tool/ClangdMain.cpp (original)
+++ clang-tools-extra/trunk/clangd/tool/ClangdMain.cpp Tue Oct 16 11:44:41 2018
@@ -253,8 +253,11 @@ int main(int argc, char *argv[]) {
// Use buffered stream to stderr (we still flush each log message). Unbuffered
// stream can cause significant (non-deterministic) latency for the logger.
llvm::errs().SetBuffered();
- JSONOutput Logger(llvm::errs(), LogLevel);
- clangd::LoggingSession LoggingSession(Logger);
+ JSONOutput Out(llvm::outs(), llvm::errs(), LogLevel,
+ InputMirrorStream ? InputMirrorStream.getPointer() : nullptr,
+ PrettyPrint);
+
+ clangd::LoggingSession LoggingSession(Out);
// If --compile-commands-dir arg was invoked, check value and override default
// path.
@@ -314,16 +317,12 @@ int main(int argc, char *argv[]) {
CCOpts.AllScopes = AllScopesCompletion;
// Initialize and run ClangdLSPServer.
- // Change stdin to binary to not lose \r\n on windows.
- llvm::sys::ChangeStdinToBinary();
- auto Transport = newJSONTransport(
- stdin, llvm::outs(),
- InputMirrorStream ? InputMirrorStream.getPointer() : nullptr, PrettyPrint,
- InputStyle);
ClangdLSPServer LSPServer(
- *Transport, CCOpts, CompileCommandsDirPath,
+ Out, CCOpts, CompileCommandsDirPath,
/*ShouldUseInMemoryCDB=*/CompileArgsFrom == LSPCompileArgs, Opts);
constexpr int NoShutdownRequestErrorCode = 1;
llvm::set_thread_name("clangd.main");
- return LSPServer.run() ? 0 : NoShutdownRequestErrorCode;
+ // Change stdin to binary to not lose \r\n on windows.
+ llvm::sys::ChangeStdinToBinary();
+ return LSPServer.run(stdin, InputStyle) ? 0 : NoShutdownRequestErrorCode;
}
Modified: clang-tools-extra/trunk/test/clangd/compile-commands-path-in-initialize.test
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/test/clangd/compile-commands-path-in-initialize.test?rev=344637&r1=344636&r2=344637&view=diff
==============================================================================
--- clang-tools-extra/trunk/test/clangd/compile-commands-path-in-initialize.test (original)
+++ clang-tools-extra/trunk/test/clangd/compile-commands-path-in-initialize.test Tue Oct 16 11:44:41 2018
@@ -24,5 +24,3 @@
# CHECK-NEXT: "message": "MACRO is one",
---
{"jsonrpc":"2.0","id":10000,"method":"shutdown"}
----
-{"jsonrpc":"2.0","method":"exit"}
Modified: clang-tools-extra/trunk/test/clangd/completion-snippets.test
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/test/clangd/completion-snippets.test?rev=344637&r1=344636&r2=344637&view=diff
==============================================================================
--- clang-tools-extra/trunk/test/clangd/completion-snippets.test (original)
+++ clang-tools-extra/trunk/test/clangd/completion-snippets.test Tue Oct 16 11:44:41 2018
@@ -52,5 +52,3 @@
# CHECK-NEXT: }
---
{"jsonrpc":"2.0","id":4,"method":"shutdown"}
----
-{"jsonrpc":"2.0","method":"exit"}
Modified: clang-tools-extra/trunk/test/clangd/completion.test
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/test/clangd/completion.test?rev=344637&r1=344636&r2=344637&view=diff
==============================================================================
--- clang-tools-extra/trunk/test/clangd/completion.test (original)
+++ clang-tools-extra/trunk/test/clangd/completion.test Tue Oct 16 11:44:41 2018
@@ -68,5 +68,3 @@
# CHECK-NEXT: ]
---
{"jsonrpc":"2.0","id":4,"method":"shutdown"}
----
-{"jsonrpc":"2.0","method":"exit"}
Modified: clang-tools-extra/trunk/test/clangd/crash-non-added-files.test
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/test/clangd/crash-non-added-files.test?rev=344637&r1=344636&r2=344637&view=diff
==============================================================================
--- clang-tools-extra/trunk/test/clangd/crash-non-added-files.test (original)
+++ clang-tools-extra/trunk/test/clangd/crash-non-added-files.test Tue Oct 16 11:44:41 2018
@@ -32,5 +32,3 @@
{"jsonrpc":"2.0","id":6,"method":"shutdown"}
---
{"jsonrpc":"2.0","method":"exit"}
----
-{"jsonrpc":"2.0","method":"exit"}
Modified: clang-tools-extra/trunk/test/clangd/execute-command.test
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/test/clangd/execute-command.test?rev=344637&r1=344636&r2=344637&view=diff
==============================================================================
--- clang-tools-extra/trunk/test/clangd/execute-command.test (original)
+++ clang-tools-extra/trunk/test/clangd/execute-command.test Tue Oct 16 11:44:41 2018
@@ -62,5 +62,3 @@
{"jsonrpc":"2.0","id":9,"method":"workspace/executeCommand","params":{"arguments":[{"custom":"foo", "changes":{"test:///foo.c":[{"range":{"start":{"line":0,"character":32},"end":{"line":0,"character":32}},"newText":"("},{"range":{"start":{"line":0,"character":37},"end":{"line":0,"character":37}},"newText":")"}]}}],"command":"clangd.applyFix"}}
---
{"jsonrpc":"2.0","id":3,"method":"shutdown"}
----
-{"jsonrpc":"2.0","method":"exit"}
Modified: clang-tools-extra/trunk/test/clangd/input-mirror.test
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/test/clangd/input-mirror.test?rev=344637&r1=344636&r2=344637&view=diff
==============================================================================
--- clang-tools-extra/trunk/test/clangd/input-mirror.test (original)
+++ clang-tools-extra/trunk/test/clangd/input-mirror.test Tue Oct 16 11:44:41 2018
@@ -12,6 +12,3 @@ Content-Length: 172
Content-Length: 44
{"jsonrpc":"2.0","id":3,"method":"shutdown"}
-Content-Length: 33
-
-{"jsonrpc":"2.0","method":"exit"}
Modified: clang-tools-extra/trunk/test/clangd/signature-help.test
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/test/clangd/signature-help.test?rev=344637&r1=344636&r2=344637&view=diff
==============================================================================
--- clang-tools-extra/trunk/test/clangd/signature-help.test (original)
+++ clang-tools-extra/trunk/test/clangd/signature-help.test Tue Oct 16 11:44:41 2018
@@ -23,5 +23,3 @@
# CHECK-NEXT: }
---
{"jsonrpc":"2.0","id":100000,"method":"shutdown"}
----
-{"jsonrpc":"2.0","method":"exit"}
Modified: clang-tools-extra/trunk/test/clangd/textdocument-didchange-fail.test
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/test/clangd/textdocument-didchange-fail.test?rev=344637&r1=344636&r2=344637&view=diff
==============================================================================
--- clang-tools-extra/trunk/test/clangd/textdocument-didchange-fail.test (original)
+++ clang-tools-extra/trunk/test/clangd/textdocument-didchange-fail.test Tue Oct 16 11:44:41 2018
@@ -35,5 +35,3 @@
# CHECK-NEXT:}
---
{"jsonrpc":"2.0","id":4,"method":"shutdown"}
----
-{"jsonrpc":"2.0","method":"exit"}
Modified: clang-tools-extra/trunk/test/clangd/trace.test
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/test/clangd/trace.test?rev=344637&r1=344636&r2=344637&view=diff
==============================================================================
--- clang-tools-extra/trunk/test/clangd/trace.test (original)
+++ clang-tools-extra/trunk/test/clangd/trace.test Tue Oct 16 11:44:41 2018
@@ -21,5 +21,3 @@
# CHECK: },
---
{"jsonrpc":"2.0","id":5,"method":"shutdown"}
----
-{"jsonrpc":"2.0","method":"exit"}
Modified: clang-tools-extra/trunk/test/clangd/xrefs.test
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/test/clangd/xrefs.test?rev=344637&r1=344636&r2=344637&view=diff
==============================================================================
--- clang-tools-extra/trunk/test/clangd/xrefs.test (original)
+++ clang-tools-extra/trunk/test/clangd/xrefs.test Tue Oct 16 11:44:41 2018
@@ -55,5 +55,3 @@
# CHECK-NEXT: ]
---
{"jsonrpc":"2.0","id":10000,"method":"shutdown"}
----
-{"jsonrpc":"2.0","method":"exit"}
Modified: clang-tools-extra/trunk/unittests/clangd/CMakeLists.txt
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/unittests/clangd/CMakeLists.txt?rev=344637&r1=344636&r2=344637&view=diff
==============================================================================
--- clang-tools-extra/trunk/unittests/clangd/CMakeLists.txt (original)
+++ clang-tools-extra/trunk/unittests/clangd/CMakeLists.txt Tue Oct 16 11:44:41 2018
@@ -27,7 +27,6 @@ add_extra_unittest(ClangdTests
GlobalCompilationDatabaseTests.cpp
HeadersTests.cpp
IndexTests.cpp
- JSONTransportTests.cpp
QualityTests.cpp
RIFFTests.cpp
SerializationTests.cpp
Removed: clang-tools-extra/trunk/unittests/clangd/JSONTransportTests.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/unittests/clangd/JSONTransportTests.cpp?rev=344636&view=auto
==============================================================================
--- clang-tools-extra/trunk/unittests/clangd/JSONTransportTests.cpp (original)
+++ clang-tools-extra/trunk/unittests/clangd/JSONTransportTests.cpp (removed)
@@ -1,199 +0,0 @@
-//===-- JSONTransportTests.cpp -------------------------------------------===//
-//
-// The LLVM Compiler Infrastructure
-//
-// This file is distributed under the University of Illinois Open Source
-// License. See LICENSE.TXT for details.
-//
-//===----------------------------------------------------------------------===//
-#include "Protocol.h"
-#include "Transport.h"
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
-#include <stdio.h>
-
-using namespace llvm;
-namespace clang {
-namespace clangd {
-namespace {
-
-// No fmemopen on windows, so we can't easily run this test.
-#ifndef WIN32
-
-// Fixture takes care of managing the input/output buffers for the transport.
-class JSONTransportTest : public ::testing::Test {
- std::string InBuf, OutBuf, MirrorBuf;
- llvm::raw_string_ostream Out, Mirror;
- std::unique_ptr<FILE, int (*)(FILE *)> In;
-
-protected:
- JSONTransportTest() : Out(OutBuf), Mirror(MirrorBuf), In(nullptr, nullptr) {}
-
- template <typename... Args>
- std::unique_ptr<Transport> transport(std::string InData, bool Pretty,
- JSONStreamStyle Style) {
- InBuf = std::move(InData);
- In = {fmemopen(&InBuf[0], InBuf.size(), "r"), &fclose};
- return newJSONTransport(In.get(), Out, &Mirror, Pretty, Style);
- }
-
- std::string input() const { return InBuf; }
- std::string output() { return Out.str(); }
- std::string input_mirror() { return Mirror.str(); }
-};
-
-// Echo is a simple server running on a transport:
-// - logs each message it gets.
-// - when it gets a call, replies to it
-// - when it gets a notification for method "call", makes a call on Target
-// Hangs up when it gets an exit notification.
-class Echo : public Transport::MessageHandler {
- Transport &Target;
- std::string LogBuf;
- raw_string_ostream Log;
-
-public:
- Echo(Transport &Target) : Target(Target), Log(LogBuf) {}
-
- std::string log() { return Log.str(); }
-
- bool onNotify(StringRef Method, json::Value Params) override {
- Log << "Notification " << Method << ": " << Params << "\n";
- if (Method == "call")
- Target.call("echo call", std::move(Params), 42);
- return Method != "exit";
- }
-
- bool onCall(StringRef Method, json::Value Params, json::Value ID) override {
- Log << "Call " << Method << "(" << ID << "): " << Params << "\n";
- if (Method == "err")
- Target.reply(ID, make_error<LSPError>("trouble at mill", ErrorCode(88)));
- else
- Target.reply(ID, std::move(Params));
- return true;
- }
-
- bool onReply(json::Value ID, Expected<json::Value> Params) override {
- if (Params)
- Log << "Reply(" << ID << "): " << *Params << "\n";
- else
- Log << "Reply(" << ID
- << "): error = " << llvm::toString(Params.takeError()) << "\n";
- return true;
- }
-};
-
-std::string trim(StringRef S) { return S.trim().str(); }
-
-// Runs an Echo session using the standard JSON-RPC format we use in production.
-TEST_F(JSONTransportTest, StandardDense) {
- auto T = transport(
- "Content-Length: 52\r\n\r\n"
- R"({"jsonrpc": "2.0", "method": "call", "params": 1234})"
- "Content-Length: 46\r\n\r\n"
- R"({"jsonrpc": "2.0", "id": 1234, "result": 5678})"
- "Content-Length: 67\r\n\r\n"
- R"({"jsonrpc": "2.0", "method": "foo", "id": "abcd", "params": "efgh"})"
- "Content-Length: 73\r\n\r\n"
- R"({"jsonrpc": "2.0", "id": "xyz", "error": {"code": 99, "message": "bad!"}})"
- "Content-Length: 68\r\n\r\n"
- R"({"jsonrpc": "2.0", "method": "err", "id": "wxyz", "params": "boom!"})"
- "Content-Length: 36\r\n\r\n"
- R"({"jsonrpc": "2.0", "method": "exit"})",
- /*Pretty=*/false, JSONStreamStyle::Standard);
- Echo E(*T);
- auto Err = T->loop(E);
- EXPECT_FALSE(bool(Err)) << llvm::toString(std::move(Err));
-
- EXPECT_EQ(trim(E.log()), trim(R"(
-Notification call: 1234
-Reply(1234): 5678
-Call foo("abcd"): "efgh"
-Reply("xyz"): error = 99: bad!
-Call err("wxyz"): "boom!"
-Notification exit: null
- )"));
- EXPECT_EQ(
- trim(output()),
- "Content-Length: 60\r\n\r\n"
- R"({"id":42,"jsonrpc":"2.0","method":"echo call","params":1234})"
- "Content-Length: 45\r\n\r\n"
- R"({"id":"abcd","jsonrpc":"2.0","result":"efgh"})"
- "Content-Length: 77\r\n\r\n"
- R"({"error":{"code":88,"message":"trouble at mill"},"id":"wxyz","jsonrpc":"2.0"})");
- EXPECT_EQ(trim(input_mirror()), trim(input()));
-}
-
-// Runs an Echo session using the "delimited" input and pretty-printed output
-// that we use in lit tests.
-TEST_F(JSONTransportTest, DelimitedPretty) {
- auto T = transport(R"jsonrpc(
-{"jsonrpc": "2.0", "method": "call", "params": 1234}
----
-{"jsonrpc": "2.0", "id": 1234, "result": 5678}
----
-{"jsonrpc": "2.0", "method": "foo", "id": "abcd", "params": "efgh"}
----
-{"jsonrpc": "2.0", "id": "xyz", "error": {"code": 99, "message": "bad!"}}
----
-{"jsonrpc": "2.0", "method": "err", "id": "wxyz", "params": "boom!"}
----
-{"jsonrpc": "2.0", "method": "exit"}
- )jsonrpc",
- /*Pretty=*/true, JSONStreamStyle::Delimited);
- Echo E(*T);
- auto Err = T->loop(E);
- EXPECT_FALSE(bool(Err)) << llvm::toString(std::move(Err));
-
- EXPECT_EQ(trim(E.log()), trim(R"(
-Notification call: 1234
-Reply(1234): 5678
-Call foo("abcd"): "efgh"
-Reply("xyz"): error = 99: bad!
-Call err("wxyz"): "boom!"
-Notification exit: null
- )"));
- EXPECT_EQ(trim(output()), "Content-Length: 77\r\n\r\n"
- R"({
- "id": 42,
- "jsonrpc": "2.0",
- "method": "echo call",
- "params": 1234
-})"
- "Content-Length: 58\r\n\r\n"
- R"({
- "id": "abcd",
- "jsonrpc": "2.0",
- "result": "efgh"
-})"
- "Content-Length: 105\r\n\r\n"
- R"({
- "error": {
- "code": 88,
- "message": "trouble at mill"
- },
- "id": "wxyz",
- "jsonrpc": "2.0"
-})");
- EXPECT_EQ(trim(input_mirror()), trim(input()));
-}
-
-// IO errors such as EOF ane reported.
-// The only successful return from loop() is if a handler returned false.
-TEST_F(JSONTransportTest, EndOfFile) {
- auto T = transport("Content-Length: 52\r\n\r\n"
- R"({"jsonrpc": "2.0", "method": "call", "params": 1234})",
- /*Pretty=*/false, JSONStreamStyle::Standard);
- Echo E(*T);
- auto Err = T->loop(E);
- EXPECT_EQ(trim(E.log()), "Notification call: 1234");
- EXPECT_TRUE(bool(Err)); // Ran into EOF with no handler signalling done.
- consumeError(std::move(Err));
- EXPECT_EQ(trim(input_mirror()), trim(input()));
-}
-
-#endif
-
-} // namespace
-} // namespace clangd
-} // namespace clang
More information about the cfe-commits
mailing list