1 //===--- ExplicitConstructorCheck.cpp - clang-tidy ------------------------===//
2 //
3 //                     The LLVM Compiler Infrastructure
4 //
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
7 //
8 //===----------------------------------------------------------------------===//
9 
10 #include "ExplicitConstructorCheck.h"
11 #include "clang/AST/ASTContext.h"
12 #include "clang/ASTMatchers/ASTMatchFinder.h"
13 #include "clang/ASTMatchers/ASTMatchers.h"
14 #include "clang/Lex/Lexer.h"
15 
16 using namespace clang::ast_matchers;
17 
18 namespace clang {
19 namespace tidy {
20 namespace google {
21 
registerMatchers(MatchFinder * Finder)22 void ExplicitConstructorCheck::registerMatchers(MatchFinder *Finder) {
23   // Only register the matchers for C++; the functionality currently does not
24   // provide any benefit to other languages, despite being benign.
25   if (!getLangOpts().CPlusPlus)
26     return;
27   Finder->addMatcher(
28       cxxConstructorDecl(unless(anyOf(isImplicit(), // Compiler-generated.
29                                       isDeleted(), isInstantiated())))
30           .bind("ctor"),
31       this);
32   Finder->addMatcher(
33       cxxConversionDecl(unless(anyOf(isExplicit(), // Already marked explicit.
34                                      isImplicit(), // Compiler-generated.
35                                      isDeleted(), isInstantiated())))
36 
37           .bind("conversion"),
38       this);
39 }
40 
41 // Looks for the token matching the predicate and returns the range of the found
42 // token including trailing whitespace.
FindToken(const SourceManager & Sources,const LangOptions & LangOpts,SourceLocation StartLoc,SourceLocation EndLoc,bool (* Pred)(const Token &))43 static SourceRange FindToken(const SourceManager &Sources,
44                              const LangOptions &LangOpts,
45                              SourceLocation StartLoc, SourceLocation EndLoc,
46                              bool (*Pred)(const Token &)) {
47   if (StartLoc.isMacroID() || EndLoc.isMacroID())
48     return SourceRange();
49   FileID File = Sources.getFileID(Sources.getSpellingLoc(StartLoc));
50   StringRef Buf = Sources.getBufferData(File);
51   const char *StartChar = Sources.getCharacterData(StartLoc);
52   Lexer Lex(StartLoc, LangOpts, StartChar, StartChar, Buf.end());
53   Lex.SetCommentRetentionState(true);
54   Token Tok;
55   do {
56     Lex.LexFromRawLexer(Tok);
57     if (Pred(Tok)) {
58       Token NextTok;
59       Lex.LexFromRawLexer(NextTok);
60       return SourceRange(Tok.getLocation(), NextTok.getLocation());
61     }
62   } while (Tok.isNot(tok::eof) && Tok.getLocation() < EndLoc);
63 
64   return SourceRange();
65 }
66 
declIsStdInitializerList(const NamedDecl * D)67 static bool declIsStdInitializerList(const NamedDecl *D) {
68   // First use the fast getName() method to avoid unnecessary calls to the
69   // slow getQualifiedNameAsString().
70   return D->getName() == "initializer_list" &&
71          D->getQualifiedNameAsString() == "std::initializer_list";
72 }
73 
isStdInitializerList(QualType Type)74 static bool isStdInitializerList(QualType Type) {
75   Type = Type.getCanonicalType();
76   if (const auto *TS = Type->getAs<TemplateSpecializationType>()) {
77     if (const TemplateDecl *TD = TS->getTemplateName().getAsTemplateDecl())
78       return declIsStdInitializerList(TD);
79   }
80   if (const auto *RT = Type->getAs<RecordType>()) {
81     if (const auto *Specialization =
82             dyn_cast<ClassTemplateSpecializationDecl>(RT->getDecl()))
83       return declIsStdInitializerList(Specialization->getSpecializedTemplate());
84   }
85   return false;
86 }
87 
check(const MatchFinder::MatchResult & Result)88 void ExplicitConstructorCheck::check(const MatchFinder::MatchResult &Result) {
89   constexpr char WarningMessage[] =
90       "%0 must be marked explicit to avoid unintentional implicit conversions";
91 
92   if (const auto *Conversion =
93       Result.Nodes.getNodeAs<CXXConversionDecl>("conversion")) {
94     if (Conversion->isOutOfLine())
95       return;
96     SourceLocation Loc = Conversion->getLocation();
97     // Ignore all macros until we learn to ignore specific ones (e.g. used in
98     // gmock to define matchers).
99     if (Loc.isMacroID())
100       return;
101     diag(Loc, WarningMessage)
102         << Conversion << FixItHint::CreateInsertion(Loc, "explicit ");
103     return;
104   }
105 
106   const auto *Ctor = Result.Nodes.getNodeAs<CXXConstructorDecl>("ctor");
107   if (Ctor->isOutOfLine() || Ctor->getNumParams() == 0 ||
108       Ctor->getMinRequiredArguments() > 1)
109     return;
110 
111   bool takesInitializerList = isStdInitializerList(
112       Ctor->getParamDecl(0)->getType().getNonReferenceType());
113   if (Ctor->isExplicit() &&
114       (Ctor->isCopyOrMoveConstructor() || takesInitializerList)) {
115     auto isKWExplicit = [](const Token &Tok) {
116       return Tok.is(tok::raw_identifier) &&
117              Tok.getRawIdentifier() == "explicit";
118     };
119     SourceRange ExplicitTokenRange =
120         FindToken(*Result.SourceManager, getLangOpts(),
121                   Ctor->getOuterLocStart(), Ctor->getLocEnd(), isKWExplicit);
122     StringRef ConstructorDescription;
123     if (Ctor->isMoveConstructor())
124       ConstructorDescription = "move";
125     else if (Ctor->isCopyConstructor())
126       ConstructorDescription = "copy";
127     else
128       ConstructorDescription = "initializer-list";
129 
130     auto Diag = diag(Ctor->getLocation(),
131                      "%0 constructor should not be declared explicit")
132                 << ConstructorDescription;
133     if (ExplicitTokenRange.isValid()) {
134       Diag << FixItHint::CreateRemoval(
135           CharSourceRange::getCharRange(ExplicitTokenRange));
136     }
137     return;
138   }
139 
140   if (Ctor->isExplicit() || Ctor->isCopyOrMoveConstructor() ||
141       takesInitializerList)
142     return;
143 
144   bool SingleArgument =
145       Ctor->getNumParams() == 1 && !Ctor->getParamDecl(0)->isParameterPack();
146   SourceLocation Loc = Ctor->getLocation();
147   diag(Loc, WarningMessage)
148       << (SingleArgument
149               ? "single-argument constructors"
150               : "constructors that are callable with a single argument")
151       << FixItHint::CreateInsertion(Loc, "explicit ");
152 }
153 
154 } // namespace google
155 } // namespace tidy
156 } // namespace clang
157