1 //===--- UseEmplaceCheck.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 "UseEmplaceCheck.h" 10 #include "../utils/OptionsUtils.h" 11 using namespace clang::ast_matchers; 12 13 namespace clang { 14 namespace tidy { 15 namespace modernize { 16 17 namespace { 18 AST_MATCHER(DeclRefExpr, hasExplicitTemplateArgs) { 19 return Node.hasExplicitTemplateArgs(); 20 } 21 22 const auto DefaultContainersWithPushBack = 23 "::std::vector; ::std::list; ::std::deque"; 24 const auto DefaultSmartPointers = 25 "::std::shared_ptr; ::std::unique_ptr; ::std::auto_ptr; ::std::weak_ptr"; 26 const auto DefaultTupleTypes = "::std::pair; ::std::tuple"; 27 const auto DefaultTupleMakeFunctions = "::std::make_pair; ::std::make_tuple"; 28 } // namespace 29 30 UseEmplaceCheck::UseEmplaceCheck(StringRef Name, ClangTidyContext *Context) 31 : ClangTidyCheck(Name, Context), IgnoreImplicitConstructors(Options.get( 32 "IgnoreImplicitConstructors", false)), 33 ContainersWithPushBack(utils::options::parseStringList(Options.get( 34 "ContainersWithPushBack", DefaultContainersWithPushBack))), 35 SmartPointers(utils::options::parseStringList( 36 Options.get("SmartPointers", DefaultSmartPointers))), 37 TupleTypes(utils::options::parseStringList( 38 Options.get("TupleTypes", DefaultTupleTypes))), 39 TupleMakeFunctions(utils::options::parseStringList( 40 Options.get("TupleMakeFunctions", DefaultTupleMakeFunctions))) {} 41 42 void UseEmplaceCheck::registerMatchers(MatchFinder *Finder) { 43 // FIXME: Bunch of functionality that could be easily added: 44 // + add handling of `push_front` for std::forward_list, std::list 45 // and std::deque. 46 // + add handling of `push` for std::stack, std::queue, std::priority_queue 47 // + add handling of `insert` for stl associative container, but be careful 48 // because this requires special treatment (it could cause performance 49 // regression) 50 // + match for emplace calls that should be replaced with insertion 51 auto CallPushBack = cxxMemberCallExpr( 52 hasDeclaration(functionDecl(hasName("push_back"))), 53 on(hasType(cxxRecordDecl(hasAnyName(SmallVector<StringRef, 5>( 54 ContainersWithPushBack.begin(), ContainersWithPushBack.end())))))); 55 56 // We can't replace push_backs of smart pointer because 57 // if emplacement fails (f.e. bad_alloc in vector) we will have leak of 58 // passed pointer because smart pointer won't be constructed 59 // (and destructed) as in push_back case. 60 auto IsCtorOfSmartPtr = hasDeclaration(cxxConstructorDecl(ofClass(hasAnyName( 61 SmallVector<StringRef, 5>(SmartPointers.begin(), SmartPointers.end()))))); 62 63 // Bitfields binds only to consts and emplace_back take it by universal ref. 64 auto BitFieldAsArgument = hasAnyArgument( 65 ignoringImplicit(memberExpr(hasDeclaration(fieldDecl(isBitField()))))); 66 67 // Initializer list can't be passed to universal reference. 68 auto InitializerListAsArgument = hasAnyArgument( 69 ignoringImplicit(cxxConstructExpr(isListInitialization()))); 70 71 // We could have leak of resource. 72 auto NewExprAsArgument = hasAnyArgument(ignoringImplicit(cxxNewExpr())); 73 // We would call another constructor. 74 auto ConstructingDerived = 75 hasParent(implicitCastExpr(hasCastKind(CastKind::CK_DerivedToBase))); 76 77 // emplace_back can't access private constructor. 78 auto IsPrivateCtor = hasDeclaration(cxxConstructorDecl(isPrivate())); 79 80 auto HasInitList = anyOf(has(ignoringImplicit(initListExpr())), 81 has(cxxStdInitializerListExpr())); 82 83 // FIXME: Discard 0/NULL (as nullptr), static inline const data members, 84 // overloaded functions and template names. 85 auto SoughtConstructExpr = 86 cxxConstructExpr( 87 unless(anyOf(IsCtorOfSmartPtr, HasInitList, BitFieldAsArgument, 88 InitializerListAsArgument, NewExprAsArgument, 89 ConstructingDerived, IsPrivateCtor))) 90 .bind("ctor"); 91 auto HasConstructExpr = has(ignoringImplicit(SoughtConstructExpr)); 92 93 auto MakeTuple = ignoringImplicit( 94 callExpr( 95 callee(expr(ignoringImplicit(declRefExpr( 96 unless(hasExplicitTemplateArgs()), 97 to(functionDecl(hasAnyName(SmallVector<StringRef, 2>( 98 TupleMakeFunctions.begin(), TupleMakeFunctions.end()))))))))) 99 .bind("make")); 100 101 // make_something can return type convertible to container's element type. 102 // Allow the conversion only on containers of pairs. 103 auto MakeTupleCtor = ignoringImplicit(cxxConstructExpr( 104 has(materializeTemporaryExpr(MakeTuple)), 105 hasDeclaration(cxxConstructorDecl(ofClass(hasAnyName( 106 SmallVector<StringRef, 2>(TupleTypes.begin(), TupleTypes.end()))))))); 107 108 auto SoughtParam = materializeTemporaryExpr( 109 anyOf(has(MakeTuple), has(MakeTupleCtor), 110 HasConstructExpr, has(cxxFunctionalCastExpr(HasConstructExpr)))); 111 112 Finder->addMatcher( 113 traverse(ast_type_traits::TK_AsIs, 114 cxxMemberCallExpr(CallPushBack, has(SoughtParam), 115 unless(isInTemplateInstantiation())) 116 .bind("call")), 117 this); 118 } 119 120 void UseEmplaceCheck::check(const MatchFinder::MatchResult &Result) { 121 const auto *Call = Result.Nodes.getNodeAs<CXXMemberCallExpr>("call"); 122 const auto *CtorCall = Result.Nodes.getNodeAs<CXXConstructExpr>("ctor"); 123 const auto *MakeCall = Result.Nodes.getNodeAs<CallExpr>("make"); 124 assert((CtorCall || MakeCall) && "No push_back parameter matched"); 125 126 if (IgnoreImplicitConstructors && CtorCall && CtorCall->getNumArgs() >= 1 && 127 CtorCall->getArg(0)->getSourceRange() == CtorCall->getSourceRange()) 128 return; 129 130 const auto FunctionNameSourceRange = CharSourceRange::getCharRange( 131 Call->getExprLoc(), Call->getArg(0)->getExprLoc()); 132 133 auto Diag = diag(Call->getExprLoc(), "use emplace_back instead of push_back"); 134 135 if (FunctionNameSourceRange.getBegin().isMacroID()) 136 return; 137 138 const auto *EmplacePrefix = MakeCall ? "emplace_back" : "emplace_back("; 139 Diag << FixItHint::CreateReplacement(FunctionNameSourceRange, EmplacePrefix); 140 141 const SourceRange CallParensRange = 142 MakeCall ? SourceRange(MakeCall->getCallee()->getEndLoc(), 143 MakeCall->getRParenLoc()) 144 : CtorCall->getParenOrBraceRange(); 145 146 // Finish if there is no explicit constructor call. 147 if (CallParensRange.getBegin().isInvalid()) 148 return; 149 150 const SourceLocation ExprBegin = 151 MakeCall ? MakeCall->getExprLoc() : CtorCall->getExprLoc(); 152 153 // Range for constructor name and opening brace. 154 const auto ParamCallSourceRange = 155 CharSourceRange::getTokenRange(ExprBegin, CallParensRange.getBegin()); 156 157 Diag << FixItHint::CreateRemoval(ParamCallSourceRange) 158 << FixItHint::CreateRemoval(CharSourceRange::getTokenRange( 159 CallParensRange.getEnd(), CallParensRange.getEnd())); 160 } 161 162 void UseEmplaceCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { 163 Options.store(Opts, "IgnoreImplicitConstructors", IgnoreImplicitConstructors); 164 Options.store(Opts, "ContainersWithPushBack", 165 utils::options::serializeStringList(ContainersWithPushBack)); 166 Options.store(Opts, "SmartPointers", 167 utils::options::serializeStringList(SmartPointers)); 168 Options.store(Opts, "TupleTypes", 169 utils::options::serializeStringList(TupleTypes)); 170 Options.store(Opts, "TupleMakeFunctions", 171 utils::options::serializeStringList(TupleMakeFunctions)); 172 } 173 174 } // namespace modernize 175 } // namespace tidy 176 } // namespace clang 177