diff --git a/clang-tools-extra/clangd/ParsedAST.cpp b/clang-tools-extra/clangd/ParsedAST.cpp --- a/clang-tools-extra/clangd/ParsedAST.cpp +++ b/clang-tools-extra/clangd/ParsedAST.cpp @@ -9,6 +9,7 @@ #include "ParsedAST.h" #include "../clang-tidy/ClangTidyCheck.h" #include "../clang-tidy/ClangTidyDiagnosticConsumer.h" +#include "../clang-tidy/ClangTidyModule.h" #include "../clang-tidy/ClangTidyModuleRegistry.h" #include "AST.h" #include "Compiler.h" @@ -25,7 +26,6 @@ #include "TidyProvider.h" #include "clang-include-cleaner/Record.h" #include "index/CanonicalIncludes.h" -#include "index/Index.h" #include "index/Symbol.h" #include "support/Logger.h" #include "support/Trace.h" @@ -50,7 +50,6 @@ #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringRef.h" -#include #include #include #include @@ -476,16 +475,19 @@ // diagnostics. if (PreserveDiags) { trace::Span Tracer("ClangTidyInit"); - tidy::ClangTidyCheckFactories CTFactories; - for (const auto &E : tidy::ClangTidyModuleRegistry::entries()) - E.instantiate()->addCheckFactories(CTFactories); + static const auto *CTFactories = [] { + auto *CTFactories = new tidy::ClangTidyCheckFactories; + for (const auto &E : tidy::ClangTidyModuleRegistry::entries()) + E.instantiate()->addCheckFactories(*CTFactories); + return CTFactories; + }(); CTContext.emplace(std::make_unique( tidy::ClangTidyGlobalOptions(), ClangTidyOpts)); CTContext->setDiagnosticsEngine(&Clang->getDiagnostics()); CTContext->setASTContext(&Clang->getASTContext()); CTContext->setCurrentFile(Filename); CTContext->setSelfContainedDiags(true); - CTChecks = CTFactories.createChecksForLanguage(&*CTContext); + CTChecks = CTFactories->createChecksForLanguage(&*CTContext); Preprocessor *PP = &Clang->getPreprocessor(); for (const auto &Check : CTChecks) { Check->registerPPCallbacks(Clang->getSourceManager(), PP, PP); @@ -610,10 +612,8 @@ Macros = Patch->mainFileMacros(); Marks = Patch->marks(); } - auto& PP = Clang->getPreprocessor(); - PP.addPPCallbacks( - std::make_unique( - PP, Macros)); + auto &PP = Clang->getPreprocessor(); + PP.addPPCallbacks(std::make_unique(PP, Macros)); PP.addPPCallbacks( collectPragmaMarksCallback(Clang->getSourceManager(), Marks)); diff --git a/clang-tools-extra/clangd/TidyProvider.cpp b/clang-tools-extra/clangd/TidyProvider.cpp --- a/clang-tools-extra/clangd/TidyProvider.cpp +++ b/clang-tools-extra/clangd/TidyProvider.cpp @@ -8,6 +8,7 @@ #include "TidyProvider.h" #include "../clang-tidy/ClangTidyModuleRegistry.h" +#include "../clang-tidy/ClangTidyOptions.h" #include "Config.h" #include "support/FileCache.h" #include "support/Logger.h" @@ -283,8 +284,15 @@ tidy::ClangTidyOptions getTidyOptionsForFile(TidyProviderRef Provider, llvm::StringRef Filename) { - tidy::ClangTidyOptions Opts = tidy::ClangTidyOptions::getDefaults(); - Opts.Checks->clear(); + // getDefaults instantiates all check factories, which are registered at link + // time. So cache the results once. + static const auto *DefaultOpts = [] { + auto *Opts = new tidy::ClangTidyOptions; + *Opts = tidy::ClangTidyOptions::getDefaults(); + Opts->Checks->clear(); + return Opts; + }(); + auto Opts = *DefaultOpts; if (Provider) Provider(Opts, Filename); return Opts; diff --git a/clang-tools-extra/clangd/unittests/CMakeLists.txt b/clang-tools-extra/clangd/unittests/CMakeLists.txt --- a/clang-tools-extra/clangd/unittests/CMakeLists.txt +++ b/clang-tools-extra/clangd/unittests/CMakeLists.txt @@ -79,8 +79,9 @@ PrintASTTests.cpp ProjectAwareIndexTests.cpp QualityTests.cpp - RenameTests.cpp RIFFTests.cpp + RenameTests.cpp + ReplayPeambleTests.cpp SelectionTests.cpp SemanticHighlightingTests.cpp SemanticSelectionTests.cpp diff --git a/clang-tools-extra/clangd/unittests/ParsedASTTests.cpp b/clang-tools-extra/clangd/unittests/ParsedASTTests.cpp --- a/clang-tools-extra/clangd/unittests/ParsedASTTests.cpp +++ b/clang-tools-extra/clangd/unittests/ParsedASTTests.cpp @@ -12,8 +12,6 @@ //===----------------------------------------------------------------------===// #include "../../clang-tidy/ClangTidyCheck.h" -#include "../../clang-tidy/ClangTidyModule.h" -#include "../../clang-tidy/ClangTidyModuleRegistry.h" #include "AST.h" #include "Annotations.h" #include "Compiler.h" @@ -29,8 +27,6 @@ #include "clang/Basic/SourceLocation.h" #include "clang/Basic/SourceManager.h" #include "clang/Basic/TokenKinds.h" -#include "clang/Lex/PPCallbacks.h" -#include "clang/Lex/Token.h" #include "clang/Tooling/Syntax/Tokens.h" #include "llvm/ADT/StringRef.h" #include "llvm/Testing/Support/Error.h" @@ -96,10 +92,6 @@ return false; } -MATCHER_P(rangeIs, R, "") { - return arg.beginOffset() == R.Begin && arg.endOffset() == R.End; -} - MATCHER_P(pragmaTrivia, P, "") { return arg.Trivia == P; } MATCHER(eqInc, "") { @@ -352,123 +344,6 @@ MATCHER_P(withFileName, Inc, "") { return arg.FileName == Inc; } -TEST(ParsedASTTest, ReplayPreambleForTidyCheckers) { - struct Inclusion { - Inclusion(const SourceManager &SM, SourceLocation HashLoc, - const Token &IncludeTok, llvm::StringRef FileName, bool IsAngled, - CharSourceRange FilenameRange) - : HashOffset(SM.getDecomposedLoc(HashLoc).second), IncTok(IncludeTok), - IncDirective(IncludeTok.getIdentifierInfo()->getName()), - FileNameOffset(SM.getDecomposedLoc(FilenameRange.getBegin()).second), - FileName(FileName), IsAngled(IsAngled) { - EXPECT_EQ( - toSourceCode(SM, FilenameRange.getAsRange()).drop_back().drop_front(), - FileName); - } - size_t HashOffset; - syntax::Token IncTok; - llvm::StringRef IncDirective; - size_t FileNameOffset; - llvm::StringRef FileName; - bool IsAngled; - }; - static std::vector Includes; - static std::vector SkippedFiles; - struct ReplayPreamblePPCallback : public PPCallbacks { - const SourceManager &SM; - explicit ReplayPreamblePPCallback(const SourceManager &SM) : SM(SM) {} - - void InclusionDirective(SourceLocation HashLoc, const Token &IncludeTok, - StringRef FileName, bool IsAngled, - CharSourceRange FilenameRange, OptionalFileEntryRef, - StringRef, StringRef, const clang::Module *, - SrcMgr::CharacteristicKind) override { - Includes.emplace_back(SM, HashLoc, IncludeTok, FileName, IsAngled, - FilenameRange); - } - - void FileSkipped(const FileEntryRef &, const Token &FilenameTok, - SrcMgr::CharacteristicKind) override { - SkippedFiles.emplace_back(FilenameTok); - } - }; - struct ReplayPreambleCheck : public tidy::ClangTidyCheck { - ReplayPreambleCheck(StringRef Name, tidy::ClangTidyContext *Context) - : ClangTidyCheck(Name, Context) {} - void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP, - Preprocessor *ModuleExpanderPP) override { - PP->addPPCallbacks(::std::make_unique(SM)); - } - }; - struct ReplayPreambleModule : public tidy::ClangTidyModule { - void - addCheckFactories(tidy::ClangTidyCheckFactories &CheckFactories) override { - CheckFactories.registerCheck( - "replay-preamble-check"); - } - }; - - static tidy::ClangTidyModuleRegistry::Add X( - "replay-preamble-module", ""); - TestTU TU; - // This check records inclusion directives replayed by clangd. - TU.ClangTidyProvider = addTidyChecks("replay-preamble-check"); - llvm::Annotations Test(R"cpp( - $hash^#$include[[import]] $filebegin^"$filerange[[bar.h]]" - $hash^#$include[[include_next]] $filebegin^"$filerange[[baz.h]]" - $hash^#$include[[include]] $filebegin^<$filerange[[a.h]]>)cpp"); - llvm::StringRef Code = Test.code(); - TU.Code = Code.str(); - TU.AdditionalFiles["bar.h"] = ""; - TU.AdditionalFiles["baz.h"] = ""; - TU.AdditionalFiles["a.h"] = ""; - // Since we are also testing #import directives, and they don't make much - // sense in c++ (also they actually break on windows), just set language to - // obj-c. - TU.ExtraArgs = {"-isystem.", "-xobjective-c"}; - - const auto &AST = TU.build(); - const auto &SM = AST.getSourceManager(); - - auto HashLocs = Test.points("hash"); - ASSERT_EQ(HashLocs.size(), Includes.size()); - auto IncludeRanges = Test.ranges("include"); - ASSERT_EQ(IncludeRanges.size(), Includes.size()); - auto FileBeginLocs = Test.points("filebegin"); - ASSERT_EQ(FileBeginLocs.size(), Includes.size()); - auto FileRanges = Test.ranges("filerange"); - ASSERT_EQ(FileRanges.size(), Includes.size()); - - ASSERT_EQ(SkippedFiles.size(), Includes.size()); - for (size_t I = 0; I < Includes.size(); ++I) { - const auto &Inc = Includes[I]; - - EXPECT_EQ(Inc.HashOffset, HashLocs[I]); - - auto IncRange = IncludeRanges[I]; - EXPECT_THAT(Inc.IncTok.range(SM), rangeIs(IncRange)); - EXPECT_EQ(Inc.IncTok.kind(), tok::identifier); - EXPECT_EQ(Inc.IncDirective, - Code.substr(IncRange.Begin, IncRange.End - IncRange.Begin)); - - EXPECT_EQ(Inc.FileNameOffset, FileBeginLocs[I]); - EXPECT_EQ(Inc.IsAngled, Code[FileBeginLocs[I]] == '<'); - - auto FileRange = FileRanges[I]; - EXPECT_EQ(Inc.FileName, - Code.substr(FileRange.Begin, FileRange.End - FileRange.Begin)); - - EXPECT_EQ(SM.getDecomposedLoc(SkippedFiles[I].location()).second, - Inc.FileNameOffset); - // This also contains quotes/angles so increment the range by one from both - // sides. - EXPECT_EQ( - SkippedFiles[I].text(SM), - Code.substr(FileRange.Begin - 1, FileRange.End - FileRange.Begin + 2)); - EXPECT_EQ(SkippedFiles[I].kind(), tok::header_name); - } -} - TEST(ParsedASTTest, PatchesAdditionalIncludes) { llvm::StringLiteral ModifiedContents = R"cpp( #include "baz.h" diff --git a/clang-tools-extra/clangd/unittests/ReplayPeambleTests.cpp b/clang-tools-extra/clangd/unittests/ReplayPeambleTests.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clangd/unittests/ReplayPeambleTests.cpp @@ -0,0 +1,166 @@ +//===-- ReplayPreambleTests.cpp -------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// These tests cover clangd's logic to replay PP events from preamble to +// clang-tidy checks. +// +//===----------------------------------------------------------------------===// + +#include "../../clang-tidy/ClangTidyCheck.h" +#include "../../clang-tidy/ClangTidyModule.h" +#include "../../clang-tidy/ClangTidyModuleRegistry.h" +#include "AST.h" +#include "Diagnostics.h" +#include "ParsedAST.h" +#include "SourceCode.h" +#include "TestTU.h" +#include "TidyProvider.h" +#include "clang/AST/DeclTemplate.h" +#include "clang/Basic/FileEntry.h" +#include "clang/Basic/LLVM.h" +#include "clang/Basic/Module.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Basic/TokenKinds.h" +#include "clang/Lex/PPCallbacks.h" +#include "clang/Lex/Token.h" +#include "clang/Tooling/Syntax/Tokens.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Registry.h" +#include "llvm/Testing/Annotations/Annotations.h" +#include "gmock/gmock-matchers.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include +#include +#include + +namespace clang::clangd { +namespace { +struct Inclusion { + Inclusion(const SourceManager &SM, SourceLocation HashLoc, + const Token &IncludeTok, llvm::StringRef FileName, bool IsAngled, + CharSourceRange FilenameRange) + : HashOffset(SM.getDecomposedLoc(HashLoc).second), IncTok(IncludeTok), + IncDirective(IncludeTok.getIdentifierInfo()->getName()), + FileNameOffset(SM.getDecomposedLoc(FilenameRange.getBegin()).second), + FileName(FileName), IsAngled(IsAngled) { + EXPECT_EQ( + toSourceCode(SM, FilenameRange.getAsRange()).drop_back().drop_front(), + FileName); + } + size_t HashOffset; + syntax::Token IncTok; + llvm::StringRef IncDirective; + size_t FileNameOffset; + llvm::StringRef FileName; + bool IsAngled; +}; +static std::vector Includes; +static std::vector SkippedFiles; +struct ReplayPreamblePPCallback : public PPCallbacks { + const SourceManager &SM; + explicit ReplayPreamblePPCallback(const SourceManager &SM) : SM(SM) {} + + void InclusionDirective(SourceLocation HashLoc, const Token &IncludeTok, + StringRef FileName, bool IsAngled, + CharSourceRange FilenameRange, OptionalFileEntryRef, + StringRef, StringRef, const clang::Module *, + SrcMgr::CharacteristicKind) override { + Includes.emplace_back(SM, HashLoc, IncludeTok, FileName, IsAngled, + FilenameRange); + } + + void FileSkipped(const FileEntryRef &, const Token &FilenameTok, + SrcMgr::CharacteristicKind) override { + SkippedFiles.emplace_back(FilenameTok); + } +}; +struct ReplayPreambleCheck : public tidy::ClangTidyCheck { + ReplayPreambleCheck(StringRef Name, tidy::ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP, + Preprocessor *ModuleExpanderPP) override { + PP->addPPCallbacks(::std::make_unique(SM)); + } +}; +llvm::StringLiteral CheckName = "replay-preamble-check"; +struct ReplayPreambleModule : public tidy::ClangTidyModule { + void + addCheckFactories(tidy::ClangTidyCheckFactories &CheckFactories) override { + CheckFactories.registerCheck(CheckName); + } +}; +static tidy::ClangTidyModuleRegistry::Add + X("replay-preamble-module", ""); + +MATCHER_P(rangeIs, R, "") { + return arg.beginOffset() == R.Begin && arg.endOffset() == R.End; +} + +TEST(ReplayPreambleTest, IncludesAndSkippedFiles) { + TestTU TU; + // This check records inclusion directives replayed by clangd. + TU.ClangTidyProvider = addTidyChecks(CheckName); + llvm::Annotations Test(R"cpp( + $hash^#$include[[import]] $filebegin^"$filerange[[bar.h]]" + $hash^#$include[[include_next]] $filebegin^"$filerange[[baz.h]]" + $hash^#$include[[include]] $filebegin^<$filerange[[a.h]]>)cpp"); + llvm::StringRef Code = Test.code(); + TU.Code = Code.str(); + TU.AdditionalFiles["bar.h"] = ""; + TU.AdditionalFiles["baz.h"] = ""; + TU.AdditionalFiles["a.h"] = ""; + // Since we are also testing #import directives, and they don't make much + // sense in c++ (also they actually break on windows), just set language to + // obj-c. + TU.ExtraArgs = {"-isystem.", "-xobjective-c"}; + + const auto &AST = TU.build(); + const auto &SM = AST.getSourceManager(); + + auto HashLocs = Test.points("hash"); + ASSERT_EQ(HashLocs.size(), Includes.size()); + auto IncludeRanges = Test.ranges("include"); + ASSERT_EQ(IncludeRanges.size(), Includes.size()); + auto FileBeginLocs = Test.points("filebegin"); + ASSERT_EQ(FileBeginLocs.size(), Includes.size()); + auto FileRanges = Test.ranges("filerange"); + ASSERT_EQ(FileRanges.size(), Includes.size()); + + ASSERT_EQ(SkippedFiles.size(), Includes.size()); + for (size_t I = 0; I < Includes.size(); ++I) { + const auto &Inc = Includes[I]; + + EXPECT_EQ(Inc.HashOffset, HashLocs[I]); + + auto IncRange = IncludeRanges[I]; + EXPECT_THAT(Inc.IncTok.range(SM), rangeIs(IncRange)); + EXPECT_EQ(Inc.IncTok.kind(), tok::identifier); + EXPECT_EQ(Inc.IncDirective, + Code.substr(IncRange.Begin, IncRange.End - IncRange.Begin)); + + EXPECT_EQ(Inc.FileNameOffset, FileBeginLocs[I]); + EXPECT_EQ(Inc.IsAngled, Code[FileBeginLocs[I]] == '<'); + + auto FileRange = FileRanges[I]; + EXPECT_EQ(Inc.FileName, + Code.substr(FileRange.Begin, FileRange.End - FileRange.Begin)); + + EXPECT_EQ(SM.getDecomposedLoc(SkippedFiles[I].location()).second, + Inc.FileNameOffset); + // This also contains quotes/angles so increment the range by one from both + // sides. + EXPECT_EQ( + SkippedFiles[I].text(SM), + Code.substr(FileRange.Begin - 1, FileRange.End - FileRange.Begin + 2)); + EXPECT_EQ(SkippedFiles[I].kind(), tok::header_name); + } +} +} // namespace +} // namespace clang::clangd