[PATCH] D53286: [clangd] Refactor JSON-over-stdin/stdout code into Transport abstraction.
Eric Liu via Phabricator via cfe-commits
cfe-commits at lists.llvm.org
Tue Oct 16 08:37:20 PDT 2018
ioeric added a comment.
Nice! The code looks much clearer. Just some nits
================
Comment at: clangd/ClangdLSPServer.cpp:156
+void ClangdLSPServer::onExit(ExitParams &Params) {
+ // XXX handler should return true.
+}
----------------
?
================
Comment at: clangd/ClangdLSPServer.h:45
/// 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
----------------
doc is outdated now
================
Comment at: clangd/JSONRPCDispatcher.cpp:218
-// 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
----------------
The comment should also be moved?
================
Comment at: clangd/JSONRPCDispatcher.h:29
+// Logs to an output stream, such as stderr.
+// TODO: rename and move.
class JSONOutput : public Logger {
----------------
nit: make the FIXME more concrete?
================
Comment at: clangd/JSONTransport.cpp:23
+ Code = L.Code;
+ return make_error<StringError>(L.Message, inconvertibleErrorCode());
+ }));
----------------
Is `E` guaranteed to be an `LSPError`? Do we need a handler for other errors?
================
Comment at: clangd/JSONTransport.cpp:83
+ vlog(Pretty ? "<<< {0:2}\n" : "<<< {0}\n", *Doc);
+ if (handleMessage(std::move(*Doc), Handler))
+ return Error::success();
----------------
Maybe add a comment here that the return value of `handleMessage` indicates whether to shutdown. It' not clear without context.
================
Comment at: clangd/JSONTransport.cpp:129
+ if (!Object || Object->getString("jsonrpc") != Optional<StringRef>("2.0")) {
+ elog("Not a JSON-RPC message: {0:2}", Message);
+ return false;
----------------
nit: add `2.0` to the log?
================
Comment at: clangd/Transport.h:50
+ virtual ~MessageHandler() = default;
+ virtual bool notify(llvm::StringRef Method, llvm::json::Value) = 0;
+ virtual bool call(llvm::StringRef Method, llvm::json::Value Params,
----------------
nit: maybe `handleNotify` to make the two directions more distinguishable? This can get a bit counter-intuitive in the call sites.
================
Comment at: unittests/clangd/JSONTransportTests.cpp:106
+ auto Err = T->loop(E);
+ EXPECT_FALSE(bool(Err)) << llvm::toString(std::move(Err));
+
----------------
Would `operator<<` be used when the error is real (i.e. check passes)?
Repository:
rCTE Clang Tools Extra
https://reviews.llvm.org/D53286
More information about the cfe-commits
mailing list