Author: timon-ul Date: 2025-12-25T00:37:39-05:00 New Revision: 56f5fda5776a9693a79ccea4e23ae1295df81a69
URL: https://github.com/llvm/llvm-project/commit/56f5fda5776a9693a79ccea4e23ae1295df81a69 DIFF: https://github.com/llvm/llvm-project/commit/56f5fda5776a9693a79ccea4e23ae1295df81a69.diff LOG: [clangd] Find references to constructors called indirectly via a forwarding function (#169742) Calls to functions that forward to a constructor, such as make_unique, are now recorded as references to the called constructor as well, so that searching for references to a constructor finds such call sites. Co-authored-by: Nathan Ridge <[email protected]> Added: Modified: clang-tools-extra/clangd/AST.cpp clang-tools-extra/clangd/AST.h clang-tools-extra/clangd/ParsedAST.h clang-tools-extra/clangd/Preamble.cpp clang-tools-extra/clangd/XRefs.cpp clang-tools-extra/clangd/index/IndexAction.cpp clang-tools-extra/clangd/index/SymbolCollector.cpp clang-tools-extra/clangd/index/SymbolCollector.h clang-tools-extra/clangd/unittests/BackgroundIndexTests.cpp clang-tools-extra/clangd/unittests/XRefsTests.cpp clang/include/clang/Index/IndexingOptions.h clang/lib/Index/IndexingAction.cpp Removed: ################################################################################ diff --git a/clang-tools-extra/clangd/AST.cpp b/clang-tools-extra/clangd/AST.cpp index 0dcff2eae05e7..3bcc89d360cdb 100644 --- a/clang-tools-extra/clangd/AST.cpp +++ b/clang-tools-extra/clangd/AST.cpp @@ -18,7 +18,6 @@ #include "clang/AST/DeclTemplate.h" #include "clang/AST/DeclarationName.h" #include "clang/AST/ExprCXX.h" -#include "clang/AST/NestedNameSpecifier.h" #include "clang/AST/PrettyPrinter.h" #include "clang/AST/RecursiveASTVisitor.h" #include "clang/AST/Stmt.h" @@ -31,8 +30,8 @@ #include "clang/Index/USRGeneration.h" #include "clang/Sema/HeuristicResolver.h" #include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/DenseSet.h" #include "llvm/ADT/STLExtras.h" -#include "llvm/ADT/SmallSet.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/Casting.h" #include "llvm/Support/raw_ostream.h" @@ -1040,5 +1039,80 @@ bool isExpandedFromParameterPack(const ParmVarDecl *D) { return getUnderlyingPackType(D) != nullptr; } +bool isLikelyForwardingFunction(const FunctionTemplateDecl *FT) { + const auto *FD = FT->getTemplatedDecl(); + const auto NumParams = FD->getNumParams(); + // Check whether its last parameter is a parameter pack... + if (NumParams > 0) { + const auto *LastParam = FD->getParamDecl(NumParams - 1); + if (const auto *PET = dyn_cast<PackExpansionType>(LastParam->getType())) { + // ... of the type T&&... or T... + const auto BaseType = PET->getPattern().getNonReferenceType(); + if (const auto *TTPT = + dyn_cast<TemplateTypeParmType>(BaseType.getTypePtr())) { + // ... whose template parameter comes from the function directly + if (FT->getTemplateParameters()->getDepth() == TTPT->getDepth()) { + return true; + } + } + } + } + return false; +} + +class ForwardingToConstructorVisitor + : public RecursiveASTVisitor<ForwardingToConstructorVisitor> { +public: + ForwardingToConstructorVisitor( + llvm::DenseSet<const FunctionDecl *> &SeenFunctions, + SmallVector<const CXXConstructorDecl *, 1> &Output) + : SeenFunctions(SeenFunctions), Constructors(Output) {} + + bool VisitCallExpr(CallExpr *E) { + // Adjust if recurison not deep enough + if (SeenFunctions.size() >= 10) + return true; + if (auto *FD = E->getDirectCallee()) { + // Check if we already visited this function to prevent endless recursion + if (SeenFunctions.contains(FD)) + return true; + if (auto *PT = FD->getPrimaryTemplate(); + PT && isLikelyForwardingFunction(PT)) { + SeenFunctions.insert(FD); + ForwardingToConstructorVisitor Visitor{SeenFunctions, Constructors}; + Visitor.TraverseStmt(FD->getBody()); + SeenFunctions.erase(FD); + } + } + return true; + } + + bool VisitCXXNewExpr(CXXNewExpr *E) { + if (auto *CE = E->getConstructExpr()) + if (auto *Callee = CE->getConstructor()) { + auto *Adjusted = &adjustDeclToTemplate(*Callee); + if (auto *Template = dyn_cast<TemplateDecl>(Adjusted)) + Adjusted = Template->getTemplatedDecl(); + if (auto *Constructor = dyn_cast<CXXConstructorDecl>(Adjusted)) + Constructors.push_back(Constructor); + } + return true; + } + + // Stack of seen functions + llvm::DenseSet<const FunctionDecl *> &SeenFunctions; + // Output of this visitor + SmallVector<const CXXConstructorDecl *, 1> &Constructors; +}; + +SmallVector<const CXXConstructorDecl *, 1> +searchConstructorsInForwardingFunction(const FunctionDecl *FD) { + SmallVector<const CXXConstructorDecl *, 1> Result; + llvm::DenseSet<const FunctionDecl *> SeenFunctions{FD}; + ForwardingToConstructorVisitor Visitor{SeenFunctions, Result}; + Visitor.TraverseStmt(FD->getBody()); + return Result; +} + } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/AST.h b/clang-tools-extra/clangd/AST.h index 2b83595e5b8e9..2bb4943b6de0b 100644 --- a/clang-tools-extra/clangd/AST.h +++ b/clang-tools-extra/clangd/AST.h @@ -18,7 +18,6 @@ #include "index/SymbolID.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclObjC.h" -#include "clang/AST/NestedNameSpecifier.h" #include "clang/AST/TypeLoc.h" #include "clang/Basic/SourceLocation.h" #include "clang/Lex/MacroInfo.h" @@ -253,6 +252,15 @@ resolveForwardingParameters(const FunctionDecl *D, unsigned MaxDepth = 10); /// reference to one (e.g. `Args&...` or `Args&&...`). bool isExpandedFromParameterPack(const ParmVarDecl *D); +/// Heuristic that checks if FT is likely to be forwarding a parameter pack to +/// another function (e.g. `make_unique`). +bool isLikelyForwardingFunction(const FunctionTemplateDecl *FT); + +/// Only call if FD is a likely forwarding function. Returns +/// constructors that might be forwarded to. +SmallVector<const CXXConstructorDecl *, 1> +searchConstructorsInForwardingFunction(const FunctionDecl *FD); + } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/ParsedAST.h b/clang-tools-extra/clangd/ParsedAST.h index 82fac96360488..6640ccccf5815 100644 --- a/clang-tools-extra/clangd/ParsedAST.h +++ b/clang-tools-extra/clangd/ParsedAST.h @@ -123,6 +123,11 @@ class ParsedAST { return Resolver.get(); } + /// Cache for constructors called through forwarding, e.g. make_unique + llvm::DenseMap<const FunctionDecl *, + SmallVector<const CXXConstructorDecl *, 1>> + ForwardingToConstructorCache; + private: ParsedAST(PathRef TUPath, llvm::StringRef Version, std::shared_ptr<const PreambleData> Preamble, diff --git a/clang-tools-extra/clangd/Preamble.cpp b/clang-tools-extra/clangd/Preamble.cpp index 8af9e4649218d..09aaf3290b585 100644 --- a/clang-tools-extra/clangd/Preamble.cpp +++ b/clang-tools-extra/clangd/Preamble.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include "Preamble.h" +#include "AST.h" #include "CollectMacros.h" #include "Compiler.h" #include "Config.h" @@ -166,27 +167,6 @@ class CppFilePreambleCallbacks : public PreambleCallbacks { collectPragmaMarksCallback(*SourceMgr, Marks)); } - static bool isLikelyForwardingFunction(FunctionTemplateDecl *FT) { - const auto *FD = FT->getTemplatedDecl(); - const auto NumParams = FD->getNumParams(); - // Check whether its last parameter is a parameter pack... - if (NumParams > 0) { - const auto *LastParam = FD->getParamDecl(NumParams - 1); - if (const auto *PET = dyn_cast<PackExpansionType>(LastParam->getType())) { - // ... of the type T&&... or T... - const auto BaseType = PET->getPattern().getNonReferenceType(); - if (const auto *TTPT = - dyn_cast<TemplateTypeParmType>(BaseType.getTypePtr())) { - // ... whose template parameter comes from the function directly - if (FT->getTemplateParameters()->getDepth() == TTPT->getDepth()) { - return true; - } - } - } - } - return false; - } - bool shouldSkipFunctionBody(Decl *D) override { // Usually we don't need to look inside the bodies of header functions // to understand the program. However when forwarding function like diff --git a/clang-tools-extra/clangd/XRefs.cpp b/clang-tools-extra/clangd/XRefs.cpp index ef45acf501612..4e330bf769d20 100644 --- a/clang-tools-extra/clangd/XRefs.cpp +++ b/clang-tools-extra/clangd/XRefs.cpp @@ -16,7 +16,6 @@ #include "Quality.h" #include "Selection.h" #include "SourceCode.h" -#include "URI.h" #include "clang-include-cleaner/Analysis.h" #include "clang-include-cleaner/Types.h" #include "index/Index.h" @@ -43,7 +42,6 @@ #include "clang/AST/StmtVisitor.h" #include "clang/AST/Type.h" #include "clang/Basic/LLVM.h" -#include "clang/Basic/LangOptions.h" #include "clang/Basic/SourceLocation.h" #include "clang/Basic/SourceManager.h" #include "clang/Basic/TokenKinds.h" @@ -60,7 +58,6 @@ #include "llvm/ADT/DenseSet.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/ScopeExit.h" -#include "llvm/ADT/SmallSet.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/Casting.h" @@ -929,12 +926,15 @@ class ReferenceFinder : public index::IndexDataConsumer { } }; - ReferenceFinder(const ParsedAST &AST, + ReferenceFinder(ParsedAST &AST, const llvm::ArrayRef<const NamedDecl *> Targets, bool PerToken) : PerToken(PerToken), AST(AST) { - for (const NamedDecl *ND : Targets) + for (const NamedDecl *ND : Targets) { TargetDecls.insert(ND->getCanonicalDecl()); + if (auto *Constructor = llvm::dyn_cast<clang::CXXConstructorDecl>(ND)) + TargetConstructors.insert(Constructor); + } } std::vector<Reference> take() && { @@ -955,12 +955,41 @@ class ReferenceFinder : public index::IndexDataConsumer { return std::move(References); } + bool forwardsToConstructor(const Decl *D) { + if (TargetConstructors.empty()) + return false; + auto *FD = llvm::dyn_cast<clang::FunctionDecl>(D); + if (FD == nullptr || !FD->isTemplateInstantiation()) + return false; + + SmallVector<const CXXConstructorDecl *, 1> *Constructors = nullptr; + if (auto Entry = AST.ForwardingToConstructorCache.find(FD); + Entry != AST.ForwardingToConstructorCache.end()) + Constructors = &Entry->getSecond(); + if (Constructors == nullptr) { + if (auto *PT = FD->getPrimaryTemplate(); + PT == nullptr || !isLikelyForwardingFunction(PT)) + return false; + + SmallVector<const CXXConstructorDecl *, 1> FoundConstructors = + searchConstructorsInForwardingFunction(FD); + auto Iter = AST.ForwardingToConstructorCache.try_emplace( + FD, std::move(FoundConstructors)); + Constructors = &Iter.first->getSecond(); + } + for (auto *Constructor : *Constructors) + if (TargetConstructors.contains(Constructor)) + return true; + return false; + } + bool handleDeclOccurrence(const Decl *D, index::SymbolRoleSet Roles, llvm::ArrayRef<index::SymbolRelation> Relations, SourceLocation Loc, index::IndexDataConsumer::ASTNodeInfo ASTNode) override { - if (!TargetDecls.contains(D->getCanonicalDecl())) + if (!TargetDecls.contains(D->getCanonicalDecl()) && + !forwardsToConstructor(ASTNode.OrigD)) return true; const SourceManager &SM = AST.getSourceManager(); if (!isInsideMainFile(Loc, SM)) @@ -1000,8 +1029,10 @@ class ReferenceFinder : public index::IndexDataConsumer { private: bool PerToken; // If true, report 3 references for split ObjC selector names. std::vector<Reference> References; - const ParsedAST &AST; + ParsedAST &AST; llvm::DenseSet<const Decl *> TargetDecls; + // Constructors need special handling since they can be hidden behind forwards + llvm::DenseSet<const CXXConstructorDecl *> TargetConstructors; }; std::vector<ReferenceFinder::Reference> diff --git a/clang-tools-extra/clangd/index/IndexAction.cpp b/clang-tools-extra/clangd/index/IndexAction.cpp index ed56c2a9d2e81..09943400f6d86 100644 --- a/clang-tools-extra/clangd/index/IndexAction.cpp +++ b/clang-tools-extra/clangd/index/IndexAction.cpp @@ -21,7 +21,6 @@ #include "clang/Frontend/FrontendAction.h" #include "clang/Index/IndexingAction.h" #include "clang/Index/IndexingOptions.h" -#include <cstddef> #include <functional> #include <memory> #include <optional> @@ -146,6 +145,11 @@ class IndexAction : public ASTFrontendAction { // inside, it becomes quadratic. So we give up on nested symbols. if (isDeeplyNested(D)) return false; + // If D is a likely forwarding function we need the body to index indirect + // constructor calls (e.g. `make_unique`) + if (auto *FT = llvm::dyn_cast<clang::FunctionTemplateDecl>(D); + FT && isLikelyForwardingFunction(FT)) + return true; auto &SM = D->getASTContext().getSourceManager(); auto FID = SM.getFileID(SM.getExpansionLoc(D->getLocation())); if (!FID.isValid()) @@ -220,6 +224,9 @@ std::unique_ptr<FrontendAction> createStaticIndexingAction( index::IndexingOptions::SystemSymbolFilterKind::All; // We index function-local classes and its member functions only. IndexOpts.IndexFunctionLocals = true; + // We need to delay indexing so instantiations of function bodies become + // available, this is so we can find constructor calls through `make_unique`. + IndexOpts.DeferIndexingToEndOfTranslationUnit = true; Opts.CollectIncludePath = true; if (Opts.Origin == SymbolOrigin::Unknown) Opts.Origin = SymbolOrigin::Static; diff --git a/clang-tools-extra/clangd/index/SymbolCollector.cpp b/clang-tools-extra/clangd/index/SymbolCollector.cpp index 39c479b5f4d5b..bd974e4c18818 100644 --- a/clang-tools-extra/clangd/index/SymbolCollector.cpp +++ b/clang-tools-extra/clangd/index/SymbolCollector.cpp @@ -25,6 +25,7 @@ #include "index/SymbolLocation.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclBase.h" +#include "clang/AST/DeclCXX.h" #include "clang/AST/DeclObjC.h" #include "clang/AST/DeclTemplate.h" #include "clang/AST/DeclarationName.h" @@ -44,7 +45,6 @@ #include "llvm/ADT/StringRef.h" #include "llvm/Support/Casting.h" #include "llvm/Support/ErrorHandling.h" -#include "llvm/Support/FileSystem.h" #include "llvm/Support/Path.h" #include <cassert> #include <memory> @@ -576,6 +576,25 @@ SymbolCollector::getRefContainer(const Decl *Enclosing, return Enclosing; } +SmallVector<const CXXConstructorDecl *, 1> +SymbolCollector::findIndirectConstructors(const Decl *D) { + auto *FD = llvm::dyn_cast<clang::FunctionDecl>(D); + if (FD == nullptr || !FD->isTemplateInstantiation()) + return {}; + if (auto Entry = ForwardingToConstructorCache.find(FD); + Entry != ForwardingToConstructorCache.end()) + return Entry->getSecond(); + if (auto *PT = FD->getPrimaryTemplate(); + PT == nullptr || !isLikelyForwardingFunction(PT)) + return {}; + + SmallVector<const CXXConstructorDecl *, 1> FoundConstructors = + searchConstructorsInForwardingFunction(FD); + auto Iter = ForwardingToConstructorCache.try_emplace( + FD, std::move(FoundConstructors)); + return Iter.first->getSecond(); +} + // Always return true to continue indexing. bool SymbolCollector::handleDeclOccurrence( const Decl *D, index::SymbolRoleSet Roles, @@ -639,10 +658,12 @@ bool SymbolCollector::handleDeclOccurrence( // ND is the canonical (i.e. first) declaration. If it's in the main file // (which is not a header), then no public declaration was visible, so assume // it's main-file only. - bool IsMainFileOnly = - SM.isWrittenInMainFile(SM.getExpansionLoc(ND->getBeginLoc())) && - !isHeaderFile(SM.getFileEntryRefForID(SM.getMainFileID())->getName(), - ASTCtx->getLangOpts()); + auto CheckIsMainFileOnly = [&](const NamedDecl *Decl) { + return SM.isWrittenInMainFile(SM.getExpansionLoc(Decl->getBeginLoc())) && + !isHeaderFile(SM.getFileEntryRefForID(SM.getMainFileID())->getName(), + ASTCtx->getLangOpts()); + }; + bool IsMainFileOnly = CheckIsMainFileOnly(ND); // In C, printf is a redecl of an implicit builtin! So check OrigD instead. if (ASTNode.OrigD->isImplicit() || !shouldCollectSymbol(*ND, *ASTCtx, Opts, IsMainFileOnly)) @@ -666,9 +687,20 @@ bool SymbolCollector::handleDeclOccurrence( auto FileLoc = SM.getFileLoc(Loc); auto FID = SM.getFileID(FileLoc); if (Opts.RefsInHeaders || FID == SM.getMainFileID()) { + auto *Container = getRefContainer(ASTNode.Parent, Opts); addRef(ID, SymbolRef{FileLoc, FID, Roles, index::getSymbolInfo(ND).Kind, - getRefContainer(ASTNode.Parent, Opts), - isSpelled(FileLoc, *ND)}); + Container, isSpelled(FileLoc, *ND)}); + // Also collect indirect constructor calls like `make_unique` + for (auto *Constructor : findIndirectConstructors(ASTNode.OrigD)) { + if (!shouldCollectSymbol(*Constructor, *ASTCtx, Opts, + CheckIsMainFileOnly(Constructor))) + continue; + if (auto ConstructorID = getSymbolIDCached(Constructor)) + addRef(ConstructorID, + SymbolRef{FileLoc, FID, Roles, + index::getSymbolInfo(Constructor).Kind, Container, + false}); + } } } // Don't continue indexing if this is a mere reference. diff --git a/clang-tools-extra/clangd/index/SymbolCollector.h b/clang-tools-extra/clangd/index/SymbolCollector.h index e9eb27fd0f664..4d51d747639b1 100644 --- a/clang-tools-extra/clangd/index/SymbolCollector.h +++ b/clang-tools-extra/clangd/index/SymbolCollector.h @@ -159,6 +159,12 @@ class SymbolCollector : public index::IndexDataConsumer { void finish() override; private: + // If D is an instantiation of a likely forwarding function, return the + // constructors it invokes so that we can record indirect references + // to those as well. + SmallVector<const CXXConstructorDecl *, 1> + findIndirectConstructors(const Decl *D); + const Symbol *addDeclaration(const NamedDecl &, SymbolID, bool IsMainFileSymbol); void addDefinition(const NamedDecl &, const Symbol &DeclSymbol, @@ -230,6 +236,9 @@ class SymbolCollector : public index::IndexDataConsumer { std::unique_ptr<HeaderFileURICache> HeaderFileURIs; llvm::DenseMap<const Decl *, SymbolID> DeclToIDCache; llvm::DenseMap<const MacroInfo *, SymbolID> MacroToIDCache; + llvm::DenseMap<const FunctionDecl *, + SmallVector<const CXXConstructorDecl *, 1>> + ForwardingToConstructorCache; }; } // namespace clangd diff --git a/clang-tools-extra/clangd/unittests/BackgroundIndexTests.cpp b/clang-tools-extra/clangd/unittests/BackgroundIndexTests.cpp index ada14c9939318..0eb4acf0469b7 100644 --- a/clang-tools-extra/clangd/unittests/BackgroundIndexTests.cpp +++ b/clang-tools-extra/clangd/unittests/BackgroundIndexTests.cpp @@ -1,3 +1,4 @@ +#include "Annotations.h" #include "CompileCommands.h" #include "Config.h" #include "Headers.h" @@ -14,7 +15,6 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" #include <deque> -#include <thread> using ::testing::_; using ::testing::AllOf; @@ -233,6 +233,126 @@ TEST_F(BackgroundIndexTest, IndexTwoFiles) { fileURI("unittest:///root/B.cc")})); } +TEST_F(BackgroundIndexTest, ConstructorForwarding) { + Annotations Header(R"cpp( + namespace std { + template <class T> T &&forward(T &t); + template <class T, class... Args> T *make_unique(Args &&...args) { + return new T(std::forward<Args>(args)...); + } + } + struct Test { + [[Test]](){} + }; + )cpp"); + Annotations Main(R"cpp( + #include "header.hpp" + int main() { + auto a = std::[[make_unique]]<Test>(); + } + )cpp"); + + MockFS FS; + llvm::StringMap<std::string> Storage; + size_t CacheHits = 0; + MemoryShardStorage MSS(Storage, CacheHits); + OverlayCDB CDB(/*Base=*/nullptr); + BackgroundIndex::Options Opts; + BackgroundIndex Idx(FS, CDB, [&](llvm::StringRef) { return &MSS; }, Opts); + + FS.Files[testPath("root/header.hpp")] = Header.code(); + FS.Files[testPath("root/test.cpp")] = Main.code(); + + tooling::CompileCommand Cmd; + Cmd.Filename = testPath("root/test.cpp"); + Cmd.Directory = testPath("root"); + Cmd.CommandLine = {"clang++", testPath("root/test.cpp")}; + CDB.setCompileCommand(testPath("root/test.cpp"), Cmd); + + ASSERT_TRUE(Idx.blockUntilIdleForTest()); + + auto Syms = runFuzzyFind(Idx, "Test"); + auto Constructor = + std::find_if(Syms.begin(), Syms.end(), [](const Symbol &S) { + return S.SymInfo.Kind == index::SymbolKind::Constructor; + }); + ASSERT_TRUE(Constructor != Syms.end()); + EXPECT_THAT(getRefs(Idx, Constructor->ID), + refsAre({fileURI("unittest:///root/header.hpp"), + fileURI("unittest:///root/test.cpp")})); +} + +TEST_F(BackgroundIndexTest, ConstructorForwardingMultiFile) { + // If a forwarding function like `make_unique` is defined in a header its body + // used to be skipped on the second encounter. This meant in practise we could + // only find constructors indirectly called by these type of functions in the + // first indexed file (and all files that were indexed at the same time, + // before a flag to skip it was set). + Annotations Header(R"cpp( + namespace std { + template <class T> T &&forward(T &t); + template <class T, class... Args> T *make_unique(Args &&...args) { + return new T(std::forward<Args>(args)...); + } + } + struct Test { + [[Test]](){} + }; + )cpp"); + Annotations First(R"cpp( + #include "header.hpp" + int main() { + auto a = std::[[make_unique]]<Test>(); + } + )cpp"); + Annotations Second(R"cpp( + #include "header.hpp" + void test() { + auto a = std::[[make_unique]]<Test>(); + } + )cpp"); + + MockFS FS; + llvm::StringMap<std::string> Storage; + size_t CacheHits = 0; + MemoryShardStorage MSS(Storage, CacheHits); + OverlayCDB CDB(/*Base=*/nullptr); + BackgroundIndex::Options Opts; + BackgroundIndex Idx(FS, CDB, [&](llvm::StringRef) { return &MSS; }, Opts); + + FS.Files[testPath("root/header.hpp")] = Header.code(); + FS.Files[testPath("root/first.cpp")] = First.code(); + FS.Files[testPath("root/second.cpp")] = Second.code(); + + tooling::CompileCommand Cmd; + Cmd.Filename = testPath("root/first.cpp"); + Cmd.Directory = testPath("root"); + Cmd.CommandLine = {"clang++", testPath("root/first.cpp")}; + CDB.setCompileCommand(testPath("root/first.cpp"), Cmd); + + // Make sure the first file is done indexing to make sure the flag for the + // header is set. + ASSERT_TRUE(Idx.blockUntilIdleForTest()); + + Cmd.Filename = testPath("root/second.cpp"); + Cmd.Directory = testPath("root"); + Cmd.CommandLine = {"clang++", testPath("root/second.cpp")}; + CDB.setCompileCommand(testPath("root/second.cpp"), Cmd); + + ASSERT_TRUE(Idx.blockUntilIdleForTest()); + + auto Syms = runFuzzyFind(Idx, "Test"); + auto Constructor = + std::find_if(Syms.begin(), Syms.end(), [](const Symbol &S) { + return S.SymInfo.Kind == index::SymbolKind::Constructor; + }); + ASSERT_TRUE(Constructor != Syms.end()); + EXPECT_THAT(getRefs(Idx, Constructor->ID), + refsAre({fileURI("unittest:///root/header.hpp"), + fileURI("unittest:///root/first.cpp"), + fileURI("unittest:///root/second.cpp")})); +} + TEST_F(BackgroundIndexTest, MainFileRefs) { MockFS FS; FS.Files[testPath("root/A.h")] = R"cpp( diff --git a/clang-tools-extra/clangd/unittests/XRefsTests.cpp b/clang-tools-extra/clangd/unittests/XRefsTests.cpp index 7ed08d7cce3d3..4106c6cf7b2d0 100644 --- a/clang-tools-extra/clangd/unittests/XRefsTests.cpp +++ b/clang-tools-extra/clangd/unittests/XRefsTests.cpp @@ -5,14 +5,15 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// -#include "Annotations.h" #include "AST.h" +#include "Annotations.h" #include "ParsedAST.h" #include "Protocol.h" #include "SourceCode.h" #include "SyncAPI.h" #include "TestFS.h" #include "TestTU.h" +#include "TestWorkspace.h" #include "XRefs.h" #include "index/MemIndex.h" #include "clang/AST/Decl.h" @@ -311,6 +312,7 @@ MATCHER_P3(sym, Name, Decl, DefOrNone, "") { MATCHER_P(sym, Name, "") { return arg.Name == Name; } MATCHER_P(rangeIs, R, "") { return arg.Loc.range == R; } +MATCHER_P(fileIs, F, "") { return arg.Loc.uri.file() == F; } MATCHER_P(containerIs, C, "") { return arg.Loc.containerName.value_or("") == C; } @@ -2713,6 +2715,140 @@ TEST(FindReferences, NoQueryForLocalSymbols) { } } +TEST(FindReferences, ConstructorForwardingInAST) { + Annotations Main(R"cpp( + namespace std { + template <class T> T &&forward(T &t); + template <class T, class... Args> T *make_unique(Args &&...args) { + return new T(std::forward<Args>(args)...); + } + } + + struct Test { + $Constructor[[T^est]](){} + }; + + int main() { + auto a = std::$Caller[[make_unique]]<Test>(); + } + )cpp"); + TestTU TU; + TU.Code = std::string(Main.code()); + auto AST = TU.build(); + + EXPECT_THAT(findReferences(AST, Main.point(), 0).References, + ElementsAre(rangeIs(Main.range("Constructor")), + rangeIs(Main.range("Caller")))); +} + +TEST(FindReferences, ConstructorForwardingInASTChained) { + Annotations Main(R"cpp( + namespace std { + template <class T> T &&forward(T &t); + template <class T, class... Args> T *make_unique(Args &&...args) { + return new T(forward<Args>(args)...); + } + template <class T, class... Args> T *make_unique2(Args &&...args) { + return make_unique<T>(forward<Args>(args)...); + } + template <class T, class... Args> T *make_unique3(Args &&...args) { + return make_unique2<T>(forward<Args>(args)...); + } + } + + struct Test { + $Constructor[[T^est]](){} + }; + + int main() { + auto a = std::$Caller[[make_unique3]]<Test>(); + } + )cpp"); + TestTU TU; + TU.Code = std::string(Main.code()); + auto AST = TU.build(); + + EXPECT_THAT(findReferences(AST, Main.point(), 0).References, + ElementsAre(rangeIs(Main.range("Constructor")), + rangeIs(Main.range("Caller")))); +} + +TEST(FindReferences, ConstructorForwardingInIndex) { + Annotations Header(R"cpp( + namespace std { + template <class T> T &&forward(T &t); + template <class T, class... Args> T *make_unique(Args &&...args) { + return new T(std::forward<Args>(args)...); + } + } + struct Test { + [[T^est]](){} + }; + )cpp"); + Annotations Main(R"cpp( + #include "header.hpp" + int main() { + auto a = std::[[make_unique]]<Test>(); + } + )cpp"); + TestWorkspace TW; + TW.addSource("header.hpp", Header.code()); + TW.addMainFile("main.cpp", Main.code()); + auto AST = TW.openFile("header.hpp").value(); + auto Index = TW.index(); + + EXPECT_THAT( + findReferences(AST, Header.point(), 0, Index.get(), + /*AddContext*/ true) + .References, + ElementsAre( + AllOf(rangeIs(Header.range()), fileIs(testPath("header.hpp"))), + AllOf(rangeIs(Main.range()), fileIs(testPath("main.cpp"))))); +} + +TEST(FindReferences, TemplatedConstructorForwarding) { + Annotations Main(R"cpp( + namespace std { + template <class T> T &&forward(T &t); + template <class T, class... Args> T *make_unique(Args &&...args) { + return new T(std::forward<Args>(args)...); + } + } + + struct Waldo { + template <typename T> + $Constructor[[W$Waldo^aldo]](T); + }; + template <typename T> + struct Waldo2 { + $Constructor2[[W$Waldo2^aldo2]](int); + }; + struct S {}; + + int main() { + S s; + Waldo $Caller[[w]](s); + std::$ForwardedCaller[[make_unique]]<Waldo>(s); + + Waldo2<int> $Caller2[[w2]](42); + std::$ForwardedCaller2[[make_unique]]<Waldo2<int>>(42); + } + )cpp"); + TestTU TU; + TU.Code = std::string(Main.code()); + auto AST = TU.build(); + + EXPECT_THAT(findReferences(AST, Main.point("Waldo"), 0).References, + ElementsAre(rangeIs(Main.range("Constructor")), + rangeIs(Main.range("Caller")), + rangeIs(Main.range("ForwardedCaller")))); + + EXPECT_THAT(findReferences(AST, Main.point("Waldo2"), 0).References, + ElementsAre(rangeIs(Main.range("Constructor2")), + rangeIs(Main.range("Caller2")), + rangeIs(Main.range("ForwardedCaller2")))); +} + TEST(GetNonLocalDeclRefs, All) { struct Case { llvm::StringRef AnnotatedCode; diff --git a/clang/include/clang/Index/IndexingOptions.h b/clang/include/clang/Index/IndexingOptions.h index 97847dd7d5d88..c670797e9fa60 100644 --- a/clang/include/clang/Index/IndexingOptions.h +++ b/clang/include/clang/Index/IndexingOptions.h @@ -36,6 +36,12 @@ struct IndexingOptions { // Has no effect if IndexFunctionLocals are false. bool IndexParametersInDeclarations = false; bool IndexTemplateParameters = false; + // Some information might only be available at the end of a translation unit, + // this flag delays the indexing for this purpose (e.g. instantiation of + // function definitions). This option only takes effect on operations that + // actually build the AST, e.g. `createIndexingAction()` and + // `createIndexingASTConsumer()`. + bool DeferIndexingToEndOfTranslationUnit = false; // If set, skip indexing inside some declarations for performance. // This prevents traversal, so skipping a struct means its declaration an diff --git a/clang/lib/Index/IndexingAction.cpp b/clang/lib/Index/IndexingAction.cpp index 73a6a8c62af2e..8118ceda9cd23 100644 --- a/clang/lib/Index/IndexingAction.cpp +++ b/clang/lib/Index/IndexingAction.cpp @@ -8,6 +8,7 @@ #include "clang/Index/IndexingAction.h" #include "IndexingContext.h" +#include "clang/AST/DeclGroup.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/FrontendAction.h" #include "clang/Index/IndexDataConsumer.h" @@ -101,6 +102,7 @@ class IndexASTConsumer final : public ASTConsumer { std::shared_ptr<IndexingContext> IndexCtx; std::shared_ptr<Preprocessor> PP; std::function<bool(const Decl *)> ShouldSkipFunctionBody; + bool DeferIndexingToEndOfTranslationUnit; public: IndexASTConsumer(std::shared_ptr<IndexDataConsumer> DataConsumer, @@ -110,7 +112,9 @@ class IndexASTConsumer final : public ASTConsumer { : DataConsumer(std::move(DataConsumer)), IndexCtx(new IndexingContext(Opts, *this->DataConsumer)), PP(std::move(PP)), - ShouldSkipFunctionBody(std::move(ShouldSkipFunctionBody)) { + ShouldSkipFunctionBody(std::move(ShouldSkipFunctionBody)), + DeferIndexingToEndOfTranslationUnit( + Opts.DeferIndexingToEndOfTranslationUnit) { assert(this->DataConsumer != nullptr); assert(this->PP != nullptr); } @@ -124,7 +128,9 @@ class IndexASTConsumer final : public ASTConsumer { } bool HandleTopLevelDecl(DeclGroupRef DG) override { - return IndexCtx->indexDeclGroupRef(DG); + if (!DeferIndexingToEndOfTranslationUnit) + return IndexCtx->indexDeclGroupRef(DG); + return true; } void HandleInterestingDecl(DeclGroupRef DG) override { @@ -132,10 +138,14 @@ class IndexASTConsumer final : public ASTConsumer { } void HandleTopLevelDeclInObjCContainer(DeclGroupRef DG) override { - IndexCtx->indexDeclGroupRef(DG); + if (!DeferIndexingToEndOfTranslationUnit) + IndexCtx->indexDeclGroupRef(DG); } void HandleTranslationUnit(ASTContext &Ctx) override { + if (DeferIndexingToEndOfTranslationUnit) + for (auto *DG : Ctx.getTranslationUnitDecl()->decls()) + IndexCtx->indexTopLevelDecl(DG); DataConsumer->finish(); } _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
