xref: /llvm-project/clang-tools-extra/clang-tidy/bugprone/ForwardingReferenceOverloadCheck.cpp (revision 6357781e3f9fbc5a14a794b8769b451c863c65c7)
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