ilya-biryukov created this revision. The arg is useful for debugging and creating test cases.
https://reviews.llvm.org/D37970 Files: clangd/JSONRPCDispatcher.cpp clangd/JSONRPCDispatcher.h clangd/tool/ClangdMain.cpp test/clangd/input-mirror.test
Index: test/clangd/input-mirror.test =================================================================== --- /dev/null +++ test/clangd/input-mirror.test @@ -0,0 +1,154 @@ +# RUN: clangd -run-synchronously -input-mirror-file %t < %s +# Note that we have to use '-Z' as -input-mirror-file does not have a newline at the end of file. +# RUN: diff -Z %t %s +# It is absolutely vital that this file has CRLF line endings. +# +Content-Length: 125 + +{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}} + +Content-Length: 172 + +{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file:///main.cpp","languageId":"cpp","version":1,"text":"int main() {\nint a;\na;\n}\n"}}} + +Content-Length: 148 + +{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":2,"character":0}}} +# Go to local variable + +Content-Length: 148 + +{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":2,"character":1}}} +# Go to local variable, end of token + +Content-Length: 214 + +{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///main.cpp","version":2},"contentChanges":[{"text":"struct Foo {\nint x;\n};\nint main() {\n Foo bar = { x : 1 };\n}\n"}]}} + +Content-Length: 149 + +{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":4,"character":14}}} +# Go to field, GNU old-style field designator + +Content-Length: 215 + +{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///main.cpp","version":3},"contentChanges":[{"text":"struct Foo {\nint x;\n};\nint main() {\n Foo baz = { .x = 2 };\n}\n"}]}} + +Content-Length: 149 + +{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":4,"character":15}}} +# Go to field, field designator + +Content-Length: 187 + +{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///main.cpp","version":4},"contentChanges":[{"text":"int main() {\n main();\n return 0;\n}"}]}} + +Content-Length: 148 + +{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":1,"character":3}}} +# Go to function declaration, function call + +Content-Length: 208 + +{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///main.cpp","version":5},"contentChanges":[{"text":"struct Foo {\n};\nint main() {\n Foo bar;\n return 0;\n}\n"}]}} + +Content-Length: 148 + +{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":3,"character":3}}} +# Go to struct declaration, new struct instance + +Content-Length: 231 + +{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///main.cpp","version":5},"contentChanges":[{"text":"namespace n1 {\nstruct Foo {\n};\n}\nint main() {\n n1::Foo bar;\n return 0;\n}\n"}]}} + +Content-Length: 148 + +{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":5,"character":4}}} +# Go to struct declaration, new struct instance, qualified name + +Content-Length: 215 + +{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///main.cpp","version":6},"contentChanges":[{"text":"struct Foo {\n int x;\n};\nint main() {\n Foo bar;\n bar.x;\n}\n"}]}} + +Content-Length: 148 + +{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":5,"character":7}}} +# Go to field declaration, field reference + +Content-Length: 220 + +{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///main.cpp","version":7},"contentChanges":[{"text":"struct Foo {\n void x();\n};\nint main() {\n Foo bar;\n bar.x();\n}\n"}]}} + +Content-Length: 148 + +{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":5,"character":7}}} +# Go to method declaration, method call + +Content-Length: 240 + +{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///main.cpp","version":7},"contentChanges":[{"text":"struct Foo {\n};\ntypedef Foo TypedefFoo;\nint main() {\n TypedefFoo bar;\n return 0;\n}\n"}]}} + +Content-Length: 149 + +{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":4,"character":10}}} +# Go to typedef + +Content-Length: 254 + +{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///main.cpp","version":7},"contentChanges":[{"text":"template <typename MyTemplateParam>\nvoid foo() {\n MyTemplateParam a;\n}\nint main() {\n return 0;\n}\n"}]}} + +Content-Length: 149 + +{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":2,"character":13}}} +# Go to template type parameter. Fails until clangIndex is modified to handle those. +# no-CHECK: {"jsonrpc":"2.0","id":1,"result":[{"uri": "file:///main.cpp", "range": {"start": {"line": 0, "character": 10}, "end": {"line": 0, "character": 34}}}]} + +Content-Length: 256 + +{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///main.cpp","version":7},"contentChanges":[{"text":"namespace ns {\nstruct Foo {\nstatic void bar() {}\n};\n}\nint main() {\n ns::Foo::bar();\n return 0;\n}\n"}]}} + +Content-Length: 148 + +{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":6,"character":4}}} +# Go to namespace, static method call + +Content-Length: 265 + +{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///main.cpp","version":7},"contentChanges":[{"text":"namespace ns {\nstruct Foo {\n int field;\n Foo(int param) : field(param) {}\n};\n}\nint main() {\n return 0;\n}\n"}]}} + +Content-Length: 149 + +{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":3,"character":21}}} +# Go to field, member initializer + +Content-Length: 204 + +{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///main.cpp","version":7},"contentChanges":[{"text":"#define MY_MACRO 0\nint main() {\n return MY_MACRO;\n}\n"}]}} + +Content-Length: 148 + +{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":2,"character":9}}} +# Go to macro. + +Content-Length: 217 + +{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///main.cpp","version":7},"contentChanges":[{"text":"#define FOO 1\nint a = FOO;\n#define FOO 2\nint b = FOO;\n#undef FOO\n"}]}} + +Content-Length: 148 + +{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":1,"character":8}}} +# Go to macro, re-defined later + +Content-Length: 148 + +{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":3,"character":8}}} +# Go to macro, undefined later + +Content-Length: 148 + +{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":4,"character":7}}} +# Go to macro, being undefined + +Content-Length: 44 + +{"jsonrpc":"2.0","id":3,"method":"shutdown"} Index: clangd/tool/ClangdMain.cpp =================================================================== --- clangd/tool/ClangdMain.cpp +++ clangd/tool/ClangdMain.cpp @@ -12,6 +12,7 @@ #include "llvm/Support/CommandLine.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/Program.h" +#include "llvm/Support/raw_ostream.h" #include <iostream> #include <memory> @@ -42,6 +43,12 @@ llvm::cl::desc("Directory for system clang headers"), llvm::cl::init(""), llvm::cl::Hidden); +static llvm::cl::opt<std::string> InputMirrorFile( + "input-mirror-file", + llvm::cl::desc( + "Mirror all LSP input to the specified file. Useful for debugging."), + llvm::cl::init(""), llvm::cl::Hidden); + int main(int argc, char *argv[]) { llvm::cl::ParseCommandLineOptions(argc, argv, "clangd"); @@ -56,9 +63,21 @@ if (RunSynchronously) WorkerThreadsCount = 0; + llvm::Optional<llvm::raw_fd_ostream> InputMirrorStream; + if (!InputMirrorFile.empty()) { + std::error_code EC; + InputMirrorStream.emplace(InputMirrorFile, /*ref*/ EC, llvm::sys::fs::F_RW); + if (EC) { + InputMirrorStream.reset(); + llvm::errs() << "Error while opening an input mirror file: " + << EC.message(); + } + } + llvm::raw_ostream &Outs = llvm::outs(); llvm::raw_ostream &Logs = llvm::errs(); - JSONOutput Out(Outs, Logs); + JSONOutput Out(Outs, Logs, + InputMirrorStream ? InputMirrorStream.getPointer() : nullptr); // Change stdin to binary to not lose \r\n on windows. llvm::sys::ChangeStdinToBinary(); Index: clangd/JSONRPCDispatcher.h =================================================================== --- clangd/JSONRPCDispatcher.h +++ clangd/JSONRPCDispatcher.h @@ -23,18 +23,25 @@ /// them. class JSONOutput { public: - JSONOutput(llvm::raw_ostream &Outs, llvm::raw_ostream &Logs) - : Outs(Outs), Logs(Logs) {} + JSONOutput(llvm::raw_ostream &Outs, llvm::raw_ostream &Logs, + llvm::raw_ostream *InputMirror = nullptr) + : Outs(Outs), Logs(Logs), InputMirror(InputMirror) {} /// Emit a JSONRPC message. void writeMessage(const Twine &Message); /// Write to the logging stream. void log(const Twine &Message); + /// 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); + private: llvm::raw_ostream &Outs; llvm::raw_ostream &Logs; + llvm::raw_ostream *InputMirror; std::mutex StreamMutex; }; Index: clangd/JSONRPCDispatcher.cpp =================================================================== --- clangd/JSONRPCDispatcher.cpp +++ clangd/JSONRPCDispatcher.cpp @@ -37,6 +37,14 @@ Logs.flush(); } +void JSONOutput::mirrorInput(const Twine &Message) { + if (!InputMirror) + return; + + *InputMirror << Message; + InputMirror->flush(); +} + void Handler::handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) { Output.log("Method ignored.\n"); // Return that this method is unsupported. @@ -147,6 +155,14 @@ continue; } + Out.mirrorInput(Line); + // Mirror '\n' that gets consumed by std::getline, but is not included in + // the resulting Line. + // Note that '\r' is part of Line, so we don't need to mirror it + // separately. + if (!In.eof()) + Out.mirrorInput("\n"); + llvm::StringRef LineRef(Line); // We allow YAML-style comments in headers. Technically this isn't part @@ -163,9 +179,8 @@ if (LineRef.consume_front("Content-Length: ")) { if (ContentLength != 0) { Out.log("Warning: Duplicate Content-Length header received. " - "The previous value for this message (" - + std::to_string(ContentLength) - + ") was ignored.\n"); + "The previous value for this message (" + + std::to_string(ContentLength) + ") was ignored.\n"); } llvm::getAsUnsignedInteger(LineRef.trim(), 0, ContentLength); @@ -185,15 +200,13 @@ // parser. std::vector<char> JSON(ContentLength + 1, '\0'); In.read(JSON.data(), ContentLength); + Out.mirrorInput(StringRef(JSON.data(), In.gcount())); // If the stream is aborted before we read ContentLength bytes, In // will have eofbit and failbit set. if (!In) { - Out.log("Input was aborted. Read only " - + std::to_string(In.gcount()) - + " bytes of expected " - + std::to_string(ContentLength) - + ".\n"); + Out.log("Input was aborted. Read only " + std::to_string(In.gcount()) + + " bytes of expected " + std::to_string(ContentLength) + ".\n"); break; } @@ -209,8 +222,8 @@ if (IsDone) break; } else { - Out.log( "Warning: Missing Content-Length header, or message has zero " - "length.\n" ); + Out.log("Warning: Missing Content-Length header, or message has zero " + "length.\n"); } } }
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits