| //===--- MissingStdForwardCheck.cpp - clang-tidy --------------------------===// |
| // |
| // 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 "MissingStdForwardCheck.h" |
| #include "../utils/Matchers.h" |
| #include "clang/AST/ASTContext.h" |
| #include "clang/AST/ExprConcepts.h" |
| #include "clang/ASTMatchers/ASTMatchFinder.h" |
| |
| using namespace clang::ast_matchers; |
| |
| namespace clang::tidy::cppcoreguidelines { |
| |
| namespace { |
| |
| using matchers::hasUnevaluatedContext; |
| |
| AST_MATCHER_P(QualType, possiblyPackExpansionOf, |
| ast_matchers::internal::Matcher<QualType>, InnerMatcher) { |
| return InnerMatcher.matches(Node.getNonPackExpansionType(), Finder, Builder); |
| } |
| |
| AST_MATCHER(ParmVarDecl, isTemplateTypeParameter) { |
| ast_matchers::internal::Matcher<QualType> Inner = possiblyPackExpansionOf( |
| qualType(rValueReferenceType(), |
| references(templateTypeParmType( |
| hasDeclaration(templateTypeParmDecl()))), |
| unless(references(qualType(isConstQualified()))))); |
| if (!Inner.matches(Node.getType(), Finder, Builder)) |
| return false; |
| |
| const auto *Function = dyn_cast<FunctionDecl>(Node.getDeclContext()); |
| if (!Function) |
| return false; |
| |
| const FunctionTemplateDecl *FuncTemplate = |
| Function->getDescribedFunctionTemplate(); |
| if (!FuncTemplate) |
| return false; |
| |
| QualType ParamType = |
| Node.getType().getNonPackExpansionType()->getPointeeType(); |
| const auto *TemplateType = ParamType->getAs<TemplateTypeParmType>(); |
| if (!TemplateType) |
| return false; |
| |
| return TemplateType->getDepth() == |
| FuncTemplate->getTemplateParameters()->getDepth(); |
| } |
| |
| AST_MATCHER_P(NamedDecl, hasSameNameAsBoundNode, std::string, BindingID) { |
| IdentifierInfo *II = Node.getIdentifier(); |
| if (nullptr == II) |
| return false; |
| StringRef Name = II->getName(); |
| |
| return Builder->removeBindings( |
| [this, Name](const ast_matchers::internal::BoundNodesMap &Nodes) { |
| const DynTypedNode &BN = Nodes.getNode(this->BindingID); |
| if (const auto *ND = BN.get<NamedDecl>()) { |
| if (!isa<FieldDecl, CXXMethodDecl, VarDecl>(ND)) |
| return true; |
| return ND->getName() != Name; |
| } |
| return true; |
| }); |
| } |
| |
| AST_MATCHER_P(LambdaCapture, hasCaptureKind, LambdaCaptureKind, Kind) { |
| return Node.getCaptureKind() == Kind; |
| } |
| |
| AST_MATCHER_P(LambdaExpr, hasCaptureDefaultKind, LambdaCaptureDefault, Kind) { |
| return Node.getCaptureDefault() == Kind; |
| } |
| |
| } // namespace |
| |
| void MissingStdForwardCheck::registerMatchers(MatchFinder *Finder) { |
| auto RefToParmImplicit = allOf( |
| equalsBoundNode("var"), hasInitializer(ignoringParenImpCasts( |
| declRefExpr(to(equalsBoundNode("param")))))); |
| auto RefToParm = capturesVar( |
| varDecl(anyOf(hasSameNameAsBoundNode("param"), RefToParmImplicit))); |
| auto HasRefToParm = hasAnyCapture(RefToParm); |
| |
| auto CaptureInRef = |
| allOf(hasCaptureDefaultKind(LambdaCaptureDefault::LCD_ByRef), |
| unless(hasAnyCapture( |
| capturesVar(varDecl(hasSameNameAsBoundNode("param")))))); |
| auto CaptureInCopy = allOf( |
| hasCaptureDefaultKind(LambdaCaptureDefault::LCD_ByCopy), HasRefToParm); |
| auto CaptureByRefExplicit = hasAnyCapture( |
| allOf(hasCaptureKind(LambdaCaptureKind::LCK_ByRef), RefToParm)); |
| |
| auto CapturedInBody = |
| lambdaExpr(anyOf(CaptureInRef, CaptureInCopy, CaptureByRefExplicit)); |
| auto CapturedInCaptureList = hasAnyCapture(capturesVar( |
| varDecl(hasInitializer(ignoringParenImpCasts(equalsBoundNode("call")))))); |
| |
| auto CapturedInLambda = hasDeclContext(cxxRecordDecl( |
| isLambda(), |
| hasParent(lambdaExpr(forCallable(equalsBoundNode("func")), |
| anyOf(CapturedInCaptureList, CapturedInBody))))); |
| |
| auto ToParam = hasAnyParameter(parmVarDecl(equalsBoundNode("param"))); |
| |
| auto ForwardCallMatcher = callExpr( |
| callExpr().bind("call"), argumentCountIs(1), |
| hasArgument( |
| 0, declRefExpr(to( |
| varDecl(optionally(equalsBoundNode("param"))).bind("var")))), |
| forCallable(anyOf(equalsBoundNode("func"), CapturedInLambda)), |
| callee(unresolvedLookupExpr(hasAnyDeclaration( |
| namedDecl(hasUnderlyingDecl(hasName("::std::forward")))))), |
| |
| unless(anyOf(hasAncestor(typeLoc()), |
| hasAncestor(expr(hasUnevaluatedContext()))))); |
| |
| Finder->addMatcher( |
| parmVarDecl(parmVarDecl().bind("param"), isTemplateTypeParameter(), |
| hasAncestor(functionDecl().bind("func")), |
| hasAncestor(functionDecl( |
| isDefinition(), equalsBoundNode("func"), ToParam, |
| unless(hasDescendant(std::move(ForwardCallMatcher)))))), |
| this); |
| } |
| |
| void MissingStdForwardCheck::check(const MatchFinder::MatchResult &Result) { |
| const auto *Param = Result.Nodes.getNodeAs<ParmVarDecl>("param"); |
| |
| if (!Param) |
| return; |
| |
| diag(Param->getLocation(), |
| "forwarding reference parameter %0 is never forwarded " |
| "inside the function body") |
| << Param; |
| } |
| |
| } // namespace clang::tidy::cppcoreguidelines |