Index: clangd/ClangdLSPServer.cpp =================================================================== --- clangd/ClangdLSPServer.cpp +++ clangd/ClangdLSPServer.cpp @@ -143,14 +143,19 @@ void ClangdLSPServer::onRename(Ctx C, RenameParams &Params) { auto File = Params.textDocument.uri.file; + auto Code = Server.getDocument(File); + if (!Code) + return replyError(C, ErrorCode::InvalidParams, + "onRename called for non-added file"); + auto Replacements = Server.rename(C, File, Params.position, Params.newName); if (!Replacements) { replyError(C, ErrorCode::InternalError, llvm::toString(Replacements.takeError())); return; } - std::string Code = Server.getDocument(File); - std::vector Edits = replacementsToEdits(Code, *Replacements); + + std::vector Edits = replacementsToEdits(*Code, *Replacements); WorkspaceEdit WE; WE.changes = {{Params.textDocument.uri.uri, Edits}}; reply(C, WE); @@ -164,10 +169,14 @@ void ClangdLSPServer::onDocumentOnTypeFormatting( Ctx C, DocumentOnTypeFormattingParams &Params) { auto File = Params.textDocument.uri.file; - std::string Code = Server.getDocument(File); - auto ReplacementsOrError = Server.formatOnType(Code, File, Params.position); + auto Code = Server.getDocument(File); + if (!Code) + return replyError(C, ErrorCode::InvalidParams, + "onDocumentOnTypeFormatting called for non-added file"); + + auto ReplacementsOrError = Server.formatOnType(*Code, File, Params.position); if (ReplacementsOrError) - reply(C, json::ary(replacementsToEdits(Code, ReplacementsOrError.get()))); + reply(C, json::ary(replacementsToEdits(*Code, ReplacementsOrError.get()))); else replyError(C, ErrorCode::UnknownErrorCode, llvm::toString(ReplacementsOrError.takeError())); @@ -176,10 +185,14 @@ void ClangdLSPServer::onDocumentRangeFormatting( Ctx C, DocumentRangeFormattingParams &Params) { auto File = Params.textDocument.uri.file; - std::string Code = Server.getDocument(File); - auto ReplacementsOrError = Server.formatRange(Code, File, Params.range); + auto Code = Server.getDocument(File); + if (!Code) + return replyError(C, ErrorCode::InvalidParams, + "onDocumentRangeFormatting called for non-added file"); + + auto ReplacementsOrError = Server.formatRange(*Code, File, Params.range); if (ReplacementsOrError) - reply(C, json::ary(replacementsToEdits(Code, ReplacementsOrError.get()))); + reply(C, json::ary(replacementsToEdits(*Code, ReplacementsOrError.get()))); else replyError(C, ErrorCode::UnknownErrorCode, llvm::toString(ReplacementsOrError.takeError())); @@ -188,10 +201,14 @@ void ClangdLSPServer::onDocumentFormatting(Ctx C, DocumentFormattingParams &Params) { auto File = Params.textDocument.uri.file; - std::string Code = Server.getDocument(File); - auto ReplacementsOrError = Server.formatFile(Code, File); + auto Code = Server.getDocument(File); + if (!Code) + return replyError(C, ErrorCode::InvalidParams, + "onDocumentFormatting called for non-added file"); + + auto ReplacementsOrError = Server.formatFile(*Code, File); if (ReplacementsOrError) - reply(C, json::ary(replacementsToEdits(Code, ReplacementsOrError.get()))); + reply(C, json::ary(replacementsToEdits(*Code, ReplacementsOrError.get()))); else replyError(C, ErrorCode::UnknownErrorCode, llvm::toString(ReplacementsOrError.takeError())); @@ -200,7 +217,11 @@ void ClangdLSPServer::onCodeAction(Ctx C, CodeActionParams &Params) { // We provide a code action for each diagnostic at the requested location // which has FixIts available. - std::string Code = Server.getDocument(Params.textDocument.uri.file); + auto Code = Server.getDocument(Params.textDocument.uri.file); + if (!Code) + return replyError(C, ErrorCode::InvalidParams, + "onCodeAction called for non-added file"); + json::ary Commands; for (Diagnostic &D : Params.context.diagnostics) { auto Edits = getFixIts(Params.textDocument.uri.file, D); Index: clangd/ClangdServer.h =================================================================== --- clangd/ClangdServer.h +++ clangd/ClangdServer.h @@ -308,11 +308,11 @@ PathRef File, Position Pos, llvm::StringRef NewName); - /// Gets current document contents for \p File. \p File must point to a - /// currently tracked file. + /// Gets current document contents for \p File. Returns None if \p File is not + /// currently tracked. /// FIXME(ibiryukov): This function is here to allow offset-to-Position /// conversions in outside code, maybe there's a way to get rid of it. - std::string getDocument(PathRef File); + llvm::Optional getDocument(PathRef File); /// Only for testing purposes. /// Waits until all requests to worker thread are finished and dumps AST for Index: clangd/ClangdServer.cpp =================================================================== --- clangd/ClangdServer.cpp +++ clangd/ClangdServer.cpp @@ -356,7 +356,6 @@ Expected> ClangdServer::rename(const Context &Ctx, PathRef File, Position Pos, llvm::StringRef NewName) { - std::string Code = getDocument(File); std::shared_ptr Resources = Units.getFile(File); RefactoringResultCollector ResultCollector; Resources->getAST().get()->runUnderLock([&](ParsedAST *AST) { @@ -402,15 +401,17 @@ return Replacements; } -std::string ClangdServer::getDocument(PathRef File) { - auto draft = DraftMgr.getDraft(File); - assert(draft.Draft && "File is not tracked, cannot get contents"); - return *draft.Draft; +llvm::Optional ClangdServer::getDocument(PathRef File) { + auto Latest = DraftMgr.getDraft(File); + if (!Latest.Draft) + return llvm::None; + return std::move(*Latest.Draft); } std::string ClangdServer::dumpAST(PathRef File) { std::shared_ptr Resources = Units.getFile(File); - assert(Resources && "dumpAST is called for non-added document"); + if (!Resources) + return ""; std::string Result; Resources->getAST().get()->runUnderLock([&Result](ParsedAST *AST) { Index: test/clangd/crash-non-added-files.test =================================================================== --- /dev/null +++ test/clangd/crash-non-added-files.test @@ -0,0 +1,45 @@ +# RUN: clangd -pretty -run-synchronously < %s | FileCheck -strict-whitespace %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: 746 + +{"jsonrpc":"2.0","id":2,"method":"textDocument/codeAction","params":{"textDocument":{"uri":"file:///foo.c"},"range":{"start":{"line":104,"character":13},"end":{"line":0,"character":35}},"context":{"diagnostics":[{"range":{"start": {"line": 0, "character": 32}, "end": {"line": 0, "character": 37}},"severity":2,"message":"using the result of an assignment as a condition without parentheses"},{"range":{"start": {"line": 0, "character": 34}, "end": {"line": 0, "character": 35}},"severity":3,"message":"place parentheses around the assignment to silence this warning"},{"range":{"start": {"line": 0, "character": 34}, "end": {"line": 0, "character": 35}},"severity":3,"message":"use '==' to turn this assignment into an equality comparison"}]}}} +# CHECK: "error": { +# CHECK-NEXT: "code": -32602 +# CHECK-NEXT: "message": "onCodeAction called for non-added file" +# CHECK-NEXT: }, +# CHECK-NEXT: "id": 2, +Content-Length: 233 + +{"jsonrpc":"2.0","id":3,"method":"textDocument/rangeFormatting","params":{"textDocument":{"uri":"file:///foo.c"},"range":{"start":{"line":1,"character":4},"end":{"line":1,"character":12}},"options":{"tabSize":4,"insertSpaces":true}}} +# CHECK: "error": { +# CHECK-NEXT: "code": -32602 +# CHECK-NEXT: "message": "onDocumentRangeFormatting called for non-added file" +# CHECK-NEXT: }, +# CHECK-NEXT: "id": 3, +Content-Length: 153 + +{"jsonrpc":"2.0","id":4,"method":"textDocument/formatting","params":{"textDocument":{"uri":"file:///foo.c"},"options":{"tabSize":4,"insertSpaces":true}}} +# CHECK: "error": { +# CHECK-NEXT: "code": -32602 +# CHECK-NEXT: "message": "onDocumentFormatting called for non-added file" +# CHECK-NEXT: }, +# CHECK-NEXT: "id": 4, +Content-Length: 204 + +{"jsonrpc":"2.0","id":5,"method":"textDocument/onTypeFormatting","params":{"textDocument":{"uri":"file:///foo.c"},"position":{"line":3,"character":1},"ch":"}","options":{"tabSize":4,"insertSpaces":true}}} +# CHECK: "error": { +# CHECK-NEXT: "code": -32602 +# CHECK-NEXT: "message": "onDocumentOnTypeFormatting called for non-added file" +# CHECK-NEXT: }, +# CHECK-NEXT: "id": 5, +Content-Length: 44 + +{"jsonrpc":"2.0","id":6,"method":"shutdown"} +Content-Length: 33 + +{"jsonrpc":"2.0","method":"exit"}