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