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