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