339 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 "PassByValueCheck.h"10#include "clang/AST/ASTContext.h"11#include "clang/AST/RecursiveASTVisitor.h"12#include "clang/ASTMatchers/ASTMatchFinder.h"13#include "clang/ASTMatchers/ASTMatchers.h"14#include "clang/Frontend/CompilerInstance.h"15#include "clang/Lex/Lexer.h"16#include "clang/Lex/Preprocessor.h"17 18using namespace clang::ast_matchers;19using namespace llvm;20 21namespace clang::tidy::modernize {22 23static bool isFirstFriendOfSecond(const CXXRecordDecl *Friend,24 const CXXRecordDecl *Class) {25 return llvm::any_of(26 Class->friends(), [Friend](FriendDecl *FriendDecl) -> bool {27 if (const TypeSourceInfo *FriendTypeSource =28 FriendDecl->getFriendType()) {29 const QualType FriendType = FriendTypeSource->getType();30 return FriendType->getAsCXXRecordDecl() == Friend;31 }32 return false;33 });34}35 36namespace {37/// Matches move-constructible classes whose constructor can be called inside38/// a CXXRecordDecl with a bound ID.39///40/// Given41/// \code42/// // POD types are trivially move constructible.43/// struct Foo { int a; };44///45/// struct Bar {46/// Bar(Bar &&) = deleted;47/// int a;48/// };49///50/// class Buz {51/// Buz(Buz &&);52/// int a;53/// friend class Outer;54/// };55///56/// class Outer {57/// };58/// \endcode59/// recordDecl(isMoveConstructibleInBoundCXXRecordDecl("Outer"))60/// matches "Foo", "Buz".61AST_MATCHER_P(CXXRecordDecl, isMoveConstructibleInBoundCXXRecordDecl, StringRef,62 RecordDeclID) {63 return Builder->removeBindings(64 [this,65 &Node](const ast_matchers::internal::BoundNodesMap &Nodes) -> bool {66 const auto *BoundClass =67 Nodes.getNode(this->RecordDeclID).get<CXXRecordDecl>();68 for (const CXXConstructorDecl *Ctor : Node.ctors()) {69 if (Ctor->isMoveConstructor() && !Ctor->isDeleted() &&70 (Ctor->getAccess() == AS_public ||71 (BoundClass && isFirstFriendOfSecond(BoundClass, &Node))))72 return false;73 }74 return true;75 });76}77} // namespace78 79static TypeMatcher notTemplateSpecConstRefType() {80 return lValueReferenceType(81 pointee(unless(templateSpecializationType()), isConstQualified()));82}83 84static TypeMatcher nonConstValueType() {85 return qualType(unless(anyOf(referenceType(), isConstQualified())));86}87 88/// Whether or not \p ParamDecl is used exactly one time in \p Ctor.89///90/// Checks both in the init-list and the body of the constructor.91static bool paramReferredExactlyOnce(const CXXConstructorDecl *Ctor,92 const ParmVarDecl *ParamDecl) {93 /// \c clang::RecursiveASTVisitor that checks that the given94 /// \c ParmVarDecl is used exactly one time.95 ///96 /// \see ExactlyOneUsageVisitor::hasExactlyOneUsageIn()97 class ExactlyOneUsageVisitor98 : public RecursiveASTVisitor<ExactlyOneUsageVisitor> {99 friend class RecursiveASTVisitor<ExactlyOneUsageVisitor>;100 101 public:102 ExactlyOneUsageVisitor(const ParmVarDecl *ParamDecl)103 : ParamDecl(ParamDecl) {}104 105 /// Whether or not the parameter variable is referred only once in106 /// the107 /// given constructor.108 bool hasExactlyOneUsageIn(const CXXConstructorDecl *Ctor) {109 Count = 0U;110 TraverseDecl(const_cast<CXXConstructorDecl *>(Ctor));111 return Count == 1U;112 }113 114 private:115 /// Counts the number of references to a variable.116 ///117 /// Stops the AST traversal if more than one usage is found.118 bool VisitDeclRefExpr(DeclRefExpr *D) {119 if (const ParmVarDecl *To = dyn_cast<ParmVarDecl>(D->getDecl())) {120 if (To == ParamDecl) {121 ++Count;122 if (Count > 1U) {123 // No need to look further, used more than once.124 return false;125 }126 }127 }128 return true;129 }130 131 const ParmVarDecl *ParamDecl;132 unsigned Count = 0U;133 };134 135 return ExactlyOneUsageVisitor(ParamDecl).hasExactlyOneUsageIn(Ctor);136}137 138/// Returns true if the given constructor is part of a lvalue/rvalue reference139/// pair, i.e. `Param` is of lvalue reference type, and there exists another140/// constructor such that:141/// - it has the same number of parameters as `Ctor`.142/// - the parameter at the same index as `Param` is an rvalue reference143/// of the same pointee type144/// - all other parameters have the same type as the corresponding parameter in145/// `Ctor` or are rvalue references with the same pointee type.146/// Examples:147/// A::A(const B& Param)148/// A::A(B&&)149///150/// A::A(const B& Param, const C&)151/// A::A(B&& Param, C&&)152///153/// A::A(const B&, const C& Param)154/// A::A(B&&, C&& Param)155///156/// A::A(const B&, const C& Param)157/// A::A(const B&, C&& Param)158///159/// A::A(const B& Param, int)160/// A::A(B&& Param, int)161static bool hasRValueOverload(const CXXConstructorDecl *Ctor,162 const ParmVarDecl *Param) {163 if (!Param->getType().getCanonicalType()->isLValueReferenceType()) {164 // The parameter is passed by value.165 return false;166 }167 const int ParamIdx = Param->getFunctionScopeIndex();168 const CXXRecordDecl *Record = Ctor->getParent();169 170 // Check whether a ctor `C` forms a pair with `Ctor` under the aforementioned171 // rules.172 const auto IsRValueOverload = [&Ctor, ParamIdx](const CXXConstructorDecl *C) {173 if (C == Ctor || C->isDeleted() ||174 C->getNumParams() != Ctor->getNumParams())175 return false;176 for (int I = 0, E = C->getNumParams(); I < E; ++I) {177 const clang::QualType CandidateParamType =178 C->parameters()[I]->getType().getCanonicalType();179 const clang::QualType CtorParamType =180 Ctor->parameters()[I]->getType().getCanonicalType();181 const bool IsLValueRValuePair =182 CtorParamType->isLValueReferenceType() &&183 CandidateParamType->isRValueReferenceType() &&184 CandidateParamType->getPointeeType()->getUnqualifiedDesugaredType() ==185 CtorParamType->getPointeeType()->getUnqualifiedDesugaredType();186 if (I == ParamIdx) {187 // The parameter of interest must be paired.188 if (!IsLValueRValuePair)189 return false;190 } else {191 // All other parameters can be similar or paired.192 if (!(CandidateParamType == CtorParamType || IsLValueRValuePair))193 return false;194 }195 }196 return true;197 };198 199 for (const auto *Candidate : Record->ctors()) {200 if (IsRValueOverload(Candidate))201 return true;202 }203 return false;204}205 206/// Find all references to \p ParamDecl across all of the207/// redeclarations of \p Ctor.208static SmallVector<const ParmVarDecl *, 2>209collectParamDecls(const CXXConstructorDecl *Ctor,210 const ParmVarDecl *ParamDecl) {211 SmallVector<const ParmVarDecl *, 2> Results;212 const unsigned ParamIdx = ParamDecl->getFunctionScopeIndex();213 214 for (const FunctionDecl *Redecl : Ctor->redecls())215 Results.push_back(Redecl->getParamDecl(ParamIdx));216 return Results;217}218 219PassByValueCheck::PassByValueCheck(StringRef Name, ClangTidyContext *Context)220 : ClangTidyCheck(Name, Context),221 Inserter(Options.getLocalOrGlobal("IncludeStyle",222 utils::IncludeSorter::IS_LLVM),223 areDiagsSelfContained()),224 ValuesOnly(Options.get("ValuesOnly", false)) {}225 226void PassByValueCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {227 Options.store(Opts, "IncludeStyle", Inserter.getStyle());228 Options.store(Opts, "ValuesOnly", ValuesOnly);229}230 231void PassByValueCheck::registerMatchers(MatchFinder *Finder) {232 Finder->addMatcher(233 traverse(234 TK_AsIs,235 cxxConstructorDecl(236 ofClass(cxxRecordDecl().bind("outer")),237 forEachConstructorInitializer(238 cxxCtorInitializer(239 unless(isBaseInitializer()),240 // Clang builds a CXXConstructExpr only when it knows241 // which constructor will be called. In dependent contexts242 // a ParenListExpr is generated instead of a243 // CXXConstructExpr, filtering out templates automatically244 // for us.245 withInitializer(cxxConstructExpr(246 has(ignoringParenImpCasts(declRefExpr(to(247 parmVarDecl(248 hasType(qualType(249 // Match only const-ref or a non-const250 // value parameters. Rvalues,251 // TemplateSpecializationValues and252 // const-values shouldn't be modified.253 ValuesOnly254 ? nonConstValueType()255 : anyOf(notTemplateSpecConstRefType(),256 nonConstValueType()))))257 .bind("Param"))))),258 hasDeclaration(cxxConstructorDecl(259 isCopyConstructor(), unless(isDeleted()),260 hasDeclContext(cxxRecordDecl(261 isMoveConstructibleInBoundCXXRecordDecl(262 "outer"))))))))263 .bind("Initializer")))264 .bind("Ctor")),265 this);266}267 268void PassByValueCheck::registerPPCallbacks(const SourceManager &SM,269 Preprocessor *PP,270 Preprocessor *ModuleExpanderPP) {271 Inserter.registerPreprocessor(PP);272}273 274void PassByValueCheck::check(const MatchFinder::MatchResult &Result) {275 const auto *Ctor = Result.Nodes.getNodeAs<CXXConstructorDecl>("Ctor");276 const auto *ParamDecl = Result.Nodes.getNodeAs<ParmVarDecl>("Param");277 const auto *Initializer =278 Result.Nodes.getNodeAs<CXXCtorInitializer>("Initializer");279 const SourceManager &SM = *Result.SourceManager;280 281 // If the parameter is used or anything other than the copy, do not apply282 // the changes.283 if (!paramReferredExactlyOnce(Ctor, ParamDecl))284 return;285 286 // If the parameter is trivial to copy, don't move it. Moving a trivially287 // copyable type will cause a problem with performance-move-const-arg288 if (ParamDecl->getType().getNonReferenceType().isTriviallyCopyableType(289 *Result.Context))290 return;291 292 // Do not trigger if we find a paired constructor with an rvalue.293 if (hasRValueOverload(Ctor, ParamDecl))294 return;295 296 auto Diag = diag(ParamDecl->getBeginLoc(), "pass by value and use std::move");297 298 // If we received a `const&` type, we need to rewrite the function299 // declarations.300 if (ParamDecl->getType()->isLValueReferenceType()) {301 // Check if we can succesfully rewrite all declarations of the constructor.302 for (const ParmVarDecl *ParmDecl : collectParamDecls(Ctor, ParamDecl)) {303 const TypeLoc ParamTL = ParmDecl->getTypeSourceInfo()->getTypeLoc();304 auto RefTL = ParamTL.getAs<ReferenceTypeLoc>();305 if (RefTL.isNull()) {306 // We cannot rewrite this instance. The type is probably hidden behind307 // some `typedef`. Do not offer a fix-it in this case.308 return;309 }310 }311 // Rewrite all declarations.312 for (const ParmVarDecl *ParmDecl : collectParamDecls(Ctor, ParamDecl)) {313 const TypeLoc ParamTL = ParmDecl->getTypeSourceInfo()->getTypeLoc();314 auto RefTL = ParamTL.getAs<ReferenceTypeLoc>();315 316 const TypeLoc ValueTL = RefTL.getPointeeLoc();317 const CharSourceRange TypeRange = CharSourceRange::getTokenRange(318 ParmDecl->getBeginLoc(), ParamTL.getEndLoc());319 std::string ValueStr =320 Lexer::getSourceText(321 CharSourceRange::getTokenRange(ValueTL.getSourceRange()), SM,322 getLangOpts())323 .str();324 ValueStr += ' ';325 Diag << FixItHint::CreateReplacement(TypeRange, ValueStr);326 }327 }328 329 // Use std::move in the initialization list.330 Diag << FixItHint::CreateInsertion(Initializer->getRParenLoc(), ")")331 << FixItHint::CreateInsertion(332 Initializer->getLParenLoc().getLocWithOffset(1), "std::move(")333 << Inserter.createIncludeInsertion(334 Result.SourceManager->getFileID(Initializer->getSourceLocation()),335 "<utility>");336}337 338} // namespace clang::tidy::modernize339