kadircet created this revision. kadircet added a reviewer: ilya-biryukov. Herald added subscribers: cfe-commits, arphaman, jkorous, MaskRay, ioeric, mgorny.
This is the second part for introducing include hierarchy into index files produced by clangd. You can see the base patch that introduces structures and discusses the future of the patches in D54817 <https://reviews.llvm.org/D54817> Repository: rCTE Clang Tools Extra https://reviews.llvm.org/D54999 Files: clangd/index/IndexAction.cpp clangd/index/IndexAction.h unittests/clangd/CMakeLists.txt unittests/clangd/IndexActionTests.cpp
Index: unittests/clangd/IndexActionTests.cpp =================================================================== --- /dev/null +++ unittests/clangd/IndexActionTests.cpp @@ -0,0 +1,129 @@ +//===------ IndexActionTests.cpp -------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "TestFS.h" +#include "index/IndexAction.h" +#include "clang/Tooling/Tooling.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace clang { +namespace clangd { +namespace { + +using ::testing::ElementsAre; +using ::testing::UnorderedPointwise; + +std::string PathToURI(llvm::StringRef Path) { + return URI::create(Path).toString(); +} + +MATCHER(PairHasURI, "") { + llvm::StringRef URI = testing::get<0>(arg); + const std::pair<std::string, std::string> &PathAndContent = + testing::get<1>(arg); + return PathToURI(PathAndContent.first) == URI; +} + +class IndexActionTest : public ::testing::Test { +public: + IndexActionTest() : InMemoryFileSystem(new llvm::vfs::InMemoryFileSystem) {} + + void runIndexingAction(llvm::StringRef MainFilePath, + const std::vector<std::string> &ExtraArgs = {}) { + IntrusiveRefCntPtr<FileManager> Files( + new FileManager(FileSystemOptions(), InMemoryFileSystem)); + + auto Action = createStaticIndexingAction( + SymbolCollector::Options(), + [&](SymbolSlab S) { Symbols = std::move(S); }, + [&](RefSlab R) { Refs = std::move(R); }, + [&](IncludeGraph IG) { this->IG = std::move(IG); }); + + std::vector<std::string> Args = {"index_action", "-fsyntax-only", + "-xc++", "-std=c++11", + "-iquote", testRoot()}; + Args.insert(Args.end(), ExtraArgs.begin(), ExtraArgs.end()); + Args.push_back(MainFilePath); + + tooling::ToolInvocation Invocation( + Args, Action.release(), Files.get(), + std::make_shared<PCHContainerOperations>()); + + Invocation.run(); + } + + void addFile(llvm::StringRef Path, llvm::StringRef Content) { + InMemoryFileSystem->addFile(Path, 0, + llvm::MemoryBuffer::getMemBuffer(Content)); + } + +protected: + IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFileSystem; + SymbolSlab Symbols; + RefSlab Refs; + IncludeGraph IG; + SymbolCollector::Options CollectorOpts; +}; + +TEST_F(IndexActionTest, CollectIncludeGraph) { + std::string MainFilePath = testPath("main.cpp"); + std::vector<std::pair<std::string, std::string>> Level1Headers = { + {testPath("level1_1.h"), "#include \"level2_1.h\""}, + {testPath("level1_2.h"), "#include \"level2_2.h\""}, + {testPath("level1_3.h"), "#include \"level2_3.h\""}}; + std::vector<std::pair<std::string, std::string>> Level2Headers = { + {testPath("level2_1.h"), ""}, + {testPath("level2_2.h"), ""}, + {testPath("level2_3.h"), ""}}; + + std::string MainCode = R"cpp( + #include "level1_1.h" + #include "level1_2.h" + #include "level1_3.h" + )cpp"; + addFile(MainFilePath, MainCode); + for (const auto &Header : Level1Headers) + addFile(Header.first, Header.second); + for (const auto &Header : Level2Headers) + addFile(Header.first, Header.second); + + runIndexingAction(MainFilePath); + + const auto &Main = IG.lookup(PathToURI(MainFilePath)); + EXPECT_EQ(Main.Digest, digest(MainCode)); + EXPECT_THAT(Main.DirectIncludes, + UnorderedPointwise(PairHasURI(), Level1Headers)); + EXPECT_TRUE(Main.IsTU); + EXPECT_EQ(Main.URI.data(), IG.find(PathToURI(MainFilePath))->getKeyData()); + + for (size_t I = 0; I < Level1Headers.size(); I++) { + const auto &HeaderInfo = Level1Headers[I]; + auto URI = PathToURI(HeaderInfo.first); + const auto &Header = IG.lookup(URI); + EXPECT_EQ(Header.Digest, digest(HeaderInfo.second)); + EXPECT_THAT(Header.DirectIncludes, + UnorderedPointwise(PairHasURI(), {Level2Headers[I]})); + EXPECT_FALSE(Header.IsTU); + EXPECT_EQ(Header.URI.data(), IG.find(URI)->getKeyData()); + } + for (size_t I = 0; I < Level2Headers.size(); I++) { + const auto &HeaderInfo = Level2Headers[I]; + auto URI = PathToURI(HeaderInfo.first); + const auto &Header = IG.lookup(URI); + EXPECT_EQ(Header.Digest, digest(HeaderInfo.second)); + EXPECT_THAT(Header.DirectIncludes, ElementsAre()); + EXPECT_FALSE(Header.IsTU); + EXPECT_EQ(Header.URI.data(), IG.find(URI)->getKeyData()); + } +} + +} // namespace +} // namespace clangd +} // namespace clang Index: unittests/clangd/CMakeLists.txt =================================================================== --- unittests/clangd/CMakeLists.txt +++ unittests/clangd/CMakeLists.txt @@ -28,6 +28,7 @@ FuzzyMatchTests.cpp GlobalCompilationDatabaseTests.cpp HeadersTests.cpp + IndexActionTests.cpp IndexTests.cpp JSONTransportTests.cpp QualityTests.cpp Index: clangd/index/IndexAction.h =================================================================== --- clangd/index/IndexAction.h +++ clangd/index/IndexAction.h @@ -9,6 +9,7 @@ #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_INDEX_INDEX_ACTION_H #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_INDEX_INDEX_ACTION_H +#include "Headers.h" #include "SymbolCollector.h" #include "clang/Frontend/FrontendActions.h" @@ -23,10 +24,11 @@ // - references are always counted // - all references are collected (if RefsCallback is non-null) // - the symbol origin is always Static -std::unique_ptr<FrontendAction> -createStaticIndexingAction(SymbolCollector::Options Opts, - std::function<void(SymbolSlab)> SymbolsCallback, - std::function<void(RefSlab)> RefsCallback); +std::unique_ptr<FrontendAction> createStaticIndexingAction( + SymbolCollector::Options Opts, + std::function<void(SymbolSlab)> SymbolsCallback, + std::function<void(RefSlab)> RefsCallback, + std::function<void(IncludeGraph)> IncludeGraphCallback = nullptr); } // namespace clangd } // namespace clang Index: clangd/index/IndexAction.cpp =================================================================== --- clangd/index/IndexAction.cpp +++ clangd/index/IndexAction.cpp @@ -9,6 +9,85 @@ namespace clangd { namespace { +llvm::Optional<std::string> URIFromFileEntry(const FileEntry *File) { + if (!File) + return llvm::None; + auto AbsolutePath = File->tryGetRealPathName(); + if (AbsolutePath.empty()) + return llvm::None; + auto U = URI::create(AbsolutePath); + return U.toString(); +} + +// Collects the nodes and edges of include graph during indexing action. +struct IncludeGraphCollector : public PPCallbacks { +public: + IncludeGraphCollector(const SourceManager &SM, IncludeGraph &IG) + : SM(SM), IG(IG) {} + + // Populates the node for a new include. + void FileChanged(SourceLocation Loc, FileChangeReason Reason, + SrcMgr::CharacteristicKind FileType, + FileID PrevFID) override { + // We only need to process each file once. So we don't care about anything + // but enteries. + if (Reason != FileChangeReason::EnterFile) + return; + + const auto FileID = SM.getFileID(Loc); + const auto File = SM.getFileEntryForID(FileID); + auto URI = URIFromFileEntry(File); + if (!URI) + return; + auto I = IG.try_emplace(*URI).first; + + auto &Node = I->getValue(); + if (auto Digest = digestFile(SM, FileID)) + Node.Digest = std::move(*Digest); + Node.IsTU = FileID == SM.getMainFileID(); + Node.URI = I->getKey(); + } + + // Add edges from including files to includes. + void InclusionDirective(SourceLocation HashLoc, const Token &IncludeTok, + StringRef FileName, bool IsAngled, + CharSourceRange FilenameRange, const FileEntry *File, + StringRef SearchPath, StringRef RelativePath, + const Module *Imported, + SrcMgr::CharacteristicKind FileType) override { + auto IncludeURI = URIFromFileEntry(File); + if (!IncludeURI) + return; + auto NodeForInclude = IG.try_emplace(*IncludeURI).first->getKey(); + + auto IncludingURI = + URIFromFileEntry(SM.getFileEntryForID(SM.getFileID(HashLoc))); + if (!IncludingURI) + return; + + auto NodeForIncluding = IG.try_emplace(*IncludingURI); + NodeForIncluding.first->getValue().DirectIncludes.push_back(NodeForInclude); + } + +#ifndef NDEBUG + // Sanity check to ensure we have already populated a skipped file. + void FileSkipped(const FileEntry &SkippedFile, const Token &FilenameTok, + SrcMgr::CharacteristicKind FileType) override { + auto URI = URIFromFileEntry(&SkippedFile); + if (!URI) + return; + auto I = IG.try_emplace(*URI); + assert(!I.second && "File inserted for the first time on skip."); + assert(I.first->getKey().data() == I.first->getValue().URI && + "Node have not populated yet"); + } +#endif + +private: + const SourceManager &SM; + IncludeGraph &IG; +}; + // Wraps the index action and reports index data after each translation unit. class IndexAction : public WrapperFrontendAction { public: @@ -16,15 +95,20 @@ std::unique_ptr<CanonicalIncludes> Includes, const index::IndexingOptions &Opts, std::function<void(SymbolSlab)> SymbolsCallback, - std::function<void(RefSlab)> RefsCallback) + std::function<void(RefSlab)> RefsCallback, + std::function<void(IncludeGraph)> IncludeGraphCallback) : WrapperFrontendAction(index::createIndexingAction(C, Opts, nullptr)), SymbolsCallback(SymbolsCallback), RefsCallback(RefsCallback), - Collector(C), Includes(std::move(Includes)), + IncludeGraphCallback(IncludeGraphCallback), Collector(C), + Includes(std::move(Includes)), PragmaHandler(collectIWYUHeaderMaps(this->Includes.get())) {} std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI, StringRef InFile) override { CI.getPreprocessor().addCommentHandler(PragmaHandler.get()); + if (IncludeGraphCallback != nullptr) + CI.getPreprocessor().addPPCallbacks( + llvm::make_unique<IncludeGraphCollector>(CI.getSourceManager(), IG)); return WrapperFrontendAction::CreateASTConsumer(CI, InFile); } @@ -46,22 +130,27 @@ SymbolsCallback(Collector->takeSymbols()); if (RefsCallback != nullptr) RefsCallback(Collector->takeRefs()); + if (IncludeGraphCallback != nullptr) + IncludeGraphCallback(std::move(IG)); } private: std::function<void(SymbolSlab)> SymbolsCallback; std::function<void(RefSlab)> RefsCallback; + std::function<void(IncludeGraph)> IncludeGraphCallback; std::shared_ptr<SymbolCollector> Collector; std::unique_ptr<CanonicalIncludes> Includes; std::unique_ptr<CommentHandler> PragmaHandler; + IncludeGraph IG; }; } // namespace -std::unique_ptr<FrontendAction> -createStaticIndexingAction(SymbolCollector::Options Opts, - std::function<void(SymbolSlab)> SymbolsCallback, - std::function<void(RefSlab)> RefsCallback) { +std::unique_ptr<FrontendAction> createStaticIndexingAction( + SymbolCollector::Options Opts, + std::function<void(SymbolSlab)> SymbolsCallback, + std::function<void(RefSlab)> RefsCallback, + std::function<void(IncludeGraph)> IncludeGraphCallback) { index::IndexingOptions IndexOpts; IndexOpts.SystemSymbolFilter = index::IndexingOptions::SystemSymbolFilterKind::All; @@ -77,7 +166,7 @@ Opts.Includes = Includes.get(); return llvm::make_unique<IndexAction>( std::make_shared<SymbolCollector>(std::move(Opts)), std::move(Includes), - IndexOpts, SymbolsCallback, RefsCallback); + IndexOpts, SymbolsCallback, RefsCallback, IncludeGraphCallback); } } // namespace clangd
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits