Skip to content

Commit

Permalink
[clangd] Fix crash when CompilerInvocation can't be created.
Browse files Browse the repository at this point in the history
Summary:
This can happen if the CompileCommand provided by compilation database
is malformed.

Reviewers: hokein, ioeric, sammccall

Reviewed By: sammccall

Subscribers: klimek, jkorous-apple, cfe-commits

Differential Revision: https://reviews.llvm.org/D43122

llvm-svn: 324732
  • Loading branch information
ilya-biryukov committed Feb 9, 2018
1 parent 0c21abd commit b6ad25c
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 2 deletions.
7 changes: 6 additions & 1 deletion clang-tools-extra/clangd/ClangdUnit.cpp
Expand Up @@ -405,10 +405,15 @@ CppFile::rebuild(ParseInputs &&Inputs) {
&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<llvm::MemoryBuffer> ContentsBuffer =
llvm::MemoryBuffer::getMemBufferCopy(Inputs.Contents, FileName);
Expand Down
5 changes: 4 additions & 1 deletion clang-tools-extra/clangd/CodeComplete.cpp
Expand Up @@ -650,7 +650,10 @@ bool semaCodeComplete(std::unique_ptr<CodeCompleteConsumer> Consumer,
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<llvm::MemoryBuffer> ContentsBuffer =
Expand Down
35 changes: 35 additions & 0 deletions clang-tools-extra/unittests/clangd/ClangdTests.cpp
Expand Up @@ -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"
Expand Down Expand Up @@ -453,6 +454,40 @@ struct Something {
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), "<no-ast>");
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) {
Expand Down
22 changes: 22 additions & 0 deletions clang-tools-extra/unittests/clangd/Matchers.h
Expand Up @@ -108,6 +108,28 @@ PolySubsequenceMatcher<Args...> HasSubsequence(Args &&... M) {
return PolySubsequenceMatcher<Args...>(std::forward<Args>(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<T>, 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

0 comments on commit b6ad25c

Please sign in to comment.