//===--- WalkAST.cpp - Find declaration references in the AST -------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "AnalysisInternal.h" #include "clang-include-cleaner/Types.h" #include "clang/AST/ASTFwd.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclCXX.h" #include "clang/AST/DeclFriend.h" #include "clang/AST/DeclTemplate.h" #include "clang/AST/Expr.h" #include "clang/AST/ExprCXX.h" #include "clang/AST/RecursiveASTVisitor.h" #include "clang/AST/TemplateBase.h" #include "clang/AST/TemplateName.h" #include "clang/AST/Type.h" #include "clang/AST/TypeLoc.h" #include "clang/Basic/IdentifierTable.h" #include "clang/Basic/SourceLocation.h" #include "clang/Basic/Specifiers.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/STLFunctionalExtras.h" #include "llvm/ADT/SmallVector.h" #include "llvm/Support/Casting.h" namespace clang::include_cleaner { namespace { using DeclCallback = llvm::function_ref; class ASTWalker : public RecursiveASTVisitor { DeclCallback Callback; void report(SourceLocation Loc, NamedDecl *ND, RefType RT = RefType::Explicit) { if (!ND || Loc.isInvalid()) return; Callback(Loc, *cast(ND->getCanonicalDecl()), RT); } NamedDecl *resolveTemplateName(TemplateName TN) { // For using-templates, only mark the alias. if (auto *USD = TN.getAsUsingShadowDecl()) return USD; return TN.getAsTemplateDecl(); } NamedDecl *getMemberProvider(QualType Base) { if (Base->isPointerType()) return getMemberProvider(Base->getPointeeType()); // Unwrap the sugar ElaboratedType. if (const auto *ElTy = dyn_cast(Base)) return getMemberProvider(ElTy->getNamedType()); if (const auto *TT = dyn_cast(Base)) return TT->getDecl(); if (const auto *UT = dyn_cast(Base)) return UT->getFoundDecl(); // A heuristic: to resolve a template type to **only** its template name. // We're only using this method for the base type of MemberExpr, in general // the template provides the member, and the critical case `unique_ptr` // is supported (the base type is a Foo*). // // There are some exceptions that this heuristic could fail (dependent base, // dependent typealias), but we believe these are rare. if (const auto *TST = dyn_cast(Base)) return resolveTemplateName(TST->getTemplateName()); return Base->getAsRecordDecl(); } // Templated as TemplateSpecializationType and // DeducedTemplateSpecializationType doesn't share a common base. template // Picks the most specific specialization for a // (Deduced)TemplateSpecializationType, while prioritizing using-decls. NamedDecl *getMostRelevantTemplatePattern(const T *TST) { // In case of exported template names always prefer the using-decl. This // implies we'll point at the using-decl even when there's an explicit // specializaiton using the exported name, but that's rare. auto *ND = resolveTemplateName(TST->getTemplateName()); if (llvm::isa_and_present(ND)) return ND; // This is the underlying decl used by TemplateSpecializationType, can be // null when type is dependent or not resolved to a pattern yet. // If so, fallback to primary template. CXXRecordDecl *TD = TST->getAsCXXRecordDecl(); if (!TD || TD->getTemplateSpecializationKind() == TSK_Undeclared) return ND; // We ignore explicit instantiations. This might imply marking the wrong // declaration as used in specific cases, but seems like the right trade-off // in general (e.g. we don't want to include a custom library that has an // explicit specialization of a common type). if (auto *Pat = TD->getTemplateInstantiationPattern()) return Pat; // For explicit specializations, use the specialized decl directly. return TD; } public: ASTWalker(DeclCallback Callback) : Callback(Callback) {} // Operators are almost always ADL extension points and by design references // to them doesn't count as uses (generally the type should provide them, so // ignore them). // Unless we're using an operator defined as a member, in such cases treat // these as regular member references. bool TraverseCXXOperatorCallExpr(CXXOperatorCallExpr *S) { if (!WalkUpFromCXXOperatorCallExpr(S)) return false; if (auto *CD = S->getCalleeDecl()) { if (llvm::isa(CD)) { // Treat this as a regular member reference. report(S->getOperatorLoc(), getMemberProvider(S->getArg(0)->getType()), RefType::Implicit); } else { report(S->getOperatorLoc(), llvm::dyn_cast(CD), RefType::Implicit); } } for (auto *Arg : S->arguments()) if (!TraverseStmt(Arg)) return false; return true; } bool VisitDeclRefExpr(DeclRefExpr *DRE) { auto *FD = DRE->getFoundDecl(); // For refs to non-meber-like decls, use the found decl. // For member-like decls, we should have a reference from the qualifier to // the container decl instead, which is preferred as it'll handle // aliases/exports properly. if (!FD->isCXXClassMember() && !llvm::isa(FD)) { report(DRE->getLocation(), FD); return true; } // If the ref is without a qualifier, and is a member, ignore it. As it is // available in current context due to some other construct (e.g. base // specifiers, using decls) that has to spell the name explicitly. // // If it's an enum constant, it must be due to prior decl. Report references // to it when qualifier isn't a type. if (llvm::isa(FD)) { if (!DRE->getQualifier() || DRE->getQualifier()->getAsNamespace()) report(DRE->getLocation(), FD); } return true; } bool VisitMemberExpr(MemberExpr *E) { // Reporting a usage of the member decl would cause issues (e.g. force // including the base class for inherited members). Instead, we report a // usage of the base type of the MemberExpr, so that e.g. code // `returnFoo().bar` can keep #include "foo.h" (rather than inserting // "bar.h" for the underlying base type `Bar`). QualType Type = E->getBase()->IgnoreImpCasts()->getType(); report(E->getMemberLoc(), getMemberProvider(Type), RefType::Implicit); return true; } bool VisitCXXDependentScopeMemberExpr(CXXDependentScopeMemberExpr *E) { report(E->getMemberLoc(), getMemberProvider(E->getBaseType()), RefType::Implicit); return true; } bool VisitCXXConstructExpr(CXXConstructExpr *E) { // Always treat consturctor calls as implicit. We'll have an explicit // reference for the constructor calls that mention the type-name (through // TypeLocs). This reference only matters for cases where there's no // explicit syntax at all or there're only braces. report(E->getLocation(), getMemberProvider(E->getType()), RefType::Implicit); return true; } bool VisitOverloadExpr(OverloadExpr *E) { // Since we can't prove which overloads are used, report all of them. for (NamedDecl *D : E->decls()) report(E->getNameLoc(), D, RefType::Ambiguous); return true; } // Report all (partial) specializations of a class/var template decl. template void reportSpecializations(SourceLocation Loc, NamedDecl *ND) { const auto *TD = llvm::dyn_cast(ND); if (!TD) return; for (auto *Spec : TD->specializations()) report(Loc, Spec, RefType::Ambiguous); llvm::SmallVector PartialSpecializations; TD->getPartialSpecializations(PartialSpecializations); for (auto *PartialSpec : PartialSpecializations) report(Loc, PartialSpec, RefType::Ambiguous); } bool VisitUsingDecl(UsingDecl *UD) { for (const auto *Shadow : UD->shadows()) { auto *TD = Shadow->getTargetDecl(); auto IsUsed = TD->isUsed() || TD->isReferenced(); report(UD->getLocation(), TD, IsUsed ? RefType::Explicit : RefType::Ambiguous); // All (partial) template specializations are visible via a using-decl, // However a using-decl only refers to the primary template (per C++ name // lookup). Thus, we need to manually report all specializations. reportSpecializations( UD->getLocation(), TD); reportSpecializations( UD->getLocation(), TD); if (const auto *FTD = llvm::dyn_cast(TD)) for (auto *Spec : FTD->specializations()) report(UD->getLocation(), Spec, RefType::Ambiguous); } return true; } bool VisitFunctionDecl(FunctionDecl *FD) { // Mark declaration from definition as it needs type-checking. if (FD->isThisDeclarationADefinition()) report(FD->getLocation(), FD); return true; } bool VisitVarDecl(VarDecl *VD) { // Ignore the parameter decl itself (its children were handled elsewhere), // as they don't contribute to the main-file #include. if (llvm::isa(VD)) return true; // Mark declaration from definition as it needs type-checking. if (VD->isThisDeclarationADefinition()) report(VD->getLocation(), VD); return true; } bool VisitEnumDecl(EnumDecl *D) { // Definition of an enum with an underlying type references declaration for // type-checking purposes. if (D->isThisDeclarationADefinition() && D->getIntegerTypeSourceInfo()) report(D->getLocation(), D); return true; } bool VisitFriendDecl(FriendDecl *D) { // We already visit the TypeLoc properly, but need to special case the decl // case. if (auto *FD = D->getFriendDecl()) report(D->getLocation(), FD); return true; } bool VisitConceptReference(const ConceptReference *CR) { report(CR->getConceptNameLoc(), CR->getFoundDecl()); return true; } // Report a reference from explicit specializations to the specialized // template. Implicit ones are filtered out by RAV and explicit instantiations // are already traversed through typelocs. bool VisitClassTemplateSpecializationDecl(ClassTemplateSpecializationDecl *CTSD) { if (CTSD->isExplicitSpecialization()) report(CTSD->getLocation(), CTSD->getSpecializedTemplate()->getTemplatedDecl()); return true; } bool VisitVarTemplateSpecializationDecl(VarTemplateSpecializationDecl *VTSD) { if (VTSD->isExplicitSpecialization()) report(VTSD->getLocation(), VTSD->getSpecializedTemplate()->getTemplatedDecl()); return true; } // TypeLoc visitors. void reportType(SourceLocation RefLoc, NamedDecl *ND) { // Reporting explicit references to types nested inside classes can cause // issues, e.g. a type accessed through a derived class shouldn't require // inclusion of the base. // Hence we report all such references as implicit. The code must spell the // outer type-location somewhere, which will trigger an explicit reference // and per IWYS, it's that spelling's responsibility to bring in necessary // declarations. RefType RT = llvm::isa(ND->getDeclContext()) ? RefType::Implicit : RefType::Explicit; return report(RefLoc, ND, RT); } bool VisitUsingTypeLoc(UsingTypeLoc TL) { reportType(TL.getNameLoc(), TL.getFoundDecl()); return true; } bool VisitTagTypeLoc(TagTypeLoc TTL) { reportType(TTL.getNameLoc(), TTL.getDecl()); return true; } bool VisitTypedefTypeLoc(TypedefTypeLoc TTL) { reportType(TTL.getNameLoc(), TTL.getTypedefNameDecl()); return true; } bool VisitTemplateSpecializationTypeLoc(TemplateSpecializationTypeLoc TL) { reportType(TL.getTemplateNameLoc(), getMostRelevantTemplatePattern(TL.getTypePtr())); return true; } bool VisitDeducedTemplateSpecializationTypeLoc( DeducedTemplateSpecializationTypeLoc TL) { reportType(TL.getTemplateNameLoc(), getMostRelevantTemplatePattern(TL.getTypePtr())); return true; } bool TraverseTemplateArgumentLoc(const TemplateArgumentLoc &TL) { auto &Arg = TL.getArgument(); // Template-template parameters require special attention, as there's no // TemplateNameLoc. if (Arg.getKind() == TemplateArgument::Template || Arg.getKind() == TemplateArgument::TemplateExpansion) { report(TL.getLocation(), resolveTemplateName(Arg.getAsTemplateOrTemplatePattern())); return true; } return RecursiveASTVisitor::TraverseTemplateArgumentLoc(TL); } bool VisitCXXStdInitializerListExpr(CXXStdInitializerListExpr *E) { // Reliance on initializer_lists requires std::initializer_list to be // visible per standard. So report a reference to it, otherwise include of // `` might not receive any use. report(E->getExprLoc(), const_cast(E->getBestDynamicClassType()), RefType::Implicit); return true; } }; } // namespace void walkAST(Decl &Root, DeclCallback Callback) { ASTWalker(Callback).TraverseDecl(&Root); } } // namespace clang::include_cleaner