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