[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@hotmail.com>
This commit is contained in:
timon-ul 2025-12-25 06:37:39 +01:00 committed by GitHub
parent 5d6c40b44f
commit 56f5fda577
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 462 additions and 44 deletions

View File

@ -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

View File

@ -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

View File

@ -123,6 +123,11 @@ public:
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,

View File

@ -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<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

View File

@ -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<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 @@ public:
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 @@ public:
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>

View File

@ -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 @@ 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<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;

View File

@ -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.

View File

@ -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<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 @@ private:
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

View File

@ -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(

View File

@ -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;

View File

@ -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

View File

@ -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 @@ 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();
}