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