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 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. 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 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 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 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