Index: clangd/ClangdUnit.cpp =================================================================== --- clangd/ClangdUnit.cpp +++ clangd/ClangdUnit.cpp @@ -405,6 +405,12 @@ &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; } 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 @@ -38,6 +38,25 @@ namespace { +// 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 + +// Checks there is an error and marks it as handled. +#define EXPECT_ERROR(expectedValue) \ + do { \ + auto &&ComputedValue = (expectedValue); \ + if (ComputedValue) { \ + ADD_FAILURE() << "expected an error"; \ + break; \ + } \ + handleAllErrors(ComputedValue.takeError(), \ + [](const llvm::ErrorInfoBase &) {}); \ + \ + } while (false) + // Don't wait for async ops in clangd test more than that to avoid blocking // indefinitely in case of bugs. static const std::chrono::seconds DefaultFutureTimeout = @@ -453,6 +472,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) {