[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:
parent
5d6c40b44f
commit
56f5fda577
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user