[clang-tools-extra] 56f5fda - [clangd] Find references to constructors called indirectly via a forwarding function (#169742)
via cfe-commits
cfe-commits at lists.llvm.org
Wed Dec 24 21:37:43 PST 2025
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 <zeratul976 at hotmail.com>
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();
}
More information about the cfe-commits
mailing list