[clangd] Do not reuse preamble if compile args changed

Reviewers: hokein, ioeric, sammccall, simark

Reviewed By: simark

Subscribers: klimek, jkorous-apple, cfe-commits, simark

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

llvm-svn: 325522
This commit is contained in:
Ilya Biryukov 2018-02-19 18:18:49 +00:00
parent 70eb508605
commit 7fac6e92a7
3 changed files with 54 additions and 2 deletions

View File

@ -34,6 +34,13 @@ using namespace clang;
namespace { namespace {
bool compileCommandsAreEqual(const tooling::CompileCommand &LHS,
const tooling::CompileCommand &RHS) {
// We don't check for Output, it should not matter to clangd.
return LHS.Directory == RHS.Directory && LHS.Filename == RHS.Filename &&
llvm::makeArrayRef(LHS.CommandLine).equals(RHS.CommandLine);
}
template <class T> std::size_t getUsedBytes(const std::vector<T> &Vec) { template <class T> std::size_t getUsedBytes(const std::vector<T> &Vec) {
return Vec.capacity() * sizeof(T); return Vec.capacity() * sizeof(T);
} }
@ -417,7 +424,7 @@ CppFile::rebuild(ParseInputs &&Inputs) {
// Compute updated Preamble. // Compute updated Preamble.
std::shared_ptr<const PreambleData> NewPreamble = std::shared_ptr<const PreambleData> NewPreamble =
rebuildPreamble(*CI, Inputs.FS, *ContentsBuffer); rebuildPreamble(*CI, Inputs.CompileCommand, Inputs.FS, *ContentsBuffer);
// Remove current AST to avoid wasting memory. // Remove current AST to avoid wasting memory.
AST = llvm::None; AST = llvm::None;
@ -445,6 +452,7 @@ CppFile::rebuild(ParseInputs &&Inputs) {
} }
// Write the results of rebuild into class fields. // Write the results of rebuild into class fields.
Command = std::move(Inputs.CompileCommand);
Preamble = std::move(NewPreamble); Preamble = std::move(NewPreamble);
AST = std::move(NewAST); AST = std::move(NewAST);
return Diagnostics; return Diagnostics;
@ -471,11 +479,12 @@ std::size_t CppFile::getUsedBytes() const {
std::shared_ptr<const PreambleData> std::shared_ptr<const PreambleData>
CppFile::rebuildPreamble(CompilerInvocation &CI, CppFile::rebuildPreamble(CompilerInvocation &CI,
const tooling::CompileCommand &Command,
IntrusiveRefCntPtr<vfs::FileSystem> FS, IntrusiveRefCntPtr<vfs::FileSystem> FS,
llvm::MemoryBuffer &ContentsBuffer) const { llvm::MemoryBuffer &ContentsBuffer) const {
const auto &OldPreamble = this->Preamble; const auto &OldPreamble = this->Preamble;
auto Bounds = ComputePreambleBounds(*CI.getLangOpts(), &ContentsBuffer, 0); auto Bounds = ComputePreambleBounds(*CI.getLangOpts(), &ContentsBuffer, 0);
if (OldPreamble && if (OldPreamble && compileCommandsAreEqual(this->Command, Command) &&
OldPreamble->Preamble.CanReuse(CI, &ContentsBuffer, Bounds, FS.get())) { OldPreamble->Preamble.CanReuse(CI, &ContentsBuffer, Bounds, FS.get())) {
log("Reusing preamble for file " + Twine(FileName)); log("Reusing preamble for file " + Twine(FileName));
return OldPreamble; return OldPreamble;

View File

@ -152,12 +152,15 @@ private:
/// This method is const to ensure we don't incidentally modify any fields. /// This method is const to ensure we don't incidentally modify any fields.
std::shared_ptr<const PreambleData> std::shared_ptr<const PreambleData>
rebuildPreamble(CompilerInvocation &CI, rebuildPreamble(CompilerInvocation &CI,
const tooling::CompileCommand &Command,
IntrusiveRefCntPtr<vfs::FileSystem> FS, IntrusiveRefCntPtr<vfs::FileSystem> FS,
llvm::MemoryBuffer &ContentsBuffer) const; llvm::MemoryBuffer &ContentsBuffer) const;
const Path FileName; const Path FileName;
const bool StorePreamblesInMemory; const bool StorePreamblesInMemory;
/// The last CompileCommand used to build AST and Preamble.
tooling::CompileCommand Command;
/// The last parsed AST. /// The last parsed AST.
llvm::Optional<ParsedAST> AST; llvm::Optional<ParsedAST> AST;
/// The last built Preamble. /// The last built Preamble.

View File

@ -373,6 +373,46 @@ struct bar { T x; };
EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags()); EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
} }
TEST_F(ClangdVFSTest, ForceReparseCompileCommandDefines) {
MockFSProvider FS;
ErrorCheckingDiagConsumer DiagConsumer;
MockCompilationDatabase CDB;
ClangdServer Server(CDB, DiagConsumer, FS,
/*AsyncThreadsCount=*/0,
/*StorePreamblesInMemory=*/true);
// No need to sync reparses, because reparses are performed on the calling
// thread.
auto FooCpp = testPath("foo.cpp");
const auto SourceContents = R"cpp(
#ifdef WITH_ERROR
this
#endif
int main() { return 0; }
)cpp";
FS.Files[FooCpp] = "";
FS.ExpectedFile = FooCpp;
// Parse with define, we expect to see the errors.
CDB.ExtraClangFlags = {"-DWITH_ERROR"};
Server.addDocument(FooCpp, SourceContents);
EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags());
// Parse without the define, no errors should be produced.
CDB.ExtraClangFlags = {};
// Currently, addDocument never checks if CompileCommand has changed, so we
// expect to see the errors.
Server.addDocument(FooCpp, SourceContents);
EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags());
// But forceReparse should reparse the file with proper flags.
Server.forceReparse(FooCpp);
EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
// Subsequent addDocument call should finish without errors too.
Server.addDocument(FooCpp, SourceContents);
EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags());
}
TEST_F(ClangdVFSTest, MemoryUsage) { TEST_F(ClangdVFSTest, MemoryUsage) {
MockFSProvider FS; MockFSProvider FS;
ErrorCheckingDiagConsumer DiagConsumer; ErrorCheckingDiagConsumer DiagConsumer;