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