Index: clangd/ASTManager.cpp =================================================================== --- clangd/ASTManager.cpp +++ clangd/ASTManager.cpp @@ -227,7 +227,7 @@ Diagnostics += R"({"range":)" + Range::unparse(R) + R"(,"severity":)" + std::to_string(getSeverity(D->getLevel())) + - R"(,"message":")" + llvm::yaml::escape(D->getMessage()) + + R"(,"message":")" + jsonEscape(D->getMessage()) + R"("},)"; // We convert to Replacements to become independent of the SourceManager. Index: clangd/Protocol.h =================================================================== --- clangd/Protocol.h +++ clangd/Protocol.h @@ -29,6 +29,8 @@ namespace clang { namespace clangd { +std::string jsonEscape(llvm::StringRef Input); + struct URI { std::string uri; std::string file; Index: clangd/Protocol.cpp =================================================================== --- clangd/Protocol.cpp +++ clangd/Protocol.cpp @@ -15,12 +15,48 @@ #include "Protocol.h" #include "clang/Basic/LLVM.h" #include "llvm/ADT/SmallString.h" +#include #include "llvm/Support/Format.h" #include "llvm/Support/raw_ostream.h" #include "llvm/Support/Path.h" using namespace clang::clangd; +std::string clang::clangd::jsonEscape(llvm::StringRef Input) { + std::string EscapedInput; + for (llvm::StringRef::iterator i = Input.begin(), e = Input.end(); i != e; ++i) { + if (*i == '\\') + EscapedInput += "\\\\"; + else if (*i == '"') + EscapedInput += "\\\""; + // bell + else if (*i == 0x07) + EscapedInput += "\\a"; + // backspace + else if (*i == 0x08) + EscapedInput += "\\b"; + // hoz tab + else if (*i == 0x09) + EscapedInput += "\\t"; + // new line + else if (*i == 0x0A) + EscapedInput += "\\n"; + // form feed + else if (*i == 0x0C) + EscapedInput += "\\f"; + // carr return + else if (*i == 0x0D) + EscapedInput += "\\r"; + else if ((unsigned char)*i < 0x20) { // Control characters not handled above. + std::string HexStr = llvm::utohexstr(*i); + EscapedInput += "\\u" + std::string(4 - HexStr.size(), '0') + HexStr; + } + else + EscapedInput.push_back(*i); + } + return EscapedInput; +} + URI URI::fromUri(llvm::StringRef uri) { URI Result; Result.uri = uri; @@ -230,7 +266,7 @@ std::string Result; llvm::raw_string_ostream(Result) << llvm::format( R"({"range": %s, "newText": "%s"})", Range::unparse(P.range).c_str(), - llvm::yaml::escape(P.newText).c_str()); + jsonEscape(P.newText).c_str()); return Result; } @@ -670,20 +706,20 @@ std::string Result = "{"; llvm::raw_string_ostream Os(Result); assert(!CI.label.empty() && "completion item label is required"); - Os << R"("label":")" << llvm::yaml::escape(CI.label) << R"(",)"; + Os << R"("label":")" << jsonEscape(CI.label) << R"(",)"; if (CI.kind != CompletionItemKind::Missing) Os << R"("kind":)" << static_cast(CI.kind) << R"(,)"; if (!CI.detail.empty()) - Os << R"("detail":")" << llvm::yaml::escape(CI.detail) << R"(",)"; + Os << R"("detail":")" << jsonEscape(CI.detail) << R"(",)"; if (!CI.documentation.empty()) - Os << R"("documentation":")" << llvm::yaml::escape(CI.documentation) + Os << R"("documentation":")" << jsonEscape(CI.documentation) << R"(",)"; if (!CI.sortText.empty()) - Os << R"("sortText":")" << llvm::yaml::escape(CI.sortText) << R"(",)"; + Os << R"("sortText":")" << jsonEscape(CI.sortText) << R"(",)"; if (!CI.filterText.empty()) - Os << R"("filterText":")" << llvm::yaml::escape(CI.filterText) << R"(",)"; + Os << R"("filterText":")" << jsonEscape(CI.filterText) << R"(",)"; if (!CI.insertText.empty()) - Os << R"("insertText":")" << llvm::yaml::escape(CI.insertText) << R"(",)"; + Os << R"("insertText":")" << jsonEscape(CI.insertText) << R"(",)"; if (CI.insertTextFormat != InsertTextFormat::Missing) { Os << R"("insertTextFormat":")" << static_cast(CI.insertTextFormat) << R"(",)"; Index: clangd/ProtocolHandlers.cpp =================================================================== --- clangd/ProtocolHandlers.cpp +++ clangd/ProtocolHandlers.cpp @@ -172,9 +172,9 @@ if (!Edits.empty()) Commands += - R"({"title":"Apply FixIt ')" + llvm::yaml::escape(D.message) + + R"({"title":"Apply FixIt ')" + jsonEscape(D.message) + R"('", "command": "clangd.applyFix", "arguments": [")" + - llvm::yaml::escape(CAP->textDocument.uri.uri) + + jsonEscape(CAP->textDocument.uri.uri) + R"(", [)" + Edits + R"(]]},)"; } Index: test/clangd/encoding.test =================================================================== --- /dev/null +++ test/clangd/encoding.test @@ -0,0 +1,17 @@ +# RUN: clangd -run-synchronously < %s | FileCheck %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: 154 + +{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file:///foo.c","languageId":"c","version":1,"text":"void main() {é;}"}}} +# +# CHECK: {"jsonrpc":"2.0","method":"textDocument/publishDiagnostics","params":{"uri":"file:///foo.c","diagnostics":[{"range":{"start": {"line": 0, "character": 1}, "end": {"line": 0, "character": 1}},"severity":2,"message":"return type of 'main' is not 'int'"},{"range":{"start": {"line": 0, "character": 1}, "end": {"line": 0, "character": 1}},"severity":3,"message":"change return type to 'int'"},{"range":{"start": {"line": 0, "character": 14}, "end": {"line": 0, "character": 14}},"severity":1,"message":"use of undeclared identifier 'é'"}]}} +# +# +Content-Length: 44 + +{"jsonrpc":"2.0","id":5,"method":"shutdown"}