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 
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     Finder->addMatcher(
27         cxxConstructorDecl(unless(isInstantiated())).bind("ctor"), this);
28 }
29 
30 // Looks for the token matching the predicate and returns the range of the found
31 // token including trailing whitespace.
32 static SourceRange FindToken(const SourceManager &Sources, LangOptions LangOpts,
33                              SourceLocation StartLoc, SourceLocation EndLoc,
34                              bool (*Pred)(const Token &)) {
35   if (StartLoc.isMacroID() || EndLoc.isMacroID())
36     return SourceRange();
37   FileID File = Sources.getFileID(Sources.getSpellingLoc(StartLoc));
38   StringRef Buf = Sources.getBufferData(File);
39   const char *StartChar = Sources.getCharacterData(StartLoc);
40   Lexer Lex(StartLoc, LangOpts, StartChar, StartChar, Buf.end());
41   Lex.SetCommentRetentionState(true);
42   Token Tok;
43   do {
44     Lex.LexFromRawLexer(Tok);
45     if (Pred(Tok)) {
46       Token NextTok;
47       Lex.LexFromRawLexer(NextTok);
48       return SourceRange(Tok.getLocation(), NextTok.getLocation());
49     }
50   } while (Tok.isNot(tok::eof) && Tok.getLocation() < EndLoc);
51 
52   return SourceRange();
53 }
54 
55 static bool declIsStdInitializerList(const NamedDecl *D) {
56   // First use the fast getName() method to avoid unnecessary calls to the
57   // slow getQualifiedNameAsString().
58   return D->getName() == "initializer_list" &&
59          D->getQualifiedNameAsString() == "std::initializer_list";
60 }
61 
62 static bool isStdInitializerList(QualType Type) {
63   Type = Type.getCanonicalType();
64   if (const auto *TS = Type->getAs<TemplateSpecializationType>()) {
65     if (const TemplateDecl *TD = TS->getTemplateName().getAsTemplateDecl())
66       return declIsStdInitializerList(TD);
67   }
68   if (const auto *RT = Type->getAs<RecordType>()) {
69     if (const auto *Specialization =
70             dyn_cast<ClassTemplateSpecializationDecl>(RT->getDecl()))
71       return declIsStdInitializerList(Specialization->getSpecializedTemplate());
72   }
73   return false;
74 }
75 
76 void ExplicitConstructorCheck::check(const MatchFinder::MatchResult &Result) {
77   const CXXConstructorDecl *Ctor =
78       Result.Nodes.getNodeAs<CXXConstructorDecl>("ctor");
79   // Do not be confused: isExplicit means 'explicit' keyword is present,
80   // isImplicit means that it's a compiler-generated constructor.
81   if (Ctor->isOutOfLine() || Ctor->isImplicit() || Ctor->isDeleted() ||
82       Ctor->getNumParams() == 0 || Ctor->getMinRequiredArguments() > 1)
83     return;
84 
85   bool takesInitializerList = isStdInitializerList(
86       Ctor->getParamDecl(0)->getType().getNonReferenceType());
87   if (Ctor->isExplicit() &&
88       (Ctor->isCopyOrMoveConstructor() || takesInitializerList)) {
89     auto isKWExplicit = [](const Token &Tok) {
90       return Tok.is(tok::raw_identifier) &&
91              Tok.getRawIdentifier() == "explicit";
92     };
93     SourceRange ExplicitTokenRange =
94         FindToken(*Result.SourceManager, Result.Context->getLangOpts(),
95                   Ctor->getOuterLocStart(), Ctor->getLocEnd(), isKWExplicit);
96     StringRef ConstructorDescription;
97     if (Ctor->isMoveConstructor())
98       ConstructorDescription = "move";
99     else if (Ctor->isCopyConstructor())
100       ConstructorDescription = "copy";
101     else
102       ConstructorDescription = "initializer-list";
103 
104     DiagnosticBuilder Diag =
105         diag(Ctor->getLocation(),
106              "%0 constructor should not be declared explicit")
107         << ConstructorDescription;
108     if (ExplicitTokenRange.isValid()) {
109       Diag << FixItHint::CreateRemoval(
110           CharSourceRange::getCharRange(ExplicitTokenRange));
111     }
112     return;
113   }
114 
115   if (Ctor->isExplicit() || Ctor->isCopyOrMoveConstructor() ||
116       takesInitializerList)
117     return;
118 
119   bool SingleArgument =
120       Ctor->getNumParams() == 1 && !Ctor->getParamDecl(0)->isParameterPack();
121   SourceLocation Loc = Ctor->getLocation();
122   diag(Loc,
123        "%0 must be marked explicit to avoid unintentional implicit conversions")
124       << (SingleArgument
125               ? "single-argument constructors"
126               : "constructors that are callable with a single argument")
127       << FixItHint::CreateInsertion(Loc, "explicit ");
128 }
129 
130 } // namespace google
131 } // namespace tidy
132 } // namespace clang
133