<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>