diff --git a/clang-tools-extra/clangd/AST.cpp b/clang-tools-extra/clangd/AST.cpp index 0dcff2eae05e..3bcc89d360cd 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(LastParam->getType())) { + // ... of the type T&&... or T... + const auto BaseType = PET->getPattern().getNonReferenceType(); + if (const auto *TTPT = + dyn_cast(BaseType.getTypePtr())) { + // ... whose template parameter comes from the function directly + if (FT->getTemplateParameters()->getDepth() == TTPT->getDepth()) { + return true; + } + } + } + } + return false; +} + +class ForwardingToConstructorVisitor + : public RecursiveASTVisitor { +public: + ForwardingToConstructorVisitor( + llvm::DenseSet &SeenFunctions, + SmallVector &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(Adjusted)) + Adjusted = Template->getTemplatedDecl(); + if (auto *Constructor = dyn_cast(Adjusted)) + Constructors.push_back(Constructor); + } + return true; + } + + // Stack of seen functions + llvm::DenseSet &SeenFunctions; + // Output of this visitor + SmallVector &Constructors; +}; + +SmallVector +searchConstructorsInForwardingFunction(const FunctionDecl *FD) { + SmallVector Result; + llvm::DenseSet 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 2b83595e5b8e..2bb4943b6de0 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 +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 82fac9636048..6640ccccf581 100644 --- a/clang-tools-extra/clangd/ParsedAST.h +++ b/clang-tools-extra/clangd/ParsedAST.h @@ -123,6 +123,11 @@ public: return Resolver.get(); } + /// Cache for constructors called through forwarding, e.g. make_unique + llvm::DenseMap> + ForwardingToConstructorCache; + private: ParsedAST(PathRef TUPath, llvm::StringRef Version, std::shared_ptr Preamble, diff --git a/clang-tools-extra/clangd/Preamble.cpp b/clang-tools-extra/clangd/Preamble.cpp index 8af9e4649218..09aaf3290b58 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 @@ public: 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(LastParam->getType())) { - // ... of the type T&&... or T... - const auto BaseType = PET->getPattern().getNonReferenceType(); - if (const auto *TTPT = - dyn_cast(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 ef45acf50161..4e330bf769d2 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 @@ public: } }; - ReferenceFinder(const ParsedAST &AST, + ReferenceFinder(ParsedAST &AST, const llvm::ArrayRef 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(ND)) + TargetConstructors.insert(Constructor); + } } std::vector take() && { @@ -955,12 +955,41 @@ public: return std::move(References); } + bool forwardsToConstructor(const Decl *D) { + if (TargetConstructors.empty()) + return false; + auto *FD = llvm::dyn_cast(D); + if (FD == nullptr || !FD->isTemplateInstantiation()) + return false; + + SmallVector *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 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 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 @@ public: private: bool PerToken; // If true, report 3 references for split ObjC selector names. std::vector References; - const ParsedAST * + ParsedAST * llvm::DenseSet TargetDecls; + // Constructors need special handling since they can be hidden behind forwards + llvm::DenseSet TargetConstructors; }; std::vector diff --git a/clang-tools-extra/clangd/index/IndexAction.cpp b/clang-tools-extra/clangd/index/IndexAction.cpp index ed56c2a9d2e8..09943400f6d8 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 #include #include #include @@ -146,6 +145,11 @@ public: // 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(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 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 39c479b5f4d5..bd974e4c1881 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 #include @@ -576,6 +576,25 @@ SymbolCollector::getRefContainer(const Decl *Enclosing, return Enclosing; } +SmallVector +SymbolCollector::findIndirectConstructors(const Decl *D) { + auto *FD = llvm::dyn_cast(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 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 e9eb27fd0f66..4d51d747639b 100644 --- a/clang-tools-extra/clangd/index/SymbolCollector.h +++ b/clang-tools-extra/clangd/index/SymbolCollector.h @@ -159,6 +159,12 @@ public: 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 + findIndirectConstructors(const Decl *D); + const Symbol *addDeclaration(const NamedDecl &, SymbolID, bool IsMainFileSymbol); void addDefinition(const NamedDecl &, const Symbol &DeclSymbol, @@ -230,6 +236,9 @@ private: std::unique_ptr HeaderFileURIs; llvm::DenseMap DeclToIDCache; llvm::DenseMap MacroToIDCache; + llvm::DenseMap> + ForwardingToConstructorCache; }; } // namespace clangd diff --git a/clang-tools-extra/clangd/unittests/BackgroundIndexTests.cpp b/clang-tools-extra/clangd/unittests/BackgroundIndexTests.cpp index ada14c993931..0eb4acf0469b 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 -#include 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 T &&forward(T &t); + template T *make_unique(Args &&...args) { + return new T(std::forward(args)...); + } + } + struct Test { + [[Test]](){} + }; + )cpp"); + Annotations Main(R"cpp( + #include "header.hpp" + int main() { + auto a = std::[[make_unique]](); + } + )cpp"); + + MockFS FS; + llvm::StringMap 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 T &&forward(T &t); + template T *make_unique(Args &&...args) { + return new T(std::forward(args)...); + } + } + struct Test { + [[Test]](){} + }; + )cpp"); + Annotations First(R"cpp( + #include "header.hpp" + int main() { + auto a = std::[[make_unique]](); + } + )cpp"); + Annotations Second(R"cpp( + #include "header.hpp" + void test() { + auto a = std::[[make_unique]](); + } + )cpp"); + + MockFS FS; + llvm::StringMap 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 7ed08d7cce3d..4106c6cf7b2d 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 T &&forward(T &t); + template T *make_unique(Args &&...args) { + return new T(std::forward(args)...); + } + } + + struct Test { + $Constructor[[T^est]](){} + }; + + int main() { + auto a = std::$Caller[[make_unique]](); + } + )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 T &&forward(T &t); + template T *make_unique(Args &&...args) { + return new T(forward(args)...); + } + template T *make_unique2(Args &&...args) { + return make_unique(forward(args)...); + } + template T *make_unique3(Args &&...args) { + return make_unique2(forward(args)...); + } + } + + struct Test { + $Constructor[[T^est]](){} + }; + + int main() { + auto a = std::$Caller[[make_unique3]](); + } + )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 T &&forward(T &t); + template T *make_unique(Args &&...args) { + return new T(std::forward(args)...); + } + } + struct Test { + [[T^est]](){} + }; + )cpp"); + Annotations Main(R"cpp( + #include "header.hpp" + int main() { + auto a = std::[[make_unique]](); + } + )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 T &&forward(T &t); + template T *make_unique(Args &&...args) { + return new T(std::forward(args)...); + } + } + + struct Waldo { + template + $Constructor[[W$Waldo^aldo]](T); + }; + template + struct Waldo2 { + $Constructor2[[W$Waldo2^aldo2]](int); + }; + struct S {}; + + int main() { + S s; + Waldo $Caller[[w]](s); + std::$ForwardedCaller[[make_unique]](s); + + Waldo2 $Caller2[[w2]](42); + std::$ForwardedCaller2[[make_unique]]>(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 97847dd7d5d8..c670797e9fa6 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 73a6a8c62af2..8118ceda9cd2 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 IndexCtx; std::shared_ptr PP; std::function ShouldSkipFunctionBody; + bool DeferIndexingToEndOfTranslationUnit; public: IndexASTConsumer(std::shared_ptr DataConsumer, @@ -110,7 +112,9 @@ public: : 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 @@ protected: } 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 @@ protected: } 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(); }