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.
AST_MATCHER(QualType,isEnableIf)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 }
AST_MATCHER_P(TemplateTypeParmDecl,hasDefaultArgument,clang::ast_matchers::internal::Matcher<QualType>,TypeMatcher)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
registerMatchers(MatchFinder * Finder)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
check(const MatchFinder::MatchResult & Result)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