1 //===--- ForwardingReferenceOverloadCheck.cpp - clang-tidy-----------------===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "ForwardingReferenceOverloadCheck.h" 10 #include "clang/AST/ASTContext.h" 11 #include "clang/ASTMatchers/ASTMatchFinder.h" 12 13 using namespace clang::ast_matchers; 14 15 namespace clang::tidy::bugprone { 16 17 namespace { 18 // Check if the given type is related to std::enable_if. 19 AST_MATCHER(QualType, isEnableIf) { 20 auto CheckTemplate = [](const TemplateSpecializationType *Spec) { 21 if (!Spec) 22 return false; 23 24 const TemplateDecl *TDecl = Spec->getTemplateName().getAsTemplateDecl(); 25 26 return TDecl && TDecl->isInStdNamespace() && 27 (TDecl->getName() == "enable_if" || 28 TDecl->getName() == "enable_if_t"); 29 }; 30 const Type *BaseType = Node.getTypePtr(); 31 // Case: pointer or reference to enable_if. 32 while (BaseType->isPointerType() || BaseType->isReferenceType()) { 33 BaseType = BaseType->getPointeeType().getTypePtr(); 34 } 35 // Case: type parameter dependent (enable_if<is_integral<T>>). 36 if (const auto *Dependent = BaseType->getAs<DependentNameType>()) { 37 BaseType = Dependent->getQualifier()->getAsType(); 38 } 39 if (!BaseType) 40 return false; 41 if (CheckTemplate(BaseType->getAs<TemplateSpecializationType>())) 42 return true; // Case: enable_if_t< >. 43 if (const auto *Elaborated = BaseType->getAs<ElaboratedType>()) { 44 if (const auto *Q = Elaborated->getQualifier()) 45 if (const auto *Qualifier = Q->getAsType()) { 46 if (CheckTemplate(Qualifier->getAs<TemplateSpecializationType>())) { 47 return true; // Case: enable_if< >::type. 48 } 49 } 50 } 51 return false; 52 } 53 AST_MATCHER_P(TemplateTypeParmDecl, hasDefaultArgument, 54 clang::ast_matchers::internal::Matcher<QualType>, TypeMatcher) { 55 return Node.hasDefaultArgument() && 56 TypeMatcher.matches( 57 Node.getDefaultArgument().getArgument().getAsType(), Finder, 58 Builder); 59 } 60 AST_MATCHER(TemplateDecl, hasAssociatedConstraints) { 61 return Node.hasAssociatedConstraints(); 62 } 63 } // namespace 64 65 void ForwardingReferenceOverloadCheck::registerMatchers(MatchFinder *Finder) { 66 auto ForwardingRefParm = 67 parmVarDecl( 68 hasType(qualType(rValueReferenceType(), 69 references(templateTypeParmType(hasDeclaration( 70 templateTypeParmDecl().bind("type-parm-decl")))), 71 unless(references(isConstQualified()))))) 72 .bind("parm-var"); 73 74 DeclarationMatcher FindOverload = 75 cxxConstructorDecl( 76 hasParameter(0, ForwardingRefParm), unless(isDeleted()), 77 unless(hasAnyParameter( 78 // No warning: enable_if as constructor parameter. 79 parmVarDecl(hasType(isEnableIf())))), 80 unless(hasParent(functionTemplateDecl(anyOf( 81 // No warning: has associated constraints (like requires 82 // expression). 83 hasAssociatedConstraints(), 84 // No warning: enable_if as type parameter. 85 has(templateTypeParmDecl(hasDefaultArgument(isEnableIf()))), 86 // No warning: enable_if as non-type template parameter. 87 has(nonTypeTemplateParmDecl( 88 hasType(isEnableIf()), 89 anyOf(hasDescendant(cxxBoolLiteral()), 90 hasDescendant(cxxNullPtrLiteralExpr()), 91 hasDescendant(integerLiteral()))))))))) 92 .bind("ctor"); 93 Finder->addMatcher(FindOverload, this); 94 } 95 96 void ForwardingReferenceOverloadCheck::check( 97 const MatchFinder::MatchResult &Result) { 98 const auto *ParmVar = Result.Nodes.getNodeAs<ParmVarDecl>("parm-var"); 99 const auto *TypeParmDecl = 100 Result.Nodes.getNodeAs<TemplateTypeParmDecl>("type-parm-decl"); 101 102 // Get the FunctionDecl and FunctionTemplateDecl containing the function 103 // parameter. 104 const auto *FuncForParam = dyn_cast<FunctionDecl>(ParmVar->getDeclContext()); 105 if (!FuncForParam) 106 return; 107 const FunctionTemplateDecl *FuncTemplate = 108 FuncForParam->getDescribedFunctionTemplate(); 109 if (!FuncTemplate) 110 return; 111 112 // Check that the template type parameter belongs to the same function 113 // template as the function parameter of that type. (This implies that type 114 // deduction will happen on the type.) 115 const TemplateParameterList *Params = FuncTemplate->getTemplateParameters(); 116 if (!llvm::is_contained(*Params, TypeParmDecl)) 117 return; 118 119 // Every parameter after the first must have a default value. 120 const auto *Ctor = Result.Nodes.getNodeAs<CXXConstructorDecl>("ctor"); 121 for (const auto *Param : llvm::drop_begin(Ctor->parameters())) { 122 if (!Param->hasDefaultArg()) 123 return; 124 } 125 bool EnabledCopy = false, DisabledCopy = false, EnabledMove = false, 126 DisabledMove = false; 127 for (const auto *OtherCtor : Ctor->getParent()->ctors()) { 128 if (OtherCtor->isCopyOrMoveConstructor()) { 129 if (OtherCtor->isDeleted() || OtherCtor->getAccess() == AS_private) 130 (OtherCtor->isCopyConstructor() ? DisabledCopy : DisabledMove) = true; 131 else 132 (OtherCtor->isCopyConstructor() ? EnabledCopy : EnabledMove) = true; 133 } 134 } 135 bool Copy = (!EnabledMove && !DisabledMove && !DisabledCopy) || EnabledCopy; 136 bool Move = !DisabledMove || EnabledMove; 137 if (!Copy && !Move) 138 return; 139 diag(Ctor->getLocation(), 140 "constructor accepting a forwarding reference can " 141 "hide the %select{copy|move|copy and move}0 constructor%s1") 142 << (Copy && Move ? 2 : (Copy ? 0 : 1)) << Copy + Move; 143 for (const auto *OtherCtor : Ctor->getParent()->ctors()) { 144 if (OtherCtor->isCopyOrMoveConstructor() && !OtherCtor->isDeleted() && 145 OtherCtor->getAccess() != AS_private) { 146 diag(OtherCtor->getLocation(), 147 "%select{copy|move}0 constructor declared here", DiagnosticIDs::Note) 148 << OtherCtor->isMoveConstructor(); 149 } 150 } 151 } 152 153 } // namespace clang::tidy::bugprone 154