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(has(templateTypeParmDecl(
78               // No warning: enable_if as type parameter.
79               hasDefaultArgument(isEnableIf())))))))
80           .bind("ctor");
81   Finder->addMatcher(FindOverload, this);
82 }
83 
84 void ForwardingReferenceOverloadCheck::check(
85     const MatchFinder::MatchResult &Result) {
86   const auto *ParmVar = Result.Nodes.getNodeAs<ParmVarDecl>("parm-var");
87   const auto *TypeParmDecl =
88       Result.Nodes.getNodeAs<TemplateTypeParmDecl>("type-parm-decl");
89 
90   // Get the FunctionDecl and FunctionTemplateDecl containing the function
91   // parameter.
92   const auto *FuncForParam = dyn_cast<FunctionDecl>(ParmVar->getDeclContext());
93   if (!FuncForParam)
94     return;
95   const FunctionTemplateDecl *FuncTemplate =
96       FuncForParam->getDescribedFunctionTemplate();
97   if (!FuncTemplate)
98     return;
99 
100   // Check that the template type parameter belongs to the same function
101   // template as the function parameter of that type. (This implies that type
102   // deduction will happen on the type.)
103   const TemplateParameterList *Params = FuncTemplate->getTemplateParameters();
104   if (!llvm::is_contained(*Params, TypeParmDecl))
105     return;
106 
107   // Every parameter after the first must have a default value.
108   const auto *Ctor = Result.Nodes.getNodeAs<CXXConstructorDecl>("ctor");
109   for (auto Iter = Ctor->param_begin() + 1; Iter != Ctor->param_end(); ++Iter) {
110     if (!(*Iter)->hasDefaultArg())
111       return;
112   }
113   bool EnabledCopy = false, DisabledCopy = false, EnabledMove = false,
114        DisabledMove = false;
115   for (const auto *OtherCtor : Ctor->getParent()->ctors()) {
116     if (OtherCtor->isCopyOrMoveConstructor()) {
117       if (OtherCtor->isDeleted() || OtherCtor->getAccess() == AS_private)
118         (OtherCtor->isCopyConstructor() ? DisabledCopy : DisabledMove) = true;
119       else
120         (OtherCtor->isCopyConstructor() ? EnabledCopy : EnabledMove) = true;
121     }
122   }
123   bool Copy = (!EnabledMove && !DisabledMove && !DisabledCopy) || EnabledCopy;
124   bool Move = !DisabledMove || EnabledMove;
125   if (!Copy && !Move)
126     return;
127   diag(Ctor->getLocation(),
128        "constructor accepting a forwarding reference can "
129        "hide the %select{copy|move|copy and move}0 constructor%s1")
130       << (Copy && Move ? 2 : (Copy ? 0 : 1)) << Copy + Move;
131   for (const auto *OtherCtor : Ctor->getParent()->ctors()) {
132     if (OtherCtor->isCopyOrMoveConstructor() && !OtherCtor->isDeleted() &&
133         OtherCtor->getAccess() != AS_private) {
134       diag(OtherCtor->getLocation(),
135            "%select{copy|move}0 constructor declared here", DiagnosticIDs::Note)
136           << OtherCtor->isMoveConstructor();
137     }
138   }
139 }
140 
141 } // namespace bugprone
142 } // namespace tidy
143 } // namespace clang
144