1 | //===--- ExplicitConstructorCheck.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 "ExplicitConstructorCheck.h" |
10 | #include "clang/AST/ASTContext.h" |
11 | #include "clang/ASTMatchers/ASTMatchFinder.h" |
12 | #include "clang/ASTMatchers/ASTMatchers.h" |
13 | #include "clang/Lex/Lexer.h" |
14 | |
15 | using namespace clang::ast_matchers; |
16 | |
17 | namespace clang::tidy::google { |
18 | |
19 | void ExplicitConstructorCheck::registerMatchers(MatchFinder *Finder) { |
20 | Finder->addMatcher( |
21 | NodeMatch: cxxConstructorDecl(unless(anyOf(isImplicit(), // Compiler-generated. |
22 | isDeleted(), isInstantiated()))) |
23 | .bind(ID: "ctor" ), |
24 | Action: this); |
25 | Finder->addMatcher( |
26 | NodeMatch: cxxConversionDecl(unless(anyOf(isExplicit(), // Already marked explicit. |
27 | isImplicit(), // Compiler-generated. |
28 | isDeleted(), isInstantiated()))) |
29 | |
30 | .bind(ID: "conversion" ), |
31 | Action: this); |
32 | } |
33 | |
34 | // Looks for the token matching the predicate and returns the range of the found |
35 | // token including trailing whitespace. |
36 | static SourceRange findToken(const SourceManager &Sources, |
37 | const LangOptions &LangOpts, |
38 | SourceLocation StartLoc, SourceLocation EndLoc, |
39 | bool (*Pred)(const Token &)) { |
40 | if (StartLoc.isMacroID() || EndLoc.isMacroID()) |
41 | return {}; |
42 | FileID File = Sources.getFileID(SpellingLoc: Sources.getSpellingLoc(Loc: StartLoc)); |
43 | StringRef Buf = Sources.getBufferData(FID: File); |
44 | const char *StartChar = Sources.getCharacterData(SL: StartLoc); |
45 | Lexer Lex(StartLoc, LangOpts, StartChar, StartChar, Buf.end()); |
46 | Lex.SetCommentRetentionState(true); |
47 | Token Tok; |
48 | do { |
49 | Lex.LexFromRawLexer(Result&: Tok); |
50 | if (Pred(Tok)) { |
51 | Token NextTok; |
52 | Lex.LexFromRawLexer(Result&: NextTok); |
53 | return {Tok.getLocation(), NextTok.getLocation()}; |
54 | } |
55 | } while (Tok.isNot(K: tok::eof) && Tok.getLocation() < EndLoc); |
56 | |
57 | return {}; |
58 | } |
59 | |
60 | static bool declIsStdInitializerList(const NamedDecl *D) { |
61 | // First use the fast getName() method to avoid unnecessary calls to the |
62 | // slow getQualifiedNameAsString(). |
63 | return D->getName() == "initializer_list" && |
64 | D->getQualifiedNameAsString() == "std::initializer_list" ; |
65 | } |
66 | |
67 | static bool isStdInitializerList(QualType Type) { |
68 | Type = Type.getCanonicalType(); |
69 | if (const auto *TS = Type->getAs<TemplateSpecializationType>()) { |
70 | if (const TemplateDecl *TD = TS->getTemplateName().getAsTemplateDecl()) |
71 | return declIsStdInitializerList(TD); |
72 | } |
73 | if (const auto *RT = Type->getAs<RecordType>()) { |
74 | if (const auto *Specialization = |
75 | dyn_cast<ClassTemplateSpecializationDecl>(Val: RT->getDecl())) |
76 | return declIsStdInitializerList(Specialization->getSpecializedTemplate()); |
77 | } |
78 | return false; |
79 | } |
80 | |
81 | void ExplicitConstructorCheck::check(const MatchFinder::MatchResult &Result) { |
82 | constexpr char NoExpressionWarningMessage[] = |
83 | "%0 must be marked explicit to avoid unintentional implicit conversions" ; |
84 | constexpr char WithExpressionWarningMessage[] = |
85 | "%0 explicit expression evaluates to 'false'" ; |
86 | |
87 | if (const auto *Conversion = |
88 | Result.Nodes.getNodeAs<CXXConversionDecl>(ID: "conversion" )) { |
89 | if (Conversion->isOutOfLine()) |
90 | return; |
91 | SourceLocation Loc = Conversion->getLocation(); |
92 | // Ignore all macros until we learn to ignore specific ones (e.g. used in |
93 | // gmock to define matchers). |
94 | if (Loc.isMacroID()) |
95 | return; |
96 | diag(Loc, Description: NoExpressionWarningMessage) |
97 | << Conversion << FixItHint::CreateInsertion(InsertionLoc: Loc, Code: "explicit " ); |
98 | return; |
99 | } |
100 | |
101 | const auto *Ctor = Result.Nodes.getNodeAs<CXXConstructorDecl>(ID: "ctor" ); |
102 | if (Ctor->isOutOfLine() || Ctor->getNumParams() == 0 || |
103 | Ctor->getMinRequiredArguments() > 1) |
104 | return; |
105 | |
106 | const ExplicitSpecifier ExplicitSpec = Ctor->getExplicitSpecifier(); |
107 | |
108 | bool TakesInitializerList = isStdInitializerList( |
109 | Ctor->getParamDecl(0)->getType().getNonReferenceType()); |
110 | if (ExplicitSpec.isExplicit() && |
111 | (Ctor->isCopyOrMoveConstructor() || TakesInitializerList)) { |
112 | auto IsKwExplicit = [](const Token &Tok) { |
113 | return Tok.is(K: tok::raw_identifier) && |
114 | Tok.getRawIdentifier() == "explicit" ; |
115 | }; |
116 | SourceRange ExplicitTokenRange = |
117 | findToken(*Result.SourceManager, getLangOpts(), |
118 | Ctor->getOuterLocStart(), Ctor->getEndLoc(), IsKwExplicit); |
119 | StringRef ConstructorDescription; |
120 | if (Ctor->isMoveConstructor()) |
121 | ConstructorDescription = "move" ; |
122 | else if (Ctor->isCopyConstructor()) |
123 | ConstructorDescription = "copy" ; |
124 | else |
125 | ConstructorDescription = "initializer-list" ; |
126 | |
127 | auto Diag = diag(Ctor->getLocation(), |
128 | "%0 constructor should not be declared explicit" ) |
129 | << ConstructorDescription; |
130 | if (ExplicitTokenRange.isValid()) { |
131 | Diag << FixItHint::CreateRemoval( |
132 | RemoveRange: CharSourceRange::getCharRange(R: ExplicitTokenRange)); |
133 | } |
134 | return; |
135 | } |
136 | |
137 | if (ExplicitSpec.isExplicit() || Ctor->isCopyOrMoveConstructor() || |
138 | TakesInitializerList) |
139 | return; |
140 | |
141 | // Don't complain about explicit(false) or dependent expressions |
142 | const Expr *ExplicitExpr = ExplicitSpec.getExpr(); |
143 | if (ExplicitExpr) { |
144 | ExplicitExpr = ExplicitExpr->IgnoreImplicit(); |
145 | if (isa<CXXBoolLiteralExpr>(Val: ExplicitExpr) || |
146 | ExplicitExpr->isInstantiationDependent()) |
147 | return; |
148 | } |
149 | |
150 | const bool SingleArgument = |
151 | Ctor->getNumParams() == 1 && !Ctor->getParamDecl(0)->isParameterPack(); |
152 | SourceLocation Loc = Ctor->getLocation(); |
153 | auto Diag = |
154 | diag(Loc, Description: ExplicitExpr ? WithExpressionWarningMessage |
155 | : NoExpressionWarningMessage) |
156 | << (SingleArgument |
157 | ? "single-argument constructors" |
158 | : "constructors that are callable with a single argument" ); |
159 | |
160 | if (!ExplicitExpr) |
161 | Diag << FixItHint::CreateInsertion(InsertionLoc: Loc, Code: "explicit " ); |
162 | } |
163 | |
164 | } // namespace clang::tidy::google |
165 | |