Index: clangd/ClangdUnit.cpp =================================================================== --- clangd/ClangdUnit.cpp +++ clangd/ClangdUnit.cpp @@ -405,10 +405,15 @@ &IgnoreDiagnostics, false); CI = createInvocationFromCommandLine(ArgStrs, CommandLineDiagsEngine, Inputs.FS); + if (!CI) { + log("Could not build CompilerInvocation for file " + FileName); + AST = llvm::None; + Preamble = nullptr; + return llvm::None; + } // createInvocationFromCommandLine sets DisableFree. CI->getFrontendOpts().DisableFree = false; } - assert(CI && "Couldn't create CompilerInvocation"); std::unique_ptr ContentsBuffer = llvm::MemoryBuffer::getMemBufferCopy(Inputs.Contents, FileName); Index: clangd/CodeComplete.cpp =================================================================== --- clangd/CodeComplete.cpp +++ clangd/CodeComplete.cpp @@ -650,7 +650,10 @@ CompilerInstance::createDiagnostics(new DiagnosticOptions, &DummyDiagsConsumer, false), Input.VFS); - assert(CI && "Couldn't create CompilerInvocation"); + if (!CI) { + log("Couldn't create CompilerInvocation");; + return false; + } CI->getFrontendOpts().DisableFree = false; std::unique_ptr ContentsBuffer = Index: unittests/clangd/ClangdTests.cpp =================================================================== --- unittests/clangd/ClangdTests.cpp +++ unittests/clangd/ClangdTests.cpp @@ -9,6 +9,7 @@ #include "ClangdLSPServer.h" #include "ClangdServer.h" +#include "Matchers.h" #include "TestFS.h" #include "clang/Config/config.h" #include "llvm/ADT/SmallVector.h" @@ -453,6 +454,40 @@ EXPECT_THAT(Server.getUsedBytesPerFile(), IsEmpty()); } +TEST_F(ClangdVFSTest, InvalidCompileCommand) { + MockFSProvider FS; + ErrorCheckingDiagConsumer DiagConsumer; + MockCompilationDatabase CDB; + + ClangdServer Server(CDB, DiagConsumer, FS, + /*AsyncThreadsCount=*/0, + /*StorePreamblesInMemory=*/true); + + auto FooCpp = getVirtualTestFilePath("foo.cpp"); + // clang cannot create CompilerInvocation if we pass two files in the + // CompileCommand. We pass the file in ExtraFlags once and CDB adds another + // one in getCompileCommand(). + CDB.ExtraClangFlags.push_back(FooCpp.str()); + + // Clang can't parse command args in that case, but we shouldn't crash. + Server.addDocument(FooCpp, "int main() {}"); + + EXPECT_EQ(Server.dumpAST(FooCpp), ""); + EXPECT_ERROR(Server.findDefinitions(FooCpp, Position{0, 0})); + EXPECT_ERROR(Server.findDocumentHighlights(FooCpp, Position{0, 0})); + EXPECT_ERROR(Server.rename(FooCpp, Position{0, 0}, "new_name")); + // FIXME: codeComplete and signatureHelp should also return errors when they + // can't parse the file. + EXPECT_THAT( + Server.codeComplete(FooCpp, Position{0, 0}, clangd::CodeCompleteOptions()) + .get() + .Value.items, + IsEmpty()); + EXPECT_THAT( + Server.signatureHelp(FooCpp, Position{0, 0}).get().Value.signatures, + IsEmpty()); +} + class ClangdThreadingTest : public ClangdVFSTest {}; TEST_F(ClangdThreadingTest, StressTest) { Index: unittests/clangd/Matchers.h =================================================================== --- unittests/clangd/Matchers.h +++ unittests/clangd/Matchers.h @@ -108,6 +108,28 @@ return PolySubsequenceMatcher(std::forward(M)...); } +// EXPECT_ERROR seems like a pretty generic name, make sure it's not defined +// already. +#ifdef EXPECT_ERROR +#error "Refusing to redefine EXPECT_ERROR" +#endif + +// Consumes llvm::Expected, checks it contains an error and marks it as +// handled. +#define EXPECT_ERROR(expectedValue) \ + do { \ + auto &&ComputedValue = (expectedValue); \ + if (ComputedValue) { \ + ADD_FAILURE() << "expected an error from " << #expectedValue \ + << " but got " \ + << ::testing::PrintToString(*ComputedValue); \ + break; \ + } \ + handleAllErrors(ComputedValue.takeError(), \ + [](const llvm::ErrorInfoBase &) {}); \ + \ + } while (false) + } // namespace clangd } // namespace clang #endif