1 //===--- ExplicitConstructorCheck.cpp - clang-tidy ------------------------===//
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
7 //===----------------------------------------------------------------------===//
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"
15 using namespace clang::ast_matchers
;
17 namespace clang::tidy::google
{
19 void ExplicitConstructorCheck::registerMatchers(MatchFinder
*Finder
) {
21 cxxConstructorDecl(unless(anyOf(isImplicit(), // Compiler-generated.
22 isDeleted(), isInstantiated())))
26 cxxConversionDecl(unless(anyOf(isExplicit(), // Already marked explicit.
27 isImplicit(), // Compiler-generated.
28 isDeleted(), isInstantiated())))
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())
42 FileID File
= Sources
.getFileID(Sources
.getSpellingLoc(StartLoc
));
43 StringRef Buf
= Sources
.getBufferData(File
);
44 const char *StartChar
= Sources
.getCharacterData(StartLoc
);
45 Lexer
Lex(StartLoc
, LangOpts
, StartChar
, StartChar
, Buf
.end());
46 Lex
.SetCommentRetentionState(true);
49 Lex
.LexFromRawLexer(Tok
);
52 Lex
.LexFromRawLexer(NextTok
);
53 return {Tok
.getLocation(), NextTok
.getLocation()};
55 } while (Tok
.isNot(tok::eof
) && Tok
.getLocation() < EndLoc
);
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";
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
);
73 if (const auto *RT
= Type
->getAs
<RecordType
>()) {
74 if (const auto *Specialization
=
75 dyn_cast
<ClassTemplateSpecializationDecl
>(RT
->getDecl()))
76 return declIsStdInitializerList(Specialization
->getSpecializedTemplate());
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'";
87 if (const auto *Conversion
=
88 Result
.Nodes
.getNodeAs
<CXXConversionDecl
>("conversion")) {
89 if (Conversion
->isOutOfLine())
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).
96 diag(Loc
, NoExpressionWarningMessage
)
97 << Conversion
<< FixItHint::CreateInsertion(Loc
, "explicit ");
101 const auto *Ctor
= Result
.Nodes
.getNodeAs
<CXXConstructorDecl
>("ctor");
102 if (Ctor
->isOutOfLine() || Ctor
->getNumParams() == 0 ||
103 Ctor
->getMinRequiredArguments() > 1)
106 const ExplicitSpecifier ExplicitSpec
= Ctor
->getExplicitSpecifier();
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(tok::raw_identifier
) &&
114 Tok
.getRawIdentifier() == "explicit";
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";
125 ConstructorDescription
= "initializer-list";
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 CharSourceRange::getCharRange(ExplicitTokenRange
));
137 if (ExplicitSpec
.isExplicit() || Ctor
->isCopyOrMoveConstructor() ||
138 TakesInitializerList
)
141 // Don't complain about explicit(false) or dependent expressions
142 const Expr
*ExplicitExpr
= ExplicitSpec
.getExpr();
144 ExplicitExpr
= ExplicitExpr
->IgnoreImplicit();
145 if (isa
<CXXBoolLiteralExpr
>(ExplicitExpr
) ||
146 ExplicitExpr
->isInstantiationDependent())
150 const bool SingleArgument
=
151 Ctor
->getNumParams() == 1 && !Ctor
->getParamDecl(0)->isParameterPack();
152 SourceLocation Loc
= Ctor
->getLocation();
154 diag(Loc
, ExplicitExpr
? WithExpressionWarningMessage
155 : NoExpressionWarningMessage
)
157 ? "single-argument constructors"
158 : "constructors that are callable with a single argument");
161 Diag
<< FixItHint::CreateInsertion(Loc
, "explicit ");
164 } // namespace clang::tidy::google