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