[clang-tools-extra] r344620 - [clangd] Refactor JSON-over-stdin/stdout code into Transport abstraction.

Sam McCall via cfe-commits cfe-commits at lists.llvm.org
Tue Oct 16 10:36:24 PDT 2018


Sorry, this broke a lot of bots (GCC? Some issue with macros and raw string
literals)

My laptop is dead and I won't be able to revert until tomorrow, please
could someone revert this?

On Tue, Oct 16, 2018, 18:50 Sam McCall via cfe-commits <
cfe-commits at lists.llvm.org> wrote:

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


More information about the cfe-commits mailing list