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 -createStaticIndexingAction(SymbolCollector::Options Opts, - std::function SymbolsCallback, - std::function RefsCallback); +std::unique_ptr createStaticIndexingAction( + SymbolCollector::Options Opts, + std::function SymbolsCallback, + std::function RefsCallback, + std::function 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 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 Includes, const index::IndexingOptions &Opts, std::function SymbolsCallback, - std::function RefsCallback) + std::function RefsCallback, + std::function 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 CreateASTConsumer(CompilerInstance &CI, StringRef InFile) override { CI.getPreprocessor().addCommentHandler(PragmaHandler.get()); + if (IncludeGraphCallback != nullptr) + CI.getPreprocessor().addPPCallbacks( + llvm::make_unique(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 SymbolsCallback; std::function RefsCallback; + std::function IncludeGraphCallback; std::shared_ptr Collector; std::unique_ptr Includes; std::unique_ptr PragmaHandler; + IncludeGraph IG; }; } // namespace -std::unique_ptr -createStaticIndexingAction(SymbolCollector::Options Opts, - std::function SymbolsCallback, - std::function RefsCallback) { +std::unique_ptr createStaticIndexingAction( + SymbolCollector::Options Opts, + std::function SymbolsCallback, + std::function RefsCallback, + std::function IncludeGraphCallback) { index::IndexingOptions IndexOpts; IndexOpts.SystemSymbolFilter = index::IndexingOptions::SystemSymbolFilterKind::All; @@ -77,7 +166,7 @@ Opts.Includes = Includes.get(); return llvm::make_unique( std::make_shared(std::move(Opts)), std::move(Includes), - IndexOpts, SymbolsCallback, RefsCallback); + IndexOpts, SymbolsCallback, RefsCallback, IncludeGraphCallback); } } // namespace clangd 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: 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 &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 &ExtraArgs = {}) { + IntrusiveRefCntPtr 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 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()); + + Invocation.run(); + } + + void addFile(llvm::StringRef Path, llvm::StringRef Content) { + InMemoryFileSystem->addFile(Path, 0, + llvm::MemoryBuffer::getMemBuffer(Content)); + } + +protected: + IntrusiveRefCntPtr InMemoryFileSystem; + SymbolSlab Symbols; + RefSlab Refs; + IncludeGraph IG; + SymbolCollector::Options CollectorOpts; +}; + +TEST_F(IndexActionTest, CollectIncludeGraph) { + std::string MainFilePath = testPath("main.cpp"); + std::vector> 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> 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